貪吃蛇遊戲修改

Creado el: 24 de diciembre de 2024

Respondido usando OpenAI 01 por OpenAI01

Pregunta

請你幫我修改以下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
);

reg [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

Pensamiento

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 秒

Respuesta

下面提供一份「已加入身體碰撞檢測(撞到自己就結束)」以及「雙位七段顯示(1=暗,0=亮)」的範例程式碼,供您參考。以下程式假設您在 Cyclone III (EP3C10E144C8) 上使用,7 段顯示器為一般常陽 (common anode) 設計,解碼器輸出為「0 亮、1 暗」。若硬體上為常陰 (common cathode) 則可能需將解碼器輸出反向。

說明重點

  1. 身體碰撞檢測

    • 原本程式其實已有基本的身體碰撞判斷(比對 head 與 body_mem_x[i], body_mem_y[i])。
    • 為了更容易擴充,我們以 for 迴圈的方式檢查所有身體節點,只要發現「蛇頭 (X, Y) 與其中一段身體 (body_mem_x[i], body_mem_y[i])」重疊,就進入失敗 (state = 2'b10) 狀態。
  2. 雙位數七段顯示器

    • 內文有提到「1 為暗、0 為亮」,這通常表示常陽顯示器。在程式中的 decode_7seg 函式所輸出的 1 代表該段熄滅、0 代表該段點亮,和常陽顯示器規格吻合。
    • 我們使用 score_onesscore_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 &rarr; 遊戲結束 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

程式重點說明

  1. 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) 與任意身體節點座標相同,就結束遊戲。

  2. 雙位七段顯示

    • seg_comm 共有 4 種狀態(2 bits):00, 01, 10, 11。
    • 在範例程式中只在 00 時顯示個位數、01 時顯示十位數,10 和 11 時則把 7 段全部關閉(7'b1111111)。若您只有兩位數顯示,可依硬體需求搭配去驅動哪一位 (如 AN0, AN1)。
    • 解碼函式 decode_7seg 已對應「0→亮, 1→暗」,符合常陽 7 段顯示器的需求。
  3. 遊戲結束條件

    • 超出邊界 (X或Y大於 7)
    • 分數 >= MAX_SCORE
    • 撞到自己身體

以上程式提供了基本「身體碰撞檢測」以及「雙位七段顯示(1=暗, 0=亮)」的實作範例,請您依實際電路連線(特別是七段顯示器的共陽或共陰)再做最終微調。若硬體為共陰(Cathode),則可能需要將 decode_7seg 的輸出反相(或用 ~decode_7seg(...))。祝您專案順利!

Pregunta

veriolog沒有for迴圈

Pensamiento

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 秒

Respuesta

以下範例示範在 不使用 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 矩陣啟用(若硬體需要可自行控制) // 若遊戲結束:分數達標&rarr;綠燈,否則&rarr;紅燈 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 &rarr; 顯示個位;01 &rarr; 顯示十位;其餘 &rarr; 全熄 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. 邊界檢查、分數檢查 &rarr; 遊戲結束 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 &rarr; 遊戲結束(可再加 restart 機制) end endmodule

程式重點說明

  1. 身體碰撞檢查 (無 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

    達成同樣效果。

  2. 蛇身位移(無 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; 作為新頭位置。

  3. 雙位數 7 段顯示

    • 透過 seg_comm 在 00 時顯示個位、01 時顯示十位,10 與 11 時將段訊號全關 (7'b1111111)。
    • decode_7seg 內部對應「0=亮、1=暗」,符合常陽(Common Anode) 7 段顯示器的規格。若實際硬體是共陰(Common Cathode),則需將解碼結果反相 (或直接在解碼表中做相反設計)。
  4. 遊戲結束條件

    • 撞牆(超過座標 7)
    • 分數超過(或達到) MAX_SCORE
    • 撞到自己身體

若您確定所在的合成環境允許使用 for 迴圈,建議仍使用 for 寫法,因為程式較為簡潔。上方範例僅在特殊作業需求(禁止使用 for)或特殊限制時供參考。祝您開發順利!

Pregunta

蛇不管從上下左右吃到食物都算加分

Pensamiento

细心调整代码

我将Verilog代码进行了调整,添加了碰撞检测和双重7段显示。用户报告蛇累积分数无论吃何方食物的问题。

关注碰撞判断

检查食物是否被蛇吃掉时,考虑蛇头是否与食物的八个邻居格子之一相同。这意味着不论食物从哪儿来,蛇都会吃掉。

确保精准碰撞

用户希望蛇只有当头部坐标与食物坐标完全匹配时才能吃到食物,而不仅是与食物的邻居格子相邻。

思考 花的時間為 4 秒

Respuesta

了解,根據您的需求,蛇只有在精確碰到食物的位置時才會加分,而不是在任意方向接近食物時都加分。以下是修改後的 Verilog 程式碼,已針對以下幾點進行調整:

  1. 精確食物碰撞檢測:只有當蛇頭 (X, Y) 完全重疊於食物位置 (item_x, item_y) 時,才會加分並生成新的食物。
  2. 身體碰撞檢測:確保蛇頭不會撞到自身的任何一部分,否則遊戲結束。
  3. 雙位數七段顯示:保留雙位數顯示分數的功能,並確保顯示器的共陽設計(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 矩陣啟用(若硬體需要可自行控制) // 若遊戲結束:分數達標&rarr;綠燈,否則&rarr;紅燈 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 &rarr; 顯示個位;01 &rarr; 顯示十位;其餘 &rarr; 全熄 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. 邊界檢查、分數檢查 &rarr; 遊戲結束 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 &rarr; 遊戲結束(可再加 restart 機制) endmodule

主要修改說明

  1. 精確的食物碰撞檢測

    原先的碰撞檢測包含了蛇頭與食物周圍一圈的位置重疊(上下左右),這會導致蛇只要接近食物就會加分。為了改進這一點,我們將條件改為 僅在蛇頭位置與食物位置完全相同 時才加分。

    修改前的條件:

    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

    這樣,只有當蛇頭正好位於食物的位置時,才會觸發加分和食物重生的動作。

  2. 移除多餘的食物碰撞方向判斷

    為了確保食物只能被蛇頭直接碰撞,移除了其他方向的碰撞判斷(上、下、左、右)。

  3. 保持其他功能不變

    • 身體碰撞檢測:繼續使用手動展開的方式檢查蛇頭是否與身體任何一部分重疊,從而判定遊戲是否結束。
    • 雙位數七段顯示:保持雙位數顯示分數的功能,並確保顯示器按照共陽設計進行控制。

其他建議

  1. 食物生成位置的優化

    目前食物的位置生成方式為:

    item_x <= (X + Y + 3) % 8; item_y <= (X + Y + 5) % 8;

    這種方式可能會導致食物生成在蛇身上。建議在生成新食物時,檢查生成位置是否已被蛇佔據,避免食物出現在蛇身上。這需要額外的邏輯判斷,可以使用類似身體碰撞檢測的方法來確保食物位置不在蛇身上。

  2. 遊戲重啟機制

    當遊戲結束後(state == 2'b10),目前程式碼並沒有提供重啟遊戲的機制。建議可以加入一個按鍵(例如按下某個按鍵)來重設遊戲狀態,允許玩家重新開始遊戲。

  3. 邊界處理

    當蛇移動到地圖邊界時,若 XY 超過了 3'b111,遊戲結束。目前的邊界判斷為:

    if(X > 3'b111 || Y > 3'b111 || point >= MAX_SCORE) begin state <= 2'b10; end

    這意味著蛇無法穿越地圖邊界。根據需求,您可以選擇讓蛇在邊界穿越至對邊(如 Pac-Man 一樣)或保持目前的邊界結束遊戲。

測試與驗證

請確保在 FPGA 上下載此修改後的程式碼之前,進行充分的模擬測試,確認以下幾點:

  1. 食物碰撞僅在精確位置加分
  2. 身體碰撞檢測正確運作
  3. 分數在七段顯示器上正確顯示
  4. 遊戲在不同方向移動時正常運作
  5. 邊界碰撞正確判定遊戲結束

祝您的專案順利完成!如有其他問題,歡迎隨時詢問。

Pregunta

蛇要吃到食物身體才會變長

Pensamiento

调整并优化代码

我正在调整蛇游戏的Verilog代码,确保蛇在准确地吃到食物时才增加长度,并且添加了深受玩家喜爱的双7段显示器。

确定增长条件

正在检查代码以确保长度变化仅在蛇准确吃到食物时发生,而不是其他时候。

检查代码块

我正在查看always @(posedge game_clk)代码块,确保蛇长度仅在食物被吃到时增加,同时检查是否有其他问题或错误。

细化代码逻辑

确保蛇只有在吃到食物时才会变化长度,同时保持移动身体的适当性。

确认逻辑细节

蛇移动时,如果准确吃到食物,就增加长度并生成新食物;否则,删除尾巴。正在检验这段逻辑的准确性。

检查身体移动

确保身体位置更新只在蛇吃到食物时进行。查看always @(posedge game_clk)块,确认当X与item_x、Y与item_y一致时,长度和坐标是否被增查。

确定细节

通过检查碰撞情况,我们能看到蛇身在吃食物时增长,而尾巴则在蛇身移动时保持不变。

初始设置调整

在游戏开始时,初始蛇身未映射到地图上。这可能导致占位错误或无法清除尾巴的情况。确保初始设置正确,以避免后续问题。

定位问题

在查看蛇的起始位置时,我发现初始蛇身未映射到地图上,导致碰撞检测无法识别。应该将初始蛇身的位置设置为0。

更新地图记录

初始蛇身未映射到地图上,导致碰撞检测无法识别。需要将初始位置设置为0,并在蛇身移动时保证地图准确反映当前位置。

地图映射初始位置
地图初始设置为全1,初始蛇身未映射,导致碰撞检测错误。需要将初始位置设置为0,并始终更新地图,确保准确反映当前位置。

更新地图映射

初始地图为全1,初始蛇身未映射,导致碰撞检测错误。需要将初始位置设置为0,并始终更新地图,确保准确反映当前位置。

更新起始地图

新增生成语句以设置初始地图。在初始和重置条件下,手动更新地图以包含初始蛇身,从而确保后续操作的精准性。

更新碰撞检测

确保地图映射更新准确,包括初始蛇身和移动中的蛇身。碰撞检测依赖于地图的正确映射。

调整方向与碰撞检测
根据控制方向调整蛇头位置,进行边界和分数检查,确保碰撞检测的准确性。

策略调整

我调整了蛇头的新位置及遍布地图的映射。根据碰撞检测结果,蛇长的增加或尾巴的清除得到了有效管理。

确保映射与调整

检查初始蛇身的映射,确保蛇身只有在吃到食物时才会增加,并具体解释这些修改。

思考 花的時間為 46 秒

Respuesta

了解,根據您的需求,以下是經過修正的 Verilog 程式碼,確保蛇只有在精確碰到食物時才會增加身體長度。此外,我們也修正了初始狀態下蛇身在 LED 矩陣中的表示,以確保遊戲一開始蛇身已正確顯示在 LED 矩陣上。

主要修改點

  1. 精確食物碰撞檢測
    • 只有當蛇頭 (X, Y) 完全重疊於食物位置 (item_x, item_y) 時,才會觸發加分並增加蛇身長度。
  2. 初始化蛇身於 LED 矩陣
    • initialRST (重設) 的區塊中,將蛇的初始位置正確地映射到 LED 矩陣上,確保遊戲一開始蛇身就顯示在 LED 矩陣中。
  3. 蛇身移動邏輯
    • 當蛇吃到食物時,增加蛇身長度且不移除尾部,實現蛇身變長的效果。
    • 當蛇未吃到食物時,移除尾部以保持蛇身長度不變。

完整修改後的程式碼

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 矩陣啟用(若硬體需要可自行控制) // 若遊戲結束:分數達標&rarr;綠燈,否則&rarr;紅燈 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 &rarr; 顯示個位;01 &rarr; 顯示十位;其餘 &rarr; 全熄 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. 邊界檢查、分數檢查 &rarr; 遊戲結束 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 &rarr; 遊戲結束(可再加 restart 機制) endmodule

詳細說明

  1. 初始化蛇身於 LED 矩陣

    • initial 區塊中,設置了蛇的初始位置 (X=2, Y=2) 及其身體 (X=1, Y=2X=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; // 蛇尾
  2. 重設遊戲時的蛇身映射

    • 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; // 蛇尾
  3. 精確食物碰撞檢測

    • 僅當蛇頭 (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_onesscore_tens 以顯示分數。
        • 增加 length,使蛇身長度增加。
        • 重新生成食物位置。
      • 否則,表示未吃到食物:
        • 清除蛇尾在 map 中的標記 (1'b1),保持蛇身長度不變。
  4. 蛇身移動邏輯

    • 當蛇前進時,蛇身的位置會向後移動。
    • 使用無 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;
  5. 食物生成優化建議

    • 目前食物生成使用簡單的數學運算 (X + Y + 3) % 8(X + Y + 5) % 8,這可能會導致食物生成在蛇身上。
    • 建議加入檢查生成位置是否已被蛇佔據,避免食物出現在蛇身上。這需要額外的邏輯判斷,可以類似於身體碰撞檢測的方法來確保食物位置不在蛇身上。

測試與驗證

在 FPGA 上下載此修改後的程式碼之前,請確保進行以下測試以確認功能正常:

  1. 食物碰撞僅在精確位置加分

    • 確認蛇只有在蛇頭精確重疊食物時才會加分並增加蛇身長度。
  2. 蛇身碰撞檢測

    • 驗證當蛇頭撞到自身任一部分時,遊戲正確結束。
  3. 七段顯示器分數顯示

    • 確認分數在雙位七段顯示器上正確顯示,並且符合共陽設計(1 為暗,0 為亮)。
  4. 遊戲重設功能

    • 驗證在遊戲結束後,透過 RST 信號能正確重設遊戲狀態,並重新初始化蛇身和地圖。
  5. 邊界碰撞

    • 確認蛇移動到地圖邊界時,遊戲正確結束。

進一步優化建議

  1. 食物生成位置檢查

    • 為避免食物生成在蛇身上,可以在生成新食物位置時加入檢查邏輯,確保食物位置未被蛇身佔據。
  2. 遊戲重啟機制

    • 增加一個按鍵或信號來重啟遊戲,讓玩家在遊戲結束後可以重新開始。
  3. 優化蛇身移動邏輯

    • 使用 FSM(有限狀態機) 或更高效的邏輯來管理蛇身移動,減少冗餘代碼並提升可維護性。

總結

以上修改確保了蛇只有在精確吃到食物時才會增加身體長度,並且正確初始化蛇身於 LED 矩陣中。請根據實際硬體配置和需求,進行相應的微調與優化。如果在實作過程中遇到其他問題,歡迎隨時詢問!

Pregunta

蛇頭不管從上下左右吃到食物都算吃到

Pensamiento

结合内容

我正在制作经典蛇游戏的Verilog代码,针对此类项目的独特需求进行设计并测试。

修改设定

蛇吃食物时长身体的规则是它的头部必须正好碰到食物,不管是从哪个方向接触。这样一来,碰撞检测也要相应更新。

调整代码需求

删除for循环,手动展开循环,添加特定碰撞检测,确保蛇头碰到食物时才增长身体,并且避免与自己身体碰撞。显示不变。

调整代码

瘦身时,蛇头正好碰到食物,碰撞检测启用。代码没有for循环,双七段显示屏用两个COM线。

精确碰撞检测

更新代码时,确保蛇头与你刚吃的食物位置一致。这样做可以避免误碰撞,保证蛇身不会意外增长。

调整代码细节

确保蛇头碰到食物才增长,避免自我碰撞。代码中双七段显示屏需要用两个COM线连接,同时要确保代码准确无误。

思考 花的時間為 12 秒

Respuesta

了解,根據您的需求,我們將對原始的 Verilog 程式碼進行以下修改:

  1. 精確食物碰撞檢測

    • 僅當蛇頭 (X, Y) 完全重疊於食物位置 (item_x, item_y) 時,才會觸發加分並增加蛇身長度。
    • 不再考慮蛇頭與食物相鄰的位置,只在精確位置重疊時才計算為吃到食物。
  2. 身體碰撞檢測

    • 確保蛇頭不會撞到自身的任何部分,否則遊戲結束。
    • 使用手動展開的方式進行碰撞檢測,避免使用 for 迴圈。
  3. 雙位數七段顯示器

    • 使用兩個七段顯示器,搭配兩個 COM 線(seg_comm[1:0])。
    • 每個顯示器的段 (seg[6:0]) 由 decode_7seg 函式控制,1 表示熄滅,0 表示點亮(常陽設計)。
  4. 蛇身初始化

    • 在初始化和重設 (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 矩陣啟用(若硬體需要可自行控制) // 若遊戲結束:分數達標&rarr;綠燈,否則&rarr;紅燈 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 &rarr; 顯示個位;01 &rarr; 顯示十位;其餘 &rarr; 全熄 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. 邊界檢查、分數檢查 &rarr; 遊戲結束 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

主要修改說明

  1. 精確食物碰撞檢測

    • 條件調整:僅當蛇頭 (X, Y) 完全重疊於食物位置 (item_x, item_y) 時,才會觸發加分和增加蛇身長度。

      // 5. 檢查是否吃到食物 (**僅在完全重疊時**) if( (X == item_x && Y == item_y) ) begin // 加分和增加蛇身長度 end
    • 移除周邊判斷:原先的條件中,蛇頭靠近食物的位置也會被認為是吃到食物,這已被移除,只保留精確重疊的條件。

  2. 蛇身碰撞檢測

    • 手動展開條件:使用多個 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
  3. 蛇身初始化於 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; // 蛇尾
  4. 雙位數七段顯示器

    • 掃描邏輯:使用 seg_comm 在 00 和 01 狀態時分別顯示個位和十位數字,其他狀態則關閉顯示。

      always @(posedge seg_clk) begin // seg_comm 00 &rarr; 顯示個位;01 &rarr; 顯示十位;其餘 &rarr; 全熄 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 為亮)。

  5. 蛇身移動邏輯

    • 增加蛇身長度:當蛇吃到食物時,增加 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
  6. 食物生成

    • 簡單生成方法:食物位置透過 (X + Y + 3) % 8(X + Y + 5) % 8 生成,這種方法可能會導致食物生成在蛇身上。建議在實際應用中加入檢查,確保新食物位置不在蛇身上。

      // 重新產生食物 item_x <= (X + Y + 3) % 8; item_y <= (X + Y + 5) % 8;

測試與驗證建議

在將此修改後的程式碼下載至 FPGA 之前,建議進行以下測試以確保功能正常運作:

  1. 食物碰撞僅在精確位置加分

    • 確認蛇只有在蛇頭精確重疊食物時才會加分並增加蛇身長度。
    • 在不同方向移動時,僅在頭部與食物位置相同時觸發加分。
  2. 蛇身碰撞檢測

    • 驗證當蛇頭撞到自身任一部分時,遊戲正確結束。
    • 測試不同長度的蛇,確保所有身體節點都被正確檢查。
  3. 七段顯示器分數顯示

    • 確認分數在雙位七段顯示器上正確顯示,並且符合常陽設計(1 為暗,0 為亮)。
    • 驗證分數超過 9 時,十位數也能正確顯示。
  4. 遊戲重設功能

    • 驗證在遊戲結束後,透過 RST 信號能正確重設遊戲狀態,並重新初始化蛇身和地圖。
  5. 邊界碰撞

    • 確認蛇移動到地圖邊界時,遊戲正確結束。
    • 測試蛇在各邊界的反應,確保不會越界或產生未預期行為。

進一步優化建議

  1. 食物生成位置檢查

    • 為避免食物生成在蛇身上,可以在生成新食物位置時加入檢查邏輯,確保食物位置未被蛇身佔據。這需要額外的邏輯判斷,可以類似於身體碰撞檢測的方法來實現。

      // 食物生成後檢查是否在蛇身上 always @(posedge game_clk) begin if( (X == item_x && Y == item_y) ) begin // 增加蛇身長度和分數 // 重新生成食物位置 // 此處可以加入檢查,確保新的食物位置不在蛇身上 end end
  2. 遊戲重啟機制

    • 增加一個按鍵或信號來重啟遊戲,讓玩家在遊戲結束後可以重新開始。

    • 例如,使用另一個按鍵 RESTART 來觸發重設。

      input RESTART; // 新增重啟按鍵 always @(posedge SYS_CLK) begin if(RST || RESTART) begin // 重設遊戲狀態 end else begin // 方向控制邏輯 end end
  3. 優化蛇身移動邏輯

    • 使用更高效的邏輯來管理蛇身移動,減少冗餘代碼並提升可維護性。
    • 例如,可以將蛇身的移動邏輯封裝成一個獨立的模組或函式,以提高代碼的模組化和可讀性。
  4. 增加遊戲速度調節

    • 根據分數或遊戲進度動態調整蛇的移動速度,增加遊戲挑戰性。
    • 這可以通過調整 MOVE_COUNT_TO 的值來實現。

總結

以上修改確保了蛇只有在精確吃到食物時才會增加身體長度,並且正確初始化蛇身於 LED 矩陣中。同時,雙位數七段顯示器也被正確實現,並符合常陽設計(1 為暗,0 為亮)。

請根據實際硬體配置和需求,進行相應的微調與優化。如果在實作過程中遇到其他問題,歡迎隨時詢問!

Compartir este Q&A