纯C++实现字符版推箱子
2023-02-18 16:42:44 时间
简介
游戏基本框架和之前的坦克大战类似,除了游戏逻辑其他源码基本都从坦克大战那直接拷来用的,逻辑比坦克大战简单很多,没有敌人AI,控制也只有上下左右四个,几个小时做完的比较简单,主要练下逻辑,也是感觉比较有意思。
成品演示
https://hctra.cn/usr/uploads/2019/11/1169940709.mp4
实现思路
界面输入输出和坦克大战基本一致,而游戏逻辑输入只有上下左右四个,控制角色移动,如果角色前面为空,就移动到那个点,如果为墙,就不移动,如果为箱子,就再判断箱子前面为空还是为墙或箱子,如果为墙或箱子不移动,如果为空就移动。 地图做法也和坦克大战一样:
其中0表示空,1表示墙,2表示箱子,9表示出生点,每次程序启动时,先在初始化init()中把地图数据读入vector中,并把箱子要推到的目标点记入进goal中。记录goal一是为了作为过关检测条件,二是因为,如果箱子推在了目标点上又移开了,那目标点就无法复原了,所以目标点需要另外存储而不能存在二维数组的地图中。 Map的声明如下:
struct Map{ //地图
int x; //地图宽
int y; //地图长
char name[30];
char map_[200][200]; //地图,下标从1开始
int boxTotal = 0; //箱子总数
Point birth; //出生点
vector<Point> goal; //所有目标点
};
每次玩家移动了,就做一个判断,检测是否所有目标点上都为2(箱子),如果都为箱子则进入下一关,否则继续。
完整源码
字符版推箱子
#include<iostream>
#include<cstring>
#include<cstdio>
#include<vector>
#include<conio.h>
#include<ctype.h>
#include<windows.h>
#define ZERO SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), {0,0})
#define POSAT(a,b) SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), {(a),(b)})
using namespace std;
class Point{
public:
int x,y;
Point() = default;
Point(int x_,int y_){x = x_;y = y_;}
};
class Prt{
private:
int length = 0;
int Len = 60; //边框宽
int padding = 2; //上下内边距
int margin = 2; //外边距
int win = 1; //如果是win10,就改为1,如果是win7,就改为2
public:
void setPadding(int padding_){padding = padding_;}
void setMargin(int margin_){margin = margin_;}
int getPadding(){return padding;}
int getMargin(){return margin;}
void mar(); //输出左右外边距
void pLine(char str[],int len_);//输出标题
void print(char str[]); //输出一行
void borP(char str[]); //按自动套边框的格式输出,如果框内要输出多行,就用&符连接,如果要输出分割线,就输入~符替代
void borPLogo(char str[]); //带logo的套边框格式输出
};
class Player{
private:
Point point;
int dir;
char shape[3];
public:
void move(int dir_);
void turnDir(int dir_); //根据脸朝方向修改样式
int getX(){return point.x;}
int getY(){return point.y;}
int setX(int x_){point.x = x_;}
int setY(int y_){point.y = y_;}
char* getShape(){return shape;}
};
struct Map{ //地图
int x; //地图宽
int y; //地图长
char name[30];
char map_[200][200]; //地图,下标从1开始
int boxTotal = 0; //箱子总数
Point birth; //出生点
vector<Point> goal; //所有目标点
};
struct Game{
vector<Map> maps; //标准地图库
Map map; //该局游戏地图
Map tmpMap; //待输出地图
int num = 0; //关卡
bool win = false;
void refreshGame();
bool isWin();
bool gaming = false;
};
Prt p;
Game g; //游戏对象
Player player;
int mTime = 1200; //提示滞留时间
void init();
void startGame();
void control();
void help();
void ex();
void dosClear(int x,int y,int n,int choose);
void printGame();
int getNum(int min,int max,int thisNum);
int main(){
init();
int choose = 1;
char key;
short x = 24+p.getMargin()*2,y = 4+p.getMargin()+p.getPadding(); //对应窗口里的列和行
bool back = false; //从其他函数回来
p.borP("&~&& 开始游戏 & 设置 & 帮助 & 退出 ");
while(1){
dosClear(x,y,4,choose); //清除界面中的指向箭头
switch(choose){ //移动光标到需要打印箭头的位置
case 1:POSAT(x,y);break;
case 2:POSAT(x,y+1);break;
case 3:POSAT(x,y+2);break;
case 4:POSAT(x,y+3);break;
}
cout << "->";
if(kbhit()){ //如果有键盘键入
key = getch();
switch(key){
case 'w':choose = choose==1?4:choose-1;break;
case 's':choose = choose==4?1:choose+1;break;
case 13: //Enter
switch(choose){
case 1:startGame();break;
case 2:control();break;
case 3:help();break;
case 4:ex();break;
}
system("cls");
x = 24+p.getMargin()*2,y = 4+p.getMargin()+p.getPadding();
p.borP("&~&& 开始游戏 & 设置 & 帮助 & 退出 ");
break;
}
if(key<0||key>128){
key = getch();
switch(key){
case 72:choose = choose==1?4:choose-1;break;
case 80:choose = choose==4?1:choose+1;break;
}
}
}
ZERO; //光标置为0,0
}
return 0;
}
void startGame(){
system("cls");
int key;
if(!g.gaming)g.refreshGame();
while(1){
ZERO;
printGame();
key = getch();
if(key==27)return;
else if(key=='r'){g.num--;g.refreshGame();}
if(key<0||key>128){
key = getch();
switch(key){
case 75:player.move(1);break;
case 72:player.move(2);break;
case 77:player.move(3);break;
case 80:player.move(4);break;
}
}
if(g.isWin()){
g.refreshGame();
}
}
}
void printGame(){
char pMap[200][200][3];
for(int i = 1;i <= g.map.y;i++){
for(int j = 1;j <= g.map.x;j++){
switch(g.map.map_[i][j]){
case '0':strcpy(pMap[i][j]," ");break;
case '1':strcpy(pMap[i][j],"█");break;
case '2':strcpy(pMap[i][j],"▓");break;
}
}
cout << endl;
}
for(Point p:g.map.goal)
if(strcmp(pMap[p.y][p.x]," ")==0)strcpy(pMap[p.y][p.x],"χ");
strcpy(pMap[player.getY()][player.getX()],player.getShape());
//打印
for(int i = 1;i <= g.map.y;i++){
for(int j = 1;j <= g.map.x;j++){
cout << pMap[i][j];
}
cout << endl;
}
}
void Game::refreshGame(){ //第一次游戏或切换关卡时调用此函数
g.map = g.maps[++g.num-1]; //根据关卡切换游戏当前地图
g.win = false;
g.gaming = true;
player.setX(g.map.birth.x);
player.setY(g.map.birth.y);
player.turnDir(2);
system("cls");
p.borP(g.map.name); //打印关卡数
Sleep(mTime);
system("cls");
}
bool Game::isWin(){
//如果目标点上都有箱子,则过关
bool isWin = true;
for(Point p:map.goal){
if(g.map.map_[p.y][p.x]!='2'){
isWin = false;break;
}
}
return isWin;
}
void control(){
system("cls");
int choose = 1;
int key;
int num;
while(1){
switch(choose){
case 1:p.borP(" 设置&~&&->外边距& 内边距");break;
case 2:p.borP(" 设置&~&& 外边距&->内边距");break;
}
if(kbhit()){ //如果有键盘键入
key = getch();
switch(key){
case 'w':choose = choose==1?2:choose-1;break;
case 's':choose = choose==2?1:choose+1;break;
case 27:return;
case 13: //Enter
switch(choose){
case 1:p.pLine("修改外边距",60); num = getNum(0,4,p.getMargin());p.setMargin(num);break;
case 2:p.pLine("修改内边距",60); num = getNum(0,4,p.getPadding());p.setPadding(num);break;
}
HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
SMALL_RECT rc = {0,0, 100+2+p.getMargin()*2*2, 34+p.getMargin()*2+p.getPadding()*2}; // 重置窗口位置和大小
SetConsoleWindowInfo(hOut,true ,&rc);
system("cls");
p.borP(" 修改成功!");
Sleep(mTime);
system("cls");
break;
}
if(key<0||key>128){
key = getch();
switch(key){
case 72:choose = choose==1?2:choose-1;break;
case 80:choose = choose==2?1:choose+1;break;
}
}
}
ZERO;
}
}
int getNum(int min,int max,int thisNum){
int num;
printf("请输入%d-%d之间的整数(当前为:%d):\n",min,max,thisNum);
scanf("%d",&num);
while(num<min||num>max){
printf("输入不合法,请重新输入:\n");
scanf("%d",&num);
}
system("cls");
return num;
}
void help(){
int key;
system("cls");
p.setMargin(11);
p.borP("&操作指南&~&&上下选择: W S或↑↓&确认选择:Enter &返回上层: ESC &角色移动:←↑→↓&~&可随时在帮助选项中查看操作方式\
&请按Enter键继续");
p.setMargin(2);
getch();
}
void ex(){
system("cls");
p.borP("正在退出,请稍后...");
Sleep(mTime);
system("cls");
exit(0);
}
void init(){
//隐藏光标
CONSOLE_CURSOR_INFO cursor_info = {1, 0};
SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &cursor_info);
SetConsoleTitle("字符版推箱子"); //控制台标题
HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
SMALL_RECT rc = {0,0, 110, 40}; // 重置窗口位置和大小
SetConsoleWindowInfo(hOut,true ,&rc);
//读取地图
Map *map;
FILE *fp;
char tmp;
if((fp=fopen("data/maps.txt","r"))!=NULL){
while(!feof(fp)){ //只要文件还有内容没读完,就继续读取
map = new Map;
fscanf(fp,"%s%d %d %d\n",map->name,&map->y,&map->x,&map->boxTotal);
for(int i = 1;i <= map->y;i++){
for(int j = 1;j <= map->x;j++){
if(feof(fp))break;
fscanf(fp,"%c",&map->map_[i][j]);
if(map->map_[i][j]=='3'){
map->goal.push_back({j,i});
map->map_[i][j] = '0';
}
else if(map->map_[i][j]=='9'){
map->birth.x = j;map->birth.y = i;
map->map_[i][j] = '0';
}
}
if(feof(fp))break;
fscanf(fp,"%c",&tmp);
}
g.maps.push_back(*map); //导入游戏对象的标准地图库
}
}
else{
cerr << "error!maps.txt打开失败!" << endl;
}
fclose(fp);
delete(map);
char key;
p.setMargin(11);
p.borP("欢迎试玩字符版推箱子&~&&上下选择: W S或↑↓&确认选择:Enter &返回上层: ESC &角色移动:←↑→↓&&~&可随时在帮助选项中查看操作方式\
&请按Enter键继续");
p.setMargin(2);
while(1){ //按回车方可继续
key = getch();
if(key==13)break;
}
system("cls");
}
//输出标题
void Prt::pLine(char str[],int len_){
int i;
int s_len = 0;
for(i = 0;str[i];i++)s_len++;
for(i = 0;i < (len_-s_len)/2;i++)printf("*");
printf("%s",str);
for(i = 0;i < (len_-s_len)/2;i++)printf("*");
if((len_-s_len)%2)printf("*"); //如果中间内容不是双数长,末尾就补一个*号
printf("\n");
}
//输出一行
void Prt::print(char str[]){
int i,s_len = 0;
for(i = 0;str[i];i++)s_len++;
mar(); //每行先输出外边距
printf("│");
for(i = 0;i < (Len-s_len)/2;i++)printf(" ");
printf("%s",str);
s_len = s_len%2?s_len-1:s_len;
for(i = 0;i < (Len-s_len)/2;i++)printf(" ");
printf("│\n");
}
//按自动套边框的格式输出,如果框内要输出多行,就用&符连接
void Prt::borP(char str[]){
int i,s_len = 0;
int begin = 0; //子串从str的哪个元素下标开始复制
bool haveLine = false;
char substr[300];
substr[0] = '\0'; //初始化
for(i = 0;i < margin;i++)printf("\n");
mar();printf("┌");for(i = 0;i < Len/win;i++)printf("─");printf("┐\n");
for(i = 0;i < padding;i++)print("");
for(i = 0;1;i++){
if(str[i]=='&'){substr[(haveLine?Len*2/win:i-begin)] = '\0';begin = i+1;print(substr);substr[0] = '\0';haveLine = false;}
else if(str[i]=='~'){
for(int j = 0;j < Len;j++){
strcat(substr,"─");
}
haveLine = true;
}
else if(str[i] == '\0'){substr[i-begin] = '\0';print(substr);break;}
else
substr[i-begin] = str[i];
}
for(i = 0;i < padding;i++)print("");
mar();printf("└");for(i = 0;i < Len/win;i++)printf("─");printf("┘\n");
for(i = 0;i < margin;i++)printf("\n");
this->Len = 60;
}
void Prt::mar(){
int i = 0;
for(;i < margin;i++)printf(" ");
}
//player类
void Player::turnDir(int dir_){ //根据脸朝方向修改样式
dir = dir_;
switch(dir){
case 1:strcpy(shape,"←");break;
case 2:strcpy(shape,"↑");break;
case 3:strcpy(shape,"→");break;
case 4:strcpy(shape,"↓");break;
}
}
void Player::move(int dir_){
turnDir(dir_);
Point change;
bool isNull = true; //脸朝方向前面没有障碍物
switch(dir){
case 1:change.x = point.x-1;change.y = point.y;break;
case 2:change.x = point.x;change.y = point.y-1;break;
case 3:change.x = point.x+1;change.y = point.y;break;
case 4:change.x = point.x;change.y = point.y+1;break;
}
if(g.map.map_[change.y][change.x]=='0'){ //地图上这个点是空地或箱子
player.setX(change.x);
player.setY(change.y);
}
else if(g.map.map_[change.y][change.x]=='2'){
Point box = change,boxchange;
switch(dir){
case 1:boxchange.x = box.x-1;boxchange.y = box.y;break;
case 2:boxchange.x = box.x;boxchange.y = box.y-1;break;
case 3:boxchange.x = box.x+1;boxchange.y = box.y;break;
case 4:boxchange.x = box.x;boxchange.y = box.y+1;break;
}
if(g.map.map_[boxchange.y][boxchange.x]=='0'){ //可以推动箱子
player.setX(change.x);
player.setY(change.y);
g.map.map_[box.y][box.x] = '0';
g.map.map_[boxchange.y][boxchange.x] = '2';
}
}
}
void dosClear(int x,int y,int n,int choose){
for(int i = 0;i < n;i++){
if(i+1==choose)continue;
POSAT(x,y+i);
cout << " ";
}
}
下载
相关文章
- Linux 安装配置 tftp 服务器
- linux 安装配置NFS服务器
- MongoDB经典故障系列六:CPU利用率太高怎么办?
- 选择困难症必看!云服务器如何选择操作系统,Windows和Linux哪个更好?
- KubeFlow-Pipeline及Argo实现原理速析
- C++ 中的复数
- Linux中那些没用但好玩的命令
- Go语言编程设计学习Day1:helloworld 变量 常量
- C++ Primer Plus习题及答案-第十五章
- 使用Go开源的一款性能监控软件
- 你应该知道的17个Golang包
- SSH Google Authenticator
- Oneinstack Nginx 反代 Google
- 嵌入式:ARM指令集分类及编码
- Linux系统中JAVA创建文件后权限不足的问题,无法设置权限的问题
- git 强制回退到指定版本
- git 切换远程仓库地址
- git 提示error setting certificate verify locations 解决方案
- Linux 查看占用内存前10的命令
- git分支使用规范