基於STL實現自動貪心尋路算法的貪喫蛇小遊戲
寫貪喫蛇小遊戲的想法來自CometOJ-Contest#13的B題,當時用STL雙端隊列維護蛇身的時候覺得非常方便,現在用EasyX圖形庫實現一下。
運行截圖:
腦殘貪喫蛇
歡迎界面:
普通模式:
無敵模式:
實現思路:
代碼很短,寫的時候基本上是想到哪寫到哪。
現在看的話大概要分成四部分。
一、初始化,全局控制等
初始化所有信息,模式選擇等。
二、蛇身
存儲蛇位置,運動方向,控制蛇移動,更改方向等。
蛇身用std::deque實現,dq的前後端添加及彈出非常方便,不用過多擔心溢出問題。
三、地圖
存儲下一個食物的座標和已經被蛇身覆蓋的座標,對於普通模式可以判斷是否會撞到自己的身體。
四、繪製
用exayX庫在屏幕上繪製已經計算好的圖形並顯示。
幾個point:
✦記錄運行方向,可以快速計算蛇頭下一個位置,同餘座標即可實現穿牆功能。
✦每次移動蛇尾pop一個單位,蛇頭push一個單位即可,當蛇頭喫到食物則蛇尾不用pop。
✦普通模式下蛇頭撞向蛇身結束遊戲。
✦無敵模式下可以蛇身覆蓋。
✦Q鍵切換到自動模式。自動尋找食物採用曼哈頓距離貪心算法,無敵模式下沒有問題,普通模式下可能會撞死自己(考慮到可以種子填充判斷,但是時空複雜度要大一倍,沒寫(懶))。
✦遊戲自動加速,初始時遊戲速度爲10fps,隨着分數增加fps線性增加,當fps達到100(10ms/幀)時達到上限,遊戲速度不再增加。
代碼:
/*
Zeolim - An AC a day keeps the bug away
*/
#include <graphics.h>
#include <iostream>
#include "stdafx.h"
#include <easyx.h>
#include <random>
#include <conio.h>
#include <queue>
#include <unordered_map>
#include <time.h>
#include <map>
#include <process.h>
typedef long long ll;
typedef unsigned long long ull;
typedef long double ld;
typedef std::pair <int, int> pii;
typedef std::vector <int> vi;
#define mp(x, y) make_pair(x, y)
#define fr(x, y, z) for(int x = y; x < z; ++x)
#define pb(x) push_back(x)
#define mem(x, y) memset(x, y, sizeof(x))
const int ROW = 32;
const int COL = 48;
const int lineHigh = 17;
const int mov[4][2] = { { 1, 0 },{ 0, 1 },{ 0, -1 },{ -1,0 } };
int gameMode = 0;
int runSpd = 100;
int coler[7][3] =
{
{ 85,107,47 }, //WALL
{ 245,245,245 }, //Back
{ 211,211,211 } // line
};
bool fucked, autoC; //記錄是否死亡,和是否開啓自動模式
struct rmap
{
int dmap[COL][ROW];
int x, y;
void init() //初始化地圖
{
memset(dmap, 0, sizeof(dmap));
getFood();
}
void getFood() //生成下一個食物
{
do
{
x = rand() % COL, y = rand() % ROW;
} while (dmap[x][y] != 0);
}
bool isFucked(int x, int y) //判斷是否死亡
{
return dmap[x][y] == 1;
}
}MP;
struct body
{
std::deque < pii > D;
int dir;
void init() //初始化蛇身 方向
{
D.clear();
dir = 1;
D.push_back(std::make_pair(COL / 2, ROW / 2));
D.push_back(std::make_pair(COL / 2, ROW / 2 - 1));
D.push_back(std::make_pair(COL / 2, ROW / 2 - 2));
for (auto x : D)
++MP.dmap[x.first][x.second];
}
void move() //向當前運行方向前方移動一格
{
pii p = D.front();
int x = (p.first + mov[dir][0] + COL) % COL;
int y = (p.second + mov[dir][1] + ROW) % ROW;
if (p.first == MP.x && p.second == MP.y) //如果遲到食物 生成新食物
{
MP.getFood();
}
else //沒有喫到尾巴縮短一格
{
pii x = D.back();
--MP.dmap[x.first][x.second];
D.pop_back();
}
if (gameMode == 1 && MP.isFucked(x, y)) //判斷死亡
{
fucked = 1;
}
++MP.dmap[x][y]; //更新蛇頭
D.push_front(std::make_pair(x, y));
}
void ChangeDir(char x) //更新方向
{
int _dir = -1;
if (x == 'A' || x == 'a' && dir != 0)
_dir = 3;
if (x == 'D' || x == 'd' && dir != 3)
_dir = 0;
if (x == 'W' || x == 'w' && dir != 1)
_dir = 2;
if (x == 'S' || x == 's' && dir != 2)
_dir = 1;
if (_dir != -1)
dir = _dir;
}
}Snk;
void drawMap()
{
BeginBatchDraw(); //開始繪製
setbkcolor(RGB(coler[1][0], coler[1][1], coler[1][2])); //繪製背景色
cleardevice();
Snk.move(); //蛇移動
setlinecolor(RGB(211, 211, 211));
for (int i = 0; i < COL; ++i) //繪製網格
{
for (int j = 0; j < ROW; ++j)
{
rectangle(i * 10, j * 10 + lineHigh, i * 10 + 10, j * 10 + 10 + lineHigh);
}
}
setlinecolor(RGB(30, 144, 255));
setfillcolor(RGB(176, 224, 230));
for (int i = 0; i < Snk.D.size(); ++i) //繪製蛇 注意座標等比例擴大
{
pii x = Snk.D[i];
fillrectangle(x.first * 10, x.second * 10 + lineHigh, x.first * 10 + 10, x.second * 10 + 10 + lineHigh);
}
setlinecolor(RGB(255, 69, 0));
setfillcolor(RGB(255, 140, 0));
fillcircle(MP.x * 10 + 5, MP.y * 10 + 5 + lineHigh, 5); //繪製食物
setlinecolor(RGB(30, 144, 255));
setfillcolor(RGB(225, 255, 255));
pii x = Snk.D.front();
fillrectangle(x.first * 10, x.second * 10 + lineHigh, x.first * 10 + 10, x.second * 10 + 10 + lineHigh);
WCHAR text[200];
settextcolor(RGB(0, 0, 0)); //繪製分數,模式等信息
wsprintf(text, L"Score: %06d GameSpeed: %06d (q) AutoMode: (%d) (e)Exit", Snk.D.size(), runSpd, autoC);
outtextxy(20, 0, text);
EndBatchDraw(); //結束繪製
}
void drawWelcome()
{
BeginBatchDraw();
setbkcolor(RGB(coler[1][0], coler[1][1], coler[1][2]));
cleardevice();
WCHAR text[50];
settextcolor(RGB(0, 0, 0));
wsprintf(text, L"Input Game Mode: 1.Normal 2.Who`s your daddy!");
outtextxy(COL / 2 * 3, ROW / 2 * 8, text);
wsprintf(text, L"Auther: Zeolim");
outtextxy(COL / 2 * 8, ROW / 2 * 11, text);
EndBatchDraw();
do //獲取鍵盤輸入並更改模式
{
char x = _getch();
if (x == '1')
gameMode = 1;
if (x == '2')
gameMode = 2;
} while (gameMode == 0);
fucked = 0, autoC = 0;
}
void autoChange() //曼哈頓距離貪心
{
int x = Snk.D.front().first, y = Snk.D.front().second;
int nowdis = abs(x - MP.x) + abs(y - MP.y), rdis = 1000, p = -1;
for (int i = 0; i < 4; ++i)
{
if (i + Snk.dir == 3)
continue;
else
{
int rx = (x + mov[i][0] + COL) % COL, ry = (y + mov[i][1] + ROW) % ROW;
if (gameMode == 1 && MP.isFucked(rx, ry)) //模式1 下一格必死不能走
continue;
if (abs(rx - MP.x) + abs(ry - MP.y) < rdis) //貪心選擇更新
{
rdis = abs(rx - MP.x) + abs(ry - MP.y);
p = i;
}
}
}
if (p != -1) //更新方向
Snk.dir = p;
}
int main()
{
srand(time(0));
initgraph(COL * 10, ROW * 10 + lineHigh);
while (true)
{
gameMode = 0;
MP.init();
Snk.init();
drawWelcome(); //繪製主頁面
double t1, t2;
t1 = GetTickCount();
while (true)
{
runSpd = max(10, 100 - int(Snk.D.size()));
t2 = GetTickCount();
if (t2 >= t1 + runSpd)
{
t1 = GetTickCount();
drawMap();
if (_kbhit()) //keyboard hit 該函數爲非阻塞函數 未敲擊時不影響其他函數繼續運行
{
auto x = _getch(); //獲取一個字符
if (x == 'q' || x == 'Q') //更新模式
autoC ^= 1;
else if (x == 'e' || x == 'E') //退出到主菜單
break;
else
{
Snk.ChangeDir(x); //更新方向 wasd
continue;
}
}
if (autoC) //如果自動 則尋找下一個位置
{
autoChange();
}
}
if (fucked) //死亡退出
{
MessageBox(GetHWnd(), L"You fucked!", L"SORRY", MB_OK);
break;
}
}
}
return 0;
}
Done!