# Tetris 烏克蘭方塊 ## 修改部分 ### 遊戲介面及規則 * 增加遊戲封面以及遊戲結束的畫面,按下空白鍵開始遊戲或離開。 * 遊戲開始倒數畫面。 * 增加顏色顯示。 * 分數機制。 ### 功能部分 * 增加方塊投影顯示的功能。 --- ## 遊戲介面及規則 ### 遊戲封面 ```c++= void printStartScreen() { printf("\033[2J"); // Clear the screen printf("\033[0;0H"); // Move the cursor to (0, 0) printf("\033[31m___________ __ .__ ________ \033[0m\n"); printf("\033[33m\\__ ___/____/ |________|__| ______ / _____/_____ _____ ____ \033[0m\n"); printf("\033[32m | |_/ __ \\ __\\_ __ \\ |/ ___/ / \\ ___\\__ \\ / \\_/ __ \\ \033[0m\n"); printf("\033[36m | |\\ ___/| | | | \\/ |\\___ \\ \\ \\_\\ \\/ __ \\| Y Y \\ ___/ \033[0m\n"); printf("\033[34m |____| \\___ >__| |__| |__/____ > \\______ (____ /__|_| /\\___ >\033[0m\n"); printf("\033[35m \\/ \\/ \\/ \\/ \\/ \\/ \033[0m\n"); printf("\n"); printf("\033[31mInstructions:\033[0m\n"); printf("\033[33mUse the arrow keys to move and rotate the blocks.\033[0m\n"); printf("\033[32mPress the spacebar to make the block fall instantly.\033[0m\n"); printf("\n"); printf("\033[37;7mPress any key to start the game...\033[0m\n"); // Wait for the user to press any key system("pause"); } ``` ### 結束畫面 ```c++= if (!move(canvas, state->x, state->y, state->rotate, state->x, state->y, state->rotate, state->queue[0])) // Gameover { printf("\033[2J"); // Clear the screen printf("\033[0;0H"); // Move the cursor to (0, 0) printf("\033[31m ________ ________ \033[0m\n"); printf("\033[33m/ _____/_____ _____ ____ \\_____ \\___ __ ___________ \033[0m\n"); printf("\033[32m/ \\ ___\\__ \\ / \\_/ __ \\ / | \\ \\/ // __ \\_ __ \\\033[0m\n"); printf("\033[36m\\ \\_\\ \\/ __ \\| Y Y \\ ___/ / | \\ /\\ ___/| | \\/\033[0m\n"); printf("\033[34m \\______ (____ /__|_| /\\___ > \\_______ /\\_/ \\___ >__| \033[0m\n"); printf("\033[35m \\/ \\/ \\/ \\/ \\/ \\/ \033[0m\n"); printf("\n"); printf("Your Score: %d\n", state->score); printf("Press any key to exit...\n"); // Wait for the user to press any key system("pause"); } ``` ### 遊戲開始倒數畫面 ```C++= void Count() { while (1) { printf("\033[2J"); // Clear the screen printf("\033[5;20H"); printf("Game Started"); Sleep(1000); system("cls"); printf("\033[5;20H"); printf("\033[31;7m3\033[0m"); Sleep(1000); system("cls"); printf("\033[5;20H"); printf("\033[33;7m2\033[0m"); Sleep(1000); system("cls"); printf("\033[5;20H"); printf("\033[32;7m1\033[0m"); Sleep(1000); system("cls"); printf("\033[5;20H"); printf("GoGo!!!"); Sleep(1000); system("cls"); break; } } ``` ### 遊戲封面 ![](https://hackmd.io/_uploads/B1RGBA2I2.png) ### 結束畫面 ![](https://hackmd.io/_uploads/HkzVB0nIh.png) ### 遊戲開始倒數畫面 ![](https://hackmd.io/_uploads/rk2O4Y6U2.png) ![](https://hackmd.io/_uploads/H1XcNFTI2.png) ![](https://hackmd.io/_uploads/rJ0cNKp82.png) ![](https://hackmd.io/_uploads/rJHs4KT8h.png) ![](https://hackmd.io/_uploads/BygT4tTI2.png) ### 顏色顯示範例 ```c++= printf("\033[37;7mPress any key to start the game...\033[0m\n"); ``` ### 分數機制 每次消完一排加10分 ```C++= state->score += clearLine(canvas)*10; ``` --- ## 功能部分 ## 方塊投影顯示的功能 ### 顯示方塊的碰撞點位置。這可以讓玩家更容易預測方塊落地的位置。 ```C++= // Render the predict block int newy = state->y + 1; while (movea(canvas, state->x, newy, state->rotate, state->x, newy, state->rotate, state->queue[0])) { newy++; } shapeData = shapes[state->queue[0]]; for (int j = 0; j < 4; j++) { for (int k = 0; k < 4; k++) { if (j < shapeData.size && k < shapeData.size && shapeData.rotates[state->rotate][j][k]) { printf("\033[%d;%dH\033[%dm_]\033[0m", newy + j + 1, 2 * (state->x + k) + 2, shapeData.color - 10); } } } ``` ### 方塊投影展示畫面 ![](https://hackmd.io/_uploads/rJH48K68n.png) ![](https://hackmd.io/_uploads/rJhE8KaI2.png) --- --- ## 完整程式碼 ```C++ #include <stdio.h> #include <stdlib.h> #include <stdbool.h> #include <time.h> #include <windows.h> #define LEFT_KEY 0x25 // The key to move left, default = 0x25 (left arrow) #define RIGHT_KEY 0x27 // The key to move right, default = 0x27 (right arrow) #define ROTATE_KEY 0x26 // The key to rotate, default = 0x26 (up arrow) #define DOWN_KEY 0x28 // The key to move down, default = 0x28 (down arrow) #define FALL_KEY 0x20 // The key to fall, default = 0x20 (spacebar) #define FALL_DELAY 500 // The delay between each fall, default = 500 #define RENDER_DELAY 100 // The delay between each frame, default = 100 #define LEFT_FUNC() GetAsyncKeyState(LEFT_KEY) & 0x8000 #define RIGHT_FUNC() GetAsyncKeyState(RIGHT_KEY) & 0x8000 #define ROTATE_FUNC() GetAsyncKeyState(ROTATE_KEY) & 0x8000 #define DOWN_FUNC() GetAsyncKeyState(DOWN_KEY) & 0x8000 #define FALL_FUNC() GetAsyncKeyState(FALL_KEY) & 0x8000 #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; } 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; // check if the new position is valid to place the block 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; } } } } // remove the old position 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]); } } } // move the block 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; } bool movea(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; // check if the new position is valid to place the block 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; } } } } } 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"); } Shape shapeData = shapes[state->queue[1]]; printf("\033[%d;%dHNext:", 3, CANVAS_WIDTH * 2 + 5); 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 "); } } } // Render the predict block int newy = state->y + 1; while (movea(canvas, state->x, newy, state->rotate, state->x, newy, state->rotate, state->queue[0])) { newy++; } shapeData = shapes[state->queue[0]]; for (int j = 0; j < 4; j++) { for (int k = 0; k < 4; k++) { if (j < shapeData.size && k < shapeData.size && shapeData.rotates[state->rotate][j][k]) { printf("\033[%d;%dH\033[%dm_]\033[0m", newy + j + 1, 2 * (state->x + k) + 2, shapeData.color - 10); } } } } printf("\033[%d;%dHScore: %d", 20, CANVAS_WIDTH * 2 + 5, (state->score)); return; } 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; for (int i = CANVAS_HEIGHT - 1; i >= 0; i--) { bool isFull = true; for (int j = 0; j < CANVAS_WIDTH; j++) { if (canvas[i][j].shape == EMPTY) { isFull = false; break; } } if (isFull) { linesCleared += 1; for (int j = i; j > 0; j--) { for (int k = 0; k < CANVAS_WIDTH; k++) { setBlock(&canvas[j][k], canvas[j - 1][k].color, canvas[j - 1][k].shape, false); resetBlock(&canvas[j - 1][k]); } } i++; } } return linesCleared; } void logic(Block canvas[CANVAS_HEIGHT][CANVAS_WIDTH], State* state) { if (ROTATE_FUNC()) { int newRotate = (state->rotate + 1) % 4; if (move(canvas, state->x, state->y, state->rotate, state->x, state->y, newRotate, state->queue[0])) { state->rotate = newRotate; } } else if (LEFT_FUNC()) { if (move(canvas, state->x, state->y, state->rotate, state->x - 1, state->y, state->rotate, state->queue[0])) { state->x -= 1; } } else if (RIGHT_FUNC()) { if (move(canvas, state->x, state->y, state->rotate, state->x + 1, state->y, state->rotate, state->queue[0])) { state->x += 1; } } else if (DOWN_FUNC()) { state->fallTime = FALL_DELAY; } else if (FALL_FUNC()) { state->fallTime += FALL_DELAY * CANVAS_HEIGHT; } state->fallTime += RENDER_DELAY; while (state->fallTime >= FALL_DELAY) { state->fallTime -= FALL_DELAY; 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->fallTime = 0; state->queue[0] = state->queue[1]; state->queue[1] = state->queue[2]; state->queue[2] = state->queue[3]; state->queue[3] = rand() % 7; if (!move(canvas, state->x, state->y, state->rotate, state->x, state->y, state->rotate, state->queue[0])) // Gameover { printf("\033[2J"); // Clear the screen printf("\033[0;0H"); // Move the cursor to (0, 0) printf("\033[31m ________ ________ \033[0m\n"); printf("\033[33m/ _____/_____ _____ ____ \\_____ \\___ __ ___________ \033[0m\n"); printf("\033[32m/ \\ ___\\__ \\ / \\_/ __ \\ / | \\ \\/ // __ \\_ __ \\\033[0m\n"); printf("\033[36m\\ \\_\\ \\/ __ \\| Y Y \\ ___/ / | \\ /\\ ___/| | \\/\033[0m\n"); printf("\033[34m \\______ (____ /__|_| /\\___ > \\_______ /\\_/ \\___ >__| \033[0m\n"); printf("\033[35m \\/ \\/ \\/ \\/ \\/ \\/ \033[0m\n"); printf("\n"); printf("\033[37;7mYour Score: %d\033[0m\n", state->score); printf("Press any key to exit...\n"); // Wait for the user to press any key system("pause"); exit(0); // exit game } } } return; } void printStartScreen() { printf("\033[2J"); // Clear the screen printf("\033[0;0H"); // Move the cursor to (0, 0) printf("\033[31m___________ __ .__ ________ \033[0m\n"); printf("\033[33m\\__ ___/____/ |________|__| ______ / _____/_____ _____ ____ \033[0m\n"); printf("\033[32m | |_/ __ \\ __\\_ __ \\ |/ ___/ / \\ ___\\__ \\ / \\_/ __ \\ \033[0m\n"); printf("\033[36m | |\\ ___/| | | | \\/ |\\___ \\ \\ \\_\\ \\/ __ \\| Y Y \\ ___/ \033[0m\n"); printf("\033[34m |____| \\___ >__| |__| |__/____ > \\______ (____ /__|_| /\\___ >\033[0m\n"); printf("\033[35m \\/ \\/ \\/ \\/ \\/ \\/ \033[0m\n"); printf("\n"); printf("\033[31mInstructions:\033[0m\n"); printf("\033[33mUse the arrow keys to move and rotate the blocks.\033[0m\n"); printf("\033[32mPress the spacebar to make the block fall instantly.\033[0m\n"); printf("\n"); printf("\033[37;7mPress any key to start the game...\033[0m\n"); // Wait for the user to press any key system("pause"); } void Count() { while (1) { printf("\033[2J"); // Clear the screen printf("\033[5;20H"); printf("Game Started"); Sleep(1000); system("cls"); printf("\033[5;20H"); printf("\033[31;7m3\033[0m"); Sleep(1000); system("cls"); printf("\033[5;20H"); printf("\033[33;7m2\033[0m"); Sleep(1000); system("cls"); printf("\033[5;20H"); printf("\033[32;7m1\033[0m"); Sleep(1000); system("cls"); printf("\033[5;20H"); printf("GoGo!!!"); Sleep(1000); system("cls"); break; } } int main() { srand(time(NULL)); printStartScreen(); Count(5); 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]); } } system("cls"); // printf("\e[?25l"); // hide cursor move(canvas, state.x, state.y, state.rotate, state.x, state.y, state.rotate, state.queue[0]); while (1) { logic(canvas, &state); printCanvas(canvas, &state); Sleep(100); } } ``` ## 示範影片 [連結](https://youtu.be/5LBISKta5qs)