#include <iostream> // 引入标准输入输出
#include <Windows.h> // 引入window程序相关
#include <graphics.h> // 引入easy_x图形库
#include <conio.h> // 获取热键需要的头文件
#include <string> // 引入字符串
#include <stdlib.h> // 引入常用的函数
#include <time.h> // 引入时间函数
// =============函数原型的声明=============
/*
实现玩家的方向控制
@param direction 输入的方向
*/
void gameControl(enum _DIRECTION direction);
/*
改变地图道具
@param row 需要改变道具的行
@param column 需要改变道具的列
@param prop 要改变为什么道具
*/
void changeMap(int row,int column, enum _PROPS prop);
/*
判断游戏是否结束
*/
bool isGameOver();
/*
游戏结束场景
@param bg 指定游戏结束之后要显示的背景图
*/
void gameOver(IMAGE *bg);
// ========== 地图窗口的一些宏定义 ==========
#define WINDOW_WIDTH 960 // 窗口的宽度
#define WINDOW_HEIGHT 768 // 窗口的高度
#define ROW 9 // 地图道具有多少行
#define COLUMN 12 // 地图道具有多少列
#define BMP_SIZE 61 // 道具图片的大小 宽度和高度
#define BRESIZE_TRUE true // 是否自动拉伸图片 是
#define START_X 100 // 偏移的x值
#define START_Y 150 // 偏移的y值
// ========== 玩家热键的一些宏定义 ==========
#define KEY_UP 'w'
#define KEY_DOWN 's'
#define KEY_LEFT 'a'
#define KEY_RIGHT 'd'
#define KEY_Quit 'q'
// ========== 判断是否越界 ==========
#define isValid(next_pos) (next_pos.x >= 0 && next_pos.x < ROW && next_pos.y >=0 && next_pos.y < COLUMN)
// 定义玩家位置的结构体
typedef struct _player_pos {
int x; // 玩家所在的行
int y; // 玩家所在的列
}PlayerPos;
// 把道具定义为枚举类型
// 墙0,地板1,箱子目的地2,小人3,箱子4,箱子命中目标5
// 使用枚举优化程序可读性
enum _PROPS {
WALL, // 0 墙
FLOOR, // 1 地板
BOX_DES, // 2 箱子目的地
MAN, // 3 小人
BOX, // 4 箱子
HIT, // 5 箱子命中目标
ALL // 道具总共有多少种
};
// 游戏方向的控制
enum _DIRECTION {
UP,
DOWN,
LEFT,
RIGHT
};
// 地图里面的道具分别是什么的数组 比如0,0 是 WALL 墙壁
// 墙0,地板1,箱子目的地2,小人3,箱子4,箱子命中目标5
int map[ROW][COLUMN] = { // 9行12列
{ WALL, WALL, WALL, WALL, WALL, WALL, WALL, WALL, WALL, WALL, WALL, WALL },
{ WALL, FLOOR, WALL, FLOOR, FLOOR, FLOOR, FLOOR, FLOOR, FLOOR, FLOOR, WALL, WALL },
{ WALL, FLOOR, BOX, FLOOR, WALL, BOX_DES, FLOOR, WALL, BOX_DES, FLOOR, WALL, WALL },
{ WALL, FLOOR, WALL, FLOOR, WALL, FLOOR, WALL, WALL, FLOOR, FLOOR, FLOOR, WALL },
{ WALL, FLOOR, WALL, BOX_DES, WALL, FLOOR, FLOOR, BOX, FLOOR, FLOOR, FLOOR, WALL },
{ WALL, FLOOR, FLOOR, FLOOR, FLOOR, MAN, FLOOR, FLOOR, FLOOR, BOX, FLOOR, WALL },
{ WALL, FLOOR, BOX_DES, FLOOR, FLOOR, BOX, FLOOR, FLOOR, FLOOR, FLOOR, FLOOR, WALL },
{ WALL, FLOOR, WALL, WALL, FLOOR, WALL, FLOOR, FLOOR, WALL, WALL, FLOOR, WALL },
{ WALL, WALL, WALL, WALL, WALL, WALL, WALL, WALL, WALL, WALL, WALL, WALL }
};
// 道具的数组 ALL总共有多少种道具
IMAGE images[ALL];
// 定义玩家位置
PlayerPos man;
// =============函数体的定义处=============
/*
改变地图道具
@param row 需要改变道具的行
@param column 需要改变道具的列
@param prop 要改变为什么道具
*/
void changeMap(int row, int column, enum _PROPS prop){
map[row][column] = prop; // 把地图里面对应坐标道具修改为我们要修改的道具 方便后面看看地图里面的箱子是不是已经都到目的地了
// 更新地图道具位置 通过偏移量 和 行列 计算出要放的位置
putimage(START_X+column* BMP_SIZE,START_Y+row*BMP_SIZE,&images[map[row][column]]);
// 如果修改的是玩家的位置 更新玩家的坐标
if(prop == MAN){ // 每次改完玩家的位置 都要把玩家的位置更新为最新的位置 以便于下次按键的时候能获取到玩家的最新位置
man.x = row;
man.y = column;
}
}
/*
实现玩家的方向控制
@param direction 输入的方向
*/
void gameControl(enum _DIRECTION direction){
// 每次按键获取玩家的最新位置
int x = man.x; // 人所在的行
int y = man.y; // 人所在的列
// 保存即将要移动的下一个位置
PlayerPos next_pos = man; // 刚开始的位置就是玩家现在所在的位置
PlayerPos next_next_pos = man; // 箱子前面那个位置 就是人的前面是箱子,箱子的前面是什么,就是人的前面两个格子
switch (direction)
{
// 根据要移动的方向去修改即将要移动的位置
case UP:
next_pos.x--; // 往上一行
next_next_pos.x -= 2; // 往前两行
break;
case DOWN:
next_pos.x++; // 往下一行
next_next_pos.x += 2; // 往下两行
break;
case LEFT:
next_pos.y--; // 往左一列
next_next_pos.y -= 2; // 往左两列
break;
case RIGHT:
next_pos.y++; // 往右一列
next_next_pos.y += 2; // 往右两列
break;
}
// ====================== 边界地板方案1 升级升级版 =======================
if (isValid(next_pos) && map[next_pos.x][next_pos.y] == FLOOR) {
// 如果下一步不会越界边界,并且下一步要移动的地方是一个地板
changeMap(x, y, FLOOR); // 把当前玩家所在的位置修改为地板
changeMap(next_pos.x, next_pos.y, MAN); // 把下一步要移动的位置变为人
}// ====================== 人推箱子 ======================
else if(isValid(next_pos) && map[next_pos.x][next_pos.y] == BOX){
// 如果下一步不会越界边界,并且下一步要移动的地方是一个箱子
// 并且箱子前面是地板或者箱子目的地
if(isValid(next_next_pos) && map[next_next_pos.x][next_next_pos.y] == FLOOR){
// 如果下一步的下一步不会越界边界 并且箱子的下一个位置是地板 那么人就可以带着箱子前进
changeMap(x, y, FLOOR); // 把当前玩家所在的位置修改为地板
changeMap(next_pos.x, next_pos.y, MAN); // 把下一步要移动的位置变为人 也就是说把原本的箱子的位置变成人
changeMap(next_next_pos.x, next_next_pos.y, BOX); // 把下一步的下一步要移动的位置变成箱子 也就是说把原本箱子的下一个变成箱子的现在所在处
}else if(isValid(next_next_pos) && map[next_next_pos.x][next_next_pos.y] == BOX_DES) {
// 如果下一步的下一步不会越界边界 并且箱子的下一个位置是箱子目的地 那么人就可以带着箱子前进
// 并且还要修改状态为5 就是箱子已经抵达目的地 HIT 不同的点 就是一个改为箱子 一个 改为箱子目的地
changeMap(x, y, FLOOR); // 把当前玩家所在的位置修改为地板
changeMap(next_pos.x, next_pos.y, MAN); // 把下一步要移动的位置变为人 也就是说把原本的箱子的位置变成人
changeMap(next_next_pos.x, next_next_pos.y, HIT); // 把下一步的下一步要移动的位置变成箱子 也就是说把原本箱子的下一个变成箱子的现在所在处
// 改为了箱子目的地之后,箱子就再也推动不了了,因为我们最外面只处理了 人前面是地板和箱子的状况,并没有处理人前面是箱子目的地的状况
}
}
// ====================== 边界地板方案1 升级版 =======================
/*if (next_pos.x >= 0 && next_pos.x < ROW && next_pos.y >=0 && next_pos.y < COLUMN && map[next_pos.x][next_pos.y] == FLOOR) {
// 如果下一步不会越界边界,并且下一步要移动的地方的一个地板
changeMap(x, y, FLOOR); // 把当前玩家所在的位置修改为地板
changeMap(next_pos.x, next_pos.y, MAN); // 把下一步要移动的位置变为人
}*/
// ====================== 边界地板方案1 =======================
//switch (direction)
//{
// case UP:// w 向上 那么要判断上面能不能走
// /*
// 1.处理前进方向是地板的情况
// 先判断人的上边能不能走 是不是空地 如果能走 人要往上边走 然后地板要往下边走 并且还要判断是否越界超出了地图
// */
// if((x - 1) >= 0 && map[x-1][y] == FLOOR){
// // 如果人在的位置 - 1 大于等于0 就证明 往上走不会超过边界,并且上面还要是一块地板 我们才能向上走
// changeMap(x, y, FLOOR); // 把当前玩家所在的位置修改为地板
// changeMap(x-1,y,MAN); // 把上一行这一列的位置修改为玩家
// }
// break;
// case DOWN:// s 向下 那么要判断下面能不能走
// /*
// 1.处理前进方向是地板的情况
// 先判断人的下边能不能走 是不是空地 如果能走 人要往下边走 然后地板要往上边走 并且还要判断是否越界超出了地图
// */
// if((x+1) <= (ROW-1) && map[x+1][y] == FLOOR){
// // 如果人在的位置 + 1 小于等于等于最下面那一行 就证明 往下走不会超过边界,并且下面还要是一块地板 我们才能向下走
// changeMap(x, y, FLOOR); // 把当前玩家所在的位置修改为地板
// changeMap(x+1,y,MAN); // 把下一行这一列的位置修改为玩家
// }
// break;
// case LEFT:// a 向左 那么要判断左面能不能走
// /*
// 1.处理前进方向是地板的情况
// 先判断人的左边能不能走 是不是空地 如果能走 人要往左边走 然后地板要往右边走 并且还要判断是否越界超出了地图
// */
// if ((y - 1) >= 0 && map[x][y-1] == FLOOR) {
// // 如果人在的位置 - 1 大于等于0 就证明 往左走不会超过边界,并且左面还要是一块地板 我们才能向左走
// changeMap(x, y, FLOOR); // 把当前玩家所在的位置修改为地板
// changeMap(x, y - 1, MAN); // 把这一行,往左一列的位置修改为玩家
// }
// break;
// case RIGHT:// d 向右 那么要判断右面能不能走
// /*
// 1.处理前进方向是地板的情况
// 先判断人的右边能不能走 是不是空地 如果能走 人要往右边走 然后地板要往左边走 并且还要判断是否越界超出了地图
// */
// if ((y + 1) <= (COLUMN - 1) && map[x][y + 1] == FLOOR) {
// // 如果人在的位置 + 1 小于等于最右边那行 就证明 往右走不会超过边界,并且右面还要是一块地板 我们才能向右走
// changeMap(x, y, FLOOR); // 把当前玩家所在的位置修改为地板
// changeMap(x, y + 1, MAN); // 把这一行,往右一列的位置修改为玩家
// }
// break;
//}
}
/*
判断游戏是否结束
*/
bool isGameOver(){
int desCount = 0;
// 遍历地图道具
for (int i = 0; i < ROW; i++) {
for (int j = 0; j < COLUMN; j++) {
if(map[i][j] == BOX_DES){ // 如果还存在箱子目的地 还有箱子没命中
desCount++;
}
}
}
return desCount == 0; // 如果是0个那么就是true 如果不是0个那么就是false
}
/*
游戏结束场景
@param bg 指定游戏结束之后要显示的背景图
*/
void gameOver(IMAGE *bg){
putimage(0,0,bg); // 喷涂背景图片
settextcolor(WHITE); // 设置要显示的字体颜色
RECT rec = {0,0,WINDOW_WIDTH,WINDOW_HEIGHT}; // 定义一个矩形 左上角 和右下角的坐标
settextstyle(20,0,_T("宋体")); // 设置文字样式
// 绘制文字 以上面的矩形为参照物 DT_CENTER 水平居中 DT_VCENTER 垂直居中 DT_SINGLELINE 文字显示单行
drawtext(_T("恭喜您~\n您终于成为了一个合格的搬箱子老司机"), &rec, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
}
using namespace std;
// 推箱子游戏程序的主入口
int main(void) {
// 背景图片
IMAGE bg_img;
// 初始化窗口 宽度高度 flag 绘图环境的样式,默认为 NULL
initgraph(WINDOW_WIDTH, WINDOW_HEIGHT);
// 加载背景图片 到bg_img去 图片的宽度和高度 为0则原本的宽高 是否拉伸图片
loadimage(&bg_img, _T("blackground.bmp"), WINDOW_WIDTH, WINDOW_HEIGHT, BRESIZE_TRUE);
// 显示图片 从0 0点开始绘制
putimage(0,0, &bg_img);
// 加载地图道具 wall_right 墙壁 floor 地板 des箱子目的地 man人 box箱子 箱子命中目标也显示箱子
loadimage(&images[WALL],_T("wall_right.bmp"), BMP_SIZE, BMP_SIZE, BRESIZE_TRUE);
loadimage(&images[FLOOR],_T("floor.bmp"), BMP_SIZE, BMP_SIZE, BRESIZE_TRUE);
loadimage(&images[BOX_DES],_T("des.bmp"), BMP_SIZE, BMP_SIZE, BRESIZE_TRUE);
loadimage(&images[MAN],_T("man.bmp"), BMP_SIZE, BMP_SIZE, BRESIZE_TRUE);
loadimage(&images[BOX],_T("box.bmp"), BMP_SIZE, BMP_SIZE, BRESIZE_TRUE);
loadimage(&images[HIT],_T("box.bmp"), BMP_SIZE, BMP_SIZE, BRESIZE_TRUE);
// 起始位置的x y
int start_x = START_X;
int start_y = START_Y;
// 绘制地图道具 遍历二维数组 显示对应的道具
for(int i = 0; i < ROW; i++){
for (int j = 0; j < COLUMN; j++) {
// 如果要显示的道具是玩家,就把玩家的位置给保存起来 MAN == MAN
if(map[i][j] == MAN){ // 玩家的代号是3 MAN 3
man.x = i; // 行
man.y = j; // 列
// 这样我们就知道玩家在几行几列的位置了
}
// 要显示的道具是什么 &images[WALL] 等 我们就可以拿到对应的图片 然后放在对应的位置
putimage(start_x, start_y, &images[map[i][j]]);
// 内层循环移动 x 的位置 每次移动一张图片的宽度
start_x += BMP_SIZE;
}
// 下一行 x 要恢复到最前面的位置 再往后移动
start_x = START_X;
// 一行结束 下一行 y 的位置要向下走一个图片的高度
start_y += BMP_SIZE;
}
// =================================地图初始化完毕以后的操作=================================
// 游戏环节 获取键盘热键 使用easy_x
bool isQuit = false; // 游戏是否退出
do{ // 获取热键
// 判断键盘是否有敲击 因为键盘不一定有使用
if(_kbhit()){
// 玩家敲击了按键 看看按了什么键
char ch = _getch();
switch (ch)
{
case KEY_UP: // w 向上 那么要判断上面能不能走
gameControl(UP);
break;
case KEY_DOWN: // s 向下 那么要判断下面能不能走
gameControl(DOWN);
break;
case KEY_LEFT: // a 向左 那么要判断左边能不能走
gameControl(LEFT);
break;
case KEY_RIGHT: // d 向右 那么要判断右边能不能走
gameControl(RIGHT);
break;
case KEY_Quit: // q 退出游戏
isQuit = true;
break;
// 其它按键不需要做处理
}
// 处理完按键的操作之后,我们判断游戏是否已经结束了 很简单 我们就循环 然后判断 des是不是全部消失了,因为到达之后会变成hit
if (isGameOver()) {
// 如果游戏结束,游戏没结束就不用做别的处理
gameOver(&bg_img); // 游戏结束
isQuit = true; // 退出循环 不再让用户对游戏进行操作
}
}else{
Sleep(100); // 如果玩家没有按键 那么休眠0.1s 再去做下一次的判断 避免 浪费cpu
}
} while (!isQuit); // 如果isQuit 为true了 !true 就是false 就退出游戏
// 游戏结束 释放绘图窗口
system("pause"); // 防止关闭屏幕太快
closegraph();
return 0;
}
|