简介

游戏基本框架和之前的坦克大战类似,除了游戏逻辑其他源码基本都从坦克大战那直接拷来用的,逻辑比坦克大战简单很多,没有敌人AI,控制也只有上下左右四个,几个小时做完的比较简单,主要练下逻辑,也是感觉比较有意思。

成品演示

实现思路

界面输入输出和坦克大战基本一致,而游戏逻辑输入只有上下左右四个,控制角色移动,如果角色前面为空,就移动到那个点,如果为墙,就不移动,如果为箱子,就再判断箱子前面为空还是为墙或箱子,如果为墙或箱子不移动,如果为空就移动。
地图做法也和坦克大战一样:
推箱子1.jpg
其中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 << "  ";
    }   
} 

下载

推箱子.rar

最后修改:2019 年 11 月 22 日
如果觉得我的文章对你有用,请随意赞赏