基于FPGA的贪吃蛇游戏 之代码解析
基于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的驱动,以及代码编写的技巧。