基于FPGA的贪吃蛇游戏 之代码解析

fpga · 浏览次数 : 0

小编点评

<= 1'b0;end                            snake_display <= 1'b1;end                        else if(flash_cnt == 25_000_000)begin//0.5-1秒低                            snake_display <= 1'b1;end                        else if(flash_cnt == 37_500_000)begin//1-1.5秒高                            snake_display <= 1'b0;end                        else if(flash_cnt == 50_000_000)begin//1.5-2秒低                            snake_display <= 1'b1;end                        else if(flash_cnt == 62_500_000)begin//2-2.5秒高                            snake_display <= 1'b0;end                        else begin                        game_status <= RESTART; //游状态 从游戏结束 到游戏重启                    end            endcase  

正文

基于FPGA的贪吃蛇游戏 之代码解析

1. 代码结构

代码结构包含7.v文件。

 

 

下面依次解析。

 

 

 

2. 代码解析

(1) seg_display.v

数码管的译码模块是最熟悉,最简单的模块了。这里是共阳极的数码管,用case语句编码即可。从上图可以看到,这个模块被例化了3次,分别驱动3个数码管显示,百,十,个位的数字。

always @(seg_data)

begin

        case(seg_data)

        4'b0001: seg_out= 7'b1111001;       //数码管显示数字1  

        4'b0010: seg_out= 7'b0100100;   //数码管显示数字2  

        4'b0011: seg_out= 7'b0110000;   //数码管显示数字3  

        4'b0100: seg_out= 7'b0011001;   //数码管显示数字4  

        4'b0101: seg_out= 7'b0010010;   //数码管显示数字5  

        4'b0110: seg_out= 7'b0000010;   //数码管显示数字6  

        4'b0111: seg_out= 7'b1111000;   //数码管显示数字7  

        4'b1000: seg_out= 7'b0000000;   //数码管显示数字8  

        4'b1001: seg_out= 7'b0011000;   //数码管显示数字9  

        4'b0000: seg_out= 7'b1000000;       //数码管显示数字0

        endcase

end

 

(2) score_ctrl.v

数码管计分模块,稍稍难一点。要实现游戏中吃一个食物,加一分,并把分数转换成BCD码,以百、十、个位,送到3个数码管显示。

计分的结果bin_data,其实就是一个8位的计数器,因为游戏规则限定了计分的最大值是100,也就限定了这个计数器的位宽。那么就按常规的计数器设计,用ifelse语句,按优先级来设计先后顺序。先判断复位,计数结果清零,再判断是否计到最大值,是也清零,否就加一,最后剩余的情况就是计数结果保持不变。当然,这个模块比常规的计数器设计多了判断是否在RESTART状态,是也清零,加1的条件要看是否吃到食物,即add_cube是否为1。然后就是用通用的取模方式,取百、十、个的数字。

always@(posedge clk or negedge rst_n) begin

        if(!rst_n)                                       //复位时分数归零

            bin_data <= 0;

        else if(game_status==RESTART)                   //重启状态下分数归零

            bin_data <= 0;  

        else if(add_cube==1 && bin_data < 8'd100)           //当分数不超过100的时候,蛇每吃掉一个苹果计数器就+1

            bin_data <= bin_data + 1;

        else 

            bin_data <= bin_data;

        end

   

    assign bcd_data[3:0]  = bin_data%10;                //算出十进制数的个位

    assign bcd_data[7:4]  = (bin_data/10)%10;           //算出十进制数的十位

    assign bcd_data[11:8] = (bin_data/100)%10;      //算出十进制数的百位

 

(3) VGA_ctrl.v

VGA控制模块,实现:

① 游戏开始时,显示欢迎界面,就是存存在rom里的图片;

② 显示游戏难度的色块和字符色块;

③ 游戏进行中,显示蛇身和食物;

④ 游戏结束,显示分数。

 

这里用的是640*480@60Hz的模式,用ADV7123驱动VGA端口。VGA显示,也是整个设计里最核心的部分,首先,需要弄清楚行扫描和场扫描的时序。

 

 

 

 

当然,每一段的参数很容易查到。难点在于理解两种同步信号的时序,先后由哪些段组成。然后,再对照代码去理解同步信号的高低电平持续的时间长度,就很容易了。这里,系统时钟为何选择25MHz,也是根据640*480*60近似得到的。

第二个难点,就是显示的对象是图片,文字,色块等等多种,需要不同的存储方式,其中色块的划分,是最基础的,很多图形都是由基本的色块组成。代码里用了case区分扫描的对象种类,snake_show这个信号,来判断扫描到的是什么对象,再分别定义显示。

第三个就是坐标的区分,因为用到了色块来表示不同的对象,而色块又是由一个个的像素点组成的,所以要弄清楚比如:食物的坐标,像素的坐标等等,还有各自的有效范围。

VGA_ctrl模块的框图如下:

 

 

根据框图,比较容易判断信号的输入、输入属性。

代码首先就是端口声明,食物的坐标行比列位宽大,是根据640480来判断的。bcd_data是计分模块输入的百、十、个的值,game_status是游戏的状态,snake_show是显示的对象,即扫描的点是什么,vga_blank_nADV7123的消隐信号,就是在非有效显示区域为0vga_hs,vga_vs是行扫描和场扫描信号,vga_rgb888RGB信号。pos_x,pos_y是像素的坐标。

下面依次解析这个代码:

1) 为了使代码更清晰,增强代码的可读性,游戏状态,扫描(显示)对象,色彩都用本地参数定义。

localparam RESTART = 2'b00;        //游戏重启

    localparam START = 2'b01;           //游戏开始

    localparam PLAY = 2'b10;            //游戏进行

    localparam DIE = 2'b11;             //游戏结束

 

    localparam NONE = 2'b00;

    localparam HEAD = 2'b01;

    localparam BODY = 2'b10;

    localparam WALL = 2'b11;

   

    localparam  RED = 24'b11111111_00000000_00000000; //红色  

    localparam  GREEN = 24'b00000000_111111111_00000000; //绿色  

    localparam  BLUE = 24'b00000000_00000000_11111111; //蓝色

     localparam YELLOW = 24'b11111111_11111111_00000000; //黄色

    localparam  PINK = 24'b11111111_00000000_11111111; //粉色

     localparam WHITE = 24'b11111111_11111111_11111111; //白色

     localparam BLACK = 24'b00000000_00000000_00000000; //黑色

2) 先设计行周期和场周期计数器,再用计数结果生产同步信号。

// 行周期计数器的实现

    always @ (posedge clk, negedge rst_n)

        if (!rst_n)

            cnt_hs <= 0;

        else

            if (cnt_hs < HS_E - 1)

                cnt_hs <= cnt_hs + 1'b1;

            else

                cnt_hs <= 0;

                     

    // 场周期计数器的实现        

    always @ (posedge clk, negedge rst_n)

        if (!rst_n)

            cnt_vs <= 0;

        else

            if (cnt_hs == HS_E - 1)

                if (cnt_vs < VS_E - 1)

                    cnt_vs <= cnt_vs + 1'b1;

                else

                    cnt_vs <= 0;

            else

                cnt_vs <= cnt_vs;

                     

    // 行同步信号时序的产生

    always @ (posedge clk, negedge rst_n)

        if (!rst_n)

            vga_hs <= 1'b1;

        else

            if (cnt_hs < HS_A - 1) //同步之前vga_hs信号都是低, 同步之后(a)vga_hs信号是高

                vga_hs <= 1'b0;

            else

                vga_hs <= 1'b1;

     

     // 场同步信号时序的产生        

    always @ (posedge clk, negedge rst_n)

        if (!rst_n)

            vga_vs <= 1'b1;

        else

            if (cnt_vs < VS_A - 1) //同步之前vga_vs 信号都是低, 同步之后(a)vga_vs 信号是高

                vga_vs <= 1'b0;

            else

                vga_vs <= 1'b1;

然后,用行有效段和列有效段圈定有效显示区域。

assign en_hs = (cnt_hs > HS_A + HS_B - 1)&& (cnt_hs < HS_E - HS_D);//en_vs 将有效数据q段标出来了,有效数据q段en_hs 才为高,否则为低

        assign en_vs = (cnt_vs > VS_A + VS_B - 1) && (cnt_vs < VS_E - VS_D);//将vga显示的有效像素点位置全部标注出来了

        assign en = en_hs && en_vs;

        assign vga_blank_n = en;

像素的坐标范围也是在有效显示区内,所以

assign pos_x = en ? (cnt_hs - (HS_A + HS_B - 1'b1)) : 0;

        assign pos_y = en ? (cnt_vs - (VS_A + VS_B - 1'b1)) : 0;

过程语句块里,需要处理不同的状态,不同的输出。两个参数:cnt_clk用来计时6秒,显示欢迎界面的图片,cnt用来计时4秒,在游戏结束状态,蛇身闪烁。

显示图片

else if ( game_status == RESTART) begin

                cnt<=0;

                if(cnt_clk < 150000000 )begin//“欢迎来到贪吃蛇游戏”的画面停留6s 时钟25M 0.04us*150_000_000=6s

                    cnt_clk <= cnt_clk+1;  

                    if(picture_flag_enable) begin//picture_flag_enable不等同于en,因为picture_flag_enable可以是比640*480还小的区域

                        vga_rgb <= rom_data;

                    end

                    else begin

                        vga_rgb<= 24'b000000000000000000000000;

                    end

                end

显示字符加色块

else if(cnt_clk >= 150000000) begin

                    if(pos_x[9:4] >=15 && pos_x[9:4] < 25 && pos_y[9:4] >= 8 && pos_y[9:4] < 10&& char[char_y][159-char_x] == 1'b1) begin

                        vga_rgb<= WHITE; end//显示“请选择难度” 字符

                       

                    else if(pos_x[9:4] >=17 && pos_x[9:4] < 18 && pos_y[9:4] >= 15 && pos_y[9:4] < 16) begin

                        vga_rgb<= GREEN;end//显示“容易”的绿方块

                       

                    else if(pos_x[9:4] >=19 && pos_x[9:4] < 20 && pos_y[9:4] >= 15 && pos_y[9:4] < 16)begin

                        vga_rgb<= YELLOW;end//显示“中等”的黄方块

                       

                    else if(pos_x[9:4] >=21 && pos_x[9:4] < 22 && pos_y[9:4] >= 15 && pos_y[9:4] < 16)begin

                        vga_rgb<= RED;end//显示“困难”的红方块

                    else begin

                        vga_rgb<= BLACK;end

                end

显示墙,蛇,空气,食物

else if ( game_status == PLAY|game_status == START) begin//在游戏开始状态下 扫描食物、蛇头、蛇身体、墙

                //led[0]<=1;

                cnt<=0;

                if(pos_x[9:4] == apple_x && pos_y[9:4] == apple_y) begin

                    vga_rgb = PINK;

                end                

                else if(snake_show == NONE) begin

                    vga_rgb = BLACK; end

                else if(snake_show == WALL) begin

                    vga_rgb = RED;end

                else if(snake_show == HEAD|snake_show == BODY) begin

                    //vga_rgb = (snake_show == HEAD) ?  GREEN : BLUE;

                    case({pos_x[3:0],pos_y[3:0]})

                        8'b00000000:vga_rgb = BLACK;

                        8'b00000001:vga_rgb = BLACK;

                        8'b00000010:vga_rgb = BLACK;

                        default:vga_rgb = (snake_show == HEAD) ?  GREEN : BLUE;

                    endcase

                end

                else begin

                    vga_rgb<= BLACK;

                end

            end

显示100,比较繁琐,就是用色块组成100的形状

else if(bcd_data[11:8]==1'd1)begin//当计分达到100则封顶,代表游戏成功

                if(pos_x[9:4] >= 12 && pos_x[9:4] < 14 && pos_y[9:4] >= 8 && pos_y[9:4] < 16)

                    vga_rgb = 24'hff80ff;

                else if(pos_x[9:4] >= 12 && pos_x[9:4] < 14 && pos_y[9:4] >= 14 && pos_y[9:4] < 22)

                    vga_rgb = 24'hff80ff;

 

                else if(pos_x[9:4] >= 16 && pos_x[9:4] < 24 && pos_y[9:4] >= 8 && pos_y[9:4] < 10)

                    vga_rgb = 24'hff80ff;

                else if(pos_x[9:4] >= 16 && pos_x[9:4] < 24 && pos_y[9:4] >= 20 && pos_y[9:4] < 22)

                    vga_rgb = 24'hff80ff;

                else if(pos_x[9:4] >= 16 && pos_x[9:4] < 18 && pos_y[9:4] >= 8 && pos_y[9:4] < 16)

                    vga_rgb = 24'hff80ff;

                else if(pos_x[9:4] >= 22 && pos_x[9:4] < 24 && pos_y[9:4] >= 8 && pos_y[9:4] < 16)

                    vga_rgb = 24'hff80ff;

                else if(pos_x[9:4] >= 16 && pos_x[9:4] < 18 && pos_y[9:4] >= 14 && pos_y[9:4] < 22)

                    vga_rgb = 24'hff80ff;

                else if(pos_x[9:4] >= 22 && pos_x[9:4] < 24 && pos_y[9:4] >= 14 && pos_y[9:4] < 22)

                    vga_rgb = 24'hff80ff;

 

                else if(pos_x[9:4] >= 26 && pos_x[9:4] < 34 && pos_y[9:4] >= 8 && pos_y[9:4] < 10)

                    vga_rgb = 24'hff80ff;

                else if(pos_x[9:4] >= 26 && pos_x[9:4] < 34 && pos_y[9:4] >= 20 && pos_y[9:4] < 22)

                    vga_rgb = 24'hff80ff;

                else if(pos_x[9:4] >= 26 && pos_x[9:4] < 28 && pos_y[9:4] >= 8 && pos_y[9:4] < 16)

                    vga_rgb = 24'hff80ff;

                else if(pos_x[9:4] >= 32 && pos_x[9:4] < 34 && pos_y[9:4] >= 8 && pos_y[9:4] < 16)

                    vga_rgb = 24'hff80ff;

                else if(pos_x[9:4] >= 26 && pos_x[9:4] < 28 && pos_y[9:4] >= 14 && pos_y[9:4] < 22)

                    vga_rgb = 24'hff80ff;

                else if(pos_x[9:4] >= 32 && pos_x[9:4] < 34 && pos_y[9:4] >= 14 && pos_y[9:4] < 22)

                    vga_rgb = 24'hff80ff;

                else 

                    vga_rgb = BLACK;

                end

DIE状态,就分4秒前和4秒后,先显示不同对象,再显示分数。

(4)apple_generate.v

食物模块两个作用,一是产生食物坐标,二是判断食物是否被吃掉。

食物坐标用加法随机产生

always@(posedge clk)

        random_num <= random_num + 999;  //用加法产生随机数  

        //随机数高六位为食物x的坐标,低五位为苹果Y坐标

 

if(apple_x == head_x && apple_y == head_y) begin//当蛇头坐标和苹果坐标一样时,表示蛇吃掉一个苹果

                add_cube <= 1;

                apple_x <= (random_num[10:5] > 38) ? (random_num[10:5] - 25) : (random_num[10:5] == 0) ? 1 : random_num[10:5];

                apple_y <= (random_num[4:0] > 28) ? (random_num[4:0] - 3) : (random_num[4:0] == 0) ? 1:random_num[4:0];

            end    //判断随机数是否超出频幕坐标范围 将随机数转换为下个苹果的X Y坐标

 

(5)snake.v

蛇运动情况控制模块。

蛇移动的速度,由三个拨动开关选定。所谓速度其实就是隔多久移动一次。蛇身共16节,运动结果有三种状态:撞墙、撞自身、移动。撞墙就是判断蛇头的坐标是否与四面墙的坐标相同,撞自身就是判断蛇头的坐标是否与后面15节蛇身的坐标有相同,移动就是把前一节的坐标赋给后一节,并再次判断蛇头的坐标是否撞墙,若否,重新定义蛇头的坐标。

else begin

            clk_cnt <= clk_cnt + 1;

                if(clk_cnt == speed) begin

                    clk_cnt <= 0;

                    if(game_status==PLAY) begin

                        if((direct_r==UP && cube_y[0] == 1)||(direct_r==DOWN && cube_y[0] == 28)||(direct_r==LEFT && cube_x[0] == 1)||(direct_r==RIGHT && cube_x[0] == 38))begin

                            hit_wall <= 1; end//撞到墙壁                

                            //如果蛇是向上运动,且蛇头的y坐标跟上面墙的y坐标(cube_y[0] == 1)重合

                            //如果蛇是向下运动,且蛇头的y坐标跟下面墙的y坐标(cube_y[0] == 28)重合

                            //如果蛇是向左运动,且蛇头的x坐标跟左面墙的x坐标(cube_x[0] == 1)重合

                            //如果蛇是向右运动,且蛇头的x坐标跟上面墙的x坐标(cube_x[0] == 38)重合

                                           

                        else if((cube_y[0] == cube_y[1] && cube_x[0] == cube_x[1] && is_exist[1] == 1)||

                                (cube_y[0] == cube_y[2] && cube_x[0] == cube_x[2] && is_exist[2] == 1)||

                                (cube_y[0] == cube_y[3] && cube_x[0] == cube_x[3] && is_exist[3] == 1)||

                                (cube_y[0] == cube_y[4] && cube_x[0] == cube_x[4] && is_exist[4] == 1)||

                                (cube_y[0] == cube_y[5] && cube_x[0] == cube_x[5] && is_exist[5] == 1)||

                                (cube_y[0] == cube_y[6] && cube_x[0] == cube_x[6] && is_exist[6] == 1)||

                                (cube_y[0] == cube_y[7] && cube_x[0] == cube_x[7] && is_exist[7] == 1)||

                                (cube_y[0] == cube_y[8] && cube_x[0] == cube_x[8] && is_exist[8] == 1)||

                                (cube_y[0] == cube_y[9] && cube_x[0] == cube_x[9] && is_exist[9] == 1)||

                                (cube_y[0] == cube_y[10] && cube_x[0] == cube_x[10] && is_exist[10] == 1)||

                                (cube_y[0] == cube_y[11] && cube_x[0] == cube_x[11] && is_exist[11] == 1)||

                                (cube_y[0] == cube_y[12] && cube_x[0] == cube_x[12] && is_exist[12] == 1)||

                                (cube_y[0] == cube_y[13] && cube_x[0] == cube_x[13] && is_exist[13] == 1)||

                                (cube_y[0] == cube_y[14] && cube_x[0] == cube_x[14] && is_exist[14] == 1)||

                                (cube_y[0] == cube_y[15] && cube_x[0] == cube_x[15] && is_exist[15] == 1)) begin

                                hit_body <= 1; end//头的Y坐标=任一位身体的Y坐标 且 头的X坐标=任一位身体的X坐标 且 身体的该长度位存在,说明碰到身体

                            else begin

                                cube_x[1] <= cube_x[0];

                                cube_y[1] <= cube_y[0];

                                                           

                                cube_x[2] <= cube_x[1];

                                cube_y[2] <= cube_y[1];

                                                           

                                cube_x[3] <= cube_x[2];

                                cube_y[3] <= cube_y[2];

                                                           

                                cube_x[4] <= cube_x[3];

                                cube_y[4] <= cube_y[3];

                                                           

                                cube_x[5] <= cube_x[4];

                                cube_y[5] <= cube_y[4];

                                                           

                                cube_x[6] <= cube_x[5];

                                cube_y[6] <= cube_y[5];

                                                           

                                cube_x[7] <= cube_x[6];

                                cube_y[7] <= cube_y[6];

                                                           

                                cube_x[8] <= cube_x[7];

                                cube_y[8] <= cube_y[7];

                                                           

                                cube_x[9] <= cube_x[8];

                                cube_y[9] <= cube_y[8];

                                                           

                                cube_x[10] <= cube_x[9];

                                cube_y[10] <= cube_y[9];

                                                           

                                cube_x[11] <= cube_x[10];

                                cube_y[11] <= cube_y[10];

                                                           

                                cube_x[12] <= cube_x[11];

                                cube_y[12] <= cube_y[11];

                                                             

                                cube_x[13] <= cube_x[12];

                                cube_y[13] <= cube_y[12];

                                                           

                                cube_x[14] <= cube_x[13];

                                cube_y[14] <= cube_y[13];

                                                           

                                cube_x[15] <= cube_x[14];

                                cube_y[15] <= cube_y[14];

                                    //身体运动算法 本长度位移动的下个坐标为下一个长度位当前坐标 运动节拍按分频后的节奏

                                    //蛇身体运动,蛇块的前一块坐标赋给后一块,比如 第0个块的坐标赋给第1块,第1块的坐标赋给第2块。。。

                                if(direct_r==UP)begin

                                        if(cube_y[0] == 1)

                                        hit_wall <= 1;//撞上墙

                                        else

                                            cube_y[0] <= cube_y[0]-1;

                                        end

                                                               

                                else if(direct_r==DOWN)begin

                                        if(cube_y[0] == 28)

                                            hit_wall <= 1;//撞下墙

                                        else

                                            cube_y[0] <= cube_y[0] + 1;

                                        end

                                                                   

                                else if(direct_r==LEFT)begin

                                        if(cube_x[0] == 1)

                                            hit_wall <= 1;//撞左墙

                                        else

                                            cube_x[0] <= cube_x[0] - 1;                                        

                                        end

 

                                else if(direct_r==RIGHT)begin

                                        if(cube_x[0] == 38)

                                            hit_wall <= 1;//撞右墙

                                        else

                                            cube_x[0] <= cube_x[0] + 1;

                                        end

                                //根据按下按键判断是否撞墙 否则按规律改变头部坐标

                            end

                        end

                        end

                    end

                end

 

运动方向的状态判断,主要是避免出现无意义的往复运动。

always @(*) begin   //根据当前运动状态即按下键位判断下一步运动情况  

        case(direct_r)  

            UP: begin   //向上运动时,  方向可以左右变

                if(~key1_left)

                    direct_next = LEFT;

                else if(~key0_right)

                    direct_next = RIGHT;

                else

                    direct_next = UP;          

                end    

            DOWN: begin //向下运动时,  方向可以左右变

                if(~key1_left)

                    direct_next = LEFT;

                else if(~key0_right)

                    direct_next = RIGHT;

                else

                    direct_next = DOWN;        

                end    

                LEFT: begin //向左运动时,  方向可以上下变

                if(~key3_up)

                    direct_next = UP;

                else if(~key2_down)

                    direct_next = DOWN;

                else

                    direct_next = LEFT;        

                end

                RIGHT: begin //向右运动时,  方向可以上下变

                if(~key3_up)

                    direct_next = UP;

                else if(~key2_down)

                    direct_next = DOWN;

                else

                    direct_next = RIGHT;

                end 

        endcase

    end

 

 

蛇身长度的增长,通过判断是否吃下食物来解决。

if(add_cube) begin

                        cube_num <= cube_num + 1;

                        is_exist[cube_num] <= 1;

显示蛇身的哪一节,是靠is_exist[cube_num]来控制的。

通过坐标,和显示控制,输出显示对象。

always @(pos_x or pos_y ) begin                

        if(pos_x >= 0 && pos_x < 640 && pos_y >= 0 && pos_y < 480) begin

            if(pos_x[9:4] == 0 || pos_y[9:4] == 0 || pos_x[9:4] == 39 || pos_y[9:4] == 29)//在VGA可显示的坐标范围内标记出墙的坐标

                snake_show = WALL;//扫描墙

               

            else if(pos_x[9:4] == cube_x[0] && pos_y[9:4] == cube_y[0] && is_exist[0] == 1)

                snake_show = (snake_display == 1) ? HEAD : NONE;//扫描头

            else if

                ((pos_x[9:4] == cube_x[1] && pos_y[9:4] == cube_y[1] && is_exist[1] == 1)|

                 (pos_x[9:4] == cube_x[2] && pos_y[9:4] == cube_y[2] && is_exist[2] == 1)|

                 (pos_x[9:4] == cube_x[3] && pos_y[9:4] == cube_y[3] && is_exist[3] == 1)|

                 (pos_x[9:4] == cube_x[4] && pos_y[9:4] == cube_y[4] && is_exist[4] == 1)|

                 (pos_x[9:4] == cube_x[5] && pos_y[9:4] == cube_y[5] && is_exist[5] == 1)|

                 (pos_x[9:4] == cube_x[6] && pos_y[9:4] == cube_y[6] && is_exist[6] == 1)|

                 (pos_x[9:4] == cube_x[7] && pos_y[9:4] == cube_y[7] && is_exist[7] == 1)|

                 (pos_x[9:4] == cube_x[8] && pos_y[9:4] == cube_y[8] && is_exist[8] == 1)|

                 (pos_x[9:4] == cube_x[9] && pos_y[9:4] == cube_y[9] && is_exist[9] == 1)|

                 (pos_x[9:4] == cube_x[10] && pos_y[9:4] == cube_y[10] && is_exist[10] == 1)|

                 (pos_x[9:4] == cube_x[11] && pos_y[9:4] == cube_y[11] && is_exist[11] == 1)|

                 (pos_x[9:4] == cube_x[12] && pos_y[9:4] == cube_y[12] && is_exist[12] == 1)|

                 (pos_x[9:4] == cube_x[13] && pos_y[9:4] == cube_y[13] && is_exist[13] == 1)|

                 (pos_x[9:4] == cube_x[14] && pos_y[9:4] == cube_y[14] && is_exist[14] == 1)|

                 (pos_x[9:4] == cube_x[15] && pos_y[9:4] == cube_y[15] && is_exist[15] == 1))

                 snake_show = (snake_display == 1) ? BODY : NONE;//扫描身体

            else snake_show = NONE;

        end

 

(6)game_ctrl_unit.v

游戏控制模块,根据游戏状态,产生相应的控制信号。

snake_display是蛇整体显示标志。

case(game_status)    

                RESTART:begin           //游戏重启状态

                    cnt_clk<=cnt_clk+1;

                    if(cnt_clk>150000000)begin// "欢迎来到贪吃蛇游戏“ 界面显示需要6s时间

                        if(sw[0]||sw[1]||sw[2]) begin

                            game_status <= START;//选择游戏难度后进入START状态

                        end

                    end

                    else begin

                        game_status <= RESTART;

                    end

                end

               

                START:begin

                    if ((~key0_right) ||( ~key1_left) || (~key2_down) ||( ~key3_up))//四个按键有任意一个按键被按下即可开始游戏

                        game_status <= PLAY;

                    else 

                        game_status <= START;

                end

               

                PLAY:begin

                    if(hit_wall || hit_body||bcd_data[11:8]>=1'd1)//如果撞墙或者撞自身或计满100分则游戏结束

                       game_status <= DIE;

                    else

                       game_status <= PLAY;

                end

               

                //下面代码是在制造闪烁效果

                //snake_display信号初始化的时候为高

                //snake_display信号在0-0.5秒为高,在 0.5-1秒为低,在 1-1.5秒高 在1.5-2低 2-2.5秒高 在2.5-3秒低

                DIE:begin

                    if(flash_cnt <= 100_000_000) begin//flash_cnt计时4秒

                        flash_cnt <= flash_cnt + 1'b1;  

                        if(flash_cnt == 12_500_000)begin//0-0.5秒 高

                            snake_display <= 1'b0;end

                        else if(flash_cnt == 25_000_000)begin//0.5-1秒低

                            snake_display <= 1'b1;end

                        else if(flash_cnt == 37_500_000)begin//1-1.5秒高

                            snake_display <= 1'b0;end

                        else if(flash_cnt == 50_000_000)begin//1.5-2秒低

                            snake_display <= 1'b1;end

                        else if(flash_cnt == 62_500_000)begin//2-2.5秒高

                            snake_display <= 1'b0;end

                        else if(flash_cnt == 75_000_000)begin//2.5-3秒低

                            snake_display <= 1'b1;

                        end

                    end

                    //游戏结束后按任意按键重新开始

                    else if((~key0_right) ||( ~key1_left) || (~key2_down) ||( ~key3_up) ) begin

                        cnt_clk<=0;

                        flash_cnt<=0;

                        game_status <= RESTART;

 

                    end        

                    else begin

                        game_status <= DIE;

                    end

                end 

               

                default:begin                

                            game_status <= RESTART; //游状态 从游戏结束 到游戏重启

                    end

            endcase 

 

这里用了一段式代码描述了游戏状态转换,共四种状态:

复位进入重启状态,6秒延时,显示欢迎界面,然后拨动开关选择难度,进入开始状态,按下任意键进入游戏状态,撞墙,撞自身,或计满100分就结束游戏,在结束状态,蛇身闪烁3秒,按键再重启。

 

结语

研究这个设计,可以更全面的熟悉状态机的设计方法,VGA的驱动,以及代码编写的技巧。

 

与基于FPGA的贪吃蛇游戏 之代码解析相似的内容:

基于FPGA的贪吃蛇游戏 之代码解析

基于FPGA的贪吃蛇游戏 之代码解析 1. 代码结构 代码结构包含7格.v文件。 下面依次解析。 2. 代码解析 (1) seg_display.v 数码管的译码模块是最熟悉,最简单的模块了。这里是共阳极的数码管,用case语句编码即可。从上图可以看到,这个模块被例化了3次,分别驱动3个数码管显示,

基于FPGA的计算器设计---第一版

欢迎各位朋友关注“郝旭帅电子设计团队”,本篇为各位朋友介绍基于FPGA的计算器设计 第一版。 功能说明: 1. 计算器的显示屏幕为数码管。 2. 4x4矩阵键盘作为计算器的输入设备。 3. 计算任意两位正整数的加减乘除。 4. 当减法结果出现负数时(一个小的数字减去一个大的数字),数码管需要显示负数

基于FPGA的4x4矩阵键盘驱动设计---第一版

欢迎各位朋友关注“郝旭帅电子设计团队”,本篇为各位朋友介绍基于FPGA的4x4矩阵键盘驱动设计 第一版 功能说明: 1. 驱动4x4矩阵键盘:按下任意一个按键,解析出对应按键信息,并给出标志 使用平台:纯代码形式 使用语言:Verilog HDL 作者QQ:746833924 说明:本篇设计中不涉及

基于FPGA的数字钟设计---第三版

欢迎各位朋友关注“郝旭帅电子设计团队”,本篇为各位朋友介绍基于FPGA的数字钟设计 第三版。 功能说明: 1. 在数码管上面显示时分秒(共计六个数码管,前两个显示小时;中间两个显示分钟;最后两个显示秒)。 2. 利用按键可以切换24/12小时制(默认24小时制)。 3. led1的亮灭表示24小时制

基于FPGA的电子琴设计(按键和蜂鸣器)----第一版

欢迎各位朋友关注“郝旭帅电子设计团队”,本篇为各位朋友介绍基于FPGA的电子琴设计(按键和蜂鸣器) 第一版。 功能说明: 外部输入七个按键,分别对应音符的“1、2、3、4、5、6、7”,唱作do、re、mi、fa、sol、la、si。当某个按键按下时,蜂鸣器发出对应的声音 1. 默认发出0.2秒(可

ZynqMP PL固件通过U-BOOT从指定位置加载FPGA BIT

原因 PL固件可能经常修改,而BOOT.BIN和文件系统、内核实际上基本不会变,在一个平台上可以用同一份。如果每次修改都要重新打包PL 固件到BOOT.BIN,操作起来非常麻烦。所以希望PL 的固件可以直接从指定位置加载。典型的可以从SD卡的FAT32分区加载。 https://xilinx-wik

基于 Three.js 的 3D 模型加载优化

作为一个3D的项目,从用户打开页面到最终模型的渲染加载的时间也会比普通的H5项目要更长一些,从而造成大量的用户流失。为了提升首屏加载的转化率,需要尽可能的降低loading的时间。这里就分享一些我们在模型加载优化方面的心得。

基于MindSpore实现BERT对话情绪识别

本文分享自华为云社区《【昇思25天学习打卡营打卡指南-第二十四天】基于 MindSpore 实现 BERT 对话情绪识别》,作者:JeffDing。 模型简介 BERT全称是来自变换器的双向编码器表征量(Bidirectional Encoder Representations from Trans

基于 Vagrant 手动部署多个 Redis Server

环境准备 宿主机环境:Windows 10 虚拟机环境:Vagrant + VirtualBox Vagrantfile 配置 首先,我们需要编写一个 Vagrantfile 来定义我们的虚拟机配置。假设已经在 D:\Vagrant\redis 目录下创建了一个 Vagrantfile,其内容如下:

基于EF Core存储的Serilog持久化服务

前言 Serilog是 .NET 上的一个原生结构化高性能日志库,这个库能实现一些比内置库更高度的定制。日志持久化是其中一个非常重要的功能,生产环境通常很难挂接调试器或者某些bug的触发条件很奇怪。为了在脱离调试环境的情况下尽可能保留更多线索来辅助解决生产问题,持久化的日志就显得很重要了。目前Ser