# PACMAN :video_game: ## 系統設定 :::warning :warning:請務必先完成 **Dev-C++** 設定 ::: :::spoiler **Dev-C++** > 1. 點選左上角 File -> Open > ![image](https://hackmd.io/_uploads/rkBZDzrNle.png) > > > 2. 選擇 桌面 -> EEcamp -> EEcamp_6.22.1 -> main.cpp 及 pacman.hpp -> 點選 open > ![image](https://hackmd.io/_uploads/SkRcPzS4el.png) > > 3. 開啟檔案後點選上方 Tools -> Compiler Options > ![image](https://hackmd.io/_uploads/HydaDMr4xg.png) > > 4. 勾選 > - [x] `Add the following commands when calling the compiler` > 並新增: > `-std=c++14 -static` > 再點選 ok✅ >![image](https://hackmd.io/_uploads/S10G_GHNel.png) ::: :::spoiler **終端機介面** > :::warning > :warning:在你的程式可以執行之後設定 > ::: > 1. 對彈出的視窗(終端機terminal)上方白色區域點擊右鍵 > 2. 選擇內容 > 3. 設定字型大小 28 > 4. 設定字體種類 Consolas > 5. 點擊下方確定 ::: ## 程式碼複製區 <!--- :::info :bulb: 請在同一個資料夾中建立 `main.cpp` 及 `pacman.hpp` 兩個檔案並將下面內容分別複製到其中 ::: ---> :::spoiler <font color="blue">**main.cpp**</font> ```cpp= #include <bits/stdc++.h> #include <windows.h> #define HEIGHT 15 #define WIDTH 66 // 定 義 遊 戲 區 域 的 高 度 和 寬 度 #define powerNum 4 #define ghostNum 4 // 能 量 球 和 鬼 的 數 量 #include "pacman.hpp" //#define __VScode // 若 使 用 VScode 請 取 消 註 解 using namespace std; int dir[5][2] = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}, {0, 0}}; // d,s,a,w,stop bool gameRunning = true; // 檢 查 是 否 與 牆 壁 碰 撞 bool checkNotCollideWall(string grid[], int i, int j) { int n = HEIGHT, m = WIDTH; if (/*question 1*/) return false; // 如 果 超 出 邊 界 或 碰 到 牆 壁 , 回 傳 false return true; // 否 則 回 傳 true } // 檢 查 是 否 與 鬼 碰 撞 bool checkCollideGhost(int (*ghostPos)[2], int playerPos[2]) { for (int i = 0; i < ghostNum; i++) { if (/*question 2*/) { return true; // 如 果 pacman 與 鬼 的 座 標 相 同 , 回 傳true } } return false; // 否 則 回 傳 false } // 計 算 最 佳 方 向 int bestDir(string grid[], int curPos[2], int curDir, int target[2]) { int n = HEIGHT, m = WIDTH; int ans = 4; // 預 設 為 停 止 int minDis = INT16_MAX; // 最 小 距 離 初 始 化 為 最 大 值 for (int i = 0; i < 4; i++) { if (i == (curDir + 2) % 4) continue; // 不 考 慮 反 方 向 int newI = curPos[0] + dir[i][0]; // 新 的 i 座 標 int newJ = curPos[1] + dir[i][1]; //新 的 j 座 標 int dis = /*question 3*/ ; if (dis < minDis && checkNotCollideWall(grid, newI, newJ)) { ans = i; // 更 新 最 佳 方 向 minDis = dis; // 更 新 最 小 距 離 } } return ans; // 回 傳 最 佳 方 向 } // 移 動 鬼 template <int _N> void moveGhost(string grid[], int (&ghostPos)[_N][2], int (&ghostDir)[_N], char ghostType[], int playerPos[2], int playerDir, bool scaredMode) { for (int i = 0; i < ghostNum; i++) { int legalRoute = 0; // 合 法 路 徑 計 數 int possibleDir[3]; // 存 儲 可 能 的 方 向 for (int j = 0; j < 4; j++) { if ((checkNotCollideWall(grid, ghostPos[i][0] + dir[j][0], ghostPos[i][1] + dir[j][1])) && (j != (ghostDir[i] + 2) % 4)) { possibleDir[legalRoute] = j; // 存 儲 有 效 方 向 legalRoute++; } } legalRoute++; if (!scaredMode) { // 如 果 不 是 驚 嚇 模 式 if (legalRoute == 1) { ghostDir[i] = (ghostDir[i] + 2) % 4; // 反 向 移 動 } else if (legalRoute == 2) { ghostDir[i] = possibleDir[0]; // 隨 機 選 擇 一 個 方 向 } else if (legalRoute == 3 || legalRoute == 4) { int target[2] = {}; if (ghostType[i] == 'a') { // 紅 鬼 :目 標 pacman 位 置 /*question 4*/ } else if (ghostType[i] == 'b') { // 粉 紅 鬼 :目 標 pacman 位 置 4 格 前 /*question 5*/ } else if (ghostType[i] == 'c') { // 淺 藍 鬼 :目 標 與 紅 色 鬼 對 稱 中 心 在 pacman 前 方 2 格 int indexOfRed = 0; for (int j = 0; j < ghostNum; j++) { if (ghostType[j] == 'a') { indexOfRed = j; break; } } int center[2]; center[0] = playerPos[0] + dir[playerDir][0] * 2; center[1] = playerPos[1] + dir[playerDir][1] * 2; /*question 6*/ } else if (ghostType[i] == 'd') { // 橙 鬼 :保 持 4 格 距 離 double a, b; a = ghostPos[i][0] - playerPos[0]; b = ghostPos[i][1] - playerPos[1]; target[0] = round(playerPos[0] + a * 4 / sqrt(a * a + b * b)); target[1] = round(playerPos[1] + b * 4 / sqrt(a * a + b * b)); } ghostDir[i] = bestDir(grid, ghostPos[i], ghostDir[i], target); // 計 算 最 佳 方 向 } } else { // 如 果 是 驚 嚇 模 式 if (isupper(ghostType[i])) { if (legalRoute == 1) { ghostDir[i] = (ghostDir[i] + 2) % 4; // 反 向 移 動 } else if (legalRoute == 2) { // 不 回 頭 ghostDir[i] = possibleDir[0]; } else if (legalRoute == 3 || legalRoute == 4) { // 不 回 頭 ghostDir[i] = possibleDir[rand() % (legalRoute - 1)]; } } if (islower(ghostType[i])) { // 復 活 先 不 動 ghostDir[i] = 4; } } if (checkNotCollideWall(grid, ghostPos[i][0] + dir[ghostDir[i]][0], ghostPos[i][1] + dir[ghostDir[i]][1])) { ghostPos[i][0] += dir[ghostDir[i]][0]; // 根 據 方 向 移 動 ghostPos[i][1] += dir[ghostDir[i]][1]; } } } // 吃 掉 鬼 void eatGhost(string grid[], int playerPos[2], int (*ghostPos)[2], int (&ghostDir)[ghostNum], char (&ghostType)[ghostNum], int &score) { for (int i = 0; i < ghostNum; i++) { if (ghostPos[i][0] == playerPos[0] && ghostPos[i][1] == playerPos[1]) { score += 200; // 吃 掉 鬼 的 得 分 /*question 7*/ ghostDir[i] = 4; // 設 置 鬼 的 方 向 為 停 止 ghostType[i] = tolower(ghostType[i]); // 將 鬼 的 類 型 改 為 小 寫 } } } int main() { srand(time(0)); // 設 置 隨 機 數 種 子 #ifndef __VScode SetConsoleCtrlHandler(ConsoleHandler, TRUE); system("chcp 65001"); #endif string initGrid[HEIGHT] = { "#################################################################", "#...............................................................#", "#..###...#...##...##.....#####....#......#...#######...#######..#", "#..#.#...#....##.##.....##...##...#......#...#.........#........#", "#..#.###.#.....###......#.........#......#...#######...#######..#", "#..#...#.#......#.......##...##...##....##...#.........#........#", "#..#...###......#........#####.....######....#######...#######..#", "#...............................0...............................#", "#...#####.....#####....##....##...########...#######...#######..#", "#..##...##...## ##...###..###...# #.........#...#........#", "#..#.........#######...#.####.#...########...#######...#######..#", "#..##...##...#.....#...#..##..#...#..........#...............#..#", "#...#####....#.....#...#......#...#..........#######...#######..#", "#...............................................................#", "#################################################################"}; int pointsCnt = 0; int playerPos[2]; // pacman 位 置 int playerDir = 3; // 初 始 方 向 為 'w' int ghostPos[ghostNum][2]; // 鬼 的 位 置 int ghostDir[ghostNum] = {4, 4, 4, 4}; // 鬼 的 方 向 char ghostType[ghostNum]; // 鬼 的 類 型 int score = 0; // 分 數 int scaredTime = 0; // 驚 嚇 時 間 bool scaredMode = false; // 是 否 在 驚 嚇 模 式 string pointsGrid[HEIGHT]; // 點 數 網 格 string gridDisplay[HEIGHT]; // 顯 示 網 格 string prevGridDisplay[HEIGHT]; // 前 一 個 顯 示 網 格 for (int i = 0; i < HEIGHT; i++) { string row = ""; row.append(WIDTH, ' '); pointsGrid[i] = row; // 初 始 化 點 數 網 格 gridDisplay[i] = row; // 初 始 化 顯 示 網 格 prevGridDisplay[i] = row; // 初 始 化 前 一 個 顯 示 網 格 } for (int i = 0; i < HEIGHT; i++) { // 設 定 pacman 位 置 的 初 始 狀 態 for (int j = 0; j < WIDTH; j++) { char c = initGrid[i][j]; if (c == '0') { /*question 8*/ } } } // 隨 機 放 置 能 量 球 for (int p = 0; p < powerNum; p++) { int x, y; x = rand() % HEIGHT; // 隨 機 列 y = rand() % WIDTH; // 隨 機 行 while (/*question 9*/){ // 確 保 能 量 球 放 原 本 是 點 數 的 位 置 上 x = rand() % HEIGHT; // 隨 機 列 y = rand() % WIDTH; // 隨 機 行 } initGrid[x][y] = '*'; // 放 置 能 量 球 } // 隨 機 放 置 鬼 for (int i = 0; i < ghostNum; i++) { int x, y; do { x = rand() % HEIGHT; // 隨 機 列 y = rand() % WIDTH; // 隨 機 行 } while (!(initGrid[x][y] == '.' && ((x - playerPos[0]) * (x - playerPos[0]) + (y - playerPos[1]) * (y - playerPos[1])) >= 49)); // 確 保 放 置 在 合 法 位 置 且 距 離 pacman 有 一 定 距 離 ghostPos[i][0] = x; // 設 置 鬼 的 行 ghostPos[i][1] = y; // 設 置 鬼 的 列 ghostType[i] = 'a' + i; // 分 配 鬼 的 類 型 a, b, c, d } for (/*question 10-1*/) { // 設 定 點 數 網 格 的 初 始 狀 態 for (/*question 10-2*/) { char c = initGrid[i][j]; gridDisplay[i][j] = c; // 初 始 化 顯 示 網 格 if (c == '.' || c == '*') { pointsGrid[i][j] = c; // 設 置 點 數 網 格 pointsCnt++; // 增 加 點 數 計 數 } } } KeyManager keyM; // 創 建 鍵 盤 管 理 器 int curDir = playerDir; // 當 前 方 向 bool gameOver = false; // 遊 戲 是 否 結 束 cout << "\x1B[2J\x1B[H" << "\033[?25l"; // 初 始 化 螢 幕 , 清 除 並 隱 藏 鼠 標 update(initGrid, playerPos, ghostPos, ghostDir, ghostType, pointsGrid, pointsCnt, gridDisplay, score, scaredMode, scaredTime); gridPrint(gridDisplay, prevGridDisplay); cout << "\033[" << (HEIGHT + 1) << ";1H\033[K"; cout << "press any key to start game (use arrows or WASD to move pacman)" << endl; keyM.anyKeyToContinue(); for (int i = 3; i > 0; i--) { cout << "\033[" << (HEIGHT + 1) << ";1H\033[K"; cout << "starting in " << i << "........." << endl; Sleep(1000); } while (!gameOver && gameRunning) { // 移 動 pacman movePacman(initGrid, playerPos, playerDir, keyM); // 檢 查 是 否 與 鬼 碰 撞 if (checkCollideGhost(ghostPos, playerPos)) { if (scaredMode) eatGhost(initGrid, playerPos, ghostPos, ghostDir, ghostType, score); // 吃 掉 鬼 else gameOver = true; // 如 果 不 是 驚 嚇 模 式 , 遊 戲 結 束 } // 移 動 鬼 if (!(scaredMode && scaredTime % 2)) // 如 果 是 驚 嚇 模 式 只 有 一 半 的 時 間 會 移 動 鬼 moveGhost(initGrid, ghostPos, ghostDir, ghostType, playerPos, playerDir, scaredMode); // 再 次 檢 查 是 否 與 鬼 碰 撞 if (checkCollideGhost(ghostPos, playerPos)) { if (scaredMode) eatGhost(initGrid, playerPos, ghostPos, ghostDir, ghostType, score); // 吃 掉 鬼 else gameOver = true; // 如 果 不 是 驚 嚇 模 式 , 遊 戲 結 束 } // 更 新 遊 戲 狀 態 update(initGrid, playerPos, ghostPos, ghostDir, ghostType, pointsGrid, pointsCnt, gridDisplay, score, scaredMode, scaredTime); // 輸 出 遊 戲 網 格 gridPrint(gridDisplay, prevGridDisplay); cout << "\033[" << (HEIGHT + 1) << ";1H\033[K"; // 移 動 鼠 標 到 顯 示 分 數 的 位 置 cout << "Score: " << score << endl; // 顯 示 分 數 Sleep(500); // 暫 停 0.5 秒 if (pointsCnt == 0) gameOver = true; // 如 果 所 有 點 數 都 被 吃 掉 , 遊 戲 結 束 } // 遊 戲 結 束 cout << "\033[?25h" << "\033[" << (HEIGHT + 2) << ";1H"; // 恢 復 鼠 標 顯 示 if (pointsCnt == 0) { cout << "You win! Final score: " << score << endl; // 如 果 贏 了 , 顯 示 勝 利 信 息 } else { cout << "Game Over! Final score: " << score << endl; // 如 果 遊 戲 結 束 , 顯 示 結 束 信 息 } return 0; // 回 傳 0 , 結 束 程 序 } ``` ::: :::spoiler <font color="blue">**pacman.hpp**</font> ```cpp= #pragma once #include <bits/stdc++.h> #include <windows.h> using namespace std; extern int dir[5][2]; extern bool gameRunning; bool checkNotCollideWall(string grid[], int i, int j); bool checkCollideGhost(int (*ghostPos)[2], int playerPos[2]); int bestDir(string grid[], int curPos[2], int curDir, int target[2]); template <int _N> void moveGhost(string grid[], int (&ghostPos)[_N][2], int (&ghostDir)[_N], char ghostType[], int playerPos[2], int playerDir, bool scaredMode); void eatGhost(string grid[], int playerPos[2], int (*ghostPos)[2], int (&ghostDir)[ghostNum], char (&ghostType)[ghostNum], int &score); // 鍵 盤 管 理 器 結 構 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; } }; // 輸 出 遊 戲 網 格 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]; // 更 新 前 一 個 網 格 } } // 移 動 pacman 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; } // 重 新 生 成 鬼 的 位 置 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; } // 更 新 遊 戲 狀 態 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]; // 正 常 鬼 顯 示 } } } #ifndef __VScode BOOL WINAPI ConsoleHandler(DWORD signal) { if (signal == CTRL_C_EVENT || signal == CTRL_CLOSE_EVENT) { gameRunning = false; return TRUE; } return FALSE; } #endif ``` ::: :::spoiler <font color="blue">pacman.hpp 備用版</font> ```cpp= #pragma once #include <bits/stdc++.h> #include <windows.h> using namespace std; extern int dir[5][2]; extern bool gameRunning; bool checkNotCollideWall(string grid[], int i, int j); bool checkCollideGhost(int (*ghostPos)[2], int playerPos[2]); int bestDir(string grid[], int curPos[2], int curDir, int target[2]); template <int _N> void moveGhost(string grid[], int (&ghostPos)[_N][2], int (&ghostDir)[_N], char ghostType[], int playerPos[2], int playerDir, bool scaredMode); void eatGhost(string grid[], int playerPos[2], int (*ghostPos)[2], int (&ghostDir)[ghostNum], char (&ghostType)[ghostNum], int &score); // 鍵 盤 管 理 器 結 構 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; } }; // 輸 出 遊 戲 網 格 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] = "O"; // 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] = "n"; // 嚇 人 的 鬼 符 號 } else if (isupper(grid[i][j])) { gridDisplayColor[i][j] = "purple"; // 鬼 的 顏 色 gridDisplayThick[i][j] = "normal"; // 鬼 的 粗 細 gridDisplay[i][j] = "n"; // 鬼 符 號 } 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] = "n"; // 小 鬼 符 號 } } } } 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]; // 更 新 前 一 個 網 格 } } // 移 動 pacman 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; } // 重 新 生 成 鬼 的 位 置 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; } // 更 新 遊 戲 狀 態 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]; // 正 常 鬼 顯 示 } } } #ifndef __VScode BOOL WINAPI ConsoleHandler(DWORD signal) { if (signal == CTRL_C_EVENT || signal == CTRL_CLOSE_EVENT) { gameRunning = false; return TRUE; } return FALSE; } #endif ``` ::: :::spoiler <font color="blue">(補充) pacman.hpp 內函數功能解說</font> :::info ```cpp KeyManager ``` 持續偵測鍵盤輸入,紀錄最後按下的方向鍵或 W,A,S,D 鍵 ```cpp void gridPrint(string grid[], string (&prevGrid)[HEIGHT]) ``` 傳入 grid 後會將其美化上色並輸出,另外需傳入 prevGrid 僅輸出有變化的部分以提高效率 ```cpp void movePacman(string grid[], int (&playerPos)[2], int &curDir, KeyManager &keyM) ``` 根據最後被按下的按鍵移動pacman ```cpp void respawnGhost(string grid[], int (&ghostPos)[2], int playerPos[2]) ``` 復活被pacman吃掉的鬼,使其距離pacman盡量遠 ```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) ``` 更新玩家和鬼的位置,檢查玩家是否吃掉了點數或能量球,並更新分數。它還會處理驚嚇模式的啟動和結束,並更新顯示的遊戲狀態 ::: :star:[PACMAN advanced](https://hackmd.io/N5q7SFtWR1OvG_SqlPJyaQ) :star:[GITHUB](https://github.com/jerrywustarwars/2025_NYCU_EEcamp) <!-------------------------code end-------------------------------> ## 回答表單 :::info [**點這裡填答案~~~**](https://forms.gle/Fgi3VAvucfiBdAgn8) ::: ## 題目及提示 ### question 1 (line19) ```cpp=19 if (/*question 1*/) return false; // 如果超出邊界或碰到牆壁,回傳false return true; // 否則回傳true ``` :::success **目標:** 檢查超出邊界或碰到牆壁 ::: > - [ ] 1. >```cpp >i < 0 || i > n || j < 0 || j > m || grid[i][j] == '#' >``` > - [ ] 2. >```cpp >i < 0 || i >= n || j < 0 || j >= m || grid[i][j] == '#' >``` > - [ ] 3. >```cpp >i < 0 || i >= n || j < 0 || j >= m || grid[i][j] == '.' >``` > - [ ] 4. >```cpp >i < 0 || i >= n || j < 0 || j >= m >``` <!-- ans:2 --> ### question 2 (line27) ```cpp=27 if (/*question 2*/) { return true; // 如果pacman與鬼的座標相同,回傳true } ``` :::success **目標:** 判斷pacman與鬼的座標是否相同 ::: > - [ ] 1. >```cpp >ghostPos[i][1] == playerPos[1] && ghostPos[i][2] == playerPos[2] >``` > - [ ] 2. >```cpp >ghostPos[i][0] == playerPos[0] || ghostPos[i][1] == playerPos[1] >``` > - [ ] 3. >```cpp >ghostPos[i][0] == playerPos[0] && ghostPos[i][1] == playerPos[1] >``` > - [ ] 4. >```cpp >ghostPos[i] == playerPos >``` <!-- ans:3 --> ### question 3 (line46) ```cpp=46 int dis = /*question 3*/ ; ``` :::success **目標:** 計算 newI, newJ 和 target 的直線距離的平方 ::: > - [ ] 1. >```cpp > (newI - target[0]) * (newI - target[0]) + (newJ - target[1]) * (newJ - target[1]) >``` > - [ ] 2. >```cpp >(newI - target[0]) + (newJ - target[1]) >``` > - [ ] 3. >```cpp >(newI - target[i]) + (newJ - target[j]) >``` > - [ ] 4. >```cpp >(newI + newJ) - target >``` <!-- ans:1 --> ### question 4 (line82) ```cpp=81 if (ghostType[i] == 'a') { // 紅鬼:目標pacman位置 /*question 4*/ } ``` :::success **目標:** 找到紅色鬼的目標位置(pacman位置) ::: > - [ ] 1. >```cpp >target == playerPos; >``` > - [ ] 2. >```cpp >target = playerPos; >``` > - [ ] 3. >```cpp >target[0] == playerPos[0]; >target[1] == playerPos[1]; >``` > - [ ] 4. >```cpp >target[0] = playerPos[0]; >target[1] = playerPos[1]; >``` <!-- ans:4 --> ### question 5 (line84) ```cpp=83 else if (ghostType[i] == 'b') { // 粉紅鬼:目標pacman位置4格前 /*question 5*/ } ``` :::success **目標:** 找到粉紅鬼的目標位置(pacman位置前方4格) ::: > - [ ] 1. >```cpp >target = playerPos + 4 dir[playerDir]; >``` > - [ ] 2. >```cpp >target[0] == playerPos[0] + dir[playerDir][0] * 4; >target[1] == playerPos[1] + dir[playerDir][1] * 4; >``` > - [ ] 3. >```cpp >target[0] = playerPos[0] + dir[playerDir][0] * 4; >target[1] = playerPos[1] + dir[playerDir][1] * 4; >``` > - [ ] 4. >```cpp >target[0] = playerPos[0] + 4 dir[playerDir][0]; >target[1] = playerPos[1] + 4 dir[playerDir][1]; >``` <!-- ans:3 --> ### question 6 (line96) ```cpp=93 int center[2]; center[0] = playerPos[0] + dir[playerDir][0] * 2; center[1] = playerPos[1] + dir[playerDir][1] * 2; /*question 6*/ ``` :::success **目標:** 找到淺藍鬼的目標位置(與紅色鬼對稱中心在pacman前方2格) ::: > - [ ] 1. >```cpp >target[0] = center[0] * 2 - ghostPos[indexOfRed][0]; >target[1] = center[1] * 2 - ghostPos[indexOfRed][1]; >``` > - [ ] 2. >```cpp >target[0] + ghostPos[indexOfRed][0] = center[0] * 2; >target[1] + ghostPos[indexOfRed][1] = center[1] * 2; >``` > - [ ] 3. >```cpp >target = center * 2 - ghostPos[indexOfRed]; >``` > - [ ] 4. >```cpp >target[0] = center[0] - ghostPos[indexOfRed][0]; >target[1] = center[1] - ghostPos[indexOfRed][1]; >``` <!-- ans:1 --> ### question 7 (line135) ```cpp=133 if (ghostPos[i][0] == playerPos[0] && ghostPos[i][1] == playerPos[1]) { score += 200; // 吃掉鬼的得分 /*question 7*/ ghostDir[i] = 4; // 設置鬼的方向為停止 ghostType[i] = tolower(ghostType[i]); // 將鬼的類型改為小寫 } ``` :::success **目標:** 鬼被吃掉之後應該做什麼? ::: > - [ ] 1. >```cpp >checkCollideGhost(ghostPos, playerPos, ghostNum); >``` > - [ ] 2. >```cpp >respawnGhost(grid, ghostPos[i], playerPos); >``` > - [ ] 3. >```cpp >ghostPos[i][0] = 0; >ghostPos[i][1] = 0; >``` > - [ ] 4. >```cpp >checkNotCollideWall(grid, ghostPos[i][0], ghostPos[i][1]); >``` <!-- ans:2 --> ### question 8 (line194) ```cpp=193 if (c == '0') { /*question 8*/ } ``` :::success **目標:** 紀錄pacman的初始位置 ::: > - [ ] 1. >```cpp >playerPos[i] = '0'; >playerPos[j] = '0'; >``` > - [ ] 2. >```cpp >playerPos = i,j; >``` > - [ ] 3. >```cpp >playerPos[0] = '0'; >playerPos[1] = '0'; >``` > - [ ] 4. >```cpp >playerPos[0] = i; >playerPos[1] = j; >``` <!-- ans:4 --> ### question 9 (line204) ```cpp=201 int x, y; x = rand() % HEIGHT; // 隨 機 列 y = rand() % WIDTH; // 隨 機 行 while (/*question 9*/){ // 確 保 能 量 球 放 原 本 是 點 數 的 位 置 上 x = rand() % HEIGHT; // 隨 機 列 y = rand() % WIDTH; // 隨 機 行 } initGrid[x][y] = '*'; // 放 置 能 量 球 ``` :::success **目標:** 確保能量球放置在原本是點數位置(`.`是點數,`*`是能量球) ::: > - [ ] 1. >```cpp >initGrid[x][y] == '.' >``` > - [ ] 2. >```cpp >initGrid[x][y] != '.' >``` > - [ ] 3. >```cpp >initGrid[x][y] = '.' >``` > - [ ] 4. >```cpp >initGrid[x][y] = . >``` <!-- ans:2 --> ### question 10 (line226) ```cpp=226 for (/*question 10-1*/) { // 設定點數網格的初始狀態 for (/*question 10-2*/) { char c = initGrid[i][j]; gridDisplay[i][j] = c; // 初始化顯示網格 if (c == '.' || c == '*') { pointsGrid[i][j] = c; // 設置點數網格 pointsCnt++; // 增加點數計數 } } } ``` :::success **目標:** 如何設定全部點數網格的初始狀態 ::: > - [ ] 1. >```cpp >10-1: int i = 0; i < HEIGHT; i++ >10-2: int j = 0; j < WIDTH; j++ >``` > - [ ] 2. >```cpp >10-1: int i = 0; i < HEIGHT; i+1 >10-2: int j = 0; j < WIDTH; j+1 >``` > - [ ] 3. >```cpp >10-1: int i = 0; i <= HEIGHT; i++ >10-2: int j = 0; j <= WIDTH; j++ >``` > - [ ] 4. >```cpp >10-1: int i = 0; i < HEIGHT; i+1 >10-2: int j = i; j < WIDTH; j+1 >``` <!-- ans:1 --> # 大功告成 :::info [**:bulb:記得要確認10題都答對喔~~~**](https://forms.gle/Fgi3VAvucfiBdAgn8) ::: :::warning [**:warning:記得設定介面(最上面有教學)**](https://hackmd.io/@NYCU-EECamp/eecamp_pacman#%E7%B5%82%E7%AB%AF%E6%A9%9F%E4%BB%8B%E9%9D%A2) ::: :::success [**:trophy:來跟別人一較高下吧**](https://forms.gle/NK1UutoiQQy65E9k9) ::: ## 及時排名 [<font color = "black">刷新頁面以更新</font>](https://hackmd.io/@NYCU-EECamp/eecamp_pacman#%E5%8F%8A%E6%99%82%E6%8E%92%E5%90%8D) <iframe id="score_chart" width="700" height="800" src="https://docs.google.com/spreadsheets/d/e/2PACX-1vRhsLO57f2JwDF0WVC8-ne0TqKZgR0bGG1nmZQF7_iQP-w8lLKVBIA-E5jpKGv4ENEeFIR_TDcGzc4H/pubchart?oid=166826881&format=interactive"></iframe> # 你很厲害的話 :::info [**記得先填完表單~~~**](https://forms.gle/Fgi3VAvucfiBdAgn8) ::: ### [**:fire:<font color = "black">點此看挑戰題</font>:fire:**](https://hackmd.io/@NYCU-EECamp/2025_advanced_problem) # 沒了 ![image](https://hackmd.io/_uploads/rJ3l2saa1l.png) ![image](https://hackmd.io/_uploads/Sy5vr52yle.png) <!------------------------- ```c++ #include <bits/stdc++.h> #include <windows.h> #define HEIGHT 15 #define WIDTH 66 // 定 義 遊 戲 區 域 的 高 度 和 寬 度 #define powerNum 4 #define ghostNum 4 // 能 量 球 和 鬼 的 數 量 #include "pacman.hpp" //#define __VScode // 若 使 用 VScode 請 取 消 註 解 using namespace std; int dir[5][2] = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}, {0, 0}}; // d,s,a,w,stop bool gameRunning = true; // 檢 查 是 否 與 牆 壁 碰 撞 bool checkNotCollideWall(string grid[], int i, int j) { int n = HEIGHT, m = WIDTH; if (i < 0 || i >= n || j < 0 || j >= m || grid[i][j] == '#') return false; // 如 果 超 出 邊 界 或 碰 到 牆 壁 , 回 傳 false return true; // 否 則 回 傳 true } // 檢 查 是 否 與 鬼 碰 撞 bool checkCollideGhost(int (*ghostPos)[2], int playerPos[2]) { for (int i = 0; i < ghostNum; i++) { if (ghostPos[i][0] == playerPos[0] && ghostPos[i][1] == playerPos[1]) { return true; // 如 果 pacman 與 鬼 的 座 標 相 同 , 回 傳true } } return false; // 否 則 回 傳 false } // 計 算 最 佳 方 向 int bestDir(string grid[], int curPos[2], int curDir, int target[2]) { int n = HEIGHT, m = WIDTH; int ans = 4; // 預 設 為 停 止 int minDis = INT16_MAX; // 最 小 距 離 初 始 化 為 最 大 值 for (int i = 0; i < 4; i++) { if (i == (curDir + 2) % 4) continue; // 不 考 慮 反 方 向 int newI = curPos[0] + dir[i][0]; // 新 的 i 座 標 int newJ = curPos[1] + dir[i][1]; //新 的 j 座 標 int dis = (newI - target[0]) * (newI - target[0]) + (newJ - target[1]) * (newJ - target[1]); if (dis < minDis && checkNotCollideWall(grid, newI, newJ)) { ans = i; // 更 新 最 佳 方 向 minDis = dis; // 更 新 最 小 距 離 } } return ans; // 回 傳 最 佳 方 向 } // 移 動 鬼 template <int _N> void moveGhost(string grid[], int (&ghostPos)[_N][2], int (&ghostDir)[_N], char ghostType[], int playerPos[2], int playerDir, bool scaredMode) { for (int i = 0; i < ghostNum; i++) { int legalRoute = 0; // 合 法 路 徑 計 數 int possibleDir[3]; // 存 儲 可 能 的 方 向 for (int j = 0; j < 4; j++) { if ((checkNotCollideWall(grid, ghostPos[i][0] + dir[j][0], ghostPos[i][1] + dir[j][1])) && (j != (ghostDir[i] + 2) % 4)) { possibleDir[legalRoute] = j; // 存 儲 有 效 方 向 legalRoute++; } } legalRoute++; if (!scaredMode) { // 如 果 不 是 驚 嚇 模 式 if (legalRoute == 1) { ghostDir[i] = (ghostDir[i] + 2) % 4; // 反 向 移 動 } else if (legalRoute == 2) { ghostDir[i] = possibleDir[0]; // 隨 機 選 擇 一 個 方 向 } else if (legalRoute == 3 || legalRoute == 4) { int target[2] = {}; if (ghostType[i] == 'a') { // 紅 鬼 :目 標 pacman 位 置 target[0] = playerPos[0]; target[1] = playerPos[1]; } else if (ghostType[i] == 'b') { // 粉 紅 鬼 :目 標 pacman 位 置 4 格 前 target[0] = playerPos[0] + dir[playerDir][0] * 4; target[1] = playerPos[1] + dir[playerDir][1] * 4; } else if (ghostType[i] == 'c') { // 淺 藍 鬼 :目 標 與 紅 色 鬼 對 稱 中 心 在 pacman 前 方 2 格 int indexOfRed = 0; for (int j = 0; j < ghostNum; j++) { if (ghostType[j] == 'a') { indexOfRed = j; break; } } int center[2]; center[0] = playerPos[0] + dir[playerDir][0] * 2; center[1] = playerPos[1] + dir[playerDir][1] * 2; target[0] = center[0] * 2 - ghostPos[indexOfRed][0]; target[1] = center[1] * 2 - ghostPos[indexOfRed][1]; } else if (ghostType[i] == 'd') { // 橙 鬼 :保 持 4 格 距 離 double a, b; a = ghostPos[i][0] - playerPos[0]; b = ghostPos[i][1] - playerPos[1]; target[0] = round(playerPos[0] + a * 4 / sqrt(a * a + b * b)); target[1] = round(playerPos[1] + b * 4 / sqrt(a * a + b * b)); } ghostDir[i] = bestDir(grid, ghostPos[i], ghostDir[i], target); // 計 算 最 佳 方 向 } } else { // 如 果 是 驚 嚇 模 式 if (isupper(ghostType[i])) { if (legalRoute == 1) { ghostDir[i] = (ghostDir[i] + 2) % 4; // 反 向 移 動 } else if (legalRoute == 2) { // 不 回 頭 ghostDir[i] = possibleDir[0]; } else if (legalRoute == 3 || legalRoute == 4) { // 不 回 頭 ghostDir[i] = possibleDir[rand() % (legalRoute - 1)]; } } if (islower(ghostType[i])) { // 復 活 先 不 動 ghostDir[i] = 4; } } if (checkNotCollideWall(grid, ghostPos[i][0] + dir[ghostDir[i]][0], ghostPos[i][1] + dir[ghostDir[i]][1])) { ghostPos[i][0] += dir[ghostDir[i]][0]; // 根 據 方 向 移 動 ghostPos[i][1] += dir[ghostDir[i]][1]; } } } // 吃 掉 鬼 void eatGhost(string grid[], int playerPos[2], int (*ghostPos)[2], int (&ghostDir)[ghostNum], char (&ghostType)[ghostNum], int &score) { for (int i = 0; i < ghostNum; i++) { if (ghostPos[i][0] == playerPos[0] && ghostPos[i][1] == playerPos[1]) { score += 200; // 吃 掉 鬼 的 得 分 respawnGhost(grid, ghostPos[i], playerPos); ghostDir[i] = 4; // 設 置 鬼 的 方 向 為 停 止 ghostType[i] = tolower(ghostType[i]); // 將 鬼 的 類 型 改 為 小 寫 } } } int main() { srand(time(0)); // 設 置 隨 機 數 種 子 #ifndef __VScode SetConsoleCtrlHandler(ConsoleHandler, TRUE); system("chcp 65001"); #endif string initGrid[HEIGHT] = { "#################################################################", "#...............................................................#", "#..###...#...##...##.....#####....#......#...#######...#######..#", "#..#.#...#....##.##.....##...##...#......#...#.........#........#", "#..#.###.#.....###......#.........#......#...#######...#######..#", "#..#...#.#......#.......##...##...##....##...#.........#........#", "#..#...###......#........#####.....######....#######...#######..#", "#...............................0...............................#", "#...#####.....#####....##....##...########...#######...#######..#", "#..##...##...## ##...###..###...# #.........#...#........#", "#..#.........#######...#.####.#...########...#######...#######..#", "#..##...##...#.....#...#..##..#...#..........#...............#..#", "#...#####....#.....#...#......#...#..........#######...#######..#", "#...............................................................#", "#################################################################"}; int pointsCnt = 0; int playerPos[2]; // pacman 位 置 int playerDir = 3; // 初 始 方 向 為 'w' int ghostPos[ghostNum][2]; // 鬼 的 位 置 int ghostDir[ghostNum] = {4, 4, 4, 4}; // 鬼 的 方 向 char ghostType[ghostNum]; // 鬼 的 類 型 int score = 0; // 分 數 int scaredTime = 0; // 驚 嚇 時 間 bool scaredMode = false; // 是 否 在 驚 嚇 模 式 string pointsGrid[HEIGHT]; // 點 數 網 格 string gridDisplay[HEIGHT]; // 顯 示 網 格 string prevGridDisplay[HEIGHT]; // 前 一 個 顯 示 網 格 for (int i = 0; i < HEIGHT; i++) { string row = ""; row.append(WIDTH, ' '); pointsGrid[i] = row; // 初 始 化 點 數 網 格 gridDisplay[i] = row; // 初 始 化 顯 示 網 格 prevGridDisplay[i] = row; // 初 始 化 前 一 個 顯 示 網 格 } for (int i = 0; i < HEIGHT; i++) { // 設 定 pacman 位 置 的 初 始 狀 態 for (int j = 0; j < WIDTH; j++) { char c = initGrid[i][j]; if (c == '0') { playerPos[0] = i; // 設 置 pacman 的 行 playerPos[1] = j; // 設 置 pacman 的 列 } } } // 隨 機 放 置 能 量 球 for (int p = 0; p < powerNum; p++) { int x, y; do { x = rand() % HEIGHT; // 隨 機 列 y = rand() % WIDTH; // 隨 機 行 } while (initGrid[x][y] != '.'); initGrid[x][y] = '*'; // 放 置 能 量 球 } // 隨 機 放 置 鬼 for (int i = 0; i < ghostNum; i++) { int x, y; do { x = rand() % HEIGHT; // 隨 機 列 y = rand() % WIDTH; // 隨 機 行 } while (!(initGrid[x][y] == '.' && ((x - playerPos[0]) * (x - playerPos[0]) + (y - playerPos[1]) * (y - playerPos[1])) >= 49)); // 確 保 放 置 在 合 法 位 置 且 距 離 pacman 有 一 定 距 離 ghostPos[i][0] = x; // 設 置 鬼 的 行 ghostPos[i][1] = y; // 設 置 鬼 的 列 ghostType[i] = 'a' + i; // 分 配 鬼 的 類 型 a, b, c, d } for (int i = 0; i < HEIGHT; i++) { // 設 定 點 數 網 格 的 初 始 狀 態 for (int j = 0; j < WIDTH; j++) { char c = initGrid[i][j]; gridDisplay[i][j] = c; // 初 始 化 顯 示 網 格 if (c == '.' || c == '*') { pointsGrid[i][j] = c; // 設 置 點 數 網 格 pointsCnt++; // 增 加 點 數 計 數 } } } KeyManager keyM; // 創 建 鍵 盤 管 理 器 int curDir = playerDir; // 當 前 方 向 bool gameOver = false; // 遊 戲 是 否 結 束 cout << "\x1B[2J\x1B[H" << "\033[?25l"; // 初 始 化 螢 幕 , 清 除 並 隱 藏 鼠 標 update(initGrid, playerPos, ghostPos, ghostDir, ghostType, pointsGrid, pointsCnt, gridDisplay, score, scaredMode, scaredTime); gridPrint(gridDisplay, prevGridDisplay); cout << "\033[" << (HEIGHT + 1) << ";1H\033[K"; cout << "press any key to start game (use arrows or WASD to move pacman)" << endl; keyM.anyKeyToContinue(); for (int i = 3; i > 0; i--) { cout << "\033[" << (HEIGHT + 1) << ";1H\033[K"; cout << "starting in " << i << "........." << endl; Sleep(1000); } while (!gameOver && gameRunning) { // 移 動 pacman movePacman(initGrid, playerPos, playerDir, keyM); // 檢 查 是 否 與 鬼 碰 撞 if (checkCollideGhost(ghostPos, playerPos)) { if (scaredMode) eatGhost(initGrid, playerPos, ghostPos, ghostDir, ghostType, score); // 吃 掉 鬼 else gameOver = true; // 如 果 不 是 驚 嚇 模 式 , 遊 戲 結 束 } // 移 動 鬼 if (!(scaredMode && scaredTime % 2)) // 如 果 是 驚 嚇 模 式 只 有 一 半 的 時 間 會 移 動 鬼 moveGhost(initGrid, ghostPos, ghostDir, ghostType, playerPos, playerDir, scaredMode); // 再 次 檢 查 是 否 與 鬼 碰 撞 if (checkCollideGhost(ghostPos, playerPos)) { if (scaredMode) eatGhost(initGrid, playerPos, ghostPos, ghostDir, ghostType, score); // 吃 掉 鬼 else gameOver = true; // 如 果 不 是 驚 嚇 模 式 , 遊 戲 結 束 } // 更 新 遊 戲 狀 態 update(initGrid, playerPos, ghostPos, ghostDir, ghostType, pointsGrid, pointsCnt, gridDisplay, score, scaredMode, scaredTime); // 輸 出 遊 戲 網 格 gridPrint(gridDisplay, prevGridDisplay); cout << "\033[" << (HEIGHT + 1) << ";1H\033[K"; // 移 動 鼠 標 到 顯 示 分 數 的 位 置 cout << "Score: " << score << endl; // 顯 示 分 數 Sleep(500); // 暫 停 0.5 秒 if (pointsCnt == 0) gameOver = true; // 如 果 所 有 點 數 都 被 吃 掉 , 遊 戲 結 束 } // 遊 戲 結 束 cout << "\033[?25h" << "\033[" << (HEIGHT + 2) << ";1H"; // 恢 復 鼠 標 顯 示 if (pointsCnt == 0) { cout << "You win! Final score: " << score << endl; // 如 果 贏 了 , 顯 示 勝 利 信 息 } else { cout << "Game Over! Final score: " << score << endl; // 如 果 遊 戲 結 束 , 顯 示 結 束 信 息 } return 0; // 回 傳 0 , 結 束 程 序 } ``` ------------------------------->