【嵌入式】探索嵌入式世界:在ARM上构建俄罗斯方块游戏的奇妙之旅

文章目录

  • 前言:
  • 1. 简介
  • 2. 总体设计思路及功能描述
    • 2.1 设计思路
    • 2.2 功能描述
    • 2.3 程序流程图
  • 3. 各部分程序功能及详细说明
    • 3.1 游戏界面函数
      • 3.1.1 游戏界面中的图片显示
      • 3.1.2 游戏开始界面
      • 3.1.3 游戏主界面
      • 3.1.4 游戏结束广告界面
      • 3.1.5 游戏界面中的触摸反馈
      • 3.1.6 游戏界面中的弹窗
    • 3.2 方块显示基本函数
      • 3.2.1 绘制方块
    • 3.2.2 擦除方块
    • 3.2.3 随机生成一个方块
    • 3.3 方块处理基本函数
      • 3.3.1 左移函数
      • 3.3.4 消行函数
      • 3.3.5 方块移动中的加速下落
    • 3.4 游戏代码中的链表
    • 3.5 游戏代码中的多线程
  • 总结:

前言:

随着科技的不断进步,嵌入式系统已经渗透到我们生活的方方面面,从家用电器到工业自动化,无处不在。在众多嵌入式应用中,游戏作为一种娱乐形式,不仅能够丰富人们的业余生活,还能有效锻炼逻辑思维和反应能力。本文将详细介绍一款基于ARM开发板GEC6818和嵌入式Linux操作系统开发的俄罗斯方块游戏。这款游戏以其经典的玩法、简洁的界面设计和流畅的运行性能,为用户带来了既富有挑战性又充满乐趣的游戏体验。文章将从设计思路、功能描述、程序流程、各模块实现等方面,全面解析这款游戏的制作过程和关键技术。

gitee:https://gitee.com/q-haodong/test_-arm/tree/master/20240619_test_tetris2
效果演示:https://live.csdn.net/v/405152?spm=1001.2014.3001.5501

基于嵌入式Linux俄罗斯方块

1. 简介

随着嵌入式技术的快速发展,嵌入式系统在各个领域的应用日益广泛。本项目以ARM开发板GEC6818为平台,基于嵌入式Linux操作系统,实现了一款具有基本功能的俄罗斯方块游戏。游戏设计遵循模块化思想,将系统分解为图形显示、触摸事件处理、游戏控制、界面显示、链表管理、移动逻辑以及主控等多个模块,以提高代码的可维护性和扩展性。通过C语言编程,利用多线程技术,实现了方块的移动、变形、随机生成、触屏控制、暂停恢复、嵌套消行和计分等功能。游戏界面简洁直观,提供了分数和等级显示,确保玩家能够轻松跟踪游戏进度。在性能方面,游戏运行流畅,代码规范,附有详细注释和文档,便于理解和维护。此外,通过全面测试,确保了游戏的稳定性和可靠性。最终,本项目不仅锻炼了嵌入式系统开发能力,也提供了一个既具有挑战性又富有趣味性的游戏体验。

2. 总体设计思路及功能描述

2.1 设计思路

本俄罗斯方块游戏的设计采用模块化的编程思想,将游戏分解为多个功能模块,每个模块负责不同的任务。主要模块包括图形显示模块、触摸事件处理模块、游戏控制模块、界面显示模块、链表管理模块、移动逻辑模块以及主控模块。程序使用C语言编写,运行在ARM平台上,利用多线程技术来提高游戏的响应速度和性能。

2.2 功能描述

  1. 图形显示模块:负责加载和显示BMP图片到屏幕上,支持指定区域的图片显示,用于游戏方块和背景的绘制。
  2. 触摸事件处理模块:监听触摸屏事件,将用户的触摸操作转换为游戏内的控制指令。
  3. 游戏控制模块:包含游戏的暂停和重启功能,允许玩家在任何时候暂停游戏,并在适当的时候恢复或重新开始。
  4. 界面显示模块:管理游戏的开始界面和结束界面,提供用户交互的界面元素。
  5. 链表管理模块:使用链表数据结构管理游戏中的方块布局,实现方块的动态添加和删除。
  6. 移动逻辑模块:控制方块的移动、变形和消行等逻辑,确保游戏规则的准确执行。
  7. 主控模块:作为程序的入口,初始化游戏环境,创建和管理线程,控制游戏的主循环。

2.3 程序流程图

在这里插入图片描述
程序流程从初始化游戏环境开始,显示欢迎界面,然后进入一个循环等待用户的触摸操作。一旦检测到触摸事件,程序将处理这些输入并更新游戏状态。随后,程序检查游戏是否结束,如果是,则显示游戏结束界面,并等待用户决定是否重启游戏或退出。如果用户选择重启,程序将重新初始化游戏环境;如果选择退出,则程序将结束运行。

3. 各部分程序功能及详细说明

3.1 游戏界面函数

3.1.1 游戏界面中的图片显示

代码中使用BMP文件格式来显示图像资源,这些图像用于游戏的图形界面,如方块、背景、按钮等元素。显示BMP图像的功能主要通过bmp_show.h头文件中声明的函数来实现。以下是与BMP显示相关的代码片段和解释:

  1. BMP显示函数声明 : 在bmp_show.h中,声明了两个函数bmp_show_mix和bmp_show_self,用于显示BMP图像:
    int bmp_show_mix(int x0, int y0, int width, int height, char *name);
    int bmp_show_self(int x0, int y0, int width, int height, char *name);

  2. BMP文件打开与读取 : 在bmp_show.c中,bmp_show_mix函数首先打开BMP文件,并读取文件状态,然后读取BMP图像数据:

int fd_bmp = open(name, O_RDONLY);
struct stat pst;
fstat(fd_bmp, &pst);
char *buf = (char *)malloc(pst.st_size);
lseek(fd_bmp, 54, SEEK_SET); // 跳过BMP文件头
read(fd_bmp, buf, pst.st_size - 54); // 读取BMP像素数据
  1. 内存映射Framebuffer : 使用mmap函数将显示设备的帧缓冲区(Framebuffer)映射到用户空间,以便于直接操作显示内存:
char *p = (char *)mmap(NULL, 800 * 480 * 4, PROT_READ | PROT_WRITE, MAP_SHARED, fd_lcd, 0);
  1. 图像数据复制 : 将读取的BMP图像数据复制到Framebuffer的指定位置:
for (j = 0; j < height; j++) {for (i = 0; i < width; i++) {memcpy(p + lcd_offset, buf + bmp_offset, 3); // 从BMP缓冲区复制到Framebuffer}
}
  1. 显示特定区域的BMP图像 : bmp_show_mix函数允许指定显示图像的起始位置(x0, y0)和大小(width, height),这可以用于在界面上显示图像的特定部分:
bmp_show_mix(x0, y0, width, height, name);
  1. 显示整个BMP图像 :bmp_show_self函数用于显示整个BMP图像,通常用于显示背景或全屏图像:
bmp_show_self(x0, y0, width, height, name);
  1. 释放资源 : 在图像显示完成后,需要释放分配的内存并关闭内存映射和文件描述符:
munmap(p, 800 * 480 * 4); // 关闭内存映射
close(fd_lcd); // 关闭Framebuffer文件描述符
close(fd_bmp); // 关闭BMP文件描述符
free(buf); // 释放分配的内存

3.1.2 游戏开始界面

  1. 效果
    在这里插入图片描述

  2. 功能: 展示游戏的初始界面,通常包含游戏的标题、开始游戏的按钮等元素。

  3. 实现: 使用bmp_show_mix函数加载和显示欢迎屏幕的背景图片

  4. 代码

void show_interface_welcome()
{bmp_show_mix(0, 0, 800, 480, "./tetris_pic/welcom_bk1.bmp"); // 显示欢迎界面背景int x, y, event_type;int button_down = 0; // 用于记录按钮是否被按下while (1){if (capture_touch_events(&x, &y, &event_type) == -1){// 触摸事件捕获失败,可能需要处理错误或退出break;}if (event_type == 1){ // 触摸按下事件if (x > 440 && x < 620 && y > 360 && y < 460){// 用户按下了按钮区域bmp_show_self(BUTTON_X, BUTTON_Y, BUTTON_W, BUTTON_H, "./tetris_pic/welcom_bk1_push.bmp"); // 显示按钮按下的图片button_down = 1;}printf("Touch down at (%d, %d)\n", y, y);}else if (event_type == 0 && button_down){   // 触摸离开事件且按钮之前被按下bmp_show_self(BUTTON_X, BUTTON_Y, BUTTON_W, BUTTON_H, "./tetris_pic/welcom_bk1.bmp"); // 恢复按钮正常状态button_down = 0;printf("Touch up at (%d, %d)\n", y, y);if (x > 440 && x < 620 && y > 360 && y < 460){break; // 离开循环,进入游戏}}}
}

3.1.3 游戏主界面

  1. 效果
    在这里插入图片描述

  2. 功能: 展示游戏进行中的界面,包括方块下落区域、下一个方块的预览区、分数和等级显示等。

  3. 实现: 在主循环中持续更新界面,显示当前活动方块、分数和等级。

  4. 代码

int main(int argc, char *argv[])
{show_interface_welcome();struct ls_all *head;// 显示背景图片bmp_show_mix(0, 0, 800, 480, "./tetris_pic/bck.bmp");int rt;pthread_t idt, idr;// 获取两种随机形状并初始化,得到初始化结构体srand((unsigned int)time(NULL));shp = ((unsigned int)rand()) % 7 + 1;shp_next = ((unsigned int)rand()) % 7 + 1;bk = bk_init(shp);bk_next = bk_init(shp_next);// 初始化掉落方块结构体head = ls_init();// 初始化分数和速度score = 0;speed = 0;// 显示移动方块 及 提示方块the_show(bk);the_show_next(bk_next);score_show(0); // 显示成绩// 创建控制方块移动线程pthread_create(&idt, NULL, auto_down, (void *)head);// 时间更新线程,时间到且无操作自动更新dir为下落状态pthread_create(&idr, NULL, time_out, NULL);while (1){// 锁定互斥锁以安全地读取 dirpthread_mutex_lock(&dir_mutex);int current_dir = dir; // 假设这是在循环中读取 dir 变量的地方pthread_mutex_unlock(&dir_mutex);if (paused == 1 || gameover == 1 || current_dir == -2){usleep(100);continue;}if (current_dir == -1){ // 变形change_type(bk);the_show_bck_type(bk);}else{ // 移动change_dir(bk->p, current_dir);the_show_bck_dir(bk->p, current_dir);}// 移动检查是否越界及掉落到底部bk = move_check(head, current_dir);if (bk == NULL){return -1;}// 显示方块形状the_show(bk);pthread_mutex_lock(&dir_mutex);dir = -2; // 在主线程中置为 -2 ,表示不动pthread_mutex_unlock(&dir_mutex);}return 0;
}

3.1.4 游戏结束广告界面

  1. 效果
    在这里插入图片描述

  2. 功能: 当游戏结束时展示的界面,通常包含游戏结束的信息、最终得分和“重新开始”或“退出游戏”的选项。

  3. 实现: 使用show_interface_end函数来显示游戏结束的界面,处理用户的选择。

  4. 程序流程图
    在这里插入图片描述

  5. 代码

// 显示时间
void time_show(int n)
{int a1, a2, a3;char s[3][50];char st[3][50];int i;a1 = n / 100;     // 计算百位数字a2 = n / 10 % 10; // 计算十位数字a3 = n % 10;      // 计算个位数字for (i = 0; i < 3; i++){bzero(s[i], 50); // 初始化字符串 s[i], 将其清零}s[0][0] = a1 + 48; // 将百位数字转换成字符,并存储到s[0]s[1][0] = a2 + 48; // 将十位数字转换成字符,并存储到s[1]s[2][0] = a3 + 48; // 将个位数字转换成字符,并存储到s[2]for (i = 0; i < 3; i++){strcat(s[i], ".bmp\0");         // 在每个字符后面添加".bmp"扩展名strcpy(st[i], "./tetris_pic/"); // 将路径 "./tetris_pic/" 复制到 st[i]strcat(st[i], s[i]);            // 将文件名连接到路径后bmp_show_mix(280 + 20 * i, 45, 20, 20, st[i]);// printf("%s\n",st[i]);}
}// 全局变量,用于线程间通信
int cut_down = 0;
pthread_mutex_t count_mutex = PTHREAD_MUTEX_INITIALIZER; // 互斥锁,用于同步对 seconds_left 的访问
pthread_cond_t count_cond = PTHREAD_COND_INITIALIZER;    // 条件变量,用于线程间同步void *touch_event_thread(void *args)
{int x, y, event_type;int button_down = 0;while (1){if (capture_touch_events(&x, &y, &event_type) == -1){// 触摸事件捕获失败,可能需要处理错误或退出break;}// 检查按钮是否被按下if (event_type == 1 && x > 440 && x < 620 && y > 360 && y < 460){// 用户按下了按钮区域bmp_show_self(BUTTON_X, BUTTON_Y+5, BUTTON_W, BUTTON_H-10, "./tetris_pic/bk_end_push.bmp"); // 显示按钮按下的图片button_down = 1; // 标记按钮被按下}// 检查按钮是否被按下并释放if (event_type == 0 && button_down){// 用户释放按钮,提前重启游戏button_down = 0; // 重置按钮状态// 触摸离开事件且按钮之前被按下bmp_show_self(BUTTON_X, BUTTON_Y, BUTTON_W, BUTTON_H, "./tetris_pic/bk_end.bmp"); // 恢复按钮正常状态if (x > 440 && x < 620 && y > 360 && y < 460){pthread_mutex_lock(&count_mutex);cut_down = 1;pthread_cond_signal(&count_cond); // 发送信号给主线程pthread_mutex_unlock(&count_mutex);break;}}pthread_mutex_unlock(&count_mutex);}return NULL;
}void show_interface_end()
{pause_game();// 重置倒计时和按钮状态int seconds_left = 20; // 20s 倒计时// 启动触摸事件线程pthread_t touch_thread_id;pthread_create(&touch_thread_id, NULL, touch_event_thread, NULL);bmp_show_mix(0, 0, 800, 480, "./tetris_pic/bk_end.bmp"); // 显示结束广告界面usleep(300000);bmp_show_mix(0, 0, 800, 480, "./tetris_pic/bk_end.bmp"); // 显示结束广告界面while (1){pthread_mutex_lock(&count_mutex);// 检查倒计时是否结束或按钮是否被按下if (seconds_left <= 0 || cut_down == 1){pthread_mutex_unlock(&count_mutex);break; // 倒计时结束或按钮被按下,退出循环}pthread_mutex_unlock(&count_mutex);time_show(seconds_left); // 显示剩余时间seconds_left--;          // 倒计时减少sleep(1);                // 等待一秒}// 取消触摸事件线程,如果它还在运行pthread_cancel(touch_thread_id);pthread_join(touch_thread_id, NULL);// 倒计时结束或用户提前重启游戏restart_game();
}

3.1.5 游戏界面中的触摸反馈

代码中的触摸反馈主要通过capture_touch_events函数来实现,该函数用于捕捉触摸屏的按下和释放(离开)事件,并根据这些事件来改变游戏的状态或者显示效果。以下是触摸反馈相关的关键代码片段和解释

图 3.5 触摸反馈效果展示

  1. 触摸事件捕捉 : capture_touch_events函数通过读取设备输入事件来捕捉触摸操作:
int capture_touch_events(int *x, int *y, int *event_type) {// ...if (ts.type == EV_KEY && ts.code == BTN_TOUCH) {if (ts.value == 1) { // 按下*event_type = 1;break;} else if (ts.value == 0) { // 离开*event_type = 0;break;}}// ...
}
  1. 触摸按下反馈 : 当用户按下触摸屏时,程序会识别为按下事件,并设置event_type为1:
if (event_type == 1) {// 触摸按下事件的处理// 例如,改变按钮的显示状态来提供反馈bmp_show_self(BUTTON_X, BUTTON_Y, BUTTON_W, BUTTON_H, "./tetris_pic/bk_end_push.bmp");button_down = 1; // 标记按钮被按下
}
  1. 触摸释放反馈 : 当用户释放触摸屏时,程序会识别为离开事件,并设置event_type为0:
else if (event_type == 0 && button_down) {// 触摸离开事件的处理// 例如,恢复按钮的原始状态bmp_show_self(BUTTON_X, BUTTON_Y, BUTTON_W, BUTTON_H, "./tetris_pic/bk_end.bmp");button_down = 0; // 重置按钮状态
}
  1. 按钮状态变化 : 在show_interface_welcome函数中,使用bmp_show_self来显示或隐藏按下的图片,以提供视觉反馈:
void show_interface_welcome() {// ...if (event_type == 1) {// 用户按下了按钮区域bmp_show_self(BUTTON_X, BUTTON_Y, BUTTON_W, BUTTON_H, "./tetris_pic/welcom_bk1_push.bmp");button_down = 1;}// ...else if (event_type == 0 && button_down) {// 用户释放按钮bmp_show_self(BUTTON_X, BUTTON_Y, BUTTON_W, BUTTON_H, "./tetris_pic/welcom_bk1.bmp");button_down = 0;// 添加进入游戏的逻辑}// ...
}
  1. 触摸事件线程 : 在touch_event_thread函数中,创建了一个线程专门处理触摸事件,以实现非阻塞的触摸反馈:
void *touch_event_thread(void *args) {// ...while (1) {// 捕捉触摸事件if (capture_touch_events(&x, &y, &event_type) == -1) {// 处理错误或退出break;}// 根据触摸事件更新游戏状态或界面// ...}return NULL;
}

3.1.6 游戏界面中的弹窗

弹窗功能主要通过bmp_show_self函数实现,该函数用于在指定位置显示图片资源,模拟弹窗效果。以下是弹窗功能相关的代码片段和解释:

图 3.6 游戏弹窗效果展示

  1. 游戏暂停弹窗 : 当用户触发暂停操作时,会显示一个暂停弹窗:
if (paused == 1)
{bmp_show_self(289, 159, 256, 115, "./tetris_pic/pause.bmp"); // 显示暂停弹窗show_pause = 1;
}
else if (show_pause == 1)
{show_pause = 0;bmp_show_self(289, 159, 256, 115, "./tetris_pic/bck.bmp"); // 恢复背景图
}
  1. 游戏结束弹窗 : 当游戏结束条件触发时,会显示一个游戏结束的弹窗:
if (gameover == 1)
{bmp_show_self(184, 157, 455, 94, "./tetris_pic/gameover.bmp"); // 显示游戏失败弹窗
}
  1. 按钮按下效果 : 在触摸事件处理中,当用户按下某个按钮区域时,会显示一个按钮按下的图片,这也是一种弹窗效果:
if (event_type == 1)
{// 用户按下了按钮区域bmp_show_self(BUTTON_X, BUTTON_Y, BUTTON_W, BUTTON_H, "./tetris_pic/bk_end_push.bmp"); // 显示按钮按下的图片button_down = 1; // 标记按钮被按下
}
  1. 触摸事件处理 : capture_touch_events函数用于捕捉触摸屏的按下和离开事件,并返回相应的坐标和事件类型,这是实现弹窗功能的基础:
int capture_touch_events(int *x, int *y, int *event_type)
{// ...if (ts.value == 1) { // 按下*event_type = 1;break;}else if (ts.value == 0) { // 离开*event_type = 0;break;}// ...
}
  1. 界面显示函数 : show_interface_welcome和show_interface_end是两个界面显示函数,它们分别用于显示欢迎界面和结束界面,这些界面可以包含弹窗元素:
void show_interface_welcome()
{// 显示欢迎界面背景bmp_show_mix(0, 0, 800, 480, "./tetris_pic/welcom_bk1.bmp");// ...
}void show_interface_end()
{// 显示结束界面bmp_show_mix(0, 0, 800, 480, "./tetris_pic/bk_end.bmp");// ...
}

3.2 方块显示基本函数

3.2.1 绘制方块

  1. 功能: 根据方块的当前状态在界面上绘制方块。
  2. 实现: 通过the_show函数,根据方块的坐标和形状类型,显示方块的图片。
  3. 代码
// LCD显示移动的方块
void the_show(struct block *bk)
{int i;int *p = bk->p;int shp = bk->shape;char s[50];switch (shp){case 1:strcpy(s, "./tetris_pic/O.bmp");break;case 2:strcpy(s, "./tetris_pic/I.bmp");break;case 3:strcpy(s, "./tetris_pic/S.bmp");break;case 4:strcpy(s, "./tetris_pic/Z.bmp");break;case 5:strcpy(s, "./tetris_pic/L.bmp");break;case 6:strcpy(s, "./tetris_pic/J.bmp");break;case 7:strcpy(s, "./tetris_pic/T.bmp");break;}for (i = 0; i < 4; i++){bmp_show_mix(p[i * 2], p[i * 2 + 1], 20, 20, s);}
}

3.2.2 擦除方块

  1. 功能: 当方块移动或变形后,需要先擦除原来的方块,再在新位置绘制。
  2. 实现: 使用the_show_bck_dir或the_show_bck_type函数显示方块原来位置的背景色。
  3. 代码
// 方块移动后需要把原来的方块--》消失--》显示背景色
void the_show_bck_dir(int *p, int dir)
{ // dir: 0-down  1-left  2-rightint i;for (i = 0; i < 4; i++){if (dir == 0){bmp_show_self(p[i * 2], p[i * 2 + 1] - 20, 20, 20, "./tetris_pic/bck.bmp");}else if (dir == 1){bmp_show_self(p[i * 2] + 20, p[i * 2 + 1], 20, 20, "./tetris_pic/bck.bmp");}else if (dir == 2){bmp_show_self(p[i * 2] - 20, p[i * 2 + 1], 20, 20, "./tetris_pic/bck.bmp");}}
}// 方块变形后让之前的--》消失--》显示背景色
void the_show_bck_type(struct block *bk)
{int i;if (bk->type == 1)bk->type = 5; // 如果是形态1将type改为5使其计算结果正确for (i = 0; i < 4; i++){ // 还原上一个位置的背景图bmp_show_self(bk->p[i * 2] - bk->p[i * 2 + (bk->type - 1) * 8],bk->p[i * 2 + 1] - bk->p[i * 2 + (bk->type - 1) * 8 + 1], 20, 20, "./tetris_pic/bck.bmp");}if (bk->type == 5)bk->type = 1; // 还原回来
}

3.2.3 随机生成一个方块

  1. 功能: 游戏需要不断生成新的方块供玩家操作。
  2. 实现: 在main函数中使用rand函数生成随机数,决定下一个方块的形状类型,并使用bk_init函数初始化方块的属性。
  3. 代码:
    // 获取两种随机形状并初始化,得到初始化结构体srand((unsigned int)time(NULL));shp = ((unsigned int)rand()) % 7 + 1;
shp_next = ((unsigned int)rand()) % 7 + 1;// 方块掉落后 得到一个新形状的方块 初始化函数--》得出初始化结构体
struct block *bk_init(int shape)
{struct block *bk;bk = (struct block *)malloc(sizeof(struct block));switch (shape){case 1:bk->p = arry_init_O();bk->shape = 1;break;case 2:bk->p = arry_init_I();bk->shape = 2;break;case 3:bk->p = arry_init_S();bk->shape = 3;break;case 4:bk->p = arry_init_Z();bk->shape = 4;break;case 5:bk->p = arry_init_L();bk->shape = 5;break;case 6:bk->p = arry_init_J();bk->shape = 6;break;case 7:bk->p = arry_init_T();bk->shape = 7;break;default:break;}bk->type = 1;return bk;
}

3.3 方块处理基本函数

3.3.1 左移函数

  1. 功能: 控制方块向左移动一格。
  2. 实现: 使用change_dir函数,设置方向参数为向左移动,更新方块的位置。
  3. 代码:
// 移动方块,仅限三种方向
void change_dir(int *p, int dir)
{ // dir: 0-down  1-left  2-right//printf("dir:%d\n", dir);int i = 0;for (; i < 4; i++){if (dir == 0){p[i * 2 + 1] += 20;}else if (dir == 1){p[i * 2] -= 20;}else if (dir == 2){p[i * 2] += 20;}}
}// 移动越界 需要恢复回原来的坐标--》dir: 0-down  1-left  2-right
void change_dir_off(int *p, int dir)
{ // dir: 0-down  1-left  2-rightint i = 0;// if(dir == 0){// printf("change_dir_off error\n");// exit(-1);// }for (; i < 4; i++){if (dir == 1){p[i * 2] += 20;}else if (dir == 2){p[i * 2] -= 20;}else if (dir == 0){p[i * 2 + 1] -= 20;}}
}3.3.2 变形函数
1)	功能: 允许方块在垂直方向上旋转,改变形状。
2)	实现: 使用change_type函数,更新方块的形状状态,并重新绘制方块。
3)	代码:// 变形
void change_type(struct block *bk)
{int i;if (bk->shape == 1){ // 如果方块直接返回return;}for (i = 0; i < 4; i++){// 更新坐标值到下一个形态bk->p[i * 2] += bk->p[i * 2 + bk->type * 8]; // 因为用int存贮,// 且一个坐标信息占两个int所以要 *8bk->p[i * 2 + 1] += bk->p[i * 2 + bk->type * 8 + 1];// printf("%d\t%d\t",bk->p[i*2+bk->type*8],bk->p[i*2+bk->type*8+1]);}// 更新到下一个旋转状态bk->type++;if (bk->type >= 5){bk->type = 1;}return;
}3.3.3 碰撞函数
1)	功能: 检测方块移动时是否与其它方块或游戏边界发生碰撞。
2)	实现: 通过bound_check函数检测方块的坐标是否越界,并相应地调整方块的位置。
3)	代码:// 检查方块左右下移动时有无越界--》下越界返回0、左越界返回-1、右越界返回-2
int bound_check(int *p)
{int i;for (i = 0; i < 4; i++){if (p[i * 2 + 1] > 460){return 0; // down out}if (p[i * 2] > 300){return -2; // right out}else if (p[i * 2] < 0){return -1; // left out}}return 1;
}

3.3.4 消行函数

  1. 功能: 当一行为完全填满时,自动消除该行并为玩家增加分数。
  2. 实现: 在ls_check_self函数中扫描整个链表,检测并消除满行,更新分数,并重新绘制界面。
  3. 代码:
// 检查整个链表有无消行--》把整个屏幕行扫描式检测--》方块到顶返回-1
int ls_check_self(struct ls_all *head)
{struct ls_all *tmp; // 零时指针,用于遍历链表int i = 460;        // 初始化为460,从屏幕底部开始扫描int n = 0;          // 统计当前行的方块数量tmp = head;tmp = tmp->next; // 初始化tmp为链表的第二个节点(链表为带头节点的双向链表)while (i >= 40){          // 从底部 460 开始一直扫描到顶部 40n = 0; // 每次开始循环将方块数置 0,tmp指向第二个节点tmp = head;tmp = tmp->next;// 1.扫描当前行while (tmp != head){if (tmp->y0 == i){ // 如果方块在当前行n++;if (i < 80){                          // 如果方块在顶部区域(游戏结束)printf("game over\n"); //gameover = 1;bmp_show_self(184,157,455,94,"./tetris_pic/gameover.bmp"); // 显示游戏失败弹窗sleep(3);return -1; // 返回-1表示游戏结束}}// printf("%d  %d\n",i,tmp->y0);tmp = tmp->next;}// 2.判断当前行已经填满(即有16个方块在同一行)if (n == 16){score++; // 消一行加一分printf("%d line\n", score);score_show(score);speed = score / 10;// 3.重新显示背景图(擦除所有的方块)bmp_show_self(0, 33, 320, 447, "./tetris_pic/bck.bmp");// 4.再次遍历链表,删除当前行的方块并下移上方的方块tmp = head;tmp = tmp->next;while (tmp != head){if (tmp->y0 == i){                      // 如果当前方块在删除行tmp = ls_del(tmp); // 删除当前方块}// printf("%d  %d\n",i,tmp->y0);else if (tmp->y0 < i){                  // 如果当前方块在当前方块之上tmp->y0 += 20; // 将方块下移}tmp = tmp->next;}// 5.重新显示所有方块ls_all_show(head);i += 20; // 因为当前行被删除,需要下移一行重新检测}i -= 20; // 上移一行}return 0; // 正常退出,游戏继续进行
}

3.3.5 方块移动中的加速下落

按下向下键通常会导致方块加速下落。这种加速下落的行为可以通过减少方块下落的时间间隔来实现,或者通过覆盖当前下落状态的变量来立即触发下落动作。以下是代码中实现按下向下键后加速下落的相关片段和解释:

  1. 加速按钮的逻辑 : 当用户按下加速按钮时(基于触摸位置判断),如果当前速度小于某个阈值(例如76),则速度会相应增加:
if (x > 760 && x < 840 && y > 390 && y < 470) {if (event_type == 1) {if (speed < 76) {speed = speed + 76; // 增加速度}bmp_show_self(584, 331, 76, 72, "./tetris_pic/bck_push.bmp"); // 显示按钮按下后的图片show_dir = 1;}
}
  1. 按下向下键的逻辑 : 当按下向下键时,如果当前方块没有达到最大速度,可以进一步加速下落:
if (event_type == 1 && dir == DOWN_KEY) { // 假设DOWN_KEY是向下键对应的值if (speed < MAX_SPEED) { // MAX_SPEED是定义的最大速度常量speed = speed + INCREMENT; // INCREMENT是每次加速增加的速度值}// 可以添加代码以立即下落到底部或更新显示
}
  1. 速度更新 : 在主循环或相关线程中,根据speed变量来调整方块下落的时间间隔,速度越快,下落越频繁:
void *auto_down(void *arg) {while (1) {// ...usleep((400 - speed * 5) * 1000); // 根据速度调整下落间隔// 执行下落动作// ...}
}
  1. 主循环中的处理 : 在主循环中,检测按下向下键或加速按钮的事件,并更新速度和方块状态:
while (1) {// 检测按下向下键或加速按钮的逻辑// ...// 根据当前速度更新方块位置// ...// 显示当前方块位置the_show(bk);
}
  1. 线程间通信 : 如果使用多线程,按下向下键或加速按钮后,需要通过互斥锁更新共享的速度变量,以通知其他线程方块状态的变化:
pthread_mutex_lock(&dir_mutex);
speed = updated_speed; // updated_speed是更新后的速度值
pthread_mutex_unlock(&dir_mutex);

3.4 游戏代码中的链表

链表在游戏中扮演着核心的数据结构角色,用于动态维护和更新俄罗斯方块中各个方块的状态和位置。通过链表,游戏能够有效地追踪每个方块的下落过程,检测方块间的碰撞,实现方块到达底部时的自动堆叠,以及在形成完整行时的消除功能。这种数据组织方式提供了灵活高效的内存管理和访问机制,确保了游戏逻辑的正确执行和流畅的用户体验。

  1. 定义链表节点结构 (struct ls_all): 在list.h文件中定义了链表节点的结构体,每个节点代表一个方块。
struct ls_all {struct ls_all *next;struct ls_all *pre;int shape; // 形状用来区分显示颜色int x0;int y0;
};
  1. 初始化链表 (ls_init 函数):创建一个新的链表头节点,并使其指向自己,形成一个循环链表。
struct ls_all *ls_init() {struct ls_all *head = (struct ls_all *)malloc(sizeof(struct ls_all));head->next = head;head->pre = head;return head;
}
  1. 添加节点到链表 (ls_add 函数):使用尾插法在链表末尾添加新的方块节点。
void ls_add(struct ls_all *head, int x0, int y0, int shape) {struct ls_all *node = (struct ls_all *)malloc(sizeof(struct ls_all));// ... 省略中间代码 ...node->x0 = x0;node->y0 = y0;node->shape = shape;
}
  1. 删除链表节点 (ls_del 函数):从链表中删除指定的节点,通常用于消行操作。
struct ls_all *ls_del(struct ls_all *node) {struct ls_all *tmp = node->pre;tmp->next = node->next;node->next->pre = tmp;free(node);return tmp;
}
  1. 检查方块是否到达底部 (ls_check 函数):检查方块是否与链表底部的节点重叠,如果是,则方块到达底部。
int ls_check(struct ls_all *head, int *p) {// ... 省略中间代码 ...return -1; // 如果到达底部或越界,返回-1
}
  1. 并检查消行 (ls_updata 函数):将方块添加到链表中,并检查是否有完整的行需要消除。
int ls_updata(struct ls_all *head, struct block *bk) {// ... 省略中间代码 ...if (ls_check_self(head) == -1) {return -1; // 如果检测到游戏结束,返回-1}
}
  1. 检查并处理消行 (ls_check_self 函数):遍历链表,检查是否有满行,如果有,则进行消行处理。
int ls_check_self(struct ls_all *head) {// ... 省略中间代码 ...if (n == 16) { // 如果一行中有16个方块,即填满一行// 执行消行操作}return 0; // 正常退出,游戏继续
}
  1. 显示链表中的所有方块 (ls_all_show 函数):遍历链表,显示每一个方块。
void ls_all_show(struct ls_all *head) {struct ls_all *tmp = head->next; // 开始遍历while (tmp != head) {// 显示方块tmp = tmp->next;}
}

3.5 游戏代码中的多线程

多线程被用于实现游戏的不同功能,如自动下落、触摸事件处理和时间更新等。多线程允许这些功能并发运行,从而提高程序的响应性和性能。以下是对游戏代码中多线程使用的详细解释:

  1. 线程创建 : 在main函数中,使用pthread_create创建了多个线程:
pthread_t idt, idr;// 创建控制方块移动线程
pthread_create(&idt, NULL, auto_down, (void *)head);// 时间更新线程,时间到且无操作自动更新dir为下落状态
pthread_mutex_lock(&dir_mutex);
dir = -2; // 初始状态为静止
pthread_mutex_unlock(&dir_mutex);
pthread_create(&idr, NULL, time_out, NULL);
  1. 自动下落线程 (auto_down) : 这个线程负责方块的自动下落逻辑。它在一个无限循环中运行,根据speed变量控制下落的速度:
void *auto_down(void *arg) {// 线程内部逻辑// ...
}
  1. 时间更新线程 (time_out) : 这个线程负责处理游戏的时间逻辑,例如,当没有用户交互时自动改变方块的下落方向:
void *time_out(void *arg) {// 线程内部逻辑// ...
}
  1. 触摸事件线程 : 在其他函数中,如show_interface_welcome或touch_event_thread,也可能创建额外的线程来处理触摸事件:
void *touch_event_thread(void *args) {// 处理触摸事件的线程逻辑// ...
}
  1. 线程同步 : 使用互斥锁(pthread_mutex_t)来同步对共享资源的访问,如方向变量dir:
pthread_mutex_lock(&dir_mutex);
dir = 0; // 设置下落方向
pthread_mutex_unlock(&dir_mutex);
  1. 线程取消 : 在某些情况下,如游戏结束或重启,可能需要取消线程:
pthread_cancel(thread_id);
  1. 线程等待 : 在主函数中,可能需要等待所有子线程完成,以确保资源被正确释放:
pthread_join(idt, NULL);
pthread_join(idr, NULL);
  1. 线程安全的操作 : 在多线程环境中,对共享资源的所有操作都应该是线程安全的。例如,更新分数或处理游戏状态的变量时,需要使用互斥锁来避免竞态条件。

  2. 条件变量 : 有时线程间需要基于某些条件进行同步,这时可以使用条件变量

pthread_cond_signal(&count_cond); // 发送信号给其他线程
pthread_cond_wait(&count_cond, &count_mutex); // 等待信号

总结:

本文详细介绍了一款基于ARM开发板GEC6818的俄罗斯方块游戏的设计和实现。从总体设计思路出发,我们采用了模块化编程方法,将游戏分解为图形显示、触摸事件处理、游戏控制、界面显示、链表管理、移动逻辑和主控等多个模块,以提高代码的可维护性和扩展性。通过C语言编程和多线程技术的应用,游戏实现了方块的移动、变形、随机生成、触屏控制、暂停恢复、嵌套消行和计分等功能,确保了游戏的流畅性和稳定性。

在界面设计上,游戏提供了直观的图形界面和触摸反馈,玩家可以轻松跟踪游戏进度和控制游戏流程。程序流程图清晰地展示了游戏从初始化到运行再到结束的整个过程,使读者能够快速把握游戏的逻辑结构。各模块的详细说明和代码实现,不仅展示了开发团队的技术实力,也为嵌入式系统开发爱好者提供了宝贵的学习资料。

此外,文章还对游戏代码中的链表和多线程技术进行了深入分析,展示了如何使用链表管理方块布局和实现动态内存管理,以及如何利用多线程提高程序的响应速度和性能。这些技术的应用,不仅提升了游戏的运行效率,也为复杂系统的开发提供了可行的解决方案。

总之,这款俄罗斯方块游戏的开发过程,不仅锻炼了嵌入式系统开发能力,也展示了模块化设计和多线程技术在实际应用中的强大功能。通过本文的阅读,读者不仅能够获得游戏开发的全面认识,还能从中学习到嵌入式系统编程的实用技巧和最佳实践。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/366098.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

全球首款搭载Google Gemini和GPT-4o的智能眼镜发布

智能眼镜仍然是一个尚未完全成熟的未来概念&#xff0c;但生成式人工智能的到来显著提升了这些设备的能力。Meta 的 Ray-Ban 智能眼镜被许多人视为当今最好的选择之一&#xff0c;而现在 Solos AirGo Vision 正在为其带来竞争&#xff0c;这款眼镜还集成了 Google Gemini 支持。…

python代码报错

1.报错信息&#xff1a; asyncio.WindowsSelectorEventLoopPolicy()^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ AttributeError: module asyncio has no attribute WindowsSelectorEventLoopPolicy 2.解决办法&#xff1a; if __name__ "__main__": # 移除或注…

❤ Gitee平台的使用

Gitee平台的使用 文章目录 Gitee平台的使用一、Gitee的注册1、注册2、添加邮箱 二、仓库的创建 和 团队成员的添加1、单击右上角的 **&#xff0b;** 号 、创建仓库2、如下填写即可 三、仓库克隆到本地1、安装好git 和 小乌龟&#xff08;TortoiseGit&#xff09;2、打开仓库 复…

(超详细)数据结构——“队列”的深度解析

目录 前言&#xff1a; 1.队列的概念 2.队列的实现 3.代码实现队列 3.1 队列的初始化 3.2 插入 3.3 删除 3.4 队列的队头&#xff0c;队尾和大小 3.5 判空 3.6 销毁 3.7 测试 前言&#xff1a; 队列与栈都是线性表&#xff0c;它们的结构也非常类似&#…

“论单元测试方法及应用”写作框架,软考高级论文,系统架构设计师论文

论文真题 1、概要叙述你参与管理和开发的软件项目,以吸你所担的主要工作。 2、结给你参与管理和开发的软件项目&#xff0c;简要叙述单元测试中静态测试和动态测试方法的基本内容。 3、结给你惨与管理和研发的软件项目,体阐述在玩测试过程中,如何确定白盒测试的覆盖标准,及如…

Three.js机器人与星系动态场景:实现3D渲染与交互式控制

内容摘要&#xff1a;使用Three.js库构建了一个交互式的3D场景。组件中创建了一个机器人模型&#xff0c;包括头部、眼睛、触角、身体和四肢&#xff0c;以及两个相同的机器人实例以实现动态效果。场景中还加入了粒子效果&#xff0c;模拟星系环境&#xff0c;增强了视觉效果。…

设备调试上位机GUI

C Fast Qt C 前端 原来真的不需要在 design 上画来画去&#xff0c;有chat-gpt 那里不知道问哪里 全是组件拼起来的,不需要画,最后发现其实也是定式模式,跟着AI 学套路

JavaScript将参数传递给事件处理程序

本篇文件我们将实现导航栏中&#xff0c;选中时候&#xff0c;会将您选中的进行高亮显示&#xff1b; ● 首先我们来获取我们想要的HTML元素 const nav document.querySelector(.nav);● 接着我们来写选中的高亮显示 nav.addEventListener(mouseover, function (e) { //鼠…

Python系统教程01

Python 是一门解释性语言&#xff0c;相对更简单、易学&#xff0c;它可以用于解决数学问题、获取与分 析数据、爬虫爬取网络数据、实现复制数学算法等等。 1、print()函数&#xff1a; print()书写时注意所有的符号都是英文符号。print()输出内容时&#xff0c;若要输出字符…

安卓实现微信聊天气泡

一搜没一个能用的&#xff0c;我来&#xff1a; 布局文件&#xff1a; <?xml version"1.0" encoding"utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android"http://schemas.android.com/apk/res/android"xml…

【MySQL】数据库事务详解

文章目录 前言1. 事务的定义2. 事务的四个特性2.1 原子性2.2 一致性2.3 隔离性2.4 持久性 3. 事务的并发问题3.1 脏读3.2 不可重复读3.3 幻读3.4 更新丢失 4. 事务的隔离级别5. 事务的使用结语 前言 假设我们现在需要操作数据库进行转账&#xff0c;A 给 B 转账 100 块钱&…

掌握React与TypeScript:从零开始绘制中国地图

最近我需要使用reactts绘制一个界面&#xff0c;里面需要以中国地图的形式展示区块链从2019-2024年这五年的备案以及注销情况&#xff0c;所以研究了一下这方面的工作&#xff0c;初步有了一些成果&#xff0c;所以现在做一些分享&#xff0c;希望对大家有帮助&#xff01; 在这…

【Kotlin】Kotlin 基础语法指南

人不走空 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌赋&#xff1a;斯是陋室&#xff0c;惟吾德馨 目录 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌…

基于TCP/QT/C++的网盘系统测试报告

目录 一、项目介绍 1、项目描述 2、项目组成模块 3、项目技术要点 二、用户功能测试 1、查看在线用户测试 1.1、运行服务器 1.2、登录两个账号 1.3、点击显示在线用户&#xff0c;可以看到jack和lucy 2、搜索用户测试 2.1、打开服务器&#xff0c;登录两个账号jack,lucy 2.2、在…

嵌入式学习——硬件(IIC、ADC)——day56

1. IIC 1.1 定义&#xff08;同步串行半双工通信总线&#xff09; IIC&#xff08;Inter-Integrated Circuit&#xff09;又称I2C&#xff0c;是是IICBus简称&#xff0c;所以中文应该叫集成电路总线。是飞利浦公司在1980年代为了让主板、嵌入式系统或手机用以连接低速周边设备…

Linux高并发服务器开发(八)Socket和TCP

文章目录 1 IPV4套接字结构体2 TCP客户端函数 3 TCP服务器流程函数代码粘包 4 三次握手5 四次挥手6 滑动窗口 1 IPV4套接字结构体 2 TCP客户端 特点&#xff1a;出错重传 每次发送数据对方都会回ACK&#xff0c;可靠 tcp是打电话的模型&#xff0c;建立连接 使用连接 关闭连接…

【代码随想录】【算法训练营】【第49天】 [300]最长递增子序列 [674]最长连续递增序列 [718]最长重复子数组

前言 思路及算法思维&#xff0c;指路 代码随想录。 题目来自 LeetCode。 day 49&#xff0c;周二&#xff0c;坚持不了一点~ 题目详情 [300] 最长递增子序列 题目描述 300 最长递增子序列 解题思路 前提&#xff1a;最大递增子序列的长度 思路&#xff1a;动态规划 d…

RedHat9 | podman容器-续集

一、管理容器存储和网络资源 使用容器来运行简单的进程&#xff0c;然后退出。可以配置容连续运行特定服务&#xff0c;如数据库服务。如果持续运行服务&#xff0c;需要向容器添加更多的资源&#xff0c;如持久存储或对其他网络的访问权限。 针对企业容器平台上的大型部署&a…

汽车零部件材料耐候性测试氙光太阳辐射系统试验箱

概述 汽车零部件等领域的材料耐候性测试是一项关键的质量控制环节&#xff0c;它关乎汽车部件在各种气候条件下的性能表现和寿命。塑料件光照老化实验箱&#xff0c;即氙灯老化试验箱&#xff0c;在其中扮演着至关重要的角色。通过模拟自然环境中的光照、温度、湿度等条件&…

遇到多语言跨境电商系统源码问题?这里有解决方案!

从手机到电脑&#xff0c;从线下到线上&#xff0c;如今&#xff0c;跨境电商正在打破地域界限&#xff0c;成为全球贸易的新引擎。在这个全球化的背景下&#xff0c;跨境电商平台的运营也面临着一系列的挑战&#xff0c;其中之一就是多语言问题。如果你遇到了多语言跨境电商系…