---
# System prepended metadata

title: Tetris第三週進度
tags: [程式設計 IV]

---

# 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;
}
```
