# Tetris第三週進度
## 課前
先將上禮拜製作的 Code 從 GitHub 上 Pull 下
可參考先前的講義內容
## 第三周功能
上個禮拜將遊戲板做出,並讓同學試著體驗將俄羅斯方塊輸出在遊戲板上
今天將讓俄羅斯方塊呈現出慢慢掉下來的感覺、輸出儲存的方塊、將方塊固定於版面上。
### 目標:
## 步驟:

### 俄羅斯方塊定時掉落
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) 迴圈重複輸出遊戲板畫面。

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.

---
### 輸出遊戲的分數 以及 備用的方塊有哪些
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)去輸出

- 指定我們的輸出位置要從 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 ");
}
}
}
}
```

---
### 讓方塊能夠進行堆疊
- 當我們的 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/@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;
}
```