【嵌入式】探索嵌入式世界:在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.pswp.cn/diannao/39084.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/39084.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/39084.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

C++11新特性【下】

一、lambda表達式 在C98中&#xff0c;如果想要對一個數據集合中的元素進行排序&#xff0c;可以使用std::sort方法。如果待排序元素為自定義類型&#xff0c;需要用戶定義排序時的比較規則&#xff0c;隨著C語法的發展&#xff0c;人們開始覺得上面的寫法太復雜了&#xff0c…

自動備份Docker中的mysql數據庫

先說一下&#xff0c;在Linux下備份mysql 1、先創建一個腳本文件 #!/bin/bash # MySQL 用戶、密碼、數據庫名稱 DB_USER"dbuser" DB_PASSWORD"dbpassword" DATABASE"mydatabase" # 創建備份目錄 BACKUP_DIR"/path/to/your/backup/dire…

化身李時珍弟子,演繹中醫藥故事,李良濟花神戲,創新傳承中醫藥文化

6月29日&#xff0c;李良濟與花神戲聯袂舉辦的兒童劇本&#xff0c;在李良濟嵩山店強勢開啟。 20余名小朋友&#xff0c;一起在這次中醫藥兒童劇本活動中&#xff0c;化身李時珍弟子&#xff0c;學中醫&#xff0c;識草藥&#xff0c;傳承中醫智慧&#xff0c;沉浸式學習傳統文…

Spring Boot與Apache Kafka的深度集成

Spring Boot與Apache Kafka的深度集成 大家好&#xff0c;我是免費搭建查券返利機器人省錢賺傭金就用微賺淘客系統3.0的小編&#xff0c;也是冬天不穿秋褲&#xff0c;天冷也要風度的程序猿&#xff01;今天我們將探討如何在Spring Boot應用中實現與Apache Kafka的深度集成&am…

關于一維,二維正態分布的繪制

繪制一維正態分布代碼 % 給定的均值和標準差 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 非功能性…

C++Primer Plus 第十四章代碼重用:編程練習,第一題

CPrimer Plus 第十四章代碼重用&#xff1a;編程練習,第一題 提示&#xff1a;這里可以添加系列文章的所有文章的目錄&#xff0c;目錄需要自己手動添加 CPrimer Plus 第十四章代碼重用&#xff1a;編程練習,第一題 提示&#xff1a;寫完文章后&#xff0c;目錄可以自動生成&am…

高職人工智能專業實訓課之“生成對抗網絡(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指針在線程中調用時如何…

理解 React 的嚴格模式

文章目錄 有什么優劣優點&#xff1a;缺點&#xff1a; 使用場景如何使用為整個應用啟用嚴格模式一部分代碼啟用嚴格模式 React 的 Strict Mode&#xff08;嚴格模式&#xff09;是一種用于檢測應用中潛在問題的開發工具。它不會渲染任何可見的 UI 元素&#xff0c;而是通過激活…

element-ui如何做表單驗證

Element UI 使用表單驗證通常涉及兩個主要組件&#xff1a;el-form 和 el-form-item。 el-form 負責管理表單數據和驗證規則&#xff0c;而 el-form-item 用于定義需要驗證的表單項。 <template><el-form :model"form" :rules"rules" ref"fo…

易校網校園綜合跑腿小程序源碼修復運營版

簡介&#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…

【圖論】樹鏈剖分

樹鏈剖分詳解 - 自為風月馬前卒 - 博客園 (cnblogs.com) P3384 【模板】重鏈剖分/樹鏈剖分 - 洛谷 | 計算機科學教育新生態 (luogu.com.cn) #include<iostream> using namespace std;void dfs1(int u,int father){ fa[u]father; dep[u]dep[father]1; sz[u]1;for(int ih…