# PACMAN advanced
## pacman.hpp 解說
### 1. gridPrint
:::spoiler code
```cpp=
void gridPrint(string grid[], string (&prevGrid)[HEIGHT]) {
int n = HEIGHT, m = WIDTH;
vector<vector<string>> gridDisplay(n, vector<string>(m, " "));
vector<vector<string>> gridDisplayColor(n, vector<string>(m, ""));
vector<vector<string>> gridDisplayThick(n, vector<string>(m, ""));
// 輸 出 顏 色 的 函 數
function<string(string, string, string)> outputColor =
[&](string color, string thick, string output) {
string ans = "\033[";
std::map<string, string> colorGrid = {
{"black", "0"},
{"red", "196"},
{"green", "46"},
{"yellow", "11"},
{"blue", "27"},
{"purple", "105"},
{"lightblue", "87"},
{"white", "255"},
{"pink", "219"},
{"skin", "224"},
{"orange", "214"}};
std::map<string, string> thickGrid = {
{"bold", ";1"},
{"normal", ""},
{"light", ";2"}};
ans += "38;5;" + colorGrid[color] + thickGrid[thick] + "m";
ans += output;
ans += "\033[0m";
return ans;
};
// 檢 查 上 方 是 否 有 牆 壁
function<bool(int, int)> checkUp = [&](int x, int y) {
if (0 <= x && x < n && 0 <= y && y < m)
if (grid[x][y] == '#')
return 1; // 如 果 是 牆 壁 , 回 傳 1
return 0; // 否 則 回 傳 0
};
// 填 充 網 格 顯 示
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if (grid[i][j] == '#') {
// 當 前 位 置 只 看 上 下 左 右 有 幾 個 #
// 一 個 : ─ or │ // 兩 個 : ─ │ ┌ ┐ └ ┘
// 三 個 : 看 缺 少 的 邊 對 馬 步 的 兩 個 角
// 有 一 個 為 空 就 是 ├ ┤ ┬ ┴ 都 是 滿 的 就 是 ─ │
// 四 個 : 根 據 缺 角
// 缺 一 個 ┌ ┐ └ ┘ 缺 兩 個 相 鄰 ├ ┤ ┬ ┴
// 缺 兩 個 對 角 ┼ 缺 三 個 以 上 ┼
gridDisplayColor[i][j] = "blue"; // 牆 壁 顏 色
gridDisplayThick[i][j] = "normal"; // 牆 壁 粗 細
string type[16] = {"X", "─", "│", "╮", "─", "─", "╭", "┬", "│", "╯", "│", "┤", "╰", "┴", "├", "┼"};
int next = checkUp(i + 1, j) + checkUp(i - 1, j) +
checkUp(i, j + 1) + checkUp(i, j - 1);
if (next == 1 || next == 2) {
int iCheck = checkUp(i - 1, j) * 8 +
checkUp(i, j + 1) * 4 +
checkUp(i + 1, j) * 2 +
checkUp(i, j - 1);
gridDisplay[i][j] = type[iCheck]; // 根 據 周 圍 牆 壁 的 數 量 選 擇 顯 示 的 字 符
} else if (next == 3) {
if (!checkUp(i + 1, j) &&
(checkUp(i - 1, j + 1) && checkUp(i - 1, j - 1)))
gridDisplay[i][j] = type[1];
else if (!checkUp(i - 1, j) &&
(checkUp(i + 1, j + 1) && checkUp(i + 1, j - 1)))
gridDisplay[i][j] = type[1];
else if (!checkUp(i, j + 1) &&
(checkUp(i - 1, j - 1) && checkUp(i + 1, j - 1)))
gridDisplay[i][j] = type[2];
else if (!checkUp(i, j - 1) &&
(checkUp(i - 1, j + 1) && checkUp(i + 1, j + 1)))
gridDisplay[i][j] = type[2];
else {
int iCheck = checkUp(i - 1, j) * 8 +
checkUp(i, j + 1) * 4 +
checkUp(i + 1, j) * 2 +
checkUp(i, j - 1);
gridDisplay[i][j] = type[iCheck];
}
} else if (next == 4) {
int angle = checkUp(i + 1, j + 1) + checkUp(i - 1, j + 1) +
checkUp(i + 1, j - 1) + checkUp(i - 1, j - 1);
if (angle == 4) {
gridDisplay[i][j] = " "; // 如 果 四 周 都 有 牆 , 顯 示 空 格
} else if (angle == 3) {
if (!checkUp(i + 1, j + 1))
gridDisplay[i][j] = type[6]; // ┌
else if (!checkUp(i - 1, j + 1))
gridDisplay[i][j] = type[12]; // └
else if (!checkUp(i + 1, j - 1))
gridDisplay[i][j] = type[3]; // ┐
else if (!checkUp(i - 1, j - 1))
gridDisplay[i][j] = type[9]; // ┘
} else if (angle == 2) {
if (!checkUp(i + 1, j + 1) && !checkUp(i + 1, j - 1))
gridDisplay[i][j] = type[13]; // ┴
else if (!checkUp(i - 1, j + 1) && !checkUp(i - 1, j - 1))
gridDisplay[i][j] = type[7]; // ┬
else if (!checkUp(i + 1, j + 1) && !checkUp(i - 1, j + 1))
gridDisplay[i][j] = type[11]; // ┤
else if (!checkUp(i + 1, j - 1) && !checkUp(i - 1, j - 1))
gridDisplay[i][j] = type[14]; // ├
else
gridDisplay[i][j] = type[15]; // ┼
} else
gridDisplay[i][j] = type[15]; // ┼
}
} else if (grid[i][j] == '0') {
gridDisplayColor[i][j] = "yellow"; // pacman 顏 色
gridDisplayThick[i][j] = "bold"; // pacman 粗 細
gridDisplay[i][j] = "●"; // pacman 符 號
} else if (grid[i][j] == '*') {
gridDisplayColor[i][j] = "skin"; // 能 量 球 顏 色
gridDisplayThick[i][j] = "normal"; // 能 量 球 粗 細
gridDisplay[i][j] = "○"; // 能 量 球 符 號
} else if (grid[i][j] == '.') {
gridDisplayColor[i][j] = "skin"; // 點 數 顏 色
gridDisplayThick[i][j] = "normal"; // 點 數 粗 細
gridDisplay[i][j] = "."; // 點 數 符 號
} else if (grid[i][j] == ' ') {
gridDisplayColor[i][j] = "white"; // 空 白 顏 色
gridDisplayThick[i][j] = "normal"; // 空 白 粗 細
gridDisplay[i][j] = " "; // 空 白 符 號
} else if (isalpha(grid[i][j])) {
if (grid[i][j] == 'G') { // 嚇 人 的 鬼
gridDisplayColor[i][j] = "white";
gridDisplayThick[i][j] = "normal";
gridDisplay[i][j] = "ö"; // 嚇 人 的 鬼 符 號
} else if (isupper(grid[i][j])) {
gridDisplayColor[i][j] = "purple"; // 鬼 的 顏 色
gridDisplayThick[i][j] = "normal"; // 鬼 的 粗 細
gridDisplay[i][j] = "ö"; // 鬼 符 號
} else if (islower(grid[i][j])) {
string ghost[4] = {"red", "pink", "lightblue", "orange"};
gridDisplayColor[i][j] = ghost[grid[i][j] - 'a']; // 小 鬼 的 顏 色
gridDisplayThick[i][j] = "normal"; // 小 鬼 的 粗 細
gridDisplay[i][j] = "∩"; // 小 鬼 符 號
}
}
}
}
cout << "\033[H"; // 移 動 鼠 標 到 左 上 角
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if (prevGrid[i][j] != grid[i][j]) {
cout << "\033[" << (i + 1) << ";" << (j + 1) << "H"; // 移 動 光 標 到 指 定 位 置
cout << outputColor(gridDisplayColor[i][j], "normal", gridDisplay[i][j]); // 輸 出 顏 色 和 符 號
}
}
}
cout << "\033[" << (n + 1) << ";1H"; // 移 動 鼠 標 到 下 一 行
for (int i = 0; i < n; i++) {
prevGrid[i] = grid[i]; // 更 新 前 一 個 網 格
}
}
```
:::
功能: 輸出遊戲當前地圖。
參數: 當前輸出用的地圖及前一個輸出用的地圖。
回傳值: 無
流程:
> 1. 初始化顯示網格。
> 2. 根據當前網格和前一個網格的
變化輸出顏色和符號。
> 3. 更新前一個網格。
### 2. movePacman
:::spoiler code:
```cpp=
void movePacman(string grid[], int (&playerPos)[2], int &curDir, KeyManager &keyM) {
int input = keyM.getLastKey(); // 獲 取 最 後 按 下 的 鍵
if (checkNotCollideWall(grid, playerPos[0] + dir[input][0],
playerPos[1] + dir[input][1])) {
playerPos[0] += dir[input][0]; // 更 新 pacman 位 置
playerPos[1] += dir[input][1];
curDir = input; // 更 新 當 前 方 向
} else {
curDir = 4; // 如 果 碰 到 牆 壁 , 設 置 為 停 止
}
return;
}
```
:::
功能: 移動 Pacman。
參數: 初始的地圖,玩家位置及方向,KeyManager
回傳值: 無
流程:
> 1. 讀取最後按下的鍵。
> 2. 檢查移動後的位置是否合法。
> 3. 更新玩家位置和當前方向。
### 3. respawnGhost
:::spoiler code:
```cpp=
void respawnGhost(string grid[], int (&ghostPos)[2], int playerPos[2]) {
int maxDistance = -1;
int farX = -1, farY = -1;
for (int x = 0; x < HEIGHT; x++) {
for (int y = 0; y < WIDTH; y++) {
if (grid[x][y] == '.' && (x != playerPos[0] || y != playerPos[1])) {
int distance = (x - playerPos[0]) * (x - playerPos[0]) +
(y - playerPos[1]) * (y - playerPos[1]);
if (distance > maxDistance) {
maxDistance = distance;
farX = x;
farY = y; // 更 新 最 遠 位 置
}
}
}
}
ghostPos[0] = farX; // 設 置 鬼 的 位 置
ghostPos[1] = farY;
}
```
:::
功能: 重新生成被吃掉的鬼的位置。
參數: 初始的地圖,被吃掉的鬼的位置,玩家位置
回傳值: 無
流程:
> 1. 找到距離玩家最遠的位置。
> 2. 更新鬼的位置為該位置。
### 4. update
:::spoiler code:
```cpp=
void update(string grid[], int playerPos[2], int (*ghostPos)[2],
int ghostDir[], char ghostType[],
string (&pointsGrid)[HEIGHT], int &pointsCnt, string (&display)[HEIGHT],
int &score, bool &scaredMode, int &scaredTime) {
int n = HEIGHT, m = WIDTH;
for (int i = 0; i < n; i++) {
string row = "";
row.append(WIDTH, ' ');
display[i] = row; // 初 始 化 顯 示
}
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if (grid[i][j] == '#') {
display[i][j] = '#'; // 牆 壁
}
if (pointsGrid[i][j] == '.') {
display[i][j] = '.'; // 點 數
}
if (pointsGrid[i][j] == '*') {
display[i][j] = '*'; // 能 量 球
}
}
}
if (pointsGrid[playerPos[0]][playerPos[1]] == '.') {
pointsGrid[playerPos[0]][playerPos[1]] = ' '; // 吃 掉 點 數
score += 10; // 正 常 得 分
pointsCnt--; // 減 少 點 數 計 數
} else if (pointsGrid[playerPos[0]][playerPos[1]] == '*') {
pointsGrid[playerPos[0]][playerPos[1]] = ' '; // 吃 掉 能 量 球
score += 50; // 能 量 球 得 分
scaredMode = true; // 啟 動 驚 嚇 模 式
scaredTime = 20; // 設 置 驚 嚇 時 間 為 20 , 實 際 時 間 0.5*20 為 10 秒
// 將 鬼 的 類 型 改 為 大 寫
for (int i = 0; i < ghostNum; i++) {
ghostType[i] = toupper(ghostType[i]);
}
}
// 減 少 驚 嚇 時 間
if (scaredMode) {
scaredTime--;
if (scaredTime <= 0) {
scaredMode = false; // 停 止 驚 嚇 模 式
// 將 鬼 的 類 型 改 回 小 寫
for (int i = 0; i < ghostNum; i++) {
ghostType[i] = tolower(ghostType[i]);
}
}
}
display[playerPos[0]][playerPos[1]] = '0'; // pacman 位 置
for (int i = 0; i < ghostNum; i++) {
// 如 果 剩 下 3 秒 , 改 變 鬼 的 顏 色
if (scaredMode && scaredTime <= 6) {
display[ghostPos[i][0]][ghostPos[i][1]] = 'G'; // 在 驚 嚇 模 式 下 用 'G' 表 示 鬼
} else {
display[ghostPos[i][0]][ghostPos[i][1]] = ghostType[i]; // 正 常 鬼 顯 示
}
}
}
```
:::
功能: 更新遊戲狀態。
參數: 初始的地圖,鬼的位置、方向、種類、數量,玩家位置,點數地圖,點數數量,輸出用地圖,玩家分數,驚嚇模式,驚嚇時間
回傳值: 無
流程:
> 1. 重置顯示地圖。
> 2. 更新玩家和鬼的位置。
> 3. 檢查玩家是否吃掉了點數或能量球,並更新分數。
> 4. 更新驚嚇模式的狀態。
### 5. KeyManager 結構
:::spoiler code:
```cpp=
struct KeyManager {
char lastDirC; // 最 後 按 下 的 方 向 鍵 字 符
int lastDir; // 最 後 按 下 的 方 向
std::map<char, int> wasdToDir = {{'d', 0}, {'s', 1}, {'a', 2}, {'w', 3}}; // WASD 鍵 對 應 的 方 向
bool start; // 是 否 開 始
bool newInput; // 是 否 有 新 輸 入
KeyManager() : lastDirC('w'), lastDir(4), start(true), newInput(false) {
thread rK(&KeyManager::updateStatus, this); // 啟 動 更 新 狀 態 的 線 程
rK.detach(); // 分 離 線 程
}
~KeyManager() {
start = false; // 停 止 更 新 狀 態
INPUT inputs[3] = {};
ZeroMemory(inputs, sizeof(inputs)); // 清 空 輸 入
inputs[1].type = INPUT_KEYBOARD;
inputs[1].ki.wVk = 'w'; // 模 擬 按 下 'w' 鍵
inputs[2].type = INPUT_KEYBOARD;
inputs[2].ki.wVk = 'w';
inputs[2].ki.dwFlags = KEYEVENTF_KEYUP; // 模 擬 釋 放 'w' 鍵
UINT unsent = SendInput(ARRAYSIZE(inputs), inputs, sizeof(INPUT)); // 發 送 輸 入
}
// 更 新 鍵 盤 狀 態
void updateStatus() {
char cur = 'w';
lastDirC = cur;
while (start) {
cur = readKey(); // 讀 取 當 前 按 鍵
newInput = true;
if (cur == 'w' || cur == 'a' || cur == 's' || cur == 'd') {
lastDirC = cur;
lastDir = wasdToDir[lastDirC]; // 更 新 方 向
}
}
return;
}
// 讀 取 按 鍵
char readKey() {
INPUT_RECORD inputRecord;
DWORD written;
HANDLE stdInH = GetStdHandle(STD_INPUT_HANDLE);
while (true) {
ReadConsoleInputA(stdInH, &inputRecord, 1, &written); // 讀 取 控 制 台 輸 入
if (inputRecord.EventType == KEY_EVENT &&
inputRecord.Event.KeyEvent.bKeyDown)
break; // 如 果 是 按 鍵 事 件 且 按 鍵 被 按 下 , 則 退 出 循 環
}
switch (inputRecord.Event.KeyEvent.wVirtualScanCode) {
case 72:
return 'w'; // 回 傳 'w'
case 75:
return 'a'; // 回 傳 'a'
case 80:
return 's'; // 回 傳 's'
case 77:
return 'd'; // 回 傳 'd'
default:
return inputRecord.Event.KeyEvent.uChar.AsciiChar; // 回 傳 其 他 字 符
}
}
// 獲 取 最 後 按 下 的 鍵
int getLastKey() {
return lastDir;
}
void anyKeyToContinue() {
newInput = false;
while (!newInput) {}
return;
}
};
```
:::
功能:管理鍵盤輸入。(箭頭或WASD)
成員函數:
> 1. `getLastKey()` 獲取最後被按下的鍵。
> 2. `anyKeyToContinue()` 等待任意鍵繼續。
> 3. `updateStatus()` 持續偵測鍵盤輸入並更新最新輸入。
> 4. `readKey()` 讀取當前按鍵。