# Tetris第三週進度 ## 課前 先將上禮拜製作的 Code 從 GitHub 上 Pull 下 可參考先前的講義內容 ## 第三周功能 上個禮拜將遊戲板做出,並讓同學試著體驗將俄羅斯方塊輸出在遊戲板上 今天將讓俄羅斯方塊呈現出慢慢掉下來的感覺、輸出儲存的方塊、將方塊固定於版面上。 ### 目標: ## 步驟: ![掉落方塊_固定版面](https://hackmd.io/_uploads/rJSHwVMm0.gif) ### 俄羅斯方塊定時掉落 1. 先將上禮拜 main 裡面負責輸出遊戲板的寫成 void printCanvas() ```c= void printCanvas(Block canvas[CANVAS_HEIGHT][CANVAS_WIDTH], State* state) { printf("\033[0;0H\n"); for (int i = 0; i < CANVAS_HEIGHT; i++) { printf("|"); for (int j = 0; j < CANVAS_WIDTH; j++) { printf("\033[%dm\u3000", canvas[i][j].color); } printf("\033[0m|\n"); } return; } ``` 2. 透過 while(1) 迴圈重複輸出遊戲板畫面。 ![image](https://hackmd.io/_uploads/BJwMwEz70.png) 3. 撰寫 logic 判斷將 y + 1 傳入 move 內,是否有超出邊界 ```c= void logic(Block canvas二維,State* state) { if (move()成立) y座標++ return; } ``` 4. 撰寫 move 判斷是否移動後方塊有超出邊界 ```c= bool move(遊戲板,原本X,原本Y,原本旋轉模式,新的X,新的Y,新的旋轉模式,形狀ID) { 1. 判斷是否新座標會撞到邊界,會撞到就回傳 false 2. 若沒有撞到邊界,重新reset整個 Block,set新座標上去 Block } ``` 5. 將 logic 放入 while(1) 內並透過 Sleep(100) 另方塊呈現慢慢掉落的動畫 6. ![掉落](https://hackmd.io/_uploads/H1b7wEGX0.gif) --- ### 輸出遊戲的分數 以及 備用的方塊有哪些 1. 在我們的 state 裡有 queue 負責放置有哪些方塊可使用,但我們在 main() 裡還未對其做初始化 運用 [rand()](https://learn.microsoft.com/zh-tw/cpp/c-runtime-library/reference/rand?view=msvc-170) 隨機選擇 7個方塊的其中一個 ```c= for (int i = 0; i < 4; i++) { state.queue[i] = rand() % 7; } ``` 2. 為了增進我們程式碼的可讀性,我們不修改原本 `printCanvas()` 裡面負責輸出遊戲板的部分 因為我們遊戲的格式是寫死的,所以透過[游標的指定](https://blog.csdn.net/qq_42372031/article/details/104137272)去輸出 ![image](https://hackmd.io/_uploads/r1CXP4GQR.png) - 指定我們的輸出位置要從 3 , CANVAS_WIDTH * 2 + 5 開始並輸出 Next: 乘以 2 的目標在於每兩個要視為 1個符號 ```c= printf("\033[%d;%dHNext:", 3, CANVAS_WIDTH * 2 + 5); ``` - 接著運用 for 三次去查看 queue 裡面的 3 個方塊有哪些[\033與\x1b的差別](https://blog.csdn.net/isaaccwoo/article/details/51852666) ```c= for (int i = 1; i <= 3; i++) { shapeData = shapes[state->queue[i]]; for (int j = 0; j < 4; j++) { printf("\033[%d;%dH", i * 4 + j, CANVAS_WIDTH * 2 + 15); for (int k = 0; k < 4; k++) { if (j < shapeData.size && k < shapeData.size && shapeData.rotates[0][j][k]) { printf("\x1b[%dm ", shapeData.color); } else { printf("\x1b[0m "); } } } } ``` ![掉落加上queue](https://hackmd.io/_uploads/Syc4D4MXR.gif) --- ### 讓方塊能夠進行堆疊 - 當我們的 logic 出現不成立時,要跑下一個方塊、將queue往前移、新增一個新的方塊至[3]、x,y座標從頭、rotate=0、**最重要的透過clearLine()將方塊固定在遊戲版面上** ```c= void logic(Block canvas二維,State* state) { if (move()成立) y座標++ else score += 透過clearLine()的方塊 x,y 從頭開始 rotate = 0 queue往前推 [3]隨機新增一個 return; } ``` - 透過迴圈 CANVAS_HEIGHT * CANVAS_WIDTH 一整個遊戲版面,當目前方塊為 true 將其設定為 false,這樣下次我們跑到 move() 時,才會去回傳 false (這邊再請各位自行去對程式碼) ```c= int clearLine(Block canvas[CANVAS_HEIGHT][CANVAS_WIDTH]) { for (int i = 0; i < CANVAS_HEIGHT; i++) { for (int j = 0; j < CANVAS_WIDTH; j++) { if (canvas[i][j].current) { canvas[i][j].current = ????; } } } int linesCleared = 0; /*....日後會再更新*/ return linesCleared; } ``` ![掉落方塊_固定版面](https://hackmd.io/_uploads/rJSHwVMm0.gif) --- ### [各個函式的作用](https://hackmd.io/@FCU1B112/rJ51klKG0) --- ## 課堂練習 1. 完成了 move(),剩下一個 logic() 試著透過閱讀把 logic 函式完成 2. 試著讓掉落的方塊換成 O 3. 把 clearline ??? 的部分作修改 ## 課堂C ```c= #include <stdio.h> #include <stdlib.h> #include <stdbool.h> #include <time.h> #include <windows.h> #define CANVAS_WIDTH 10 #define CANVAS_HEIGHT 20 typedef enum { RED = 41, GREEN, YELLOW, BLUE, PURPLE, CYAN, WHITE, BLACK = 0, } Color; typedef enum { EMPTY = -1, I, J, L, O, S, T, Z } ShapeId; typedef struct { ShapeId shape; Color color; int size; char rotates[4][4][4]; } Shape; typedef struct { int x; int y; int score; int rotate; int fallTime; ShapeId queue[4]; } State; typedef struct { Color color; ShapeId shape; bool current; } Block; Shape shapes[7] = { {.shape = I, .color = CYAN, .size = 4, .rotates = { {{0, 0, 0, 0}, {1, 1, 1, 1}, {0, 0, 0, 0}, {0, 0, 0, 0}}, {{0, 0, 1, 0}, {0, 0, 1, 0}, {0, 0, 1, 0}, {0, 0, 1, 0}}, {{0, 0, 0, 0}, {0, 0, 0, 0}, {1, 1, 1, 1}, {0, 0, 0, 0}}, {{0, 1, 0, 0}, {0, 1, 0, 0}, {0, 1, 0, 0}, {0, 1, 0, 0}}}}, {.shape = J, .color = BLUE, .size = 3, .rotates = { {{1, 0, 0}, {1, 1, 1}, {0, 0, 0}}, {{0, 1, 1}, {0, 1, 0}, {0, 1, 0}}, {{0, 0, 0}, {1, 1, 1}, {0, 0, 1}}, {{0, 1, 0}, {0, 1, 0}, {1, 1, 0}}}}, {.shape = L, .color = YELLOW, .size = 3, .rotates = { {{0, 0, 1}, {1, 1, 1}, {0, 0, 0}}, {{0, 1, 0}, {0, 1, 0}, {0, 1, 1}}, {{0, 0, 0}, {1, 1, 1}, {1, 0, 0}}, {{1, 1, 0}, {0, 1, 0}, {0, 1, 0}}}}, {.shape = O, .color = WHITE, .size = 2, .rotates = { {{1, 1}, {1, 1}}, {{1, 1}, {1, 1}}, {{1, 1}, {1, 1}}, {{1, 1}, {1, 1}}}}, {.shape = S, .color = GREEN, .size = 3, .rotates = { {{0, 1, 1}, {1, 1, 0}, {0, 0, 0}}, {{0, 1, 0}, {0, 1, 1}, {0, 0, 1}}, {{0, 0, 0}, {0, 1, 1}, {1, 1, 0}}, {{1, 0, 0}, {1, 1, 0}, {0, 1, 0}}}}, {.shape = T, .color = PURPLE, .size = 3, .rotates = { {{0, 1, 0}, {1, 1, 1}, {0, 0, 0}}, {{0, 1, 0}, {0, 1, 1}, {0, 1, 0}}, {{0, 0, 0}, {1, 1, 1}, {0, 1, 0}}, {{0, 1, 0}, {1, 1, 0}, {0, 1, 0}}}}, {.shape = Z, .color = RED, .size = 3, .rotates = { {{1, 1, 0}, {0, 1, 1}, {0, 0, 0}}, {{0, 0, 1}, {0, 1, 1}, {0, 1, 0}}, {{0, 0, 0}, {1, 1, 0}, {0, 1, 1}}, {{0, 1, 0}, {1, 1, 0}, {1, 0, 0}}}}, }; void setBlock(Block *block, Color color, ShapeId shape, bool current) { block->color = color; block->shape = shape; block->current = current; } void resetBlock(Block *block) { block->color = BLACK; block->shape = EMPTY; block->current = false; } void printCanvas(Block canvas[CANVAS_HEIGHT][CANVAS_WIDTH], State *state) { printf("\033[0;0H\n"); for (int i = 0; i < CANVAS_HEIGHT; i++) { printf("|"); for (int j = 0; j < CANVAS_WIDTH; j++) { printf("\033[%dm\u3000", canvas[i][j].color); } printf("\033[0m|\n"); } // 輸出Next: printf("\033[%d;%dHNext:", 3, CANVAS_WIDTH * 2 + 5); // 輸出有甚麼方塊 for (int i = 1; i <= 3; i++) { Shape shapeData = shapes[state->queue[i]]; for (int j = 0; j < 4; j++) { printf("\033[%d;%dH", i * 4 + j, CANVAS_WIDTH * 2 + 15); for (int k = 0; k < 4; k++) { if (j < shapeData.size && k < shapeData.size && shapeData.rotates[0][j][k]) { printf("\x1b[%dm ", shapeData.color); } else { printf("\x1b[0m "); } } } } return; } bool move(Block canvas[CANVAS_HEIGHT][CANVAS_WIDTH], int originalX, int originalY, int originalRotate, int newX, int newY, int newRotate, ShapeId shapeId) { Shape shapeData = shapes[shapeId]; int size = shapeData.size; // 判斷方塊有沒有不符合條件 for (int i = 0; i < size; i++) { for (int j = 0; j < size; j++) { if (shapeData.rotates[newRotate][i][j]) { // 判斷有沒有出去邊界 if (newX + j < 0 || newX + j >= CANVAS_WIDTH || newY + i < 0 || newY + i >= CANVAS_HEIGHT) { return false; } // 判斷有沒有碰到別的方塊 if (!canvas[newY + i][newX + j].current && canvas[newY + i][newX + j].shape != EMPTY) { return false; } } } } // 移除方塊舊的位置 for (int i = 0; i < size; i++) { for (int j = 0; j < size; j++) { if (shapeData.rotates[originalRotate][i][j]) { resetBlock(&canvas[originalY + i][originalX + j]); } } } // 移動方塊至新的位置 for (int i = 0; i < size; i++) { for (int j = 0; j < size; j++) { if (shapeData.rotates[newRotate][i][j]) { setBlock(&canvas[newY + i][newX + j], shapeData.color, shapeId, true); } } } return true; } int clearLine(Block canvas[CANVAS_HEIGHT][CANVAS_WIDTH]) { for (int i = 0; i < CANVAS_HEIGHT; i++) { for (int j = 0; j < CANVAS_WIDTH; j++) { if (canvas[i][j].current) { canvas[i][j].current = false; } } } int linesCleared = 0; /*....日後會再更新*/ return linesCleared; } void logic(Block canvas[CANVAS_HEIGHT][CANVAS_WIDTH], State *state) { if (move(canvas, state->x, state->y, state->rotate, state->x, state->y + 1, state->rotate, state->queue[0])) { state->y++; } else { state->score += clearLine(canvas); state->x = CANVAS_WIDTH / 2; state->y = 0; state->rotate = 0; state->queue[0] = state->queue[1]; state->queue[1] = state->queue[2]; state->queue[2] = state->queue[3]; state->queue[3] = rand() % 7; } return; } int main() { srand(time(NULL)); State state = { .x = CANVAS_WIDTH / 2, .y = 0, .score = 0, .rotate = 0, .fallTime = 0}; for (int i = 0; i < 4; i++) { state.queue[i] = rand() % 7; } Block canvas[CANVAS_HEIGHT][CANVAS_WIDTH]; for (int i = 0; i < CANVAS_HEIGHT; i++) { for (int j = 0; j < CANVAS_WIDTH; j++) { resetBlock(&canvas[i][j]); } } Shape shapeData = shapes[state.queue[0]]; for (int i = 0; i < shapeData.size; i++) { for (int j = 0; j < shapeData.size; j++) { if (shapeData.rotates[0][i][j]) { setBlock(&canvas[state.y + i][state.x + j], shapeData.color, state.queue[0], true); } } } while (1) { printCanvas(canvas, &state); logic(canvas, &state); Sleep(100); } // printf("\e[?25l"); // hide cursor return 0; } ```