貪吃蛇遊戲修改
Created on: December 24, 2024
Created on: December 24, 2024
Piecing together the game
OK, let me see. The request involves coding the Snake game in Verilog on a Cyclone3, ensuring collision detection with the snake's body. A 7-segment display is set up for a "1 is off and 0 is on" indicator.
Confirming code consistency
OK, let's confirm that the decode_7seg function aligns with active low segments on a common anode 7-segment display, ensuring the game logic handles hardware as intended.
Enhancing collision detection
OK, let me see. The code already includes a collision detection block. To ensure the game ends correctly when the snake collides with itself, I might need to tweak or confirm this existing logic.
Strengthening collision detection
The collision detection logic is already in place, but strengthening it to ensure the game ends seamlessly when the snake hits itself is a priority. Moving forward, I’ll work on enhancing this aspect.
Enhancing display functionality
I’m mapping out how to cycle between two 7-segment displays using COM signals, ensuring a seamless transition between active and inactive states, effectively managing the display logic.
Enhancing display logic
I’m deeply considering how to manage two 7-segment displays with seg_comm, ensuring each digit's state is accurately controlled for the game's score representation.
Managing display transitions
I'm refining the seg_comm logic to ensure smooth transitions between digits on dual 7-segment displays, accurately controlling each digit's state. This involves a careful balance of signals and segment states.
Mapping collision detection
I'm digging into a case statement for seg_comm to enhance collision detection by examining the entire body memory, ensuring a more comprehensive approach to handling self-collisions.
Confirming game logic
I’ve verified that collision detection for the Snake game is already implemented, checking the entire body memory. The logic for seg_comm and the 7-segment displays is also confirmed.
Mapping out game logic
I’ve established that collision detection for the Snake game is already functioning, monitoring the entire body memory. The logic for seg_comm and 7-segment displays is confirmed to be in place.
Caution with display logic
I'm considering if skipping se_version 2'b10 and 2'b11 in the seg_comm logic might omit crucial segments of the 7-segment displays, and how this could affect the overall display setup.
Taking a closer look at seg_comm
I'm thinking through the necessity of defining the 2-bit seg_comm signals, noting that skipping 2'b10 and 2'b11 might exclude essential display segments in the 7-segment setup.
Ensuring accuracy
I’m confirming that the seg_comm logic covers all necessary segments of the 7-segment display, including 2'b10 and 2'b11, for a complete game representation.
Ensuring alignment
I’m making sure the decode_7seg code aligns with a common anode setup, confirming segments are active high and inactive low, and verifying compatibility with a 2-digit display.
Enhancing the display logic
I'm figuring out how to ensure seg_comm is effectively used for driving digit lines in the FPGA, boosting the clarity and linearity of the 7-segment display's operation.
Confirming collision handling
I’m verifying that the code already includes collision detection for the Snake game, examining the entire body memory. This ensures the game accurately identifies and responds to collisions with the snake's body.
Highlighting collision detection
I'm revisiting the collision detection logic, emphasizing a for loop to check each body segment from index 1 to 7, ensuring the game ends when the snake collides with itself.
Finalizing collision detection
I’m integrating collision detection by adding a loop to check each body segment. I’m also confirming the decode_7seg function for the 7-segment display, ensuring it aligns with active low segments on a common anode display.
Finalizing collision logic
I'm wrapping up the code to include collision detection with a for loop and the 7-segment display logic. This ensures consistent functionality with a common anode display.
Thought about snake game modification 花的時間為 25 秒
下面提供一份「已加入身體碰撞檢測(撞到自己就結束)」以及「雙位七段顯示(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)。實作時可依實際硬體腳位做對應。以下為範例完整程式,請您比對並依照您實際的硬體管腳、需要做適度調整:
module 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
迴圈做身體碰撞檢查
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
只要新頭(X, Y) 與任意身體節點座標相同,就結束遊戲。
雙位七段顯示
seg_comm
共有 4 種狀態(2 bits):00, 01, 10, 11。decode_7seg
已對應「0→亮, 1→暗」,符合常陽 7 段顯示器的需求。遊戲結束條件
以上程式提供了基本「身體碰撞檢測」以及「雙位七段顯示(1=暗, 0=亮)」的實作範例,請您依實際電路連線(特別是七段顯示器的共陽或共陰)再做最終微調。若硬體為共陰(Cathode),則可能需要將 decode_7seg
的輸出反相(或用 ~decode_7seg(...)
)。祝您專案順利!
Examining for loop limitations
I’m looking into why for loops might not work well in Verilog, especially in combinational logic or older versions. Thinking through alternatives like arrays and generation blocks.
Mapping out manual checks
I'm piecing together manual checks by unrolling the loop manually, which replicates standard for loop logic within Verilog's constraints. This approach maintains the original code structure.
Thought about Verilog body collision check 花的時間為 7 秒
以下範例示範在 不使用 for
迴圈 的情況下,如何進行「身體碰撞檢測」與「身體位移更新」,並且同時實作了雙位數 7 段顯示(1=暗,0=亮) 的功能。程式中將「檢測蛇頭是否撞到身體」的流程,與「將蛇身往後挪動」的流程,全部透過「手寫展開」(unrolled) 方式完成。
請注意,Verilog 其實是可以使用 for
迴圈(屬於可合成語法);若您的合成環境特別不允許,或是作業規格「禁止使用 for
迴圈」,才需要把它用「手動展開」的方式完成。以下程式請依您實際的管腳配置(尤其是 7 段顯示器與 LED 矩陣的接腳)做對應、或做微調。
for
迴圈版本module 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
)
以往常見作法是:
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( (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
)
同理,將蛇尾(最後一節)到蛇頭的索引依序往後拷貝:
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 > 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代码进行了调整,添加了碰撞检测和双重7段显示。用户报告蛇累积分数无论吃何方食物的问题。
关注碰撞判断
检查食物是否被蛇吃掉时,考虑蛇头是否与食物的八个邻居格子之一相同。这意味着不论食物从哪儿来,蛇都会吃掉。
确保精准碰撞
用户希望蛇只有当头部坐标与食物坐标完全匹配时才能吃到食物,而不仅是与食物的邻居格子相邻。
思考 花的時間為 4 秒
了解,根據您的需求,蛇只有在精確碰到食物的位置時才會加分,而不是在任意方向接近食物時都加分。以下是修改後的 Verilog 程式碼,已針對以下幾點進行調整:
X
, Y
) 完全重疊於食物位置 (item_x
, item_y
) 時,才會加分並生成新的食物。1
為暗,0
為亮)。以下是完整的修改後程式碼:
module 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
精確的食物碰撞檢測:
原先的碰撞檢測包含了蛇頭與食物周圍一圈的位置重疊(上下左右),這會導致蛇只要接近食物就會加分。為了改進這一點,我們將條件改為 僅在蛇頭位置與食物位置完全相同 時才加分。
修改前的條件:
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 // 加分邏輯 end
修改後的條件:
if( (X == item_x && Y == item_y) ) begin // 加分邏輯 end
這樣,只有當蛇頭正好位於食物的位置時,才會觸發加分和食物重生的動作。
移除多餘的食物碰撞方向判斷:
為了確保食物只能被蛇頭直接碰撞,移除了其他方向的碰撞判斷(上、下、左、右)。
保持其他功能不變:
食物生成位置的優化:
目前食物的位置生成方式為:
item_x <= (X + Y + 3) % 8; item_y <= (X + Y + 5) % 8;
這種方式可能會導致食物生成在蛇身上。建議在生成新食物時,檢查生成位置是否已被蛇佔據,避免食物出現在蛇身上。這需要額外的邏輯判斷,可以使用類似身體碰撞檢測的方法來確保食物位置不在蛇身上。
遊戲重啟機制:
當遊戲結束後(state == 2'b10
),目前程式碼並沒有提供重啟遊戲的機制。建議可以加入一個按鍵(例如按下某個按鍵)來重設遊戲狀態,允許玩家重新開始遊戲。
邊界處理:
當蛇移動到地圖邊界時,若 X
或 Y
超過了 3'b111
,遊戲結束。目前的邊界判斷為:
if(X > 3'b111 || Y > 3'b111 || point >= MAX_SCORE) begin state <= 2'b10; end
這意味著蛇無法穿越地圖邊界。根據需求,您可以選擇讓蛇在邊界穿越至對邊(如 Pac-Man 一樣)或保持目前的邊界結束遊戲。
請確保在 FPGA 上下載此修改後的程式碼之前,進行充分的模擬測試,確認以下幾點:
祝您的專案順利完成!如有其他問題,歡迎隨時詢問。
调整并优化代码
我正在调整蛇游戏的Verilog代码,确保蛇在准确地吃到食物时才增加长度,并且添加了深受玩家喜爱的双7段显示器。
确定增长条件
正在检查代码以确保长度变化仅在蛇准确吃到食物时发生,而不是其他时候。
检查代码块
我正在查看always @(posedge game_clk)
代码块,确保蛇长度仅在食物被吃到时增加,同时检查是否有其他问题或错误。
细化代码逻辑
确保蛇只有在吃到食物时才会变化长度,同时保持移动身体的适当性。
确认逻辑细节
蛇移动时,如果准确吃到食物,就增加长度并生成新食物;否则,删除尾巴。正在检验这段逻辑的准确性。
检查身体移动
确保身体位置更新只在蛇吃到食物时进行。查看always @(posedge game_clk)
块,确认当X与item_x、Y与item_y一致时,长度和坐标是否被增查。
确定细节
通过检查碰撞情况,我们能看到蛇身在吃食物时增长,而尾巴则在蛇身移动时保持不变。
初始设置调整
在游戏开始时,初始蛇身未映射到地图上。这可能导致占位错误或无法清除尾巴的情况。确保初始设置正确,以避免后续问题。
定位问题
在查看蛇的起始位置时,我发现初始蛇身未映射到地图上,导致碰撞检测无法识别。应该将初始蛇身的位置设置为0。
更新地图记录
初始蛇身未映射到地图上,导致碰撞检测无法识别。需要将初始位置设置为0,并在蛇身移动时保证地图准确反映当前位置。
地图映射初始位置
地图初始设置为全1,初始蛇身未映射,导致碰撞检测错误。需要将初始位置设置为0,并始终更新地图,确保准确反映当前位置。
更新地图映射
初始地图为全1,初始蛇身未映射,导致碰撞检测错误。需要将初始位置设置为0,并始终更新地图,确保准确反映当前位置。
更新起始地图
新增生成语句以设置初始地图。在初始和重置条件下,手动更新地图以包含初始蛇身,从而确保后续操作的精准性。
更新碰撞检测
确保地图映射更新准确,包括初始蛇身和移动中的蛇身。碰撞检测依赖于地图的正确映射。
调整方向与碰撞检测
根据控制方向调整蛇头位置,进行边界和分数检查,确保碰撞检测的准确性。
策略调整
我调整了蛇头的新位置及遍布地图的映射。根据碰撞检测结果,蛇长的增加或尾巴的清除得到了有效管理。
确保映射与调整
检查初始蛇身的映射,确保蛇身只有在吃到食物时才会增加,并具体解释这些修改。
思考 花的時間為 46 秒
了解,根據您的需求,以下是經過修正的 Verilog 程式碼,確保蛇只有在精確碰到食物時才會增加身體長度。此外,我們也修正了初始狀態下蛇身在 LED 矩陣中的表示,以確保遊戲一開始蛇身已正確顯示在 LED 矩陣上。
X
, Y
) 完全重疊於食物位置 (item_x
, item_y
) 時,才會觸發加分並增加蛇身長度。initial
和 RST
(重設) 的區塊中,將蛇的初始位置正確地映射到 LED 矩陣上,確保遊戲一開始蛇身就顯示在 LED 矩陣中。module 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
,表示這些位置被蛇佔據。
// 將初始蛇身映射到地圖上 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
,確保每次重設遊戲時蛇身正確顯示。
// 將初始蛇身映射到地圖上 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
) 完全重疊時,才會觸發加分及增加蛇身長度。
// 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
迴圈的方式手動展開移動邏輯,確保每個蛇身節點正確地跟隨前一節。
// 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代码,针对此类项目的独特需求进行设计并测试。
修改设定
蛇吃食物时长身体的规则是它的头部必须正好碰到食物,不管是从哪个方向接触。这样一来,碰撞检测也要相应更新。
调整代码需求
删除for循环,手动展开循环,添加特定碰撞检测,确保蛇头碰到食物时才增长身体,并且避免与自己身体碰撞。显示不变。
调整代码
瘦身时,蛇头正好碰到食物,碰撞检测启用。代码没有for循环,双七段显示屏用两个COM线。
精确碰撞检测
更新代码时,确保蛇头与你刚吃的食物位置一致。这样做可以避免误碰撞,保证蛇身不会意外增长。
调整代码细节
确保蛇头碰到食物才增长,避免自我碰撞。代码中双七段显示屏需要用两个COM线连接,同时要确保代码准确无误。
思考 花的時間為 12 秒
了解,根據您的需求,我們將對原始的 Verilog 程式碼進行以下修改:
精確食物碰撞檢測:
X
, Y
) 完全重疊於食物位置 (item_x
, item_y
) 時,才會觸發加分並增加蛇身長度。身體碰撞檢測:
for
迴圈。雙位數七段顯示器:
seg_comm[1:0]
)。seg[6:0]
) 由 decode_7seg
函式控制,1
表示熄滅,0
表示點亮(常陽設計)。蛇身初始化:
RST
) 時,將蛇身正確映射到 LED 矩陣上。以下是完整的修改後的 Verilog 程式碼:
module 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
) 時,才會觸發加分和增加蛇身長度。
// 5. 檢查是否吃到食物 (**僅在完全重疊時**) if( (X == item_x && Y == item_y) ) begin // 加分和增加蛇身長度 end
移除周邊判斷:原先的條件中,蛇頭靠近食物的位置也會被認為是吃到食物,這已被移除,只保留精確重疊的條件。
蛇身碰撞檢測:
手動展開條件:使用多個 if
條件來逐一檢查蛇頭是否撞到任何一段身體,避免使用 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
蛇身初始化於 LED 矩陣:
初始映射:在 initial
區塊和重設 (RST
) 條件中,將蛇的初始位置和身體正確地映射到 LED 矩陣上。
// 將初始蛇身映射到地圖上 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 狀態時分別顯示個位和十位數字,其他狀態則關閉顯示。
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
解碼函式:decode_7seg
函式將數字轉換為七段顯示器的信號,符合常陽設計(1
為暗,0
為亮)。
蛇身移動邏輯:
增加蛇身長度:當蛇吃到食物時,增加 length
並保留蛇尾不變,實現蛇身變長。
不吃食物時移除尾部:當蛇未吃到食物時,將蛇尾在 map
中的標記設為 1
,保持蛇身長度不變。
if( (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
生成,這種方法可能會導致食物生成在蛇身上。建議在實際應用中加入檢查,確保新食物位置不在蛇身上。
// 重新產生食物 item_x <= (X + Y + 3) % 8; item_y <= (X + Y + 5) % 8;
在將此修改後的程式碼下載至 FPGA 之前,建議進行以下測試以確保功能正常運作:
食物碰撞僅在精確位置加分:
蛇身碰撞檢測:
七段顯示器分數顯示:
1
為暗,0
為亮)。遊戲重設功能:
RST
信號能正確重設遊戲狀態,並重新初始化蛇身和地圖。邊界碰撞:
食物生成位置檢查:
為避免食物生成在蛇身上,可以在生成新食物位置時加入檢查邏輯,確保食物位置未被蛇身佔據。這需要額外的邏輯判斷,可以類似於身體碰撞檢測的方法來實現。
// 食物生成後檢查是否在蛇身上 always @(posedge game_clk) begin if( (X == item_x && Y == item_y) ) begin // 增加蛇身長度和分數 // 重新生成食物位置 // 此處可以加入檢查,確保新的食物位置不在蛇身上 end end
遊戲重啟機制:
增加一個按鍵或信號來重啟遊戲,讓玩家在遊戲結束後可以重新開始。
例如,使用另一個按鍵 RESTART
來觸發重設。
input RESTART; // 新增重啟按鍵 always @(posedge SYS_CLK) begin if(RST || RESTART) begin // 重設遊戲狀態 end else begin // 方向控制邏輯 end end
優化蛇身移動邏輯:
增加遊戲速度調節:
MOVE_COUNT_TO
的值來實現。以上修改確保了蛇只有在精確吃到食物時才會增加身體長度,並且正確初始化蛇身於 LED 矩陣中。同時,雙位數七段顯示器也被正確實現,並符合常陽設計(1
為暗,0
為亮)。
請根據實際硬體配置和需求,進行相應的微調與優化。如果在實作過程中遇到其他問題,歡迎隨時詢問!