基于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. #扫描线,并查集,切比雪夫距离#洛谷 5193 [TJOI2012]炸弹

    题目 在平面上有 \(n\) 个炸弹 \([1 \ldots n]\) , 每个炸弹的爆炸范围是 \(|x-x_i|+|y-yi| \leq R\) 如果某个炸弹爆炸了,那么它将引燃它范围内的所有炸弹 ...

  2. 一个库帮你快速实现EF Core数据仓储模式

    前言 EF Core是我们.NET日常开发中比较常用的ORM框架,今天大姚要分享的内容是如何使用EF Core Generic Repository通用仓储库来快速实现EF Core数据仓储模式. E ...

  3. openGauss/MogDB零字节问题处理

    openGauss/MogDB 零字节问题处理 问题描述:java 应用端程序调用 GZIP 压缩类对数据进行编码压缩后入库 ,然后从数据库取出进行解压,原来再 mysql 数据库中是正常的,但迁移到 ...

  4. 国产开源数据库OpenGauss的安装运行

    步骤一:OpenGauss 的安装 环境 OS:openEuler 20.03 64bit with ARM 架构:arm64 部署:单机 安装过程 1.环境配置 安装依赖包: yum install ...

  5. 打造美团外卖新体验,HarmonyOS SDK 持续赋能开发者共赢鸿蒙生态

    从今年 8 月起,所有升级到 HarmonyOS 4 的手机用户在美团外卖下单后,可通过屏幕上的一个"小窗口",随时追踪到"出餐.取餐.送达"等订单状态.这个能 ...

  6. 直播预告丨Hello HarmonyOS进阶课程第四课——ArkUI动画开发

    为了帮助初识HarmonyOS的开发者快速入门,我们曾推出Hello HarmonyOS系列课程,从最基础的配置IDE和创建Hello World开始,详细介绍HarmonyOS基础.开发环境搭建.I ...

  7. 【开发者说】XstoryMaker快速书写剧本场景动画

    原文:https://mp.weixin.qq.com/s/63V0dfD2IufbX92JeD-G_A,点击链接查看更多技术内容. [开发者说]栏目是为HarmonyOS开发者提供的展示和分享平台, ...

  8. JavaScript中的事件模型如何理解?

    一.事件与事件流 javascript中的事件,可以理解就是在HTML文档或者浏览器中发生的一种交互操作,使得网页具备互动性, 常见的有加载事件.鼠标事件.自定义事件等 由于DOM是一个树结构,如果在 ...

  9. 鸿蒙HarmonyOS实战-ArkUI动画(布局更新动画)

    前言 动画是一种通过连续展示一系列静止的图像(称为帧)来创造出运动效果的艺术形式.它可以以手绘.计算机生成或其他各种形式呈现.在动画中,每一帧都具有微小的变化,当这些帧被快速播放时,人眼会产生视觉上的 ...

  10. 力扣68(java)-文本左右对齐(困难)

    题目: 给定一个单词数组 words 和一个长度 maxWidth ,重新排版单词,使其成为每行恰好有 maxWidth 个字符,且左右两端对齐的文本. 你应该使用 "贪心算法" ...