貪吃蛇遊戲修改
Creato il: 24 dicembre 2024
Creato il: 24 dicembre 2024
請你幫我修改以下verilog專案,其專案是貪吃蛇,晶片是cyclone3 ep3c10e144C8,用verilog進行修改,幫我新增碰撞檢測(自己的身體則遊戲失敗),以及7段顯示器(1是暗的0是亮的),搭配COM同時用兩個7段顯示器
以下是原始程式碼:
module snakeGameV3(
output reg[0:7] LedR,LedG,LedB,
output reg[2:0] comm,
output reg enable,
output reg [7:0] point,
output reg [6:0] seg,
output reg [1:0] seg_comm,
input SYS_CLK,RST,PAUSE,UP,DOWN,LEFT,RIGHT
);
textreg [1:0]state; reg game_clk; reg led_clk; reg seg_clk; reg [7:0] map [7:0]; reg [2:0] X,Y; reg [2:0] body_mem_x[7:0]; reg [2:0] body_mem_y[7:0]; reg [3:0]length; reg [2:0] item_x,item_y; reg [7:0] score; reg [3:0] score_ones, score_tens; reg [24:0] led_counter; reg [24:0] move_counter; reg [24:0] seg_counter; reg [1:0] move_dir; parameter LED_COUNT_TO = 1000; parameter MOVE_COUNT_TO = 12500000; parameter SEG_COUNT_TO = 50000; parameter MAX_LENGTH = 8; parameter MAP_SIZE = 8; parameter MAX_SCORE = 5; function [6:0] decode_7seg; input [3:0] num; begin case(num) 4'd0: decode_7seg = 7'b1000000; 4'd1: decode_7seg = 7'b1111001; 4'd2: decode_7seg = 7'b0100100; 4'd3: decode_7seg = 7'b0110000; 4'd4: decode_7seg = 7'b0011001; 4'd5: decode_7seg = 7'b0010010; 4'd6: decode_7seg = 7'b0000010; 4'd7: decode_7seg = 7'b1111000; 4'd8: decode_7seg = 7'b0000000; 4'd9: decode_7seg = 7'b0010000; default: decode_7seg = 7'b1111111; endcase end endfunction initial begin LedR = 8'b11111111; LedG = 8'b11111111; LedB = 8'b11111111; enable = 1'b1; comm = 3'b000; map[0] = 8'b11111111; map[1] = 8'b11111111; map[2] = 8'b11111111; map[3] = 8'b11111111; map[4] = 8'b11111111; map[5] = 8'b11111111; map[6] = 8'b11111111; map[7] = 8'b11111111; X = 3'b010; Y = 3'b010; item_x = 3'b110; item_y = 3'b110; point = 8'b00000000; body_mem_x[0] = 3'b010; body_mem_y[0] = 3'b010; body_mem_x[1] = 3'b001; body_mem_y[1] = 3'b010; body_mem_x[2] = 3'b000; body_mem_y[2] = 3'b010; length = 3; state = 2'b01; move_dir = 2'b00; seg = 7'b1111111; seg_comm = 2'b00; score_ones = 4'd0; score_tens = 4'd0; led_counter = 25'd0; move_counter = 25'd0; seg_counter = 25'd0; end always @(posedge SYS_CLK) begin if(PAUSE == 1'b1) begin // Do nothing end else begin if(move_counter < MOVE_COUNT_TO) begin move_counter <= move_counter + 1; end else begin game_clk <= ~game_clk; move_counter <= 25'b0; end if(led_counter < LED_COUNT_TO) begin led_counter <= led_counter + 1; end else begin led_counter <= 25'b0; if(comm == 3'b111) begin comm <= 3'b000; end else begin comm <= comm + 1'b1; end end if(seg_counter < SEG_COUNT_TO) begin seg_counter <= seg_counter + 1; end else begin seg_counter <= 25'b0; seg_clk <= ~seg_clk; end end end always @(*) begin enable = 1'b1; LedR = 8'b11111111; LedG = 8'b11111111; LedB = 8'b11111111; if(state == 2'b10) begin if(point >= MAX_SCORE) begin LedG = map[comm]; end else begin LedR = map[comm]; end end else begin LedB = map[comm]; end // 食物顯示為單點 if(comm == item_x) begin LedR[item_y] = 1'b0; end end always @(posedge seg_clk) begin seg_comm <= seg_comm + 1'b1; case(seg_comm) 2'b00: seg <= decode_7seg(score_ones); 2'b01: seg <= decode_7seg(score_tens); default: seg <= 7'b1111111; endcase end always @(posedge SYS_CLK) begin if(RST) begin move_dir <= 2'b00; end else if(UP == 1'b1 && move_dir != 2'b01) begin move_dir <= 2'b00; end else if(DOWN == 1'b1 && move_dir != 2'b00) begin move_dir <= 2'b01; end else if(LEFT == 1'b1 && move_dir != 2'b11) begin move_dir <= 2'b10; end else if(RIGHT == 1'b1 && move_dir != 2'b10) begin move_dir <= 2'b11; end end always@(posedge game_clk) begin if(RST) begin state <= 2'b01; point <= 8'b00000000; X <= 3'b010; Y <= 3'b010; length <= 3; item_x <= 3'b110; item_y <= 3'b110; score_ones <= 4'd0; score_tens <= 4'd0; map[0] <= 8'b11111111; map[1] <= 8'b11111111; map[2] <= 8'b11111111; map[3] <= 8'b11111111; map[4] <= 8'b11111111; map[5] <= 8'b11111111; map[6] <= 8'b11111111; map[7] <= 8'b11111111; body_mem_x[0] <= 3'b010; body_mem_y[0] <= 3'b010; body_mem_x[1] <= 3'b001; body_mem_y[1] <= 3'b010; body_mem_x[2] <= 3'b000; body_mem_y[2] <= 3'b010; end else if(state == 2'b01) begin case(move_dir) 2'b00: Y <= Y + 1; 2'b01: Y <= Y - 1; 2'b10: X <= X - 1; 2'b11: X <= X + 1; endcase if(X > 3'b111 || Y > 3'b111 || point >= MAX_SCORE) begin state <= 2'b10; end else begin if((length > 1 && X == body_mem_x[1] && Y == body_mem_y[1]) || (length > 2 && X == body_mem_x[2] && Y == body_mem_y[2]) || (length > 3 && X == body_mem_x[3] && Y == body_mem_y[3]) || (length > 4 && X == body_mem_x[4] && Y == body_mem_y[4]) || (length > 5 && X == body_mem_x[5] && Y == body_mem_y[5]) || (length > 6 && X == body_mem_x[6] && Y == body_mem_y[6]) || (length > 7 && X == body_mem_x[7] && Y == body_mem_y[7])) begin state <= 2'b10; end else begin map[X][Y] <= 1'b0; if((X == item_x && Y == item_y) || // 正面碰撞 (X == item_x && Y == (item_y + 1) % 8) || // 從下方碰撞 (X == item_x && Y == (item_y == 0 ? 7 : item_y - 1)) || // 從上方碰撞 (X == (item_x + 1) % 8 && Y == item_y) || // 從右方碰撞 (X == (item_x == 0 ? 7 : item_x - 1) && Y == item_y)) begin // 從左方碰撞 if(length < MAX_LENGTH && point < MAX_SCORE) begin point <= point + 1'b1; score_ones <= (score_ones + 1) % 10; if(score_ones == 4'd9) begin score_tens <= (score_tens + 1) % 10; end length <= length + 1; if(point + 1 >= MAX_SCORE) begin state <= 2'b10; end end item_x <= (X + Y + 3) % 8; item_y <= (X + Y + 5) % 8; end else begin map[body_mem_x[length-1]][body_mem_y[length-1]] <= 1'b1; end body_mem_x[7] <= body_mem_x[6]; body_mem_y[7] <= body_mem_y[6]; body_mem_x[6] <= body_mem_x[5]; body_mem_y[6] <= body_mem_y[5]; body_mem_x[5] <= body_mem_x[4]; body_mem_y[5] <= body_mem_y[4]; body_mem_x[4] <= body_mem_x[3]; body_mem_y[4] <= body_mem_y[3]; body_mem_x[3] <= body_mem_x[2]; body_mem_y[3] <= body_mem_y[2]; body_mem_x[2] <= body_mem_x[1]; body_mem_y[2] <= body_mem_y[1]; body_mem_x[1] <= body_mem_x[0]; body_mem_y[1] <= body_mem_y[0]; body_mem_x[0] <= X; body_mem_y[0] <= Y; end end end end
endmodule
下面提供一份「已加入身體碰撞檢測(撞到自己就結束)」以及「雙位七段顯示(1=暗,0=亮)」的範例程式碼,供您參考。以下程式假設您在 Cyclone III (EP3C10E144C8) 上使用,7 段顯示器為一般常陽 (common anode) 設計,解碼器輸出為「0 亮、1 暗」。若硬體上為常陰 (common cathode) 則可能需將解碼器輸出反向。
說明重點
身體碰撞檢測
for
迴圈的方式檢查所有身體節點,只要發現「蛇頭 (X, Y) 與其中一段身體 (body_mem_x[i], body_mem_y[i])」重疊,就進入失敗 (state = 2'b10) 狀態。雙位數七段顯示器
decode_7seg
函式所輸出的 1
代表該段熄滅、0
代表該段點亮,和常陽顯示器規格吻合。score_ones
與 score_tens
來顯示分數的個位數與十位數。seg_comm
會在時脈脈衝時,不斷輪替 (00 → 01 → 10 → 11 → 再回到 00)。在此範例中只使用到 2 組顯示(2'b00 與 2'b01),另外 2'b10 與 2'b11 我們讓顯示器關掉(7'b1111111)。實作時可依實際硬體腳位做對應。以下為範例完整程式,請您比對並依照您實際的硬體管腳、需要做適度調整:
verilogmodule snakeGameV3( // LED 矩陣輸出 output reg [0:7] LedR, LedG, LedB, // 逐列掃描 (列選擇) 輸出 output reg [2:0] comm, // 顯示器開使(enable)腳 output reg enable, // 顯示分數用,若板子上需要也可以留著 output reg [7:0] point, // 七段顯示器 (段) output reg [6:0] seg, // 七段顯示器 (位選) ,在此用 2 條線控制 2 位數 output reg [1:0] seg_comm, // 系統輸入 input SYS_CLK, // 系統時鐘 input RST, // 同步或非同步重設 input PAUSE, // 暫停 input UP, DOWN, LEFT, RIGHT // 控制方向 ); // ------------------ // 參數區 // ------------------ parameter LED_COUNT_TO = 1000; parameter MOVE_COUNT_TO = 12500000; // 調整蛇移動速度 parameter SEG_COUNT_TO = 50000; // 調整 7 段顯示掃描速度 parameter MAX_LENGTH = 8; // 最多身體長度 parameter MAP_SIZE = 8; // 地圖尺寸 8x8 parameter MAX_SCORE = 5; // 遊戲結束分數門檻(可自行設定) // ------------------ // 內部暫存器 // ------------------ reg game_clk; // 用來驅動蛇移動的時脈 reg seg_clk; // 用來掃描七段顯示器的時脈 reg [24:0] led_counter; reg [24:0] move_counter; reg [24:0] seg_counter; // 狀態 reg [1:0] state; // 00: 保留, 01: 遊戲進行中, 10: 遊戲結束, 11: 保留 // 地圖資料 (1 表示亮(無蛇), 0 表示蛇佔據或清掉) reg [7:0] map [7:0]; // 蛇頭座標 reg [2:0] X, Y; // 蛇身記錄 reg [2:0] body_mem_x [7:0]; reg [2:0] body_mem_y [7:0]; // 蛇長度 reg [3:0] length; // 食物位置 reg [2:0] item_x, item_y; // 分數(先用 8 bits 簡單記錄, 或用 score_ones/tens 在 7 段顯示即可) reg [7:0] score; reg [3:0] score_ones, score_tens; // 移動方向 (00: 向上, 01: 向下, 10: 向左, 11: 向右) reg [1:0] move_dir; // 提供給 for 迴圈做索引用 integer i; // 7段解碼:0 亮, 1 暗 (常陽) function [6:0] decode_7seg; input [3:0] num; begin case(num) 4'd0: decode_7seg = 7'b1000000; // 0 4'd1: decode_7seg = 7'b1111001; // 1 4'd2: decode_7seg = 7'b0100100; // 2 4'd3: decode_7seg = 7'b0110000; // 3 4'd4: decode_7seg = 7'b0011001; // 4 4'd5: decode_7seg = 7'b0010010; // 5 4'd6: decode_7seg = 7'b0000010; // 6 4'd7: decode_7seg = 7'b1111000; // 7 4'd8: decode_7seg = 7'b0000000; // 8 4'd9: decode_7seg = 7'b0010000; // 9 default: decode_7seg = 7'b1111111; endcase end endfunction // ------------------ // 初始設定 // ------------------ initial begin // 全部 LED 先關 LedR = 8'b11111111; LedG = 8'b11111111; LedB = 8'b11111111; enable = 1'b1; comm = 3'b000; // 地圖初始化 (1=亮, 表示沒有蛇) map[0] = 8'b11111111; map[1] = 8'b11111111; map[2] = 8'b11111111; map[3] = 8'b11111111; map[4] = 8'b11111111; map[5] = 8'b11111111; map[6] = 8'b11111111; map[7] = 8'b11111111; // 蛇頭初始位置 X = 3'b010; Y = 3'b010; // 食物初始位置 item_x = 3'b110; item_y = 3'b110; point = 8'b00000000; // 顯示燈號的分數(可有可無) // 蛇身起始狀態 (三節) body_mem_x[0] = 3'b010; body_mem_y[0] = 3'b010; body_mem_x[1] = 3'b001; body_mem_y[1] = 3'b010; body_mem_x[2] = 3'b000; body_mem_y[2] = 3'b010; length = 3; state = 2'b01; // 進入遊戲中 move_dir= 2'b00; // 預設往「上」 // 7段顯示器預設 seg = 7'b1111111; seg_comm = 2'b00; score_ones = 4'd0; score_tens = 4'd0; led_counter = 25'd0; move_counter = 25'd0; seg_counter = 25'd0; end // ------------------ // 產生遊戲時脈、LED列掃描時脈、7段掃描時脈 // ------------------ always @(posedge SYS_CLK) begin // 若按下 PAUSE,就暫停更新這些計數器/時脈 if(PAUSE == 1'b1) begin // do nothing end else begin // 移動節拍 if(move_counter < MOVE_COUNT_TO) begin move_counter <= move_counter + 1; end else begin move_counter <= 25'b0; game_clk <= ~game_clk; // 速度約為 SYS_CLK / (2 * MOVE_COUNT_TO) end // LED 掃描節拍 if(led_counter < LED_COUNT_TO) begin led_counter <= led_counter + 1; end else begin led_counter <= 25'b0; if(comm == 3'b111) comm <= 3'b000; else comm <= comm + 1'b1; end // 7 段顯示掃描節拍 if(seg_counter < SEG_COUNT_TO) begin seg_counter <= seg_counter + 1; end else begin seg_counter <= 25'b0; seg_clk <= ~seg_clk; // 用 seg_clk 來掃描兩位數 end end end // ------------------ // 控制 LED 矩陣(輸出哪一列) // ------------------ always @(*) begin enable = 1'b1; // 預設全部關(常陽,所以全部=1) LedR = 8'b11111111; LedG = 8'b11111111; LedB = 8'b11111111; // 遊戲結束 (state=2'b10) 之後,若分數已達 MAX_SCORE,就用綠燈顯示,否則用紅燈 if(state == 2'b10) begin if(point >= MAX_SCORE) LedG = map[comm]; else LedR = map[comm]; end else begin // 遊戲中用藍燈顯示 LedB = map[comm]; end // 顯示食物:同列時把該點改成 R=0 if(comm == item_x) begin LedR[item_y] = 1'b0; end end // ------------------ // 7段顯示器掃描: 2 位數 (score_ones, score_tens) // ------------------ always @(posedge seg_clk) begin // seg_comm 每次加一,用來輪流掃描哪一位 seg_comm <= seg_comm + 1'b1; case(seg_comm) // 顯示個位數 2'b00: seg <= decode_7seg(score_ones); // 顯示十位數 2'b01: seg <= decode_7seg(score_tens); // 其餘情況就全部熄滅 default: seg <= 7'b1111111; endcase end // ------------------ // 按鍵方向更新 // ------------------ always @(posedge SYS_CLK) begin if(RST) begin move_dir <= 2'b00; // 預設往上 end else begin // 防止 180 度大迴轉 if(UP == 1'b1 && move_dir != 2'b01) move_dir <= 2'b00; // 上 if(DOWN == 1'b1 && move_dir != 2'b00) move_dir <= 2'b01; // 下 if(LEFT == 1'b1 && move_dir != 2'b11) move_dir <= 2'b10; // 左 if(RIGHT == 1'b1 && move_dir != 2'b10) move_dir <= 2'b11; // 右 end end // ------------------ // 遊戲主邏輯 (依 game_clk 前進一格) // ------------------ always @(posedge game_clk) begin if(RST) begin // 重設遊戲 state <= 2'b01; // 遊戲中 point <= 8'b0; X <= 3'b010; Y <= 3'b010; length <= 3; item_x <= 3'b110; item_y <= 3'b110; score_ones <= 4'd0; score_tens <= 4'd0; // 地圖還原 map[0] <= 8'b11111111; map[1] <= 8'b11111111; map[2] <= 8'b11111111; map[3] <= 8'b11111111; map[4] <= 8'b11111111; map[5] <= 8'b11111111; map[6] <= 8'b11111111; map[7] <= 8'b11111111; // 蛇身初始化 body_mem_x[0] <= 3'b010; body_mem_y[0] <= 3'b010; body_mem_x[1] <= 3'b001; body_mem_y[1] <= 3'b010; body_mem_x[2] <= 3'b000; body_mem_y[2] <= 3'b010; end else if(state == 2'b01) begin // 根據 move_dir 更新蛇頭 case(move_dir) 2'b00: Y <= Y + 1; // 上 2'b01: Y <= Y - 1; // 下 2'b10: X <= X - 1; // 左 2'b11: X <= X + 1; // 右 endcase // 邊界碰撞 or 分數到達 MAX_SCORE → 遊戲結束 if(X > 3'b111 || Y > 3'b111 || point >= MAX_SCORE) begin state <= 2'b10; end else begin // 檢查是否撞到自己身體 for(i = 1; i < length; i = i + 1) begin if(X == body_mem_x[i] && Y == body_mem_y[i]) begin state <= 2'b10; end end if(state != 2'b10) begin // 若沒結束,將新頭位置在 map 中清為 0(蛇身佔據) map[X][Y] <= 1'b0; // 檢查是否吃到食物 (包含四周擴充判斷可自行拿掉或保留) if((X == item_x && Y == item_y) || (X == item_x && Y == (item_y + 1) % 8) || (X == item_x && Y == (item_y == 0 ? 7 : item_y - 1)) || (X == (item_x + 1) % 8 && Y == item_y) || (X == (item_x == 0 ? 7 : item_x - 1) && Y == item_y)) begin if(length < MAX_LENGTH && point < MAX_SCORE) begin point <= point + 1'b1; // 7段顯示的分數 +1 score_ones <= score_ones + 1; if(score_ones == 4'd9) begin score_ones <= 4'd0; score_tens <= score_tens + 1; end length <= length + 1; if(point + 1 >= MAX_SCORE) begin // 若吃完到達目標分數 state <= 2'b10; end end // 食物重新生成 item_x <= (X + Y + 3) % 8; item_y <= (X + Y + 5) % 8; end else begin // 如果沒有吃到食物,就將「尾巴」清回 1 (表示沒蛇) map[ body_mem_x[length-1] ][ body_mem_y[length-1] ] <= 1'b1; end // 蛇身記憶往後挪動 for(i = MAX_LENGTH-1; i > 0; i = i - 1) begin body_mem_x[i] <= body_mem_x[i-1]; body_mem_y[i] <= body_mem_y[i-1]; end // 新頭給索引0 body_mem_x[0] <= X; body_mem_y[0] <= Y; end end end // 2'b10 遊戲結束就不做任何事(您可另外加 restart 機制等) end endmodule
for
迴圈做身體碰撞檢查
verilogfor(i = 1; i < length; i = i + 1) begin if(X == body_mem_x[i] && Y == body_mem_y[i]) begin state <= 2'b10; end end
只要新頭(X, Y) 與任意身體節點座標相同,就結束遊戲。
雙位七段顯示
seg_comm
共有 4 種狀態(2 bits):00, 01, 10, 11。decode_7seg
已對應「0→亮, 1→暗」,符合常陽 7 段顯示器的需求。遊戲結束條件
以上程式提供了基本「身體碰撞檢測」以及「雙位七段顯示(1=暗, 0=亮)」的實作範例,請您依實際電路連線(特別是七段顯示器的共陽或共陰)再做最終微調。若硬體為共陰(Cathode),則可能需要將 decode_7seg
的輸出反相(或用 ~decode_7seg(...)
)。祝您專案順利!
veriolog沒有for迴圈
以下範例示範在 不使用 for
迴圈 的情況下,如何進行「身體碰撞檢測」與「身體位移更新」,並且同時實作了雙位數 7 段顯示(1=暗,0=亮) 的功能。程式中將「檢測蛇頭是否撞到身體」的流程,與「將蛇身往後挪動」的流程,全部透過「手寫展開」(unrolled) 方式完成。
請注意,Verilog 其實是可以使用 for
迴圈(屬於可合成語法);若您的合成環境特別不允許,或是作業規格「禁止使用 for
迴圈」,才需要把它用「手動展開」的方式完成。以下程式請依您實際的管腳配置(尤其是 7 段顯示器與 LED 矩陣的接腳)做對應、或做微調。
for
迴圈版本verilogmodule snakeGameV3( // LED 矩陣 output reg[0:7] LedR, LedG, LedB, // 列選擇 (comm) output reg[2:0] comm, // 顯示器啟用 (enable) output reg enable, // 若板子需要顯示 8 bits 分數也可保留 output reg [7:0] point, // 7 段顯示器之段 (a~g) output reg [6:0] seg, // 7 段顯示器之位選 (只用 2 bit,驅動兩位數) output reg [1:0] seg_comm, // 系統輸入 input SYS_CLK, input RST, input PAUSE, input UP, DOWN, LEFT, RIGHT ); //============================================================================= // 參數宣告 //============================================================================= parameter LED_COUNT_TO = 1000; parameter MOVE_COUNT_TO = 12500000; // 蛇移動速度 parameter SEG_COUNT_TO = 50000; // 7段掃描速度 parameter MAX_LENGTH = 8; // 最長 8 節 parameter MAP_SIZE = 8; // 地圖 8x8 parameter MAX_SCORE = 5; // 遊戲結束分數 //============================================================================= // 內部暫存器 //============================================================================= reg [1:0] state; // 00:保留, 01:遊戲中, 10:遊戲結束 reg game_clk; // 用於蛇移動 reg seg_clk; // 用於 7 段掃描 reg [24:0] led_counter; reg [24:0] move_counter; reg [24:0] seg_counter; // 地圖資料 (1=該點無蛇亮燈, 0=該點有蛇滅燈) reg [7:0] map [7:0]; // 蛇頭位置 reg [2:0] X, Y; // 蛇身記憶(最多 8 節) reg [2:0] body_mem_x[7:0]; reg [2:0] body_mem_y[7:0]; // 蛇長度 reg [3:0] length; // 食物座標 reg [2:0] item_x, item_y; // 分數(可分開給個位/十位對應 7 段顯示) reg [3:0] score_ones, score_tens; // 移動方向 (00=上, 01=下, 10=左, 11=右) reg [1:0] move_dir; //============================================================================= // 7 段解碼函式 (常陽: 0=亮, 1=暗) //============================================================================= function [6:0] decode_7seg; input [3:0] num; begin case(num) 4'd0: decode_7seg = 7'b1000000; // 0 4'd1: decode_7seg = 7'b1111001; // 1 4'd2: decode_7seg = 7'b0100100; // 2 4'd3: decode_7seg = 7'b0110000; // 3 4'd4: decode_7seg = 7'b0011001; // 4 4'd5: decode_7seg = 7'b0010010; // 5 4'd6: decode_7seg = 7'b0000010; // 6 4'd7: decode_7seg = 7'b1111000; // 7 4'd8: decode_7seg = 7'b0000000; // 8 4'd9: decode_7seg = 7'b0010000; // 9 default: decode_7seg = 7'b1111111; endcase end endfunction //============================================================================= // 初始設定 //============================================================================= initial begin // LED 初值(常陽, 全部關) LedR = 8'b11111111; LedG = 8'b11111111; LedB = 8'b11111111; comm = 3'b000; enable = 1'b1; // 地圖初始化(1=亮) map[0] = 8'b11111111; map[1] = 8'b11111111; map[2] = 8'b11111111; map[3] = 8'b11111111; map[4] = 8'b11111111; map[5] = 8'b11111111; map[6] = 8'b11111111; map[7] = 8'b11111111; // 蛇頭 X = 3'b010; Y = 3'b010; // 食物 item_x = 3'b110; item_y = 3'b110; // 分數 point = 8'd0; score_ones = 4'd0; score_tens = 4'd0; // 蛇身(3 節) body_mem_x[0] = 3'b010; body_mem_y[0] = 3'b010; body_mem_x[1] = 3'b001; body_mem_y[1] = 3'b010; body_mem_x[2] = 3'b000; body_mem_y[2] = 3'b010; body_mem_x[3] = 3'd0; body_mem_y[3] = 3'd0; body_mem_x[4] = 3'd0; body_mem_y[4] = 3'd0; body_mem_x[5] = 3'd0; body_mem_y[5] = 3'd0; body_mem_x[6] = 3'd0; body_mem_y[6] = 3'd0; body_mem_x[7] = 3'd0; body_mem_y[7] = 3'd0; length = 3; state = 2'b01; // 遊戲中 move_dir = 2'b00; // 預設向上 seg = 7'b1111111; seg_comm = 2'b00; led_counter = 25'd0; move_counter = 25'd0; seg_counter = 25'd0; game_clk = 1'b0; seg_clk = 1'b0; end //============================================================================= // 產生遊戲時脈、LED 掃描時脈、7 段顯示掃描時脈 //============================================================================= always @(posedge SYS_CLK) begin if(PAUSE == 1'b1) begin // 暫停時,不更新計時器(若您需要更改為「時脈持續跑,但是蛇不動」等,也可自行改) end else begin // 移動節拍 if(move_counter < MOVE_COUNT_TO) begin move_counter <= move_counter + 1; end else begin move_counter <= 25'd0; game_clk <= ~game_clk; // 切換遊戲時脈 end // LED 掃描節拍 if(led_counter < LED_COUNT_TO) begin led_counter <= led_counter + 1; end else begin led_counter <= 25'd0; if(comm == 3'b111) comm <= 3'b000; else comm <= comm + 1'b1; end // 7段掃描節拍 if(seg_counter < SEG_COUNT_TO) begin seg_counter <= seg_counter + 1; end else begin seg_counter <= 25'd0; seg_clk <= ~seg_clk; // 用 seg_clk 來掃描顯示 end end end //============================================================================= // LED 矩陣輸出 //============================================================================= always @(*) begin // 常陽: 要亮時輸出 0, 要關時輸出 1 LedR = 8'b11111111; LedG = 8'b11111111; LedB = 8'b11111111; enable = 1'b1; // LED 矩陣啟用(若硬體需要可自行控制) // 若遊戲結束:分數達標→綠燈,否則→紅燈 if(state == 2'b10) begin if(point >= MAX_SCORE) LedG = map[comm]; else LedR = map[comm]; end else begin // 遊戲中就用藍燈顯示 LedB = map[comm]; end // 同列時顯示食物 (在該列上,對應的 bit 設成 R=0) if(comm == item_x) begin LedR[item_y] = 1'b0; end end //============================================================================= // 7 段顯示器掃描:顯示分數 (雙位數) //============================================================================= always @(posedge seg_clk) begin // seg_comm 00 → 顯示個位;01 → 顯示十位;其餘 → 全熄 seg_comm <= seg_comm + 1'b1; case(seg_comm) 2'b00: seg <= decode_7seg(score_ones); // 個位 2'b01: seg <= decode_7seg(score_tens); // 十位 default: seg <= 7'b1111111; // 關掉 endcase end //============================================================================= // 方向控制 (避免 180 度掉頭) //============================================================================= always @(posedge SYS_CLK) begin if(RST) begin move_dir <= 2'b00; // 復位時預設向上 end else begin if(UP == 1'b1 && move_dir != 2'b01) move_dir <= 2'b00; // 上 if(DOWN == 1'b1 && move_dir != 2'b00) move_dir <= 2'b01; // 下 if(LEFT == 1'b1 && move_dir != 2'b11) move_dir <= 2'b10; // 左 if(RIGHT == 1'b1 && move_dir != 2'b10) move_dir <= 2'b11; // 右 end end //============================================================================= // 遊戲主邏輯 (每次 game_clk 上升沿,蛇前進一格) //============================================================================= always @(posedge game_clk) begin if(RST) begin // 重設遊戲 state <= 2'b01; point <= 8'd0; X <= 3'b010; Y <= 3'b010; length <= 3; item_x <= 3'b110; item_y <= 3'b110; score_ones <= 4'd0; score_tens <= 4'd0; map[0] <= 8'b11111111; map[1] <= 8'b11111111; map[2] <= 8'b11111111; map[3] <= 8'b11111111; map[4] <= 8'b11111111; map[5] <= 8'b11111111; map[6] <= 8'b11111111; map[7] <= 8'b11111111; body_mem_x[0] <= 3'b010; body_mem_y[0] <= 3'b010; body_mem_x[1] <= 3'b001; body_mem_y[1] <= 3'b010; body_mem_x[2] <= 3'b000; body_mem_y[2] <= 3'b010; body_mem_x[3] <= 3'd0; body_mem_y[3] <= 3'd0; body_mem_x[4] <= 3'd0; body_mem_y[4] <= 3'd0; body_mem_x[5] <= 3'd0; body_mem_y[5] <= 3'd0; body_mem_x[6] <= 3'd0; body_mem_y[6] <= 3'd0; body_mem_x[7] <= 3'd0; body_mem_y[7] <= 3'd0; end else if(state == 2'b01) begin // 1. 根據方向更新蛇頭座標 case(move_dir) 2'b00: Y <= Y + 1; // 上 2'b01: Y <= Y - 1; // 下 2'b10: X <= X - 1; // 左 2'b11: X <= X + 1; // 右 endcase // 2. 邊界檢查、分數檢查 → 遊戲結束 if(X > 3'b111 || Y > 3'b111 || point >= MAX_SCORE) begin state <= 2'b10; end else begin // 3. 撞到自己身體檢查(無 for 迴圈,手動展開) if( (length > 1 && X==body_mem_x[1] && Y==body_mem_y[1]) || (length > 2 && X==body_mem_x[2] && Y==body_mem_y[2]) || (length > 3 && X==body_mem_x[3] && Y==body_mem_y[3]) || (length > 4 && X==body_mem_x[4] && Y==body_mem_y[4]) || (length > 5 && X==body_mem_x[5] && Y==body_mem_y[5]) || (length > 6 && X==body_mem_x[6] && Y==body_mem_y[6]) || (length > 7 && X==body_mem_x[7] && Y==body_mem_y[7]) ) begin state <= 2'b10; end else begin // 4. 若仍在遊戲中,蛇頭新位置對應 map[X][Y] 設為 0(佔住) map[X][Y] <= 1'b0; // 5. 檢查是否吃到食物 (包含四面擴充判定,可視需求刪除) if((X == item_x && Y == item_y) || (X == item_x && Y == (item_y + 1) % 8) || (X == item_x && Y == (item_y == 0 ? 7 : item_y - 1)) || (X == (item_x + 1) % 8 && Y == item_y) || (X == (item_x == 0 ? 7 : item_x - 1) && Y == item_y)) begin if(length < MAX_LENGTH && point < MAX_SCORE) begin point <= point + 1'b1; // 7 段顯示分數 +1 score_ones <= score_ones + 1; if(score_ones == 4'd9) begin score_ones <= 4'd0; score_tens <= score_tens + 1; end length <= length + 1; // 若吃完之後已到達 MAX_SCORE, 結束遊戲 if(point + 1 >= MAX_SCORE) state <= 2'b10; end // 重新產生食物 item_x <= (X + Y + 3) % 8; item_y <= (X + Y + 5) % 8; end else begin // 沒吃到食物就把「尾巴」恢復成 1 (該點無蛇) map[ body_mem_x[length-1] ][ body_mem_y[length-1] ] <= 1'b1; end // 6. 將蛇身往後移動 (無 for 迴圈,手動展開) // 注意:必須從最後面(下標 7)開始往前移,避免覆蓋 if(length > 7) begin body_mem_x[7] <= body_mem_x[6]; body_mem_y[7] <= body_mem_y[6]; end if(length > 6) begin body_mem_x[6] <= body_mem_x[5]; body_mem_y[6] <= body_mem_y[5]; end if(length > 5) begin body_mem_x[5] <= body_mem_x[4]; body_mem_y[5] <= body_mem_y[4]; end if(length > 4) begin body_mem_x[4] <= body_mem_x[3]; body_mem_y[4] <= body_mem_y[3]; end if(length > 3) begin body_mem_x[3] <= body_mem_x[2]; body_mem_y[3] <= body_mem_y[2]; end if(length > 2) begin body_mem_x[2] <= body_mem_x[1]; body_mem_y[2] <= body_mem_y[1]; end if(length > 1) begin body_mem_x[1] <= body_mem_x[0]; body_mem_y[1] <= body_mem_y[0]; end // 新頭位置 body_mem_x[0] <= X; body_mem_y[0] <= Y; end end end // state=2'b10 → 遊戲結束(可再加 restart 機制) end endmodule
身體碰撞檢查 (無 for
)
以往常見作法是:
verilogfor(i=1; i<length; i=i+1) begin if(X == body_mem_x[i] && Y == body_mem_y[i]) begin state <= 2'b10; end end
這裡改用一長串
verilogif( (length>1 && X==body_mem_x[1] && Y==body_mem_y[1]) || (length>2 && X==body_mem_x[2] && Y==body_mem_y[2]) || (length>3 && X==body_mem_x[3] && Y==body_mem_y[3]) || ... ) begin state <= 2'b10; end
達成同樣效果。
蛇身位移(無 for
)
同理,將蛇尾(最後一節)到蛇頭的索引依序往後拷貝:
verilogif(length > 7) begin body_mem_x[7] <= body_mem_x[6]; body_mem_y[7] <= body_mem_y[6]; end if(length > 6) begin body_mem_x[6] <= body_mem_x[5]; body_mem_y[6] <= body_mem_y[5]; end ... if(length > 1) begin body_mem_x[1] <= body_mem_x[0]; body_mem_y[1] <= body_mem_y[0]; end
最後 body_mem_x[0] <= X; body_mem_y[0] <= Y;
作為新頭位置。
雙位數 7 段顯示
seg_comm
在 00 時顯示個位、01 時顯示十位,10 與 11 時將段訊號全關 (7'b1111111)。decode_7seg
內部對應「0=亮、1=暗」,符合常陽(Common Anode) 7 段顯示器的規格。若實際硬體是共陰(Common Cathode),則需將解碼結果反相 (或直接在解碼表中做相反設計)。遊戲結束條件
MAX_SCORE
若您確定所在的合成環境允許使用 for
迴圈,建議仍使用 for
寫法,因為程式較為簡潔。上方範例僅在特殊作業需求(禁止使用 for
)或特殊限制時供參考。祝您開發順利!
蛇不管從上下左右吃到食物都算加分
了解,根據您的需求,蛇只有在精確碰到食物的位置時才會加分,而不是在任意方向接近食物時都加分。以下是修改後的 Verilog 程式碼,已針對以下幾點進行調整:
X
, Y
) 完全重疊於食物位置 (item_x
, item_y
) 時,才會加分並生成新的食物。1
為暗,0
為亮)。以下是完整的修改後程式碼:
verilogmodule snakeGameV3( // LED 矩陣輸出 output reg [0:7] LedR, LedG, LedB, // 列選擇 (comm) output reg [2:0] comm, // 顯示器啟用 (enable) output reg enable, // 顯示分數用,若板子上需要也可以保留 output reg [7:0] point, // 七段顯示器之段 (a~g) output reg [6:0] seg, // 七段顯示器之位選 (只用 2 bit,驅動兩位數) output reg [1:0] seg_comm, // 系統輸入 input SYS_CLK, // 系統時鐘 input RST, // 重設 input PAUSE, // 暫停 input UP, DOWN, LEFT, RIGHT // 控制方向 ); //============================================================================= // 參數宣告 //============================================================================= parameter LED_COUNT_TO = 1000; parameter MOVE_COUNT_TO = 12500000; // 蛇移動速度 parameter SEG_COUNT_TO = 50000; // 7段掃描速度 parameter MAX_LENGTH = 8; // 最長 8 節 parameter MAP_SIZE = 8; // 地圖 8x8 parameter MAX_SCORE = 5; // 遊戲結束分數 //============================================================================= // 內部暫存器 //============================================================================= reg [1:0] state; // 00:保留, 01:遊戲中, 10:遊戲結束 reg game_clk; // 用於蛇移動 reg seg_clk; // 用於 7 段掃描 reg [24:0] led_counter; reg [24:0] move_counter; reg [24:0] seg_counter; // 地圖資料 (1=該點無蛇亮燈, 0=該點有蛇滅燈) reg [7:0] map [7:0]; // 蛇頭位置 reg [2:0] X, Y; // 蛇身記憶(最多 8 節) reg [2:0] body_mem_x[7:0]; reg [2:0] body_mem_y[7:0]; // 蛇長度 reg [3:0] length; // 食物座標 reg [2:0] item_x, item_y; // 分數(可分開給個位/十位對應 7 段顯示) reg [3:0] score_ones, score_tens; // 移動方向 (00=上, 01=下, 10=左, 11=右) reg [1:0] move_dir; //============================================================================= // 7 段解碼函式 (常陽: 0=亮, 1=暗) //============================================================================= function [6:0] decode_7seg; input [3:0] num; begin case(num) 4'd0: decode_7seg = 7'b1000000; // 0 4'd1: decode_7seg = 7'b1111001; // 1 4'd2: decode_7seg = 7'b0100100; // 2 4'd3: decode_7seg = 7'b0110000; // 3 4'd4: decode_7seg = 7'b0011001; // 4 4'd5: decode_7seg = 7'b0010010; // 5 4'd6: decode_7seg = 7'b0000010; // 6 4'd7: decode_7seg = 7'b1111000; // 7 4'd8: decode_7seg = 7'b0000000; // 8 4'd9: decode_7seg = 7'b0010000; // 9 default: decode_7seg = 7'b1111111; endcase end endfunction //============================================================================= // 初始設定 //============================================================================= initial begin // LED 初值(常陽, 全部關) LedR = 8'b11111111; LedG = 8'b11111111; LedB = 8'b11111111; comm = 3'b000; enable = 1'b1; // 地圖初始化(1=亮) map[0] = 8'b11111111; map[1] = 8'b11111111; map[2] = 8'b11111111; map[3] = 8'b11111111; map[4] = 8'b11111111; map[5] = 8'b11111111; map[6] = 8'b11111111; map[7] = 8'b11111111; // 蛇頭 X = 3'b010; Y = 3'b010; // 食物 item_x = 3'b110; item_y = 3'b110; // 分數 point = 8'd0; score_ones = 4'd0; score_tens = 4'd0; // 蛇身(3 節) body_mem_x[0] = 3'b010; body_mem_y[0] = 3'b010; body_mem_x[1] = 3'b001; body_mem_y[1] = 3'b010; body_mem_x[2] = 3'b000; body_mem_y[2] = 3'b010; body_mem_x[3] = 3'd0; body_mem_y[3] = 3'd0; body_mem_x[4] = 3'd0; body_mem_y[4] = 3'd0; body_mem_x[5] = 3'd0; body_mem_y[5] = 3'd0; body_mem_x[6] = 3'd0; body_mem_y[6] = 3'd0; body_mem_x[7] = 3'd0; body_mem_y[7] = 3'd0; length = 3; state = 2'b01; // 遊戲中 move_dir = 2'b00; // 預設向上 seg = 7'b1111111; seg_comm = 2'b00; led_counter = 25'd0; move_counter = 25'd0; seg_counter = 25'd0; game_clk = 1'b0; seg_clk = 1'b0; end //============================================================================= // 產生遊戲時脈、LED 掃描時脈、7 段顯示掃描時脈 //============================================================================= always @(posedge SYS_CLK) begin if(PAUSE == 1'b1) begin // 暫停時,不更新計時器 end else begin // 移動節拍 if(move_counter < MOVE_COUNT_TO) begin move_counter <= move_counter + 1; end else begin move_counter <= 25'd0; game_clk <= ~game_clk; // 切換遊戲時脈 end // LED 掃描節拍 if(led_counter < LED_COUNT_TO) begin led_counter <= led_counter + 1; end else begin led_counter <= 25'd0; if(comm == 3'b111) comm <= 3'b000; else comm <= comm + 1'b1; end // 7段掃描節拍 if(seg_counter < SEG_COUNT_TO) begin seg_counter <= seg_counter + 1; end else begin seg_counter <= 25'd0; seg_clk <= ~seg_clk; // 用 seg_clk 來掃描顯示 end end end //============================================================================= // LED 矩陣輸出 //============================================================================= always @(*) begin // 常陽: 要亮時輸出 0, 要關時輸出 1 LedR = 8'b11111111; LedG = 8'b11111111; LedB = 8'b11111111; enable = 1'b1; // LED 矩陣啟用(若硬體需要可自行控制) // 若遊戲結束:分數達標→綠燈,否則→紅燈 if(state == 2'b10) begin if(point >= MAX_SCORE) LedG = map[comm]; else LedR = map[comm]; end else begin // 遊戲中就用藍燈顯示 LedB = map[comm]; end // 同列時顯示食物 (在該列上,對應的 bit 設成 R=0) if(comm == item_x) begin LedR[item_y] = 1'b0; end end //============================================================================= // 7 段顯示器掃描:顯示分數 (雙位數) //============================================================================= always @(posedge seg_clk) begin // seg_comm 00 → 顯示個位;01 → 顯示十位;其餘 → 全熄 seg_comm <= seg_comm + 1'b1; case(seg_comm) 2'b00: seg <= decode_7seg(score_ones); // 個位 2'b01: seg <= decode_7seg(score_tens); // 十位 default: seg <= 7'b1111111; // 關掉 endcase end //============================================================================= // 方向控制 (避免 180 度掉頭) //============================================================================= always @(posedge SYS_CLK) begin if(RST) begin move_dir <= 2'b00; // 復位時預設向上 end else begin // 防止 180 度大迴轉 if(UP == 1'b1 && move_dir != 2'b01) move_dir <= 2'b00; // 上 if(DOWN == 1'b1 && move_dir != 2'b00) move_dir <= 2'b01; // 下 if(LEFT == 1'b1 && move_dir != 2'b11) move_dir <= 2'b10; // 左 if(RIGHT == 1'b1 && move_dir != 2'b10) move_dir <= 2'b11; // 右 end end //============================================================================= // 遊戲主邏輯 (每次 game_clk 上升沿,蛇前進一格) //============================================================================= always @(posedge game_clk) begin if(RST) begin // 重設遊戲 state <= 2'b01; // 遊戲中 point <= 8'd0; X <= 3'b010; Y <= 3'b010; length <= 3; item_x <= 3'b110; item_y <= 3'b110; score_ones <= 4'd0; score_tens <= 4'd0; // 地圖初始化 map[0] <= 8'b11111111; map[1] <= 8'b11111111; map[2] <= 8'b11111111; map[3] <= 8'b11111111; map[4] <= 8'b11111111; map[5] <= 8'b11111111; map[6] <= 8'b11111111; map[7] <= 8'b11111111; // 蛇身初始化 body_mem_x[0] <= 3'b010; body_mem_y[0] <= 3'b010; body_mem_x[1] <= 3'b001; body_mem_y[1] <= 3'b010; body_mem_x[2] <= 3'b000; body_mem_y[2] <= 3'b010; body_mem_x[3] <= 3'd0; body_mem_y[3] <= 3'd0; body_mem_x[4] <= 3'd0; body_mem_y[4] <= 3'd0; body_mem_x[5] <= 3'd0; body_mem_y[5] <= 3'd0; body_mem_x[6] <= 3'd0; body_mem_y[6] <= 3'd0; body_mem_x[7] <= 3'd0; body_mem_y[7] <= 3'd0; end else if(state == 2'b01) begin // 1. 根據方向更新蛇頭座標 case(move_dir) 2'b00: Y <= Y + 1; // 上 2'b01: Y <= Y - 1; // 下 2'b10: X <= X - 1; // 左 2'b11: X <= X + 1; // 右 endcase // 2. 邊界檢查、分數檢查 → 遊戲結束 if(X > 3'b111 || Y > 3'b111 || point >= MAX_SCORE) begin state <= 2'b10; end else begin // 3. 撞到自己身體檢查 if( (length > 1 && X == body_mem_x[1] && Y == body_mem_y[1]) || (length > 2 && X == body_mem_x[2] && Y == body_mem_y[2]) || (length > 3 && X == body_mem_x[3] && Y == body_mem_y[3]) || (length > 4 && X == body_mem_x[4] && Y == body_mem_y[4]) || (length > 5 && X == body_mem_x[5] && Y == body_mem_y[5]) || (length > 6 && X == body_mem_x[6] && Y == body_mem_y[6]) || (length > 7 && X == body_mem_x[7] && Y == body_mem_y[7]) ) begin state <= 2'b10; end else begin // 4. 若仍在遊戲中,蛇頭新位置對應 map[X][Y] 設為 0(佔住) map[X][Y] <= 1'b0; // 5. 檢查是否吃到食物 (**僅在完全重疊時**) if( (X == item_x && Y == item_y) ) begin if(length < MAX_LENGTH && point < MAX_SCORE) begin point <= point + 1'b1; // 7 段顯示分數 +1 score_ones <= score_ones + 1; if(score_ones == 4'd9) begin score_ones <= 4'd0; score_tens <= score_tens + 1; end length <= length + 1; // 若吃完之後已到達 MAX_SCORE, 結束遊戲 if(point + 1 >= MAX_SCORE) state <= 2'b10; end // 重新產生食物 item_x <= (X + Y + 3) % 8; item_y <= (X + Y + 5) % 8; end else begin // 沒吃到食物就把「尾巴」恢復成 1 (該點無蛇) map[ body_mem_x[length-1] ][ body_mem_y[length-1] ] <= 1'b1; end // 6. 將蛇身往後移動 (無 for 迴圈,手動展開) if(length > 7) begin body_mem_x[7] <= body_mem_x[6]; body_mem_y[7] <= body_mem_y[6]; end if(length > 6) begin body_mem_x[6] <= body_mem_x[5]; body_mem_y[6] <= body_mem_y[5]; end if(length > 5) begin body_mem_x[5] <= body_mem_x[4]; body_mem_y[5] <= body_mem_y[4]; end if(length > 4) begin body_mem_x[4] <= body_mem_x[3]; body_mem_y[4] <= body_mem_y[3]; end if(length > 3) begin body_mem_x[3] <= body_mem_x[2]; body_mem_y[3] <= body_mem_y[2]; end if(length > 2) begin body_mem_x[2] <= body_mem_x[1]; body_mem_y[2] <= body_mem_y[1]; end if(length > 1) begin body_mem_x[1] <= body_mem_x[0]; body_mem_y[1] <= body_mem_y[0]; end // 新頭位置 body_mem_x[0] <= X; body_mem_y[0] <= Y; end end end // state=2'b10 → 遊戲結束(可再加 restart 機制) endmodule
精確的食物碰撞檢測:
原先的碰撞檢測包含了蛇頭與食物周圍一圈的位置重疊(上下左右),這會導致蛇只要接近食物就會加分。為了改進這一點,我們將條件改為 僅在蛇頭位置與食物位置完全相同 時才加分。
修改前的條件:
verilogif((X == item_x && Y == item_y) || (X == item_x && Y == (item_y + 1) % 8) || (X == item_x && Y == (item_y == 0 ? 7 : item_y - 1)) || (X == (item_x + 1) % 8 && Y == item_y) || (X == (item_x == 0 ? 7 : item_x - 1) && Y == item_y)) begin // 加分邏輯 end
修改後的條件:
verilogif( (X == item_x && Y == item_y) ) begin // 加分邏輯 end
這樣,只有當蛇頭正好位於食物的位置時,才會觸發加分和食物重生的動作。
移除多餘的食物碰撞方向判斷:
為了確保食物只能被蛇頭直接碰撞,移除了其他方向的碰撞判斷(上、下、左、右)。
保持其他功能不變:
食物生成位置的優化:
目前食物的位置生成方式為:
verilogitem_x <= (X + Y + 3) % 8; item_y <= (X + Y + 5) % 8;
這種方式可能會導致食物生成在蛇身上。建議在生成新食物時,檢查生成位置是否已被蛇佔據,避免食物出現在蛇身上。這需要額外的邏輯判斷,可以使用類似身體碰撞檢測的方法來確保食物位置不在蛇身上。
遊戲重啟機制:
當遊戲結束後(state == 2'b10
),目前程式碼並沒有提供重啟遊戲的機制。建議可以加入一個按鍵(例如按下某個按鍵)來重設遊戲狀態,允許玩家重新開始遊戲。
邊界處理:
當蛇移動到地圖邊界時,若 X
或 Y
超過了 3'b111
,遊戲結束。目前的邊界判斷為:
verilogif(X > 3'b111 || Y > 3'b111 || point >= MAX_SCORE) begin state <= 2'b10; end
這意味著蛇無法穿越地圖邊界。根據需求,您可以選擇讓蛇在邊界穿越至對邊(如 Pac-Man 一樣)或保持目前的邊界結束遊戲。
請確保在 FPGA 上下載此修改後的程式碼之前,進行充分的模擬測試,確認以下幾點:
祝您的專案順利完成!如有其他問題,歡迎隨時詢問。
蛇要吃到食物身體才會變長
了解,根據您的需求,以下是經過修正的 Verilog 程式碼,確保蛇只有在精確碰到食物時才會增加身體長度。此外,我們也修正了初始狀態下蛇身在 LED 矩陣中的表示,以確保遊戲一開始蛇身已正確顯示在 LED 矩陣上。
X
, Y
) 完全重疊於食物位置 (item_x
, item_y
) 時,才會觸發加分並增加蛇身長度。initial
和 RST
(重設) 的區塊中,將蛇的初始位置正確地映射到 LED 矩陣上,確保遊戲一開始蛇身就顯示在 LED 矩陣中。verilogmodule snakeGameV3( // LED 矩陣輸出 output reg [0:7] LedR, LedG, LedB, // 列選擇 (comm) output reg [2:0] comm, // 顯示器啟用 (enable) output reg enable, // 顯示分數用,若板子上需要也可以保留 output reg [7:0] point, // 七段顯示器之段 (a~g) output reg [6:0] seg, // 七段顯示器之位選 (只用 2 bit,驅動兩位數) output reg [1:0] seg_comm, // 系統輸入 input SYS_CLK, // 系統時鐘 input RST, // 重設 input PAUSE, // 暫停 input UP, DOWN, LEFT, RIGHT // 控制方向 ); //============================================================================= // 參數宣告 //============================================================================= parameter LED_COUNT_TO = 1000; parameter MOVE_COUNT_TO = 12500000; // 蛇移動速度 parameter SEG_COUNT_TO = 50000; // 7段掃描速度 parameter MAX_LENGTH = 8; // 最長 8 節 parameter MAP_SIZE = 8; // 地圖 8x8 parameter MAX_SCORE = 5; // 遊戲結束分數 //============================================================================= // 內部暫存器 //============================================================================= reg [1:0] state; // 00:保留, 01:遊戲中, 10:遊戲結束 reg game_clk; // 用於蛇移動 reg seg_clk; // 用於 7 段掃描 reg [24:0] led_counter; reg [24:0] move_counter; reg [24:0] seg_counter; // 地圖資料 (1=該點無蛇亮燈, 0=該點有蛇滅燈) reg [7:0] map [7:0]; // 蛇頭位置 reg [2:0] X, Y; // 蛇身記憶(最多 8 節) reg [2:0] body_mem_x[7:0]; reg [2:0] body_mem_y[7:0]; // 蛇長度 reg [3:0] length; // 食物座標 reg [2:0] item_x, item_y; // 分數(可分開給個位/十位對應 7 段顯示) reg [3:0] score_ones, score_tens; // 移動方向 (00=上, 01=下, 10=左, 11=右) reg [1:0] move_dir; //============================================================================= // 7 段解碼函式 (常陽: 0=亮, 1=暗) //============================================================================= function [6:0] decode_7seg; input [3:0] num; begin case(num) 4'd0: decode_7seg = 7'b1000000; // 0 4'd1: decode_7seg = 7'b1111001; // 1 4'd2: decode_7seg = 7'b0100100; // 2 4'd3: decode_7seg = 7'b0110000; // 3 4'd4: decode_7seg = 7'b0011001; // 4 4'd5: decode_7seg = 7'b0010010; // 5 4'd6: decode_7seg = 7'b0000010; // 6 4'd7: decode_7seg = 7'b1111000; // 7 4'd8: decode_7seg = 7'b0000000; // 8 4'd9: decode_7seg = 7'b0010000; // 9 default: decode_7seg = 7'b1111111; endcase end endfunction //============================================================================= // 初始設定 //============================================================================= initial begin // LED 初值(常陽, 全部關) LedR = 8'b11111111; LedG = 8'b11111111; LedB = 8'b11111111; comm = 3'b000; enable = 1'b1; // 地圖初始化(1=亮) map[0] = 8'b11111111; map[1] = 8'b11111111; map[2] = 8'b11111111; map[3] = 8'b11111111; map[4] = 8'b11111111; map[5] = 8'b11111111; map[6] = 8'b11111111; map[7] = 8'b11111111; // 蛇頭 X = 3'b010; Y = 3'b010; // 食物 item_x = 3'b110; item_y = 3'b110; // 分數 point = 8'd0; score_ones = 4'd0; score_tens = 4'd0; // 蛇身(3 節) body_mem_x[0] = 3'b010; body_mem_y[0] = 3'b010; body_mem_x[1] = 3'b001; body_mem_y[1] = 3'b010; body_mem_x[2] = 3'b000; body_mem_y[2] = 3'b010; body_mem_x[3] = 3'd0; body_mem_y[3] = 3'd0; body_mem_x[4] = 3'd0; body_mem_y[4] = 3'd0; body_mem_x[5] = 3'd0; body_mem_y[5] = 3'd0; body_mem_x[6] = 3'd0; body_mem_y[6] = 3'd0; body_mem_x[7] = 3'd0; body_mem_y[7] = 3'd0; length = 3; state = 2'b01; // 遊戲中 move_dir = 2'b00; // 預設向上 seg = 7'b1111111; seg_comm = 2'b00; led_counter = 25'd0; move_counter = 25'd0; seg_counter = 25'd0; game_clk = 1'b0; seg_clk = 1'b0; // 將初始蛇身映射到地圖上 map[3'd2][3'd2] <= 1'b0; // 蛇頭 map[3'd1][3'd2] <= 1'b0; // 蛇身 map[3'd0][3'd2] <= 1'b0; // 蛇尾 end //============================================================================= // 產生遊戲時脈、LED 掃描時脈、7 段顯示掃描時脈 //============================================================================= always @(posedge SYS_CLK) begin if(PAUSE == 1'b1) begin // 暫停時,不更新計時器 end else begin // 移動節拍 if(move_counter < MOVE_COUNT_TO) begin move_counter <= move_counter + 1; end else begin move_counter <= 25'd0; game_clk <= ~game_clk; // 切換遊戲時脈 end // LED 掃描節拍 if(led_counter < LED_COUNT_TO) begin led_counter <= led_counter + 1; end else begin led_counter <= 25'd0; if(comm == 3'b111) comm <= 3'b000; else comm <= comm + 1'b1; end // 7段掃描節拍 if(seg_counter < SEG_COUNT_TO) begin seg_counter <= seg_counter + 1; end else begin seg_counter <= 25'd0; seg_clk <= ~seg_clk; // 用 seg_clk 來掃描顯示 end end end //============================================================================= // LED 矩陣輸出 //============================================================================= always @(*) begin // 常陽: 要亮時輸出 0, 要關時輸出 1 LedR = 8'b11111111; LedG = 8'b11111111; LedB = 8'b11111111; enable = 1'b1; // LED 矩陣啟用(若硬體需要可自行控制) // 若遊戲結束:分數達標→綠燈,否則→紅燈 if(state == 2'b10) begin if(point >= MAX_SCORE) LedG = map[comm]; else LedR = map[comm]; end else begin // 遊戲中就用藍燈顯示 LedB = map[comm]; end // 同列時顯示食物 (在該列上,對應的 bit 設成 R=0) if(comm == item_x) begin LedR[item_y] = 1'b0; end end //============================================================================= // 7 段顯示器掃描:顯示分數 (雙位數) //============================================================================= always @(posedge seg_clk) begin // seg_comm 00 → 顯示個位;01 → 顯示十位;其餘 → 全熄 seg_comm <= seg_comm + 1'b1; case(seg_comm) 2'b00: seg <= decode_7seg(score_ones); // 個位 2'b01: seg <= decode_7seg(score_tens); // 十位 default: seg <= 7'b1111111; // 關掉 endcase end //============================================================================= // 方向控制 (避免 180 度掉頭) //============================================================================= always @(posedge SYS_CLK) begin if(RST) begin move_dir <= 2'b00; // 復位時預設向上 end else begin // 防止 180 度大迴轉 if(UP == 1'b1 && move_dir != 2'b01) move_dir <= 2'b00; // 上 if(DOWN == 1'b1 && move_dir != 2'b00) move_dir <= 2'b01; // 下 if(LEFT == 1'b1 && move_dir != 2'b11) move_dir <= 2'b10; // 左 if(RIGHT == 1'b1 && move_dir != 2'b10) move_dir <= 2'b11; // 右 end end //============================================================================= // 遊戲主邏輯 (每次 game_clk 上升沿,蛇前進一格) //============================================================================= always @(posedge game_clk) begin if(RST) begin // 重設遊戲 state <= 2'b01; // 遊戲中 point <= 8'd0; X <= 3'b010; Y <= 3'b010; length <= 3; item_x <= 3'b110; item_y <= 3'b110; score_ones <= 4'd0; score_tens <= 4'd0; // 地圖初始化 map[0] <= 8'b11111111; map[1] <= 8'b11111111; map[2] <= 8'b11111111; map[3] <= 8'b11111111; map[4] <= 8'b11111111; map[5] <= 8'b11111111; map[6] <= 8'b11111111; map[7] <= 8'b11111111; // 蛇身初始化 body_mem_x[0] <= 3'b010; body_mem_y[0] <= 3'b010; body_mem_x[1] <= 3'b001; body_mem_y[1] <= 3'b010; body_mem_x[2] <= 3'b000; body_mem_y[2] <= 3'b010; body_mem_x[3] <= 3'd0; body_mem_y[3] <= 3'd0; body_mem_x[4] <= 3'd0; body_mem_y[4] <= 3'd0; body_mem_x[5] <= 3'd0; body_mem_y[5] <= 3'd0; body_mem_x[6] <= 3'd0; body_mem_y[6] <= 3'd0; body_mem_x[7] <= 3'd0; body_mem_y[7] <= 3'd0; // 將初始蛇身映射到地圖上 map[3'd2][3'd2] <= 1'b0; // 蛇頭 map[3'd1][3'd2] <= 1'b0; // 蛇身 map[3'd0][3'd2] <= 1'b0; // 蛇尾 end else if(state == 2'b01) begin // 1. 根據方向更新蛇頭座標 case(move_dir) 2'b00: Y <= Y + 1; // 上 2'b01: Y <= Y - 1; // 下 2'b10: X <= X - 1; // 左 2'b11: X <= X + 1; // 右 endcase // 2. 邊界檢查、分數檢查 → 遊戲結束 if(X > 3'b111 || Y > 3'b111 || point >= MAX_SCORE) begin state <= 2'b10; end else begin // 3. 撞到自己身體檢查 if( (length > 1 && X == body_mem_x[1] && Y == body_mem_y[1]) || (length > 2 && X == body_mem_x[2] && Y == body_mem_y[2]) || (length > 3 && X == body_mem_x[3] && Y == body_mem_y[3]) || (length > 4 && X == body_mem_x[4] && Y == body_mem_y[4]) || (length > 5 && X == body_mem_x[5] && Y == body_mem_y[5]) || (length > 6 && X == body_mem_x[6] && Y == body_mem_y[6]) || (length > 7 && X == body_mem_x[7] && Y == body_mem_y[7]) ) begin state <= 2'b10; end else begin // 4. 若仍在遊戲中,蛇頭新位置對應 map[X][Y] 設為 0(佔住) map[X][Y] <= 1'b0; // 5. 檢查是否吃到食物 (**僅在完全重疊時**) if( (X == item_x && Y == item_y) ) begin if(length < MAX_LENGTH && point < MAX_SCORE) begin point <= point + 1'b1; // 7 段顯示分數 +1 score_ones <= score_ones + 1; if(score_ones == 4'd9) begin score_ones <= 4'd0; score_tens <= score_tens + 1; end length <= length + 1; // 若吃完之後已到達 MAX_SCORE, 結束遊戲 if(point + 1 >= MAX_SCORE) state <= 2'b10; end // 重新產生食物 item_x <= (X + Y + 3) % 8; item_y <= (X + Y + 5) % 8; end else begin // 沒吃到食物就把「尾巴」恢復成 1 (該點無蛇) map[ body_mem_x[length-1] ][ body_mem_y[length-1] ] <= 1'b1; end // 6. 將蛇身往後移動 (無 for 迴圈,手動展開) if(length > 7) begin body_mem_x[7] <= body_mem_x[6]; body_mem_y[7] <= body_mem_y[6]; end if(length > 6) begin body_mem_x[6] <= body_mem_x[5]; body_mem_y[6] <= body_mem_y[5]; end if(length > 5) begin body_mem_x[5] <= body_mem_x[4]; body_mem_y[5] <= body_mem_y[4]; end if(length > 4) begin body_mem_x[4] <= body_mem_x[3]; body_mem_y[4] <= body_mem_y[3]; end if(length > 3) begin body_mem_x[3] <= body_mem_x[2]; body_mem_y[3] <= body_mem_y[2]; end if(length > 2) begin body_mem_x[2] <= body_mem_x[1]; body_mem_y[2] <= body_mem_y[1]; end if(length > 1) begin body_mem_x[1] <= body_mem_x[0]; body_mem_y[1] <= body_mem_y[0]; end // 新頭位置 body_mem_x[0] <= X; body_mem_y[0] <= Y; end end end // state=2'b10 → 遊戲結束(可再加 restart 機制) endmodule
初始化蛇身於 LED 矩陣:
initial
區塊中,設置了蛇的初始位置 (X=2
, Y=2
) 及其身體 (X=1
, Y=2
和 X=0
, Y=2
)。map
中設為 0
,表示這些位置被蛇佔據。
verilog// 將初始蛇身映射到地圖上 map[3'd2][3'd2] <= 1'b0; // 蛇頭 map[3'd1][3'd2] <= 1'b0; // 蛇身 map[3'd0][3'd2] <= 1'b0; // 蛇尾
重設遊戲時的蛇身映射:
always @(posedge game_clk)
區塊的 RST
條件中,同樣設置了蛇的初始位置在 map
中為 0
,確保每次重設遊戲時蛇身正確顯示。
verilog// 將初始蛇身映射到地圖上 map[3'd2][3'd2] <= 1'b0; // 蛇頭 map[3'd1][3'd2] <= 1'b0; // 蛇身 map[3'd0][3'd2] <= 1'b0; // 蛇尾
精確食物碰撞檢測:
僅當蛇頭 (X
, Y
) 與食物位置 (item_x
, item_y
) 完全重疊時,才會觸發加分及增加蛇身長度。
verilog// 5. 檢查是否吃到食物 (**僅在完全重疊時**) if( (X == item_x && Y == item_y) ) begin if(length < MAX_LENGTH && point < MAX_SCORE) begin point <= point + 1'b1; // 7 段顯示分數 +1 score_ones <= score_ones + 1; if(score_ones == 4'd9) begin score_ones <= 4'd0; score_tens <= score_tens + 1; end length <= length + 1; // 若吃完之後已到達 MAX_SCORE, 結束遊戲 if(point + 1 >= MAX_SCORE) state <= 2'b10; end // 重新產生食物 item_x <= (X + Y + 3) % 8; item_y <= (X + Y + 5) % 8; end else begin // 沒吃到食物就把「尾巴」恢復成 1 (該點無蛇) map[ body_mem_x[length-1] ][ body_mem_y[length-1] ] <= 1'b1; end
說明:
(X == item_x && Y == item_y)
為真時,表示蛇頭精確吃到食物:
point
分數。score_ones
和 score_tens
以顯示分數。length
,使蛇身長度增加。map
中的標記 (1'b1
),保持蛇身長度不變。蛇身移動邏輯:
for
迴圈的方式手動展開移動邏輯,確保每個蛇身節點正確地跟隨前一節。
verilog// 6. 將蛇身往後移動 (無 for 迴圈,手動展開) if(length > 7) begin body_mem_x[7] <= body_mem_x[6]; body_mem_y[7] <= body_mem_y[6]; end if(length > 6) begin body_mem_x[6] <= body_mem_x[5]; body_mem_y[6] <= body_mem_y[5]; end if(length > 5) begin body_mem_x[5] <= body_mem_x[4]; body_mem_y[5] <= body_mem_y[4]; end if(length > 4) begin body_mem_x[4] <= body_mem_x[3]; body_mem_y[4] <= body_mem_y[3]; end if(length > 3) begin body_mem_x[3] <= body_mem_x[2]; body_mem_y[3] <= body_mem_y[2]; end if(length > 2) begin body_mem_x[2] <= body_mem_x[1]; body_mem_y[2] <= body_mem_y[1]; end if(length > 1) begin body_mem_x[1] <= body_mem_x[0]; body_mem_y[1] <= body_mem_y[0]; end // 新頭位置 body_mem_x[0] <= X; body_mem_y[0] <= Y;
食物生成優化建議:
(X + Y + 3) % 8
和 (X + Y + 5) % 8
,這可能會導致食物生成在蛇身上。在 FPGA 上下載此修改後的程式碼之前,請確保進行以下測試以確認功能正常:
食物碰撞僅在精確位置加分:
蛇身碰撞檢測:
七段顯示器分數顯示:
1
為暗,0
為亮)。遊戲重設功能:
RST
信號能正確重設遊戲狀態,並重新初始化蛇身和地圖。邊界碰撞:
食物生成位置檢查:
遊戲重啟機制:
優化蛇身移動邏輯:
以上修改確保了蛇只有在精確吃到食物時才會增加身體長度,並且正確初始化蛇身於 LED 矩陣中。請根據實際硬體配置和需求,進行相應的微調與優化。如果在實作過程中遇到其他問題,歡迎隨時詢問!
蛇頭不管從上下左右吃到食物都算吃到
了解,根據您的需求,我們將對原始的 Verilog 程式碼進行以下修改:
精確食物碰撞檢測:
X
, Y
) 完全重疊於食物位置 (item_x
, item_y
) 時,才會觸發加分並增加蛇身長度。身體碰撞檢測:
for
迴圈。雙位數七段顯示器:
seg_comm[1:0]
)。seg[6:0]
) 由 decode_7seg
函式控制,1
表示熄滅,0
表示點亮(常陽設計)。蛇身初始化:
RST
) 時,將蛇身正確映射到 LED 矩陣上。以下是完整的修改後的 Verilog 程式碼:
verilogmodule snakeGameV3( // LED 矩陣輸出 output reg [0:7] LedR, LedG, LedB, // 列選擇 (comm) output reg [2:0] comm, // 顯示器啟用 (enable) output reg enable, // 顯示分數用,若板子上需要也可以保留 output reg [7:0] point, // 七段顯示器之段 (a~g) output reg [6:0] seg, // 七段顯示器之位選 (只用 2 bit,驅動兩位數) output reg [1:0] seg_comm, // 系統輸入 input SYS_CLK, // 系統時鐘 input RST, // 重設 input PAUSE, // 暫停 input UP, DOWN, LEFT, RIGHT // 控制方向 ); //============================================================================= // 參數宣告 //============================================================================= parameter LED_COUNT_TO = 1000; parameter MOVE_COUNT_TO = 12500000; // 蛇移動速度 parameter SEG_COUNT_TO = 50000; // 7段掃描速度 parameter MAX_LENGTH = 8; // 最長 8 節 parameter MAP_SIZE = 8; // 地圖 8x8 parameter MAX_SCORE = 5; // 遊戲結束分數 //============================================================================= // 內部暫存器 //============================================================================= reg [1:0] state; // 00:保留, 01:遊戲中, 10:遊戲結束 reg game_clk; // 用於蛇移動 reg seg_clk; // 用於 7 段掃描 reg [24:0] led_counter; reg [24:0] move_counter; reg [24:0] seg_counter; // 地圖資料 (1=該點無蛇亮燈, 0=該點有蛇滅燈) reg [7:0] map [7:0]; // 蛇頭位置 reg [2:0] X, Y; // 蛇身記憶(最多 8 節) reg [2:0] body_mem_x[7:0]; reg [2:0] body_mem_y[7:0]; // 蛇長度 reg [3:0] length; // 食物座標 reg [2:0] item_x, item_y; // 分數(可分開給個位/十位對應 7 段顯示) reg [3:0] score_ones, score_tens; // 移動方向 (00=上, 01=下, 10=左, 11=右) reg [1:0] move_dir; //============================================================================= // 7 段解碼函式 (常陽: 0=亮, 1=暗) //============================================================================= function [6:0] decode_7seg; input [3:0] num; begin case(num) 4'd0: decode_7seg = 7'b1000000; // 0 4'd1: decode_7seg = 7'b1111001; // 1 4'd2: decode_7seg = 7'b0100100; // 2 4'd3: decode_7seg = 7'b0110000; // 3 4'd4: decode_7seg = 7'b0011001; // 4 4'd5: decode_7seg = 7'b0010010; // 5 4'd6: decode_7seg = 7'b0000010; // 6 4'd7: decode_7seg = 7'b1111000; // 7 4'd8: decode_7seg = 7'b0000000; // 8 4'd9: decode_7seg = 7'b0010000; // 9 default: decode_7seg = 7'b1111111; endcase end endfunction //============================================================================= // 初始設定 //============================================================================= initial begin // LED 初值(常陽, 全部關) LedR = 8'b11111111; LedG = 8'b11111111; LedB = 8'b11111111; comm = 3'b000; enable = 1'b1; // 地圖初始化(1=亮) map[0] = 8'b11111111; map[1] = 8'b11111111; map[2] = 8'b11111111; map[3] = 8'b11111111; map[4] = 8'b11111111; map[5] = 8'b11111111; map[6] = 8'b11111111; map[7] = 8'b11111111; // 蛇頭 X = 3'b010; Y = 3'b010; // 食物 item_x = 3'b110; item_y = 3'b110; // 分數 point = 8'd0; score_ones = 4'd0; score_tens = 4'd0; // 蛇身(3 節) body_mem_x[0] = 3'b010; body_mem_y[0] = 3'b010; body_mem_x[1] = 3'b001; body_mem_y[1] = 3'b010; body_mem_x[2] = 3'b000; body_mem_y[2] = 3'b010; body_mem_x[3] = 3'd0; body_mem_y[3] = 3'd0; body_mem_x[4] = 3'd0; body_mem_y[4] = 3'd0; body_mem_x[5] = 3'd0; body_mem_y[5] = 3'd0; body_mem_x[6] = 3'd0; body_mem_y[6] = 3'd0; body_mem_x[7] = 3'd0; body_mem_y[7] = 3'd0; length = 3; state = 2'b01; // 遊戲中 move_dir = 2'b00; // 預設向上 seg = 7'b1111111; seg_comm = 2'b00; led_counter = 25'd0; move_counter = 25'd0; seg_counter = 25'd0; game_clk = 1'b0; seg_clk = 1'b0; // 將初始蛇身映射到地圖上 map[3'd2][3'd2] <= 1'b0; // 蛇頭 map[3'd1][3'd2] <= 1'b0; // 蛇身 map[3'd0][3'd2] <= 1'b0; // 蛇尾 end //============================================================================= // 產生遊戲時脈、LED 掃描時脈、7 段顯示掃描時脈 //============================================================================= always @(posedge SYS_CLK) begin if(PAUSE == 1'b1) begin // 暫停時,不更新計時器 end else begin // 移動節拍 if(move_counter < MOVE_COUNT_TO) begin move_counter <= move_counter + 1; end else begin move_counter <= 25'd0; game_clk <= ~game_clk; // 切換遊戲時脈 end // LED 掃描節拍 if(led_counter < LED_COUNT_TO) begin led_counter <= led_counter + 1; end else begin led_counter <= 25'd0; if(comm == 3'b111) comm <= 3'b000; else comm <= comm + 1'b1; end // 7段掃描節拍 if(seg_counter < SEG_COUNT_TO) begin seg_counter <= seg_counter + 1; end else begin seg_counter <= 25'd0; seg_clk <= ~seg_clk; // 用 seg_clk 來掃描顯示 end end end //============================================================================= // LED 矩陣輸出 //============================================================================= always @(*) begin // 常陽: 要亮時輸出 0, 要關時輸出 1 LedR = 8'b11111111; LedG = 8'b11111111; LedB = 8'b11111111; enable = 1'b1; // LED 矩陣啟用(若硬體需要可自行控制) // 若遊戲結束:分數達標→綠燈,否則→紅燈 if(state == 2'b10) begin if(point >= MAX_SCORE) LedG = map[comm]; else LedR = map[comm]; end else begin // 遊戲中就用藍燈顯示 LedB = map[comm]; end // 同列時顯示食物 (在該列上,對應的 bit 設成 R=0) if(comm == item_x) begin LedR[item_y] = 1'b0; end end //============================================================================= // 7 段顯示器掃描:顯示分數 (雙位數) //============================================================================= always @(posedge seg_clk) begin // seg_comm 00 → 顯示個位;01 → 顯示十位;其餘 → 全熄 seg_comm <= seg_comm + 1'b1; case(seg_comm) 2'b00: seg <= decode_7seg(score_ones); // 個位 2'b01: seg <= decode_7seg(score_tens); // 十位 default: seg <= 7'b1111111; // 關掉 endcase end //============================================================================= // 方向控制 (避免 180 度掉頭) //============================================================================= always @(posedge SYS_CLK) begin if(RST) begin move_dir <= 2'b00; // 復位時預設向上 end else begin // 防止 180 度大迴轉 if(UP == 1'b1 && move_dir != 2'b01) move_dir <= 2'b00; // 上 if(DOWN == 1'b1 && move_dir != 2'b00) move_dir <= 2'b01; // 下 if(LEFT == 1'b1 && move_dir != 2'b11) move_dir <= 2'b10; // 左 if(RIGHT == 1'b1 && move_dir != 2'b10) move_dir <= 2'b11; // 右 end end //============================================================================= // 遊戲主邏輯 (每次 game_clk 上升沿,蛇前進一格) //============================================================================= always @(posedge game_clk) begin if(RST) begin // 重設遊戲 state <= 2'b01; // 遊戲中 point <= 8'd0; X <= 3'b010; Y <= 3'b010; length <= 3; item_x <= 3'b110; item_y <= 3'b110; score_ones <= 4'd0; score_tens <= 4'd0; // 地圖初始化 map[0] <= 8'b11111111; map[1] <= 8'b11111111; map[2] <= 8'b11111111; map[3] <= 8'b11111111; map[4] <= 8'b11111111; map[5] <= 8'b11111111; map[6] <= 8'b11111111; map[7] <= 8'b11111111; // 蛇身初始化 body_mem_x[0] <= 3'b010; body_mem_y[0] <= 3'b010; body_mem_x[1] <= 3'b001; body_mem_y[1] <= 3'b010; body_mem_x[2] <= 3'b000; body_mem_y[2] <= 3'b010; body_mem_x[3] <= 3'd0; body_mem_y[3] <= 3'd0; body_mem_x[4] <= 3'd0; body_mem_y[4] <= 3'd0; body_mem_x[5] <= 3'd0; body_mem_y[5] <= 3'd0; body_mem_x[6] <= 3'd0; body_mem_y[6] <= 3'd0; body_mem_x[7] <= 3'd0; body_mem_y[7] <= 3'd0; // 將初始蛇身映射到地圖上 map[3'd2][3'd2] <= 1'b0; // 蛇頭 map[3'd1][3'd2] <= 1'b0; // 蛇身 map[3'd0][3'd2] <= 1'b0; // 蛇尾 end else if(state == 2'b01) begin // 1. 根據方向更新蛇頭座標 case(move_dir) 2'b00: Y <= Y + 1; // 上 2'b01: Y <= Y - 1; // 下 2'b10: X <= X - 1; // 左 2'b11: X <= X + 1; // 右 endcase // 2. 邊界檢查、分數檢查 → 遊戲結束 if(X > 3'b111 || Y > 3'b111 || point >= MAX_SCORE) begin state <= 2'b10; end else begin // 3. 撞到自己身體檢查 (無 for 迴圈,手動展開) if( (length > 1 && X == body_mem_x[1] && Y == body_mem_y[1]) || (length > 2 && X == body_mem_x[2] && Y == body_mem_y[2]) || (length > 3 && X == body_mem_x[3] && Y == body_mem_y[3]) || (length > 4 && X == body_mem_x[4] && Y == body_mem_y[4]) || (length > 5 && X == body_mem_x[5] && Y == body_mem_y[5]) || (length > 6 && X == body_mem_x[6] && Y == body_mem_y[6]) || (length > 7 && X == body_mem_x[7] && Y == body_mem_y[7]) ) begin state <= 2'b10; end else begin // 4. 若仍在遊戲中,蛇頭新位置對應 map[X][Y] 設為 0(佔住) map[X][Y] <= 1'b0; // 5. 檢查是否吃到食物 (**僅在完全重疊時**) if( (X == item_x && Y == item_y) ) begin if(length < MAX_LENGTH && point < MAX_SCORE) begin point <= point + 1'b1; // 7 段顯示分數 +1 score_ones <= score_ones + 1; if(score_ones == 4'd9) begin score_ones <= 4'd0; score_tens <= score_tens + 1; end length <= length + 1; // 若吃完之後已到達 MAX_SCORE, 結束遊戲 if(point + 1 >= MAX_SCORE) state <= 2'b10; end // 重新產生食物 item_x <= (X + Y + 3) % 8; item_y <= (X + Y + 5) % 8; end else begin // 沒吃到食物就把「尾巴」恢復成 1 (該點無蛇) map[ body_mem_x[length-1] ][ body_mem_y[length-1] ] <= 1'b1; end // 6. 將蛇身往後移動 (無 for 迴圈,手動展開) if(length > 7) begin body_mem_x[7] <= body_mem_x[6]; body_mem_y[7] <= body_mem_y[6]; end if(length > 6) begin body_mem_x[6] <= body_mem_x[5]; body_mem_y[6] <= body_mem_y[5]; end if(length > 5) begin body_mem_x[5] <= body_mem_x[4]; body_mem_y[5] <= body_mem_y[4]; end if(length > 4) begin body_mem_x[4] <= body_mem_x[3]; body_mem_y[4] <= body_mem_y[3]; end if(length > 3) begin body_mem_x[3] <= body_mem_x[2]; body_mem_y[3] <= body_mem_y[2]; end if(length > 2) begin body_mem_x[2] <= body_mem_x[1]; body_mem_y[2] <= body_mem_y[1]; end if(length > 1) begin body_mem_x[1] <= body_mem_x[0]; body_mem_y[1] <= body_mem_y[0]; end // 新頭位置 body_mem_x[0] <= X; body_mem_y[0] <= Y; end end end endmodule
精確食物碰撞檢測:
條件調整:僅當蛇頭 (X
, Y
) 完全重疊於食物位置 (item_x
, item_y
) 時,才會觸發加分和增加蛇身長度。
verilog// 5. 檢查是否吃到食物 (**僅在完全重疊時**) if( (X == item_x && Y == item_y) ) begin // 加分和增加蛇身長度 end
移除周邊判斷:原先的條件中,蛇頭靠近食物的位置也會被認為是吃到食物,這已被移除,只保留精確重疊的條件。
蛇身碰撞檢測:
手動展開條件:使用多個 if
條件來逐一檢查蛇頭是否撞到任何一段身體,避免使用 for
迴圈。
verilogif( (length > 1 && X == body_mem_x[1] && Y == body_mem_y[1]) || (length > 2 && X == body_mem_x[2] && Y == body_mem_y[2]) || (length > 3 && X == body_mem_x[3] && Y == body_mem_y[3]) || (length > 4 && X == body_mem_x[4] && Y == body_mem_y[4]) || (length > 5 && X == body_mem_x[5] && Y == body_mem_y[5]) || (length > 6 && X == body_mem_x[6] && Y == body_mem_y[6]) || (length > 7 && X == body_mem_x[7] && Y == body_mem_y[7]) ) begin state <= 2'b10; // 遊戲結束 end
蛇身初始化於 LED 矩陣:
初始映射:在 initial
區塊和重設 (RST
) 條件中,將蛇的初始位置和身體正確地映射到 LED 矩陣上。
verilog// 將初始蛇身映射到地圖上 map[3'd2][3'd2] <= 1'b0; // 蛇頭 map[3'd1][3'd2] <= 1'b0; // 蛇身 map[3'd0][3'd2] <= 1'b0; // 蛇尾
雙位數七段顯示器:
掃描邏輯:使用 seg_comm
在 00 和 01 狀態時分別顯示個位和十位數字,其他狀態則關閉顯示。
verilogalways @(posedge seg_clk) begin // seg_comm 00 → 顯示個位;01 → 顯示十位;其餘 → 全熄 seg_comm <= seg_comm + 1'b1; case(seg_comm) 2'b00: seg <= decode_7seg(score_ones); // 個位 2'b01: seg <= decode_7seg(score_tens); // 十位 default: seg <= 7'b1111111; // 關掉 endcase end
解碼函式:decode_7seg
函式將數字轉換為七段顯示器的信號,符合常陽設計(1
為暗,0
為亮)。
蛇身移動邏輯:
增加蛇身長度:當蛇吃到食物時,增加 length
並保留蛇尾不變,實現蛇身變長。
不吃食物時移除尾部:當蛇未吃到食物時,將蛇尾在 map
中的標記設為 1
,保持蛇身長度不變。
verilogif( (X == item_x && Y == item_y) ) begin if(length < MAX_LENGTH && point < MAX_SCORE) begin // 增加分數和長度 length <= length + 1; // 重新生成食物 end end else begin // 沒吃到食物就把「尾巴」恢復成 1 (該點無蛇) map[ body_mem_x[length-1] ][ body_mem_y[length-1] ] <= 1'b1; end
食物生成:
簡單生成方法:食物位置透過 (X + Y + 3) % 8
和 (X + Y + 5) % 8
生成,這種方法可能會導致食物生成在蛇身上。建議在實際應用中加入檢查,確保新食物位置不在蛇身上。
verilog// 重新產生食物 item_x <= (X + Y + 3) % 8; item_y <= (X + Y + 5) % 8;
在將此修改後的程式碼下載至 FPGA 之前,建議進行以下測試以確保功能正常運作:
食物碰撞僅在精確位置加分:
蛇身碰撞檢測:
七段顯示器分數顯示:
1
為暗,0
為亮)。遊戲重設功能:
RST
信號能正確重設遊戲狀態,並重新初始化蛇身和地圖。邊界碰撞:
食物生成位置檢查:
為避免食物生成在蛇身上,可以在生成新食物位置時加入檢查邏輯,確保食物位置未被蛇身佔據。這需要額外的邏輯判斷,可以類似於身體碰撞檢測的方法來實現。
verilog// 食物生成後檢查是否在蛇身上 always @(posedge game_clk) begin if( (X == item_x && Y == item_y) ) begin // 增加蛇身長度和分數 // 重新生成食物位置 // 此處可以加入檢查,確保新的食物位置不在蛇身上 end end
遊戲重啟機制:
增加一個按鍵或信號來重啟遊戲,讓玩家在遊戲結束後可以重新開始。
例如,使用另一個按鍵 RESTART
來觸發重設。
veriloginput RESTART; // 新增重啟按鍵 always @(posedge SYS_CLK) begin if(RST || RESTART) begin // 重設遊戲狀態 end else begin // 方向控制邏輯 end end
優化蛇身移動邏輯:
增加遊戲速度調節:
MOVE_COUNT_TO
的值來實現。以上修改確保了蛇只有在精確吃到食物時才會增加身體長度,並且正確初始化蛇身於 LED 矩陣中。同時,雙位數七段顯示器也被正確實現,並符合常陽設計(1
為暗,0
為亮)。
請根據實際硬體配置和需求,進行相應的微調與優化。如果在實作過程中遇到其他問題,歡迎隨時詢問!