【嵌入式】探索嵌入式世界:在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.mzph.cn/diannao/39084.shtml

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

相关文章

C++11新特性【下】

一、lambda表达式 在C98中&#xff0c;如果想要对一个数据集合中的元素进行排序&#xff0c;可以使用std::sort方法。如果待排序元素为自定义类型&#xff0c;需要用户定义排序时的比较规则&#xff0c;随着C语法的发展&#xff0c;人们开始觉得上面的写法太复杂了&#xff0c…

化身李时珍弟子,演绎中医药故事,李良济花神戏,创新传承中医药文化

6月29日&#xff0c;李良济与花神戏联袂举办的儿童剧本&#xff0c;在李良济嵩山店强势开启。 20余名小朋友&#xff0c;一起在这次中医药儿童剧本活动中&#xff0c;化身李时珍弟子&#xff0c;学中医&#xff0c;识草药&#xff0c;传承中医智慧&#xff0c;沉浸式学习传统文…

关于一维,二维正态分布的绘制

绘制一维正态分布代码 % 给定的均值和标准差 mu 0; % 例如&#xff0c;你可以改变这个值 sigma 1; % 例如&#xff0c;你可以改变这个值 % 定义x的范围&#xff08;例如&#xff0c;从mu-3*sigma到mu3*sigma&#xff0c;步长为0.1&#xff09; x mu - 3*sigma:0.1:m…

STM32 中断编程入门

目录 一、中断系统 1、中断的原理 2、中断类型 外部中断 定时器中断 DMA中断 3、中断处理函数 中断标志位清除 中断服务程序退出 二、实际应用 中断控制LED 任务要求 代码示例 中断控制串口通信 任务要求1 代码示例 任务要求2 代码示例 总结 学习目标&…

ROS学习笔记(17):建图与定位(1)

目录 0.前言 1.定位和建图 1.里程计&#xff08;Odometry&#xff09; 2.扫描匹配&#xff08;Scan Matching&#xff09; 3.结尾 0.前言 好久不见各位&#xff0c;前段时间忙着考试&#xff08;6级和一些专业课&#xff09;和摆烂断更了近30天&#xff0c;现在哥们回来更…

计算机毕业设计Python+Spark股票基金推荐与预测系统 股票基金可视化 股票基金推荐系统 股票基金可视化系统 股票基金数据分析 股票基金爬虫大数据

目 录 摘 要 Abstract 第1章 前 言 1.1 项目的背景和意义 1.2 研究现状 1.3 项目的目标和范围 1.4 论文结构简介 第2章 技术与原理 2.1 开发原理 2.2 开发工具 2.3 关键技术 第3章 需求建模 3.1 系统可行性分析 3.2 功能需求分析 3.3 非功能性…

高职人工智能专业实训课之“生成对抗网络(GAN)”

一、前言 生成对抗网络&#xff08;GAN&#xff09;作为人工智能领域的一项重要技术&#xff0c;已经在图像生成、风格迁移、数据增强等多个领域展现出巨大的潜力和应用价值。为了满足高职院校对GAN专业实训课程的需求&#xff0c;唯众人工智能教学实训凭借其前沿的教育技术平…

mst[讲课留档]

最小生成树(Minimum Spanning Tree) (1)概念 我们知道&#xff0c;树是有 n n n个结点&#xff0c; n − 1 n-1 n−1条边的无向无环的连通图。 一个连通图的生成树是一个极小的连通子图&#xff0c;它包含图中全部的 n n n个顶点&#xff0c;但只有构成一棵树的 n − 1 n-1 …

内容营销专家刘鑫炜:越是赚不到钱,越要加大推广力度

这两天&#xff0c;一位跟我们有长期合作关系的小微企业主老苏问我。 “现在钱这么不好赚&#xff0c;品牌推广应该怎么做&#xff1f;” 我说&#xff1a;“这是好机会&#xff0c;加大投放力度&#xff01;” 老苏很是不解&#xff0c;这时候不开源节流&#xff0c;还要加…

使用Git从Github上克隆仓库,修改并提交修改

前言 本次任务主要是进行github提交修改的操作练习实践&#xff0c;本文章是对实践过程以及遇到的问题进行的一个记录。 在此之前&#xff0c;我已经简单使用过github&#xff0c;Git之前已经下好了&#xff0c;所以就省略一些步骤。 步骤记录 注册github账号&#xff0c;gi…

【C++】C++指针在线程中调用与受保护内存空间读取方法

引言 在C的多线程编程中&#xff0c;正确地管理内存和同步访问是确保程序稳定性和安全性的关键。特别是当涉及到指针在线程中的调用时&#xff0c;对受保护内存空间的访问必须谨慎处理&#xff0c;以防止数据竞争、死锁和内存损坏等问题。本文将详细探讨C指针在线程中调用时如何…

易校网校园综合跑腿小程序源码修复运营版

简介&#xff1a; 易校网校园综合跑腿小程序源码修复运营版&#xff0c;带服务端客户端前端文档说明。 源码安装方法&#xff1a; 需要准备小程序服务号 服务器 备案域名 校园网跑腿小程序源码需要准备 1.小程序 2.服务器&#xff08;推荐配置2h4g3m&#xff09; 3.域名…

使用JMeter+Grafana+Influxdb搭建可视化性能测试监控平台

【背景说明】 使用jmeter进行性能测试时&#xff0c;工具自带的查看结果方式往往不够直观和明了&#xff0c;所以我们需要搭建一个可视化监控平台来完成结果监控&#xff0c;这里我们采用三种JMeterGrafanaInfluxdb的方法来完成平台搭建 【实现原理】 通过influxdb数据库存储…

开源模型应用落地-FastAPI-助力模型交互-WebSocket篇(五)

一、前言 使用 FastAPI 可以帮助我们更简单高效地部署 AI 交互业务。FastAPI 提供了快速构建 API 的能力,开发者可以轻松地定义模型需要的输入和输出格式,并编写好相应的业务逻辑。 FastAPI 的异步高性能架构,可以有效支持大量并发的预测请求,为用户提供流畅的交互体验。此外,F…

SpringBoot中整合ONLYOFFICE在线编辑

SpringBoot整合OnlyOffice SpringBoot整合OnlyOffice实现在线编辑1. 搭建私有的OnlyOffice的服务2. SpringBoot进行交互2.1 环境2.2 我们的流程2.3 接口规划2.3.1 获取编辑器配置的接口2.3.2 文件下载地址2.3.3 文件下载地址 3. 总结4. 注意4.1 你的项目的地址一定一定要和only…

三层交换基础

一、什么是三层交换 三层交换是一种在OSI模型第三层&#xff0c;即网络层上工作的网络设备技术&#xff0c;它整合了二层交换机的功能和路由器的部分功能&#xff0c;以实现更高效的网络数据转发和路由选择。三层交换技术的核心在于结合了二层交换技术和三层转发技术&#xff…

【RabbitMQ实战】Springboot 整合RabbitMQ组件,多种编码示例,带你实践 看完这一篇就够了

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、对RabbitMQ管理界面深入了解1、在这个界面里面我们可以做些什么&#xff1f; 二、编码练习&#xff08;1&#xff09;使用direct exchange(直连型交换机)&a…

2024 年的 13 个 AI 趋势

2024 年的 13 个 AI 趋势 人工智能对环境的影响和平人工智能人工智能支持的问题解决和决策针对人工智能公司的诉讼2024 年美国总统大选与人工智能威胁人工智能、网络犯罪和社会工程威胁人工智能治疗孤独与对人工智能的情感依赖人工智能影响者中国争夺人工智能霸主地位人工智能…

【Lua小知识】Vscode中Emmylua插件大量报错的解决方法

起因 Vscode写Lua用的好好的&#xff0c;最近突然出现了大量报错。 看报错是有未定义的全局变量&#xff0c;这里查日志才发现是由于0.7.5版本新增诊断启用配置&#xff0c;所以导致了原先好的代码&#xff0c;现在出现了大量的报错。 解决方案一 最直接的方法当然是在配置中直…

用摄像头实现识别道路中的车道线、行人与车辆检测(级联分类器、HOG+SVM、行人检测)

基于树莓派的智能小车&#xff0c;用摄像头实现识别道路中的车道线识别、行人检测与车辆检测。 本项目旨在开发一套基于摄像头的智能道路环境感知系统&#xff0c;该系统能够实时识别道路中的车道线、行人与车辆&#xff0c;为自动驾驶汽车、智能交通管理以及辅助驾驶系统提供关…