基于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,也就限定了这个计数器的位宽。那么就按常规的计数器设计,用if、else语句,按优先级来设计先后顺序。先判断复位,计数结果清零,再判断是否计到最大值,是也清零,否就加一,最后剩余的情况就是计数结果保持不变。当然,这个模块比常规的计数器设计多了判断是否在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模块的框图如下:

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

代码首先就是端口声明,食物的坐标行比列位宽大,是根据640和480来判断的。bcd_data是计分模块输入的百、十、个的值,game_status是游戏的状态,snake_show是显示的对象,即扫描的点是什么,vga_blank_n是ADV7123的消隐信号,就是在非有效显示区域为0,vga_hs,vga_vs是行扫描和场扫描信号,vga_rgb是888的RGB信号。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的贪吃蛇游戏 之代码解析的更多相关文章

  1. 基于React的贪吃蛇游戏的设计与实现

    代码地址如下:http://www.demodashi.com/demo/11818.html 贪吃蛇小游戏(第二版) 一年半前层用react写过贪吃蛇小游戏https://github.com/ca ...

  2. Java实现贪吃蛇游戏【代码】

    花了两个下午写了一个贪吃蛇小游戏,本人想写这游戏很长时间了.作为以前诺基亚手机上的经典游戏,贪吃蛇和俄罗斯方块一样,都曾经在我们的童年给我们带来了很多乐趣.世间万物斗转星移,诺基亚曾经作为手机业的龙头 ...

  3. 用C++实现的贪吃蛇游戏

    我是一个C++初学者,控制台实现了一个贪吃蛇游戏. 代码如下: //"贪吃蛇游戏"V1.0 //李国良于2016年12月29日编写完成 #include <iostream& ...

  4. WebGL实现HTML5的3D贪吃蛇游戏

    js1k.com收集了小于1k的javascript小例子,里面有很多很炫很酷的游戏和特效,今年规则又增加了新花样,传统的classic类型基础上又增加了WebGL类型,以及允许增加到2K的++类型, ...

  5. 100行JS实现HTML5的3D贪吃蛇游戏

    js1k.com收集了小于1k的javascript小例子,里面有很多很炫很酷的游戏和特效,今年规则又增加了新花样,传统的classic类型基础上又增加了WebGL类型,以及允许增加到2K的++类型, ...

  6. Qt 学习之路 2(34):贪吃蛇游戏(4)

    Qt 学习之路 2(34):贪吃蛇游戏(4) 豆子 2012年12月30日 Qt 学习之路 2 73条评论 这将是我们这个稍大一些的示例程序的最后一部分.在本章中,我们将完成GameControlle ...

  7. Qt 学习之路 2(31):贪吃蛇游戏(1)

    Qt 学习之路 2(31):贪吃蛇游戏(1) 豆子 2012年12月18日 Qt 学习之路 2 41条评论 经过前面一段时间的学习,我们已经了解到有关 Qt 相当多的知识.现在,我们将把前面所讲过的知 ...

  8. 看我是如何用C#编写一个小于8KB的贪吃蛇游戏的

    译者注:这是Michal Strehovský大佬的一篇文章,他目前在微软.NET Runtime团队工作,主要是负责.NET NativeAOT功能的开发.我在前几天看到这篇文章,非常喜欢,虽然它的 ...

  9. 8KB的C#贪吃蛇游戏热点答疑和.NET7版本

    在之前的一篇文章<看我是如何用C#编写一个小于8KB的贪吃蛇游戏>中,介绍了在.NET Core 3.0的环境下如何将贪吃蛇游戏降低到8KB.不过也有很多小伙伴提出了一些疑问和看法,主要是 ...

  10. H5实现的可自定义贪吃蛇游戏

    原创游戏,使用lufylegend.js开发 用canvas实现的贪吃蛇游戏,与一般的贪吃蛇游戏不同,图片经过美工设计,代码设计支持扩展和自定义. 游戏元素丰富,包括障碍物(仙人掌),金币(奖励),苹 ...

随机推荐

  1. Avalonia的UI组件

    Avalonia是一个强大的跨平台UI框架,允许开发者构建丰富的桌面应用程序. 它提供了众多UI组件.灵活的布局系统.可定制的样式以及事件处理机制. 在这篇博客中,我们将详细解析Avalonia的UI ...

  2. ABA问题的本质及其解决办法

    目录 简介 第一类问题 第二类问题 第一类问题的解决 第二类问题的解决 总结 简介 CAS的全称是compare and swap,它是java同步类的基础,java.util.concurrent中 ...

  3. Java 数学运算与条件语句全解析

    Java Math Java 的 Math 类 拥有许多方法,允许您在数字上执行数学任务. 常用方法: Math.max(x, y): 找到 x 和 y 的最大值 Math.min(x, y): 找到 ...

  4. Seaborn结构化图形绘制(FacetGrid)

    结构化图形绘制(FacetGrid) 可实现多行多列个性化绘制图形. sns.FacetGrid( data, row=None, col=None, hue=None, col_wrap=None, ...

  5. 【直播预告】HarmonyOS极客松赋能直播第三期:一次开发多端部署与ArkTS卡片开发

  6. HarmonyOS 极客马拉松2023 正式启动,诚邀极客们用键盘码出无限可能!

      原文:https://mp.weixin.qq.com/s/p2yIs0rMmDE2BwhzsAtr7A,点击链接查看更多技术内容. 2023年6月15日, HarmonyOS极客马拉松2023开 ...

  7. 一个.NET内置依赖注入的小型强化版

    前言 .NET生态中有许多依赖注入容器.在大多数情况下,微软提供的内置容器在易用性和性能方面都非常优秀.外加ASP.NET Core默认使用内置容器,使用很方便. 但是笔者在使用中一直有一个头疼的问题 ...

  8. gensim的word2vec的简单使用

    from gensim.models import Word2Vec as wtv import jieba s1 = "刘新宇是一个自然语言处理算法工程师" s2 = " ...

  9. 暑期集训 Day9 —— 模拟赛复盘

    ${\color{Green} \mathrm{Problem\ 1 :大河的序列 }} $ 巨思维... 其实只需要输出序列 max 即可. 死因: \({\tiny 去你的}\) 快速幂 int ...

  10. 可观测|时序数据降采样在Prometheus实践复盘

    简介: 基于 Prometheus 的监控实践中,尤其是在规模较大时,时序数据的存储与查询是其中非常关键,而且问题点较多的一环.如何应对大数据量下的长周期查询,原生的 Prometheus 体系并未能 ...