本帖最后由 MOPPLAYER 于 2016-9-30 12:43 编辑
前言:
本實作遊戲是回味古早的NOKIA年代的手機遊戲-貪食蛇,保留原來的遊戲規則和整合多方面驅動的實際應用,並按照需求修改了部分驅動和函式,並盡量優化其效能和減少Bug,第一版著重在於完整的NOKIA貪食蛇遊戲的重現,供小伙伴們參考學習
準備:
1. MM32F103開發板
2. SPI TFTLCD,箱內有附
3. MicroUSB傳輸線
實作:
1. 本版本和舊有的NOKIA手機遊戲的規則差異如下:
- 1. 保留原來四周牆壁可穿越的設定
- 2. 加分道具可放在下一版增加,第一版為最原始
- 3. 增加顯示最高分,並達到最高分時候死亡時會儲存
- 4. 頭部撞到自己身體才會死亡
- 5. 每吃到一個蛋,固定增加1分
复制代码
Fig. 1 NOKIA手機的經典遊戲-貪食蛇
2. 接下來根據MindMo tion所提供的TFTLCD來特製化此遊戲,首先思路策略是定義場地和蛇蛇大小,蛇蛇初始位置和移動
- 1. 定義蛇蛇大小為半徑3的圓
- 2. 盡可能提高場地,以橫屏表示,正方形表示的的話最大場地高度為240,因此寬度也是240
- 3. 要顯示場地方格,必須佔用1x2的寬度來繪製
- 4. 剩下238的寬度場地,為7的倍數
- 5. 半徑3的圓含圓心,則距離必須為3x2+1=7,連接下一個圓,代表身體,移動也必須為7的倍數
- 6. 初始位置必須讓圓不佔據到場地方格,因此圓心座標-3~+3必要空間,即至少>=3
- 7. 場地方格內的範圍為1~239,所以初始位置必須為7xn+1+3,1為場地方格寬,3為蛇蛇圓的半徑
- 8. 如此以來座標行走為4~235,最後一個位置為235+3=238,不會占用到239的場地方格,剛好佔滿場地內
- 9. 完整的流暢顯示貪食蛇的移動,不切到身體和場地方格
复制代码
3. 顯示方面的思路解決以後再來是遊戲規則邏輯
- 1. 蛇蛇移動時,頭部會移動至下一個7的倍數的座標,即圓心座標7x(n+1)+4
- 2. 消除掉最後一個蛇蛇的尾巴,即7x蛇蛇長度+4的圓心座標
- 3. 定義為動態陣列,隨時更新蛇蛇的頭部圓心座標,和消除最後的尾巴
- 4. 定義蛋蛋大小為2x2正方形,隨機出現在場地高度-2(蛋高度)-3(蛇身體半徑),寬度同理,範圍內
- 5. 每次移動一個身體的距離,同時判斷是否吃到蛋蛋,這裡定義頭部的圓心座標延伸2x2方形範圍和蛋大小2x2方形範圍有相等時才會吃到,吃到時再次亂數決定蛋蛋座標,增加分數1,消除原先蛋蛋,增加蛇蛇長度1,增加蛇蛇移動速度
- 6. 每次移動一個身體的距離,同時也判斷是否觸及身體,掃描整個身體圓心座標即可,相等時候就是碰觸,臨界時可以轉彎所以要下一次重疊時才算碰觸
- 7. 若死亡時,分數比最高分高,就將數字寫入SPI FLASH
- 8. 按下Reset可重新開始,不自動重新開始
复制代码
4. 特別注意的是方形和圓型的畫法
- 1. 方形的座標起點是在左上方,所以只有正偏移繪製
- 2. 圓形的座標起點是在圓心,所以有正負偏移繪製
复制代码
5. 定義按鈕,本版本只用兩個按鈕,因為實際上蛇蛇移動時,只能轉向兩個方向,後續版本會擴展到觸控,但考量回味機械式按鈕玩法,所以此版本保留按鈕,定義如下:
- KEY0 <=> 上和右
- KEY1 <=> 下和左
复制代码
6. 顯示分數的部分則採用5位數字,前面補0的顯示方式,和NOKIA類似,顯示在右上方,包含當前分數和最高分數,24字體大小
7. 色彩部分定義如下:
- 背景 <=> 白色
- 蛋蛋 <=> 藍色
- 場地方格框 <=> 紅色
- 當前分數 <=> 黑色
- 最高分數 <=> 灰色
- 蛇蛇頭部 <=> 黑色
- 蛇蛇身體 <=> 灰色
- 遊戲結束字串 <=> 綠色
复制代码
8. 速度上考量以後,初始採用0.1秒,每次吃到蛋蛋時,增加速度0.01秒,最快就是0.01秒,並且改寫計時方面的驅動,同時能支援亂數種子的變動
9. 綜合以上寫上程式碼,完整如下:
- #include "led.h"
- #include "delay.h"
- #include "sys.h"
- #include "uart.h"
- #include "lcd.h"
- #include "key.h"
- #include "spi.h"
- #include "malloc.h"
- #include
- #define FIELD_ORIG_X 1
- #define FIELD_ORIG_Y 1
- #define FIELD_WIDTH 239
- #define FIELD_HEIGHT 239
- #define SNAKE_SHIFT 4
- #define SNAKE_SPACE 7
- #define SNAKE_LARGE 3
- #define EGG_LARGE 2
- #define DATA_LEN 5
- #define TX_SIZE DATA_LEN
- #define RX_SIZE TX_SIZE
- enum
- {
- LEFT=0,
- RIGHT,
- UP,
- DOWN
- };
- s16 *snake_x;
- s16 *snake_y;
- u16 snake_len=3;
- u8 snake_dir=RIGHT;
- u16 snake_speed=100;
- s16 egg_x,egg_y;
- u16 score=0;
- u8 btn;
- unsigned char high[DATA_LEN];
- void EXTI0_IRQHandler(void)
- {
- if(EXTI_GetITStatus(EXTI_Line0) != RESET)
- {
- delay_ms(10);
- if(WK_UP)
- {
- btn=WKUP_PRES;
- }
- EXTI_ClearITPendingBit(EXTI_Line0);
- }
- }
- void EXTI9_5_IRQHandler(void)
- {
- if(EXTI_GetITStatus(EXTI_Line5) != RESET)
- {
- delay_ms(10);
- if(!KEY0)
- {
- btn=KEY0_PRES;
- }
- EXTI_ClearITPendingBit(EXTI_Line5);
- }
- }
- void EXTI1_IRQHandler(void)
- {
- if(EXTI_GetITStatus(EXTI_Line1) != RESET)
- {
- delay_ms(10);
- if(!KEY1)
- {
- btn=KEY1_PRES;
- }
- EXTI_ClearITPendingBit(EXTI_Line1);
- }
- }
- void draw_rectangle_fill(u16 x1, u16 y1, u16 x2, u16 y2, u16 color)
- {
- POINT_COLOR=color;
- LCD_DrawLine(x1,y1,x2,y1);
- LCD_DrawLine(x1,y1,x1,y2);
- LCD_DrawLine(x1,y2,x2,y2);
- LCD_DrawLine(x2,y1,x2,y2);
- LCD_Fill(x1,y1,x2,y2,color);
- }
- void draw_circle_fill(u16 x0,u16 y0,u8 r,u16 color)
- {
- int a,b;
- int di;
- a=0;b=r;
- di=3-(r<<1); //判断下个点位置的标志
- POINT_COLOR=color;
- while(a<=b)
- {
- LCD_DrawPoint(x0+a,y0-b); //5
- LCD_DrawPoint(x0+b,y0-a); //0
- LCD_DrawPoint(x0+b,y0+a); //4
- LCD_DrawPoint(x0+a,y0+b); //6
- LCD_DrawPoint(x0-a,y0+b); //1
- LCD_DrawPoint(x0-b,y0+a);
- LCD_DrawPoint(x0-a,y0-b); //2
- LCD_DrawPoint(x0-b,y0-a); //7
- LCD_Fill(x0-a,y0-b,x0+a,y0+b,color);
- LCD_Fill(x0-b,y0-a,x0+b,y0+a,color);
- a++;
- //使用Bresenham算法画圆
- if(di<0)di +=4*a+6;
- else
- {
- di+=10+4*(a-b);
- b--;
- }
- }
- }
- void draw_snake(void)
- {
- draw_rectangle_fill(egg_x,egg_y,egg_x+EGG_LARGE,egg_y+EGG_LARGE,BLUE);
- draw_circle_fill(snake_x[0],snake_y[0],SNAKE_LARGE,BLACK);
- draw_circle_fill(snake_x[1],snake_y[1],SNAKE_LARGE,GRAY);
- draw_circle_fill(snake_x[snake_len],snake_y[snake_len],SNAKE_LARGE,WHITE);
- }
- void check_btn(void)
- {
- switch (btn)
- {
- case KEY0_PRES:
- {
- if(snake_dir == RIGHT)
- snake_dir = UP;
- else if (snake_dir == LEFT)
- snake_dir = UP;
- else if (snake_dir == UP)
- snake_dir = RIGHT;
- else if (snake_dir == DOWN)
- snake_dir = RIGHT;
- break;
- }
- case KEY1_PRES:
- {
- if(snake_dir == RIGHT)
- snake_dir = DOWN;
- else if (snake_dir == LEFT)
- snake_dir = DOWN;
- else if (snake_dir == UP)
- snake_dir = LEFT;
- else if (snake_dir == DOWN)
- snake_dir = LEFT;
- break;
- }
- }
- }
- bool check_game(void)
- {
- u16 i;
- for(i=1;i< snake_len;i++)
- {
- if(snake_x[i]==snake_x[0] && snake_y[i]==snake_y[0])
- return true;
- }
- return false;
- }
- void change_array_size_t(void)
- {
- s16 *temp_x;
- s16 *temp_y;
-
- temp_x=myrealloc(snake_x,snake_len*sizeof(s16));
-
- if(snake_x!=temp_x)
- snake_x=temp_x;
-
- temp_y=myrealloc(snake_y,snake_len*sizeof(s16));
-
- if(snake_y!=temp_y)
- snake_y=temp_y;
-
- }
- void check_egg(void)
- {
- if( snake_x[0]==egg_x //D=0
- || snake_x[0]+1==egg_x //D=1
- || snake_x[0]==egg_x+1 //D=1
- || snake_x[0]+2==egg_x //D=2
- || snake_x[0]==egg_x+2 //D=2
- || snake_x[0]-1==egg_x+2 //D=3
- || snake_x[0]-2==egg_x+2 //D=4
- )
- {
- if( snake_y[0]==egg_y //D=0
- || snake_y[0]+1==egg_y //D=1
- || snake_y[0]==egg_y+1 //D=1
- || snake_y[0]+2==egg_y //D=2
- || snake_y[0]==egg_y+2 //D=2
- || snake_y[0]-1==egg_y+2 //D=3
- || snake_y[0]-2==egg_y+2 //D=4
- )
- {
- if(snake_speed>10)
- snake_speed--;
- snake_len++;
- score++;
-
- POINT_COLOR=BLACK;
- LCD_ShowxNum(250,5,score,DATA_LEN,24,0x80);
-
- change_array_size_t();
-
- draw_rectangle_fill(egg_x,egg_y,egg_x+EGG_LARGE,egg_y+EGG_LARGE,WHITE);
-
- srand(micros());
- egg_x=rand()%(FIELD_WIDTH-2*(EGG_LARGE+SNAKE_LARGE))+(EGG_LARGE+SNAKE_LARGE);// 5-233
- egg_y=rand()%(FIELD_HEIGHT-2*(EGG_LARGE+SNAKE_LARGE))+(EGG_LARGE+SNAKE_LARGE);
-
- }
- }
- }
- void move_snake(void)
- {
- s16 temp_x,temp_y;
- s16 orig_x,orig_y;
- u16 i;
- printf("x=%d,y=%drn",snake_x[0],snake_y[0]);
- check_btn();
- btn=NO_PRESS;
-
- switch (snake_dir)
- {
- case LEFT:
- {
- temp_x=snake_x[0]-SNAKE_SPACE;
- temp_y=snake_y[0];
- break;
- }
-
- case RIGHT:
- {
- temp_x=snake_x[0]+SNAKE_SPACE;
- temp_y=snake_y[0];
- break;
- }
-
- case UP:
- {
- temp_x=snake_x[0];
- temp_y=snake_y[0]-SNAKE_SPACE;
- break;
- }
-
- case DOWN:
- {
- temp_x=snake_x[0];
- temp_y=snake_y[0]+SNAKE_SPACE;
- break;
- }
- }
-
- if(temp_x>=(FIELD_WIDTH))
- temp_x-=(FIELD_WIDTH-FIELD_ORIG_X);
- else if (temp_x<=(FIELD_ORIG_X))
- temp_x+=(FIELD_WIDTH-FIELD_ORIG_X);
-
- if (temp_y>=(FIELD_HEIGHT))
- temp_y-=(FIELD_HEIGHT-FIELD_ORIG_Y);
- else if (temp_y<=(FIELD_ORIG_Y))
- temp_y+=(FIELD_HEIGHT-FIELD_ORIG_Y);
-
-
- for(i=0; i<=snake_len;i++)
- {
- orig_x=snake_x[i];
- orig_y=snake_y[i];
- snake_x[i]=temp_x;
- snake_y[i]=temp_y;
- temp_x=orig_x;
- temp_y=orig_y;
- }
- draw_snake();
- }
- u32 int_pow(u8 m,u8 n)
- {
- u32 result=1;
- while(n--)
- result*=m;
- return result;
- }
- bool save_high()
- {
- u8 temp,t;
- unsigned char buf[DATA_LEN];
- bool highest=false;
-
- if(atoi((char*)high)
- {
- highest=true;
- for(t=0;t<5;t++)
- {
- temp=(score/int_pow(10,4-t))%10;
- if (temp==0)
- {
- buf[t]='0';
- }
- else
- {
- buf[t]='0'+temp;
- }
- }
- SPIM_SectorErase(SPI1,0);
- delay_ms(100);
- printf("scores=%srn",buf);
- SPIM_PageProgram(SPI1,0,buf,TX_SIZE);
- delay_ms(100);
- }
- return highest;
- }
- int main(void)
- {
- u16 i;
- snake_x=mymalloc(snake_len*sizeof(s16));
- snake_y=mymalloc(snake_len*sizeof(s16));
-
- NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
- delay_init(); //延时函数初始化
- NVIC_SetPriority(SysTick_IRQn,0);
-
- uart_init(9600); //串口初始化为9600
- LED_Init(); //初始化与LED连接的硬件接口
- LCD_Init();
- KEY_Init();
- SPIM_Init(SPI1,0x2);//SPI1频率设置为36M
-
- while(SPIM_ReadID(SPI1))
- {
- printf("S25FL008A Check Failed!rn");
- delay_ms(500);
- printf("Please Check! rn");
- delay_ms(500);
- LED0=!LED0;//DS0闪烁
- }
-
- LCD_Clear(WHITE);
-
- POINT_COLOR=RED;
- LCD_DrawRectangle(0,0,239,239);
-
- for(i=0;i
- {
- snake_x[i]=140+SNAKE_SHIFT-SNAKE_SPACE*i;
- snake_y[i]=140+SNAKE_SHIFT;
- }
-
- draw_circle_fill(snake_x[0],snake_y[0],SNAKE_LARGE,BLACK);
- for(i=1;i
- draw_circle_fill(snake_x[i],snake_y[i],SNAKE_LARGE,GRAY);
-
- egg_x=rand()%(FIELD_WIDTH-2*(EGG_LARGE+SNAKE_LARGE))+(EGG_LARGE+SNAKE_LARGE);// 5-233
- egg_y=rand()%(FIELD_HEIGHT-2*(EGG_LARGE+SNAKE_LARGE))+(EGG_LARGE+SNAKE_LARGE);
- draw_rectangle_fill(egg_x,egg_y,egg_x+EGG_LARGE,egg_y+EGG_LARGE,BLUE);
-
- POINT_COLOR=BLACK;
- LCD_ShowxNum(250,5,score,DATA_LEN,24,0x80);
-
- SPIM_PageRead(SPI1,0,(unsigned char*)high,RX_SIZE);
- delay_ms(100);
- printf("scores=%srn",high);
-
- POINT_COLOR=GRAY;
- LCD_ShowxNum(250,30,atoi((char*)high),DATA_LEN,24,0x80);
-
- while(1)
- {
- move_snake();
- if(check_game())
- break;
- check_egg();
-
- LED0=!LED0;
- delay_ms(snake_speed);
- }
- POINT_COLOR=GREEN;
- if(save_high())
- LCD_ShowString(20,100,200,24,24,(u8 *)"YOU GOT HIGH SCORES");
- LCD_ShowString(60,130,200,24,24,(u8 *)"GAME OVER");
- }
复制代码
其中delay關於計時有做改寫,key的話改寫成外部中斷觸發,malloc和LCD,SPI驅動則直接取用官方提供的,程式碼部分說明可參考前面思路策略來對照,並且盡量去除Magic Number,淺顯易懂,另外我去除LCD官方初始函式的Lcd_Init()內無關此型號的TFT程式碼
10. 編譯好後開始燒寫,用官方提供的燒寫工具或者SWD/JTAG都可,記得BOOT0腳位的套帽要套在高電位
Fig. 2 官方燒寫工具
11. 開始驗證,燒寫完後要改回BOOT0套帽位置
Fig. 3 初始畫面
Fig. 4 蛇蛇轉彎
Fig. 5 蛇蛇,蛇行!
Fig. 6 蛇蛇穿越
12. 剩下的就給小伙伴們體驗了,並附上燒寫檔案可直接測試
V1.1 修正蛋蛋位置問題
|