加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
文件
克隆/下载
cgame4(boxes)v3.cpp 14.15 KB
一键复制 编辑 原始数据 按行查看 历史
Anbang24 提交于 2021-02-23 16:57 . 第4章 推箱子
//https://gitee.com/devcpp/cgames anbangli@foxmail.com GNU GPL v3
//cgame4(boxes)v3 推箱子游戏第3版:添加显示隐藏光标、读取关卡文件、保存和读取进度
#include <iostream>
#include <fstream>
#include <windows.h>
#include <conio.h>
using namespace std;
const int HT = 16, WD = 24; //表示地图高度和宽度的整型常量
char map[HT][WD] = {""}; //关卡地图二维数组
char cfgfile[50] = "boxes.ini"; //配置参数文件名
char xsbfile[50] = "BoxWorld.xsb"; //关卡地图集文件名
char lurdfile[50] = ""; //关卡解答文件
bool saveConfig(int level, int total, char track[]) {
ofstream output;
output.open(cfgfile); //以追加方式打开输出文件流
if (!output) {
cout << "试图打开程序配置参数文件时出错。无法保存参数。" << endl;
return false;
}
output << "Boxes configuration" << endl;
output << "xsbfile" << endl << xsbfile << endl;
output << "level" << endl << level << endl;
output << "total" << endl << total << endl;
track[total] = '\0'; //0 -- total-1为有效动作,末尾写入空字符作为字符串结束标志
output << "track" << endl << track << endl; //
return true;
}
bool loadConfig(int &level, int &total, char track[]) {
//启动程序时,xsbfile已有默认值,需要设置total默认值
total = 0;
ifstream input; //定义输入文件流
input.open(cfgfile); //打开配置参数文件
char str[50];
if (input) { //如果正常打开文件,则依次读取所有参数(否则使用默认值)
input.getline(str, 80); //文件头注释行
while (!input.eof()) {
input >> str;
if (strstr(str, "xsbfile"))
input >> xsbfile; //关卡地图集文件名
if (strstr(str, "level"))
input >> level; //当前关卡编号
if (strstr(str, "total"))
input >> total; //当前步数
if (strstr(str, "track"))
input >> track; //动作轨迹
}
input.close(); //关闭文件
}
//检查关卡地图集文件是否正常,若不正常则要求用户输入(最多3次)
int err = 0;
do {
input.open(xsbfile); //输入文件流绑定到文件。打开失败时得到空指针
if (!input) { //如果打开文件失败,则要求用户输入文件名
err ++;
cout << "错误:未找到推箱子关卡地图文件 " << xsbfile << " 。\n";
cout << "请输入推箱子的关卡文件:";
cin >> xsbfile; //由用户输入
}
} while (!input && err < 3);
input.close();
if (err >= 3) {
cout << "打开关卡地图文件失败。退出程序。";
return false;
}
//根据关卡地图文件名而设置准备关卡答案文件名(添加 .lurd 扩展名)
strcpy(lurdfile, xsbfile);
char *pch = strstr(lurdfile, ".xsb");
if (pch != NULL)
*pch = '\0';
strcat(lurdfile, ".lurd");
return true;
}
bool saveAnswer(int level, int total, char track[]) {
ofstream output;
output.open(lurdfile, ios::app); //以追加方式打开输出文件流
if (!output) {
cout << "试图打开关卡答案文件时出错。无法保存答案。" << endl;
return false;
}
output << ";Level " << level << endl; //写入关卡编号
for (int i = 0; i < total; i++) { //写入动作序列,每行60字符
output << track[i] << (i % 60 == 59 ? "\n" : "");
}
output << "#" << endl; //末尾加 # 表示结束
output.close();
return true;
}
bool loadAnswer(int level, int total, char track[]) {
ifstream input;
input.open(lurdfile); //以追加方式打开输出文件流
if (!input) {
cout << "试图打开关卡答案文件时出错。无法读取答案。" << endl;
return false;
}
char line[80], s1[20];
sprintf(s1, ";Level %d", level); //把编号写成 ";level k" 格式的字符串
do { //逐行读入,直到读得 ";level k" 或 "; k"
input.getline(line, 80);
} while (!strstr(line, s1) && !input.eof()) ;
if (input.eof())
return false;
total = 1; //步数从1开始计数
do {
input >> track[total - 1]; //读入第total步,数组元素的下标为 total-1
if (strchr("lLuUrRdD", track[total - 1]))
total++;
} while (track[total - 1] != '#');
input.close();
return true;
}
bool initMap(int level, int &x, int &y) {//初始化第 level 关,并获得人的坐标(x, y)
char st[HT][WD + 1] = {""};
ifstream infile; //定义输入文件流
infile.open(xsbfile); //输入文件流绑定到文件。打开失败时得到空指针
if (!infile) { //如果打开文件失败,则出错退出
cout << "错误:打开关卡地图文件出错。\n";
return false;
}
char line[80], s1[20], s2[20];
sprintf(s1, ";Level %d", level); //把编号写成 ";level k" 格式的字符串
sprintf(s2, "; %d", level); //把编号写成 "; k" 形式的字符串
char titleline[80], authorline[80];
int ix, iy, n;
do { //逐行读入,直到读得 ";level k" 或 "; k"
infile.getline(line, 80);
} while (!strstr(line, s1) && !strstr(line, s2) && !infile.eof()) ;
if (infile.eof())
return false;
do //跳过在编号行与地图内容之间可能的空行
infile.getline(line, 80);
while (strlen(line) == 0);
iy = 0; //地图行数
while (strlen(line) > 0) { //处理连续的多个非空行,直到空行结束
n = 0;
for (ix = 0; ix < strlen(line); ix++)
if (strchr("#_-$@", line[ix])) //对 # - _ $ . 等字符计数
n++;
if (n > 3) { //至少要3个以上才认为是合法的地图内容
strcpy (st[iy], line); //复制到关卡地图数组
iy++;
}
if (strstr(line, "Title:")) {
strcpy (titleline, line);
} else if (strstr(line, "Author:")) {
strcpy (authorline, line);
}
infile.getline(line, 80);
}
infile.close(); //关闭文件
// @ 人 + 人在目标点 $ 箱子 * 箱子在目标点 # 墙 . 目标点 _ 空地
// if (level == 1) {
// strcpy(st[0], "__###___");//snail 1
// strcpy(st[1], "__#.#___");
// strcpy(st[2], "__#_####");
// strcpy(st[3], "###$_$.#");
// strcpy(st[4], "#._$@###");
// strcpy(st[5], "####$#__");
// strcpy(st[6], "___#.#__");
// strcpy(st[7], "___###__");
// } else if (level == 2) {
// strcpy(st[0], "#####____");//snail 2
// strcpy(st[1], "#@__#____");
// strcpy(st[2], "#_$$#_###");
// strcpy(st[3], "#_$_#_#.#");
// strcpy(st[4], "###_###.#");
// strcpy(st[5], "_##____.#");
// strcpy(st[6], "_#___#__#");
// strcpy(st[7], "_#___####");
// strcpy(st[8], "_#####___");
// } else if (level == 3) {
// strcpy(st[0], "_####_");//snail 3
// strcpy(st[1], "##__#_");
// strcpy(st[2], "#_@$#_");
// strcpy(st[3], "##$_##");
// strcpy(st[4], "##_$_#");
// strcpy(st[5], "#.$__#");
// strcpy(st[6], "#..*.#");
// strcpy(st[7], "######");
// } else if (level == 4) {
// strcpy(st[0], "_####___");//snail 4
// strcpy(st[1], "_#__###_");
// strcpy(st[2], "_#@$__#_");
// strcpy(st[3], "###_#_##");
// strcpy(st[4], "#.#_#__#");
// strcpy(st[5], "#.$__#_#");
// strcpy(st[6], "#.___$_#");
// strcpy(st[7], "########");
// } else if (level == 5) {
// strcpy(st[0], "_#######__");//
// strcpy(st[1], "_#_____###");
// strcpy(st[2], "##$###___#");
// strcpy(st[3], "#_@_$__$_#");
// strcpy(st[4], "#_..#_$_##");
// strcpy(st[5], "##..#___#_");
// strcpy(st[6], "_########_");
// } else {
// return false;
// }
//找出新地图的高度和宽度(非空格点的二维最大下标值+1)
int hgt = 0, wid = 0;
for (iy = 0; iy < HT; iy++)
for (ix = 0; ix < WD; ix++) {
if (st[iy][ix] == '-' || st[iy][ix] == ' ' )
st[iy][ix] = '_'; //把'-'和' '替换为'_'
if (st[iy][ix] > 0 && st[iy][ix] != '_') {
hgt = (hgt > iy + 1 ? hgt : iy + 1);
wid = (wid > ix + 1 ? wid : ix + 1);
}
}
//原地图全部清空
for (iy = 0; iy < HT; iy++)
for (ix = 0; ix < WD; ix++)
map[iy][ix] = '_';
//把新地图的信息复制到数组的中央部分
int yup = (HT - hgt) / 2;
int xleft = (WD - wid) / 2;
for (iy = 0; iy < hgt; iy++)
for (ix = 0; ix < wid; ix++) {
map[iy + yup][ix + xleft] = st[iy][ix];
if (map[iy + yup][ix + xleft] == '@') {
x = ix + xleft;
y = iy + yup;
}
}
return true;
}
void gotoxy(short x, short y) { //控制台窗口光标定位
COORD crd = {x, y}; //定义 COORD 类型(窗口坐标)的变量 crd 并以形参x和y初始化
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), crd);
//GetStdHandle获取控制台输出句柄并用SetConsoleCursorPosition设置光标位置
return;
}
void showCursor(bool visible) { //显示或隐藏光标
CONSOLE_CURSOR_INFO cursor = {20, visible};
//CONSOLE_CURSOR_INFO结构体包含控制台光标信息,两个成员分别表示光标厚度和是否可见
SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &cursor);
}
void drawMap(int level, int step, int total) { //绘制地图
gotoxy(0, 0); //定位到窗口左上角
cout << "\n\t推箱子 第【" << level << "】关" ;
cout << " 第 " << step << " 步 共 " << total << " 步 \n";
int ix, iy;
for (iy = 0; iy < HT; iy++) { //行循环
for (ix = 0; ix < WD; ix++) { //列循环
if (map[iy][ix] == '#')
cout << "█"; //墙
else if (map[iy][ix] == '$')
cout << "□"; //箱子
else if (map[iy][ix] == '.')
cout << "☆"; //目标点
else if (map[iy][ix] == '@')
cout << "@"; //人
else if (map[iy][ix] == '+')
cout << "◎"; //人站在目标点上
else if (map[iy][ix] == '*')
cout << "★"; //箱子在目标点上
else //空地:空格、'-' 或 '_'
cout << " ";
}
cout << endl;
}
gotoxy(0, 0); //光标移到窗口左上角
return;
}
bool isWin() { //判断是否胜利
int ix, iy;
for (iy = 0; iy < HT; iy++)
for (ix = 0; ix < WD; ix++)
if (map[iy][ix] == '$') //若有空闲箱子则尚未胜利
return false;
return true; //没有空闲箱子为胜利
}
int main() {
int x, y, dx = 1, dy = 1; //(x,y)是人的位置坐标,dx和dy是移动量(初始化为非0值)
int x2, y2, x3, y3; //(x2, y2)和(x3, y3)是两个位置坐标
char key;
int play = 1; //操作标志:玩家正向操作时为1,撤消时为-1,重做时为2
int level = 1, step = 0, total = 0; //关卡编号,推动箱子的当前步数和总步数
char track[500] = ""; //轨迹记录(小写lurd表示左上右下移动,大写LURD表示推动)
//数组的下标从0开始,第step(从1开始)步的动作保存在下标为step-1的数组元素中
cout << "\n";
cout << " *==== || _ | ===_ \n";
cout << " | +====+ || += +====+ |====+ = +====+ \n";
cout << " ====== | | ||* | | | | +===== | | \n";
cout << " | |. .| || =_ | | | | | | | | \n";
cout << " =====+ +====+ || = ====== +====+ *====** | | \n";
cout << " 推箱子(anbangli@foxmail.com, 2021.1)\n\n";
cout << "操作说明: W↑ 上移 R 重做\n";
cout << " A← 左移 S↓ 下移 D→ 右移 Z 撤消 ESC 退出\n\n";
if (!loadConfig(level, total, track)) //读入程序配置参数
return 0;
else
cout << "关卡地图文件名:" << xsbfile << "\t当前关卡:" << level << endl;
cout << "\n按回车键开始";
getch();
system("cls"); //清除屏幕
showCursor(false); //隐藏光标
if (!initMap(level, x, y)) { //初始化地图并找出初始坐标(x, y),处理可能的出错
cout << "\n\t\t读取第 " << level << " 关地图出错。" << endl;
return 0;
}
while (true) {
drawMap(level, step, total); //绘制地图
if (isWin()) { //通过本关
cout << "\n\n\t\t恭喜你过了第 " << level << " 关!";
saveAnswer(level, total, track); //保存答案
getch();
if (!initMap(++level, x, y)) { //初始化地图并找出初始坐标(x, y),处理出错
cout << "\n\t\t读取第 " << level << " 关地图出错。" << endl;
break;
}
total = step = 0; //本关步数置0
continue;
}
play = 1; //操作标志:默认为玩家正向操作1(撤消时为-1,重做时为2)
key = getch(); //获得字符输入
if (key <= 0 || key > 127) //读取方向键时,第一次读得的不是实际键值
key = getch(); //读取方向键时,第二次才读得实际键值
if (key == 75 || key == 'a' || key == 'A') { //left
key = 'l';
} else if (key == 77 || key == 'd' || key == 'D') { //right
key = 'r';
} else if (key == 72 || key == 'w' || key == 'W') { //up
key = 'u';
} else if (key == 80 || key == 's' || key == 'S') { //down
key = 'd';
} else if (key == 'z' || key == 'Z') { //撤消
if (step <= 0)
continue; //不能撤消。跳过到下一次循环
key = track[step - 1]; //获取轨迹中的当前步数的动作指令
play = -1; //操作标志:撤消时为-1
} else if (key == 'r' || key == 'R') { //重做
if (step >= total)
continue; //跳过到下一次循环
key = track[step]; //获取轨迹中第step+1步的动作指令
play = 2; //操作标志:重做时为2
} else if (key == 27) { //ESC
gotoxy(0, HT);
showCursor(true);
cout << "\t\t结束游戏(Y/N)?";
if ((key = getch()) == 'Y' || key == 'y') {
break;
}
cout << "\r ";
showCursor(false);
continue;
}
dx = dy = 0;
if (key == 'l' || key == 'L')
dx = (play > 0 ? -1 : +1);
else if (key == 'r' || key == 'R')
dx = (play > 0 ? +1 : -1);
else if (key == 'u' || key == 'U')
dy = (play > 0 ? -1 : +1);
else if (key == 'd' || key == 'D')
dy = (play > 0 ? +1 : -1);
if (dx == 0 && dy == 0)
continue; //跳过到下一次循环
x2 = x + dx;
y2 = y + dy;
if (play > 0) { //玩家操作或重做时,(x3, y3)为同一方向的下下一个位置
x3 = x2 + dx;
y3 = y2 + dy;
} else { //撤消时,(x3, y3)为反方向的位置
x3 = x - dx;
y3 = y - dy;
}
if (play > 0 && (map[y2][x2] == '$' || map[y2][x2] == '*')
&& (map[y3][x3] == '_' || map[y3][x3] == '.')) {
//小人前面是箱子或 箱子+目标点,而且箱子前面是空地或目标点,箱子可推动
map[y3][x3] = (map[y3][x3] == '_' ? '$' : '*'); //箱子新位置
map[y2][x2] = (map[y2][x2] == '$' ? '_' : '.'); //箱子原位置
key = toupper(key); //变为大写字母
}
if (map[y2][x2] == '_' || map[y2][x2] == '.') { //小人前面是空地或目标点,可移动
map[y2][x2] = (map[y2][x2] == '_' ? '@' : '+'); //新位置变为人或"人+目标点"
map[y][x] = (map[y][x] == '@' ? '_' : '.'); //原位置变为空地或目标点
if (play == -1 && key == toupper(key)) {//撤消推动
//箱子变空地,或“箱子+目标”变为目标
map[y3][x3] = (map[y3][x3] == '$' ? '_' : '.');
//空地变箱子,或目标点变为“箱子+目标点”
map[y][x] = (map[y][x] == '_' ? '$' : '*');
}
y = y + dy; //更新人的位置坐标y
x = x + dx; //更新人的位置坐标x
if (play == 1) { //玩家正向操作时
track[++step - 1] = key; //步数增1,记录当前操作(下标比步数小1)
total = step; //当前最大步数
} else if (play == 2) //重做时步数增1
step++;
else //撤消时步数减1
step--;
}
}
saveConfig(level, total, track); //保存程序配置参数
cout << "\n\t\t游戏结束,谢谢使用";
getchar();
return 0;
}
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化