八、贪吃蛇之状态机设计
八、贪吃蛇之状态机设计
贪吃游戏采用mealy状态机。
1. 游戏状态控制状态机
用一段式编码来完成游戏控制过程:
各状态说明:
(1) RESTART:复位后进入重启状态,屏幕显示欢迎界面,6秒后进入游戏难度选择界面,等待选择难度。
(2) START:用SW[2:0]选择游戏难度,按下KEY[3:0]任意键,开始游戏。进入PLAY状态。
(3) PLAY:按上下左右方向键,控制蛇身移动,蛇身最长16格,吃食物获得分数显示在数码管上。
(4) DIE:当蛇撞墙、撞自身或分数达到100时,蛇身闪烁3秒,屏幕显示总分,游戏结束。
//游戏控制模块,根据游戏状态,产生相应控制信号
module game_ctrl_unit
(
input clk, //25MHz
input rst_n,
input key0_right, //选方向
input key1_left,
input key2_down,
input key3_up,
input [2:0] sw, //选难度
input hit_wall, //撞墙
input hit_body, //撞自身
input [11:0]bcd_data, //分数
output reg snake_display, //蛇整体显示标识
output reg [1:0]game_status //当前游戏状态
);
localparam RESTART = 2'b00; //游戏重启
localparam START = 2'b01; //游戏开始
localparam PLAY = 2'b10; //游戏进行
localparam DIE = 2'b11; //游戏结束
reg [32:0]cnt_clk ;
reg [31:0]flash_cnt ; //蛇闪烁时间计数器
//状态机定义初始状态,并描述状态转移与输出
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cnt_clk<=0;
flash_cnt<=0;
snake_display<=1;
game_status<=RESTART; //复位后进入重启状态
end
else begin
case (game_status)
RESTART:begin
cnt_clk<=cnt_clk+1;
if(cnt_clk>150000000)begin
//欢迎界面
if(sw[0]||sw[1]||sw[2]) begin
game_status <= 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)
game_status <= DIE;
else
game_status <= PLAY;
end
//蛇身闪烁,间隔0.5秒
DIE:begin
if(flash_cnt <= 100_000_000) begin //
flash_cnt <= flash_cnt+1'b1;
if(flash_cnt == 12_500_000) begin
snake_display <= 1'b0;
end
else if(flash_cnt == 25_000_000) begin
snake_display <= 1'b1;
end
else if(flash_cnt == 37_500_000) begin
snake_display <= 1'b0;
end
else if(flash_cnt == 50_000_000) begin
snake_display <= 1'b1;
end
else if(flash_cnt == 62_500_000) begin
snake_display <= 1'b0;
end
else if(flash_cnt == 75_000_000) begin
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
end
end
endmodule
其中,在RESTART状态,cnt_clk >150000000,这里用大于而不是判断相等,原因是如果等了6秒,没有及时选择难度,就错过了,所有用大于。在DIE状态,cnt_clk和flash_cnt都要清零,是为了游戏重启后,和初始状态一样。
2. 蛇身方向控制状态机
蛇身方向状态用三段式代码编写,分上下左右四个状态。
(1) 第一段:状态转换时序逻辑,初始化状态。
//蛇身方向初始化状态
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
speed<=24'd12500000;
direct_r<=RIGHT;
end
这里初始化的方向是右,实际是根据key值判断。
(2) 第二段:状态转换的组合逻辑。
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
(3) 第三段:根据当前状态和输入产生输出的时序逻辑。
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
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
参考文献
https://mp.weixin.qq.com/s/YSrbtbRneUtc1d8IYhk-RA