基于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的驱动,以及代码编写的技巧。
基于FPGA的贪吃蛇游戏 之代码解析的更多相关文章
- 基于React的贪吃蛇游戏的设计与实现
代码地址如下:http://www.demodashi.com/demo/11818.html 贪吃蛇小游戏(第二版) 一年半前层用react写过贪吃蛇小游戏https://github.com/ca ...
- Java实现贪吃蛇游戏【代码】
花了两个下午写了一个贪吃蛇小游戏,本人想写这游戏很长时间了.作为以前诺基亚手机上的经典游戏,贪吃蛇和俄罗斯方块一样,都曾经在我们的童年给我们带来了很多乐趣.世间万物斗转星移,诺基亚曾经作为手机业的龙头 ...
- 用C++实现的贪吃蛇游戏
我是一个C++初学者,控制台实现了一个贪吃蛇游戏. 代码如下: //"贪吃蛇游戏"V1.0 //李国良于2016年12月29日编写完成 #include <iostream& ...
- WebGL实现HTML5的3D贪吃蛇游戏
js1k.com收集了小于1k的javascript小例子,里面有很多很炫很酷的游戏和特效,今年规则又增加了新花样,传统的classic类型基础上又增加了WebGL类型,以及允许增加到2K的++类型, ...
- 100行JS实现HTML5的3D贪吃蛇游戏
js1k.com收集了小于1k的javascript小例子,里面有很多很炫很酷的游戏和特效,今年规则又增加了新花样,传统的classic类型基础上又增加了WebGL类型,以及允许增加到2K的++类型, ...
- Qt 学习之路 2(34):贪吃蛇游戏(4)
Qt 学习之路 2(34):贪吃蛇游戏(4) 豆子 2012年12月30日 Qt 学习之路 2 73条评论 这将是我们这个稍大一些的示例程序的最后一部分.在本章中,我们将完成GameControlle ...
- Qt 学习之路 2(31):贪吃蛇游戏(1)
Qt 学习之路 2(31):贪吃蛇游戏(1) 豆子 2012年12月18日 Qt 学习之路 2 41条评论 经过前面一段时间的学习,我们已经了解到有关 Qt 相当多的知识.现在,我们将把前面所讲过的知 ...
- 看我是如何用C#编写一个小于8KB的贪吃蛇游戏的
译者注:这是Michal Strehovský大佬的一篇文章,他目前在微软.NET Runtime团队工作,主要是负责.NET NativeAOT功能的开发.我在前几天看到这篇文章,非常喜欢,虽然它的 ...
- 8KB的C#贪吃蛇游戏热点答疑和.NET7版本
在之前的一篇文章<看我是如何用C#编写一个小于8KB的贪吃蛇游戏>中,介绍了在.NET Core 3.0的环境下如何将贪吃蛇游戏降低到8KB.不过也有很多小伙伴提出了一些疑问和看法,主要是 ...
- H5实现的可自定义贪吃蛇游戏
原创游戏,使用lufylegend.js开发 用canvas实现的贪吃蛇游戏,与一般的贪吃蛇游戏不同,图片经过美工设计,代码设计支持扩展和自定义. 游戏元素丰富,包括障碍物(仙人掌),金币(奖励),苹 ...
随机推荐
- #扫描线,并查集,切比雪夫距离#洛谷 5193 [TJOI2012]炸弹
题目 在平面上有 \(n\) 个炸弹 \([1 \ldots n]\) , 每个炸弹的爆炸范围是 \(|x-x_i|+|y-yi| \leq R\) 如果某个炸弹爆炸了,那么它将引燃它范围内的所有炸弹 ...
- 一个库帮你快速实现EF Core数据仓储模式
前言 EF Core是我们.NET日常开发中比较常用的ORM框架,今天大姚要分享的内容是如何使用EF Core Generic Repository通用仓储库来快速实现EF Core数据仓储模式. E ...
- openGauss/MogDB零字节问题处理
openGauss/MogDB 零字节问题处理 问题描述:java 应用端程序调用 GZIP 压缩类对数据进行编码压缩后入库 ,然后从数据库取出进行解压,原来再 mysql 数据库中是正常的,但迁移到 ...
- 国产开源数据库OpenGauss的安装运行
步骤一:OpenGauss 的安装 环境 OS:openEuler 20.03 64bit with ARM 架构:arm64 部署:单机 安装过程 1.环境配置 安装依赖包: yum install ...
- 打造美团外卖新体验,HarmonyOS SDK 持续赋能开发者共赢鸿蒙生态
从今年 8 月起,所有升级到 HarmonyOS 4 的手机用户在美团外卖下单后,可通过屏幕上的一个"小窗口",随时追踪到"出餐.取餐.送达"等订单状态.这个能 ...
- 直播预告丨Hello HarmonyOS进阶课程第四课——ArkUI动画开发
为了帮助初识HarmonyOS的开发者快速入门,我们曾推出Hello HarmonyOS系列课程,从最基础的配置IDE和创建Hello World开始,详细介绍HarmonyOS基础.开发环境搭建.I ...
- 【开发者说】XstoryMaker快速书写剧本场景动画
原文:https://mp.weixin.qq.com/s/63V0dfD2IufbX92JeD-G_A,点击链接查看更多技术内容. [开发者说]栏目是为HarmonyOS开发者提供的展示和分享平台, ...
- JavaScript中的事件模型如何理解?
一.事件与事件流 javascript中的事件,可以理解就是在HTML文档或者浏览器中发生的一种交互操作,使得网页具备互动性, 常见的有加载事件.鼠标事件.自定义事件等 由于DOM是一个树结构,如果在 ...
- 鸿蒙HarmonyOS实战-ArkUI动画(布局更新动画)
前言 动画是一种通过连续展示一系列静止的图像(称为帧)来创造出运动效果的艺术形式.它可以以手绘.计算机生成或其他各种形式呈现.在动画中,每一帧都具有微小的变化,当这些帧被快速播放时,人眼会产生视觉上的 ...
- 力扣68(java)-文本左右对齐(困难)
题目: 给定一个单词数组 words 和一个长度 maxWidth ,重新排版单词,使其成为每行恰好有 maxWidth 个字符,且左右两端对齐的文本. 你应该使用 "贪心算法" ...