做完C的贪吃蛇游戏后,感觉还不错,刚好记得在HDU上做过一道关于推箱子游戏的算法题目,即双BFS。
所以我决定来做做C++的小游戏推箱子,由于刚学C++,对C++还是不很熟练,但是思路还是很清楚的,
编写代码还是很舒服的。!
现在晒晒我的代码和详细解释,希望多交流~!
//*******************************************************
//********************小小推箱子*************************
//*******************************************************
//**************版权所有***2011.9.24***咸鱼**************
//*******************************************************
//*****************如写的不好,请见谅*********************
//************每次运行程序的地图是不同的*****************
//*******************************************************
/*
Sokoban.h: 类定义 Sokoban.c: 类成员函数实现
Use_Sokoban.c: 主函数
请用VC6(别编译器的也行)先运行Use_Sokoban.c文件,要编译该文件一下,
再点Project-> Add To Project-> Files 选择Sokoban.c文件,
即将Sokoban.c加载到工程里,最后运行就OK拉。
*/
//*******************************************************
Sokoban.h
//*******************************************************
#ifndef SOKOBAN_H_ //防止文件重复包含
#define SOKOBAN_H_
#include
using std::queue;
//每一步的数据类型
struct node
{
int bx, by; //箱子的坐标
int px, py; //人的坐标
};
//推箱子类
class Sokoban
{
private:
enum {L = 15, H = 7};
char GameMap[H][L]; //地图
int Pex, Pey; //人的位置
int Boxx, Boxy; //箱子的位置
int Succeed, Prove; //是否成功到目的地, 是否可玩性
int dx[4], dy[4]; //方向数组
protected:
char Empty;
char People;
char Box;
char Block;
char Target;
int dir; //记录按键方向
node s, e;
public:
Sokoban(); //构建函数
~Sokoban() {} //析构函数,即为inline
//地图初始化函数
void Initial();
//箱子路劲验证函数,参数为箱子坐标(bx,by),人坐标(px,py)
void Box_Bfs(int bx, int by, int px, int py);
//人路劲验证函数,人所到的目的地(ex,ey)
bool People_Bfs(int ex, int ey);
//地图刷新函数
void Show();
//按键判断函数
void Button();
//箱子人移动函数
void Move();
//验证越界函数
bool Check(int x, int y);
};
#endif
//*******************************************************
Sokoban.cpp
//*******************************************************
#include "Sokoban.h"
#include
#include
#include
#include
#include
using std::cout;
using std::endl;
Sokoban::Sokoban() //构建函数即对变量初始化
{
dir = -1;
Succeed = Prove = 0;
memset(GameMap, '.', sizeof(GameMap));
Empty = '.';
People = 'P';
Box = '#';
Block = '*';
Target = 'T';
//方向依次为上右下左
dx[0] = -1; dx[1] = 0; dx[2] = 1; dx[3] = 0;
dy[0] = 0; dy[1] = 1; dy[2] = 0; dy[3] = -1;
//随机种子,使程序每次运行时所产生的随机数不同
srand(time(0));
}
//地图初始化函数
void Sokoban::Initial()
{
int count = 0, x, y;
//对地图中随机产生25个阻碍物
while(count != 25)
{
x = rand()%H;
y = rand()%L;
if(GameMap[x][y] == Empty)
{
GameMap[x][y] = Block;
count++;
}
}
while(true) //随机产生人开始的位置
{
x = rand()%H;
y = rand()%L;
if(GameMap[x][y] == Empty)
{
GameMap[x][y] = People;
Pex = x;
Pey = y;
break;
}
}
while(true) //随机产生箱子开始的位置
{
x = rand()%H;
y = rand()%L;
//不让箱子在地图的边界处
if(GameMap[x][y] == Empty && x != 0 && y != 0
&& x != H-1 && y != L-1)
{
GameMap[x][y] = Box;
Boxx = x;
Boxy = y;
break;
}
}
while(true) //随机产生目标的位置
{
x = rand()%H;
y = rand()%L;
if(GameMap[x][y] == Empty)
{
GameMap[x][y] = Target;
break;
}
}
//对游戏地图检查是否可将箱子推到目的地,即判断游戏可玩性
Sokoban::Box_Bfs(Boxx, Boxy, Pex, Pey);
//如游戏不可玩,即再随机产生地图
if(!Prove)
{
memset(GameMap, '.', sizeof(GameMap));
Sokoban::Initial();
}
else
Sokoban::Show();
}
//箱子路劲验证函数
//用BFS算法对箱子验证是否可到目的地
void Sokoban::Box_Bfs(int bx, int by, int px, int py)
{
queue_Box; //创建箱子队列
//visit对上一步走到下一步的记录,防止箱子走重复路劲
//visit[i][j][z][k]表示箱子从点(i,j)到点(z,k)
//visit[][][][]为0时表示为走过,1时表示已走过
int visit[H][L][H][L];
memset(visit, 0, sizeof(visit)); //visit数组初始化
s.bx = bx; s.by = by; //将起始的箱子、人位置放入队列
s.px = px; s.py = py;
_Box.push(s);
int pe_x, pe_y;
while(!_Box.empty()) //队列为空时跳出
{
s = _Box.front();
_Box.pop();
if(GameMap[s.bx][s.by] == Target) //到达目的地
{
Prove = 1;
break;
}
for(int i = 0; i < 4; i++)
{
e.bx = s.bx + dx[i]; e.by = s.by + dy[i];
switch(i) //人推箱子的位置
{
case 0: pe_x = s.bx + dx[2]; pe_y = s.by + dy[2]; break;
case 1: pe_x = s.bx + dx[3]; pe_y = s.by + dy[3]; break;
case 2: pe_x = s.bx + dx[0]; pe_y = s.by + dy[0]; break;
case 3: pe_x = s.bx + dx[1]; pe_y = s.by + dy[1]; break;
}
//验证箱子和人的位置的合法性
if(!Check(e.bx, e.by) || !Check(pe_x, pe_y)
|| GameMap[e.bx][e.by] == Block || GameMap[pe_x][pe_y] == Block
|| visit[s.bx][s.by][e.bx][e.by] )
continue;
//如人可推箱子即进入队列
if(Sokoban::People_Bfs(pe_x, pe_y))
{
//保存人推箱子后的位置
e.px = pe_x; e.py = pe_y;
_Box.push(e);
visit[s.bx][s.by][e.bx][e.by] = 1; //箱子路劲的标记
}
}
}
}
//人路劲验证函数
//用BFS算法对人验证是否可推箱子
bool Sokoban::People_Bfs(int ex, int ey)
{
queue_People;
node t, end;
//visit数组对人的路劲进行标记,0为未走过,1为走过
int visit[H][L];
//visit数组初始化为0
memset(visit, 0, sizeof(visit));
t.px = s.px; t.py = s.py; //人初始位置进入队列
_People.push(t);
visit[t.px][t.py] = 1;
while(!_People.empty()) //对立为空时跳出
{
t = _People.front();
_People.pop();
if(t.px == ex && t.py == ey) //人可到达(ex,ey)该点
return 1;
for(int i = 0; i < 4; i++)
{
end.px = t.px + dx[i]; end.py = t.py + dy[i];
//检查人的位置合法性
if(!Check(end.px, end.py) || GameMap[end.px][end.py] == Block
|| GameMap[end.px][end.py] == Box || visit[end.px][end.py])
continue;
//进入队列
_People.push(end);
visit[end.px][end.py] = 1; //记录
}
}
return 0;
}
//地图刷新函数
void Sokoban::Show()
{
int i, j;
while(true)
{
//每半秒刷新一次地图
clock_t s = clock();
while(clock() - s < CLOCKS_PER_SEC/2)
;
//先判断按键在移动
Sokoban::Button();
Sokoban::Move();
system("cls");
for(i = 0; i < H; i++)
{
for(j = 0; j < L; j++)
cout << GameMap[i][j];
cout << endl;
}
cout << endl;
cout << "\n**********************************" << endl;
cout << "* 小小C++语言推箱子游戏 *" << endl;
cout << "* 游戏规则: *" << endl;
cout << "* P: 人 #: 箱子 *" << endl;
cout << "* *: 障碍物 T: 目的地 *" << endl;
cout << "**********************************" << endl;
cout << "* 每次游戏地图不一样 *" << endl;
cout << "* 人将箱子推到目的地即过关 *" << endl;
cout << "*所给地图,一定可过关,请慎重移箱子*" << endl;
cout << "* 箱子无路可走时,机器不会提示 *" << endl;
cout << "**********************************" << endl;
//箱子成功到达目的地
if(Succeed)
{
cout << "\n ^_^ >_<" << endl;
cout << "恭喜过关成功! 再来一盘吧" << endl;
getchar();
break;
}
}
}
//按键判断函数
void Sokoban::Button()
{
int key;
if(kbhit() != 0) //检查当前是否有键盘输入,若有则返回一个非0值,否则返回0
{
while(kbhit() != 0) //可能存在多个按键,要全部取完,以最后一个为主
key = getch(); //将按键从控制台中取出并保存到key中
switch(key)
{
//上
case 72: dir = 0;
break;
//右
case 77: dir = 1;
break;
//下
case 80: dir = 2;
break;
//左
case 75: dir = 3;
break;
}
}
}
//人推箱子移动函数
void Sokoban::Move()
{
int x, y;
//有按键时
if(dir != -1)
{
//人所推向的位置坐标
x = Pex + dx[dir]; y = Pey + dy[dir];
//人所推位置为空,即走向该位置
if(Check(x, y) && GameMap[x][y] == '.')
{
GameMap[Pex][Pey] = '.'; //人的位置改变
GameMap[x][y] = 'P';
Pex = x; Pey = y;
dir = -1; //按键记录为无即-1
}
else //人所推位置为箱子,即将箱子推向该方向的前面这点
if(Check(x, y) && GameMap[x][y] == '#'
&& Check(x+dx[dir], y+dy[dir])
&& GameMap[ x+dx[dir] ][ y+dy[dir] ] == '.')
{
GameMap[Boxx][Boxy] = '.'; //箱子的位置改变
GameMap[x+dx[dir] ][ y+dy[dir] ] = '#';
Boxx = x + dx[dir]; Boxy = y + dy[dir];
GameMap[Pex][Pey] = '.'; //人的位置改变
GameMap[x][y] = 'P';
Pex = x; Pey = y;
dir = -1;
}
else //将箱子推向该方向的前面这点为目的地
if(Check(x, y) && GameMap[x][y] == '#'
&& Check(x+dx[dir], y+dy[dir])
&& GameMap[ x+dx[dir] ][ y+dy[dir] ] == 'T')
{
GameMap[Boxx][Boxy] = '.'; //箱子的位置改变
GameMap[x+dx[dir] ][ y+dy[dir] ] = '#';
Boxx = x + dx[dir]; Boxy = y + dy[dir];
GameMap[Pex][Pey] = '.'; //人的位置改变
GameMap[x][y] = 'P';
Pex = x; Pey = y;
dir = -1;
Succeed = 1; //记录成功到达目的地
}
}
}
//判断越界情况
bool Sokoban::Check(int x, int y)
{
if(x < 0 || x >= H || y < 0 || y >= L)
return 0;
else
return 1;
}
//*************************************************
Use_Sokoban.cpp
//*************************************************
#include
#include "Sokoban.h"
using namespace std;
int main()
{
Sokoban s;
s.Initial();
return 0;
}