天天看點

基于單片機的貪吃蛇遊戲

一. 點亮一個燈

這個很簡單,就是把某一行線置1,并且把某一列線置0,其他的行線全部為0,列線全部為1.

void Led(u8 dat,u8 dat2) //dat為行線值,dat2為列線值

{

u16 i;
RCLKS = 1;
SRCLK = 1;


for(i=0;i<8;i++)
{
//行線的輸入是由74HC595子產品輸入的,
//通過時鐘的上升沿把每一位的資料輸入即可,高位優先
    SET = dat>>7;
    dat = dat <<1;
    SRCLK = 0;
    SRCLK =1;
}

RCLKS = 0;

RCLKS = 1;
P0 = dat2;   //列線由P0端口輸入           

}

這裡令dat = 0x20,dat2=fb的時候,一個發光二極管就被點亮啦!

二. 一個燈的移動

前面我們已經知道了,行線可以控制那一行亮那一行不亮,列線也同樣如此。

假設當我們的列線不變,行線左移一位或者右移一位,是不是我們的燈向上或者向下移動了一個!行線不變時,列線的移動同樣如此,但是要注意,列線是為0點亮,每一次左移或右移,低位或高位就會多出來一個0,這時,我們還需要加上0x01或者0x80以消掉這個多出來的0

1.向上移動

列線不變,行線左移一位即可

if(Rows[0] != 0x80) //防止移過頭了

Rows[0] <<=1;           

2.向下移動

列線不變,行線右移一位即可

if(Rows[0] != 0x01)

Rows[0]>>=1;
           

3.向左移動

行線不變,列線左移一位加0x01即可

if(Cols[0] != 0x7F)

{
    Cols[0] <<=1;
    Cols[0] += 1;
}
           

4.向右移動

行線不變,列線右移一位加0x80即可

if(Cols[0] != 0xFE)

{
    Cols[0] >>=1;
    Cols[0] += 0x80;
}
           

自此,一個燈的上下左右的移動我們已經完成了,接下來蛇的移動了

三. 貪吃蛇

認真看玩過貪吃蛇的朋友應該都清楚,蛇身是随着蛇頭的移動而移動的。其中不難發現。目前蛇的某一節的位置是它前面一節蛇的上一個狀态的位置,知道了這個規律之後,我們就可以很友善地更新蛇的狀态了。

由于8×8點陣顯示有限,我就隻設定了蛇的最大長度為5,防止閃爍。

1.蛇的存儲

這裡我采取的是數組的方式,存儲每一節的行線值和列線值

u8 Rows[5]; //行線值

u8 Cols[5]; //列線值

2.蛇的更新

前面說過,蛇的目前蛇的某一節的位置是它前面一節蛇的上一個狀态的位置,也就是說,Rows[n-1] = Rows[n],Cols[n-1] = Cols[n]

當然這裡的Rows[n],和Cols[n]是上一個狀态的值了

void Up(u8 len) //目前蛇的長度

u8 i;
u8 tmp = Rows[0];
u8 rr,cc_1,cc_2;
if(Rows[0] != 0x80)
    Rows[0] <<=1;
cc_1 = Cols[0];
    //後一節依次等于前一節的上一個狀态的值
for(i=1;i<len;i++)
{
    
    rr = Rows[i];  // 儲存上一個狀态
    Rows[i] =tmp;  //Rows[i]為目前狀态
    tmp = rr;
    
    cc_2 = Cols[i];   // 儲存上一個狀态
    Cols[i] = cc_1;   
    cc_1 = cc_2;
}           

這裡就隻拿了向上移動的代碼作為參考,其他方向的移動類型,基本上一樣,這裡蛇的移動也完成了。

3.食物

食物的行線與列線同樣用兩個變量存儲。食物的位置是随機的,這裡我們用rand産生随機數并對8求餘數,這樣就得到了0-7的随機數,然後對0x01進行相應的左移,就得到了標明的行,取反就得到了標明的列。

Food_col = ~(0x01 << rand()%8);

Food_row = 0x01 << rand()%8;

4.吃到食物

這也是很好辦的事,隻需要判斷蛇頭的Row和Col是否等于食物的Row和Col,

如果相等,則更新食物的位置,蛇身加一.

if((Rows[0] == Food_row) && (Cols[0] == Food_col)) // 判斷是否吃到食物

{
        if(len<5)  //判斷蛇長是否達到最大值
        {
            Rows[len] = Rows[len-1];
            Cols[len] = Cols[len-1];
            len++;
        }
        //随機更新食物的位置
        Food_col = ~(0x01 << rand()%8);
        Food_row = 0x01 << rand()%8;
    }
           

自此,貪吃蛇可以說已經基本完成了,剩下的就是用鍵盤來控制蛇的移動方向了

四. 蛇方向的控制

方向的控制我把它放在中斷裡面,這樣可以解決如下問題

如果不用延遲,蛇移動得太快,根本看不清楚蛇的移動。

如果用延遲來控制蛇移動的速度,那麼鍵盤的讀取就會變動不靈敏,

要按下許久之後,才能讀取,遊戲效果不好。

是以我決定把它放在定時器中斷中,通過設定初值,以極小的時間差,來讀取鍵盤的内容,即可達到實時的效果,而且延時對這個完全沒有幹擾。

void Time_0() interrupt 1

u8 key;
key = KEY();
if(key != -1)
{
    if(key == 1)
    {
        //Up(len);
        direct = 0;
        delay(10000); // 不用也沒有關系,不會造成影響
    }
    if(key == 9)
    {
        //Down(len);
        direct = 1;
        delay(10000);
    }
    if(key == 4)
    {
        //Left(len);
        direct = 2;
        delay(10000);
    }
    if(key == 6)
    {
        //Right(len);
        direct = 3;
        delay(10000);
    }
}
//防止p過大
if(p>8*100)
    p = 0;
p++;
//每8次移動一下,這是合理的,由于每一次的時間差極小
//8次加起來的時間差也是非常小的,
//該時間差小于先後按下兩個不同方向的時間差,因為這是合理的
if(p%8 == 0)
{
switch(direct)
{
    case 0: Up(len);break;
    case 1: Down(len);break;
    case 2: Left(len);break;
    case 3: Right(len);break;
}
}
TH0 = 0x0c;
TL0 = 0x0c;           

五. main函數

這部分也是非常簡單的。

void main()

u8 i;
//u8 len = 1;  // 初始化長度,這個放到全局變量中了
u8 Food_row = 0x20;
u8 Food_col = 0xfb;
//u8 direct = 0;// 初始化方向,這個放到全局變量中了
Rows[0] = Row;  //初始化,蛇頭的位置
Cols[0] = Col;
Init_time_0();   //配置定時器0,開啟中斷
while(1)
{
    //判斷蛇頭是否到達食物的位置
    if((Rows[0] == Food_row) && (Cols[0] == Food_col))
    {
        if(len<5)
        {
            Rows[len] = Rows[len-1];
            Cols[len] = Cols[len-1];
            len++;
        }
                    //随機更新食物的位置
            Food_col = ~(0x01 << rand()%8);
            Food_row = 0x01 << rand()%8;
    }
    
    Led(Food_row,Food_col);  //顯示食物
    for(i=0;i<len;i++)
        Led(Rows[i],Cols[i]);  // 顯示蛇
        
    //這裡用延時來控制速度的話,容易産生閃爍。
}