每天一個(gè)C語言小項(xiàng)目,提升你的編程能力!?
玩家被困在一個(gè)迷宮里,擁有一盞油燈,油燈能夠照亮以玩家為中心的一片圓形區(qū)域,隨著時(shí)間的流逝,油燈的照明力會(huì)逐漸下降,迷宮內(nèi)隨機(jī)分布著一些加油站(黃色的圓角矩形),經(jīng)過這些加油站能夠恢復(fù)油燈的照明力,找到地圖右下角的終點(diǎn)(綠色圓角矩形)。就算過關(guān)。
游戲采用圖塊(N * N 的正方形)的方式構(gòu)建地圖,且墻壁,地面,玩家,終點(diǎn)采用四個(gè)獨(dú)立的函數(shù)繪制,如果想改變地圖的風(fēng)格,只需要修改這些函數(shù)的內(nèi)容即可。
迷宮生成采用的深度優(yōu)先算法,有明顯的主路。
完整的游戲源代碼如下:
?
////////////////////////////////////////////// // 程序名稱:迷宮 // #include#include #include using std::stack; // 使用STL的棧 using std::vector; // 使用STL的數(shù)組容器 // 游戲信息 #define WIN_WIDTH 400 // 窗口的寬度(單位:像素) #define WIN_HEIGHT 300 // 窗口的高度(單位:像素) // !!注:由于隨機(jī)生成算法的原因,地圖寬高只能為奇數(shù) #define GAME_WIDTH 41 // 地圖的寬度(單位:塊) #define GAME_HEIGHT 51 // 地圖的高度(單位:塊) #define WALL 1 // 墻壁的數(shù)字標(biāo)記 #define GROUND 0 // 地面的數(shù)字標(biāo)記 #define FILLSTATE 2 // 加油站的數(shù)字標(biāo)記 #define ENDPOS 3 // 終點(diǎn)的數(shù)字標(biāo)記 #define MAXVIEW 8.0 // 最大的視野 #define MINVIEW 1 // 最小的視野 #define FILLNUM 10 // 加油站的數(shù)量 #define DARKTIME 12 // 視野下降1圖塊所需的時(shí)間 // 全局變量列表 int g_BlockSize; // 塊大小 int g_GameMap[GAME_HEIGHT][GAME_WIDTH]; // 地圖(寬高單位為塊) POINT g_EndPos; // 終點(diǎn)位置 POINT g_PlayerPos; // 玩家在地圖上的位置 POINT g_CameraPos; // 攝像機(jī)(屏幕左上角)在地圖上的位置 IMAGE g_MapImage; // 地圖的圖片(由于地圖是固定的,在不改變縮放的情況下只需要繪制一次) double g_ViewArray; // 視野 UINT g_BeginTime; // 游戲開始時(shí)的時(shí)間 UINT g_LastFillTime; // 上次為油燈加油的時(shí)間 // 函數(shù)列表 void initGame(); // 初始化游戲 void endGame(); // 結(jié)束游戲 void draw(); // 繪制函數(shù) bool upDate(); // 數(shù)據(jù)更新函數(shù) void absDelay(int delay); // 絕對(duì)延遲 bool canMove(POINT pos); // 判斷某個(gè)位置是否可以移動(dòng) void computeCameraPos(); // 計(jì)算攝像機(jī)在地圖上的位置 void rePaintMap(); // 重繪地圖 void drawWall(POINT pos); // 繪制墻壁圖塊的函數(shù) void drawGround(POINT pos); // 繪制地面圖塊的函數(shù) void drawFillState(POINT pos); // 繪制油燈圖塊的函數(shù) void drawEndPos(POINT pos); // 繪制終點(diǎn) void drawPlayer(); // 繪制人物的函數(shù) void drawView(); // 繪制視野 int main() { initGame(); while (1) { if (!upDate()) break; // 更新 draw(); // 繪制 absDelay(16); // 絕對(duì)延遲 16 毫秒,控制每秒 60 幀 } endGame(); return 0; } void initGame() { g_BlockSize = 32; // 初始圖塊大小為 32 個(gè)像素 srand(GetTickCount()); // 初始化隨機(jī)數(shù)生成 // 初始化間隔室 for (int i = 0; i < GAME_HEIGHT; i++) { for (int j = 0; j < GAME_WIDTH; j++) { if (i % 2 == 0 || j % 2 == 0) // 奇數(shù)行奇數(shù)列設(shè)為墻壁 g_GameMap[i][j] = WALL; else g_GameMap[i][j] = GROUND; } } // 隨機(jī)生成地圖(使用深度優(yōu)先遍歷) stack stepStack; // 步驟棧 vector stepPoint; // 四周的點(diǎn) POINT nowPoint; // 當(dāng)前步的所在點(diǎn) stepStack.push({ 1,1 }); // 寫入初始點(diǎn) (1,1) 作為起點(diǎn) nowPoint = { 1,1 }; g_GameMap[1][1] = 0xFFFF; // 標(biāo)記這個(gè)點(diǎn) while (!stepStack.empty()) // 只要步驟棧不空就繼續(xù)循環(huán) { // 得到四周的點(diǎn) POINT tempPoint; for (int i = -1; i <= 1; i += 2) { tempPoint = { nowPoint.x,nowPoint.y + i * 2 }; // 計(jì)算點(diǎn) // 判斷坐標(biāo)是否合法 if (tempPoint.x >= 0 && tempPoint.x <= GAME_WIDTH - 1 && tempPoint.y >= 0 && tempPoint.y <= GAME_HEIGHT - 1 && g_GameMap[tempPoint.y][tempPoint.x] != 0xFFFF) { stepPoint.push_back(tempPoint); } tempPoint = { nowPoint.x + i * 2 ,nowPoint.y }; // 計(jì)算點(diǎn) // 判斷坐標(biāo)是否合法 if (tempPoint.x >= 0 && tempPoint.x <= GAME_WIDTH - 1 && tempPoint.y >= 0 && tempPoint.y <= GAME_HEIGHT - 1 && g_GameMap[tempPoint.y][tempPoint.x] != 0xFFFF) { stepPoint.push_back(tempPoint); } } // 根據(jù)周圍點(diǎn)的量選擇操作 if (stepPoint.empty()) // 如果周圍點(diǎn)都被遍歷過了 { stepStack.pop(); // 出棧當(dāng)前點(diǎn) if (!stepStack.empty()) nowPoint = stepStack.top(); // 更新當(dāng)前點(diǎn) } else { stepStack.push(stepPoint[rand() % stepPoint.size()]); // 入棧當(dāng)前點(diǎn) g_GameMap[(nowPoint.y + stepStack.top().y) / 2][(nowPoint.x + stepStack.top().x) / 2] = 0; // 打通墻壁 nowPoint = stepStack.top(); // 更新當(dāng)前點(diǎn) g_GameMap[nowPoint.y][nowPoint.x] = 0xFFFF; // 標(biāo)記當(dāng)前點(diǎn) } stepPoint.clear(); // 清空周圍點(diǎn)以便下一次循環(huán) } // 清洗標(biāo)記點(diǎn) for (int i = 0; i < GAME_HEIGHT; i++) { for (int j = 0; j < GAME_WIDTH; j++) { if (g_GameMap[i][j] == 0xFFFF) g_GameMap[i][j] = 0; } } // 隨機(jī)生成加油站的位置 for (int i = 0; i < FILLNUM; i++) { POINT fillPoint = { rand() % GAME_WIDTH,rand() % GAME_HEIGHT }; // 保證在空地生成加油站 while (g_GameMap[fillPoint.y][fillPoint.x] != GROUND) fillPoint = { rand() % GAME_WIDTH,rand() % GAME_HEIGHT }; // 標(biāo)記油燈 g_GameMap[fillPoint.y][fillPoint.x] = FILLSTATE; } g_GameMap[GAME_HEIGHT - 2][GAME_WIDTH - 2] = ENDPOS; // 標(biāo)記終點(diǎn) g_EndPos = { GAME_WIDTH - 2,GAME_HEIGHT - 2 }; // 確定終點(diǎn)位置 g_ViewArray = MAXVIEW; // 初始視野是最大的 g_BeginTime = GetTickCount(); // 開始計(jì)時(shí) g_LastFillTime = GetTickCount(); // 油燈加油的時(shí)間 rePaintMap(); // 繪制地圖 g_PlayerPos = { g_BlockSize * 3 / 2,g_BlockSize * 3 / 2 }; // 初始化人的位置 computeCameraPos(); // 計(jì)算攝像機(jī)的位置 initgraph(WIN_WIDTH, WIN_HEIGHT); // 初始化畫布 setbkmode(TRANSPARENT); // 設(shè)置背景為透明 BeginBatchDraw(); // 開始緩沖繪制 } void endGame() { EndBatchDraw(); // 結(jié)束緩沖繪制 closegraph(); // 關(guān)閉畫布 } void draw() { // 清空設(shè)備 cleardevice(); // 繪制視野 drawView(); // 繪制人 drawPlayer(); // 繪制時(shí)間 TCHAR timeStr[256]; int loseTime = GetTickCount() - g_BeginTime; // 計(jì)算流失的時(shí)間 _stprintf_s(timeStr, _T("游戲時(shí)間:%02d:%02d"), loseTime / 1000 / 60, loseTime / 1000 % 60); settextcolor(RGB(140, 140, 140)); outtextxy((WIN_WIDTH - textwidth(timeStr)) / 2, 3, timeStr); FlushBatchDraw(); // 刷新屏幕 } bool upDate() { POINT nextPos = g_PlayerPos; // 下一個(gè)位置 // 計(jì)算下一個(gè)位置 if (GetKeyState(VK_UP) & 0x8000) nextPos.y -= 2; if (GetKeyState(VK_DOWN) & 0x8000) nextPos.y += 2; if (GetKeyState(VK_LEFT) & 0x8000) nextPos.x -= 2; if (GetKeyState(VK_RIGHT) & 0x8000) nextPos.x += 2; // 如果下一個(gè)位置不合法 if (!canMove(nextPos)) { if (canMove({ g_PlayerPos.x, nextPos.y })) // y 軸移動(dòng)合法 nextPos = { g_PlayerPos.x, nextPos.y }; else if (canMove({ nextPos.x, g_PlayerPos.y })) // x 軸移動(dòng)合法 nextPos = { nextPos.x, g_PlayerPos.y }; else // 都不合法 nextPos = g_PlayerPos; } // 如果是油燈則更新時(shí)間 if (g_GameMap[nextPos.y / g_BlockSize][nextPos.x / g_BlockSize] == FILLSTATE) g_LastFillTime = GetTickCount(); // 如果是終點(diǎn)則通關(guān) else if (g_GameMap[nextPos.y / g_BlockSize][nextPos.x / g_BlockSize] == ENDPOS) { outtextxy(WIN_WIDTH / 2 - 40, WIN_HEIGHT / 2 - 12, _T("恭喜過關(guān)!")); FlushBatchDraw(); Sleep(1000); return false; } g_PlayerPos = nextPos; // 更新位置 computeCameraPos(); // 計(jì)算攝像機(jī)的位置 // 根據(jù)時(shí)間縮減視野 static unsigned int lastTime = GetTickCount(); int loseTime = GetTickCount() - g_LastFillTime; // 計(jì)算流失的時(shí)間 g_ViewArray = MAXVIEW - loseTime / 1000.0 / DARKTIME; // 每一段時(shí)間油燈的照明力會(huì)下降一個(gè)圖塊 if (g_ViewArray < MINVIEW) g_ViewArray = MINVIEW; // 處理鼠標(biāo)消息 MOUSEMSG mouseMsg; // 鼠標(biāo)信息 int lastBlockSize = g_BlockSize; // 保存原本的大小 while (MouseHit()) { mouseMsg = GetMouseMsg(); if (mouseMsg.uMsg = WM_MOUSEWHEEL) // 滾輪消息 { g_BlockSize += mouseMsg.wheel / 120; } } // 如果沒有滾輪消息就退出 if (lastBlockSize == g_BlockSize) return true; // 處理滾輪消息 if (g_BlockSize >= 10 && g_BlockSize <= 50) // 塊大小沒有達(dá)到極限值 { // 保證縮放后的地圖不會(huì)比窗口小 if (GAME_WIDTH * g_BlockSize < WIN_WIDTH || GAME_HEIGHT * g_BlockSize < WIN_HEIGHT) g_BlockSize = lastBlockSize; rePaintMap(); // 重繪地圖 // 重新計(jì)算玩家在地圖上的位置 POINT mapPos = { g_PlayerPos.x / lastBlockSize,g_PlayerPos.y / lastBlockSize }; // 計(jì)算在地圖上的位置 g_PlayerPos.x = mapPos.x * g_BlockSize + g_BlockSize / 2; // 計(jì)算映射后的位置 g_PlayerPos.y = mapPos.y * g_BlockSize + g_BlockSize / 2; // 計(jì)算映射后的位置 computeCameraPos(); // 重新計(jì)算攝像機(jī)位置 } // 保證圖塊不會(huì)過大和過小 if (g_BlockSize < 10) g_BlockSize = 10; if (g_BlockSize > 50) g_BlockSize = 50; return true; } void absDelay(int delay) { static int curtime = GetTickCount(); static int pretime = GetTickCount(); while (curtime - pretime < delay) { curtime = GetTickCount(); Sleep(1); } pretime = curtime; } bool canMove(POINT pos) { // 只要外接矩形的四個(gè)頂點(diǎn)不在墻壁內(nèi)就必定合法 return g_GameMap[(pos.y - 3) / g_BlockSize][(pos.x - 3) / g_BlockSize] != WALL && g_GameMap[(pos.y + 3) / g_BlockSize][(pos.x + 3) / g_BlockSize] != WALL && g_GameMap[(pos.y - 3) / g_BlockSize][(pos.x + 3) / g_BlockSize] != WALL && g_GameMap[(pos.y + 3) / g_BlockSize][(pos.x - 3) / g_BlockSize] != WALL; } void computeCameraPos() { // 以人物位置為中心計(jì)算攝像機(jī)的理論位置 g_CameraPos.x = g_PlayerPos.x - WIN_WIDTH / 2; g_CameraPos.y = g_PlayerPos.y - WIN_HEIGHT / 2; // 防止攝像機(jī)越界 if (g_CameraPos.x < 0) g_CameraPos.x = 0; if (g_CameraPos.y < 0) g_CameraPos.y = 0; if (g_CameraPos.x > GAME_WIDTH * g_BlockSize - WIN_WIDTH) g_CameraPos.x = GAME_WIDTH * g_BlockSize - WIN_WIDTH; if (g_CameraPos.y > GAME_HEIGHT * g_BlockSize - WIN_HEIGHT) g_CameraPos.y = GAME_HEIGHT * g_BlockSize - WIN_HEIGHT; } void rePaintMap() { g_MapImage.Resize(GAME_WIDTH * g_BlockSize, GAME_HEIGHT * g_BlockSize); // 重置地圖圖片大小 SetWorkingImage(&g_MapImage); // 設(shè)置地圖圖片為當(dāng)前工作圖片 for (int i = 0; i < GAME_HEIGHT; i++) { for (int j = 0; j < GAME_WIDTH; j++) { switch (g_GameMap[i][j]) { case WALL: drawWall({ j*g_BlockSize,i*g_BlockSize }); // 繪制墻壁 break; case FILLSTATE: drawFillState({ j*g_BlockSize,i*g_BlockSize }); // 繪制加油站 break; case GROUND: drawGround({ j*g_BlockSize,i*g_BlockSize }); // 繪制地面 break; case ENDPOS: drawEndPos({ j*g_BlockSize,i*g_BlockSize }); break; } } } SetWorkingImage(); // 復(fù)位工作圖片 } void drawWall(POINT pos) { setfillcolor(RGB(254, 109, 19)); solidrectangle(pos.x, pos.y, pos.x + g_BlockSize, pos.y + g_BlockSize); } void drawGround(POINT pos) { setfillcolor(RGB(255, 255, 255)); solidrectangle(pos.x, pos.y, pos.x + g_BlockSize, pos.y + g_BlockSize); } void drawFillState(POINT pos) { drawGround(pos); // 繪制圓角矩形 pos.x += g_BlockSize / 5; pos.y += g_BlockSize / 5; setfillcolor(RGB(252, 213, 11)); solidroundrect(pos.x, pos.y, pos.x + g_BlockSize / 5 * 3, pos.y + g_BlockSize / 5 * 3, g_BlockSize / 8, g_BlockSize / 8); } void drawEndPos(POINT pos) { drawGround(pos); // 繪制圓角矩形 pos.x += g_BlockSize / 5; pos.y += g_BlockSize / 5; setfillcolor(RGB(87, 116, 48)); solidroundrect(pos.x, pos.y, pos.x + g_BlockSize / 5 * 3, pos.y + g_BlockSize / 5 * 3, g_BlockSize / 8, g_BlockSize / 8); } void drawPlayer() { setfillcolor(RGB(252, 213, 11)); solidcircle(g_PlayerPos.x - g_CameraPos.x, g_PlayerPos.y - g_CameraPos.y, 3); } void drawView() { // 鎖定視野 HRGN viewArr; int r = int(g_BlockSize * g_ViewArray + 0.5); // 計(jì)算視野半徑 POINT orgin = g_PlayerPos; orgin.x -= g_CameraPos.x; // 計(jì)算在屏幕上的位置 orgin.y -= g_CameraPos.y; // 計(jì)算在屏幕上的位置 viewArr = CreateEllipticRgn(orgin.x - r, orgin.y - r, orgin.x + r, orgin.y + r); // 創(chuàng)建一個(gè)圓形的區(qū)域 setcliprgn(viewArr); // 鎖定區(qū)域 // 繪制地圖 putimage(0, 0, WIN_WIDTH, WIN_HEIGHT, &g_MapImage, g_CameraPos.x, g_CameraPos.y); // 刪除區(qū)域 DeleteObject(viewArr); // 消除區(qū)域 setcliprgn(NULL); }
?
大家趕緊去動(dòng)手試試吧!
審核編輯:湯梓紅
評(píng)論
查看更多