C++推箱子小游戏——个人分析与部分功能重构

一,项目来源
来源:https://www.cnblogs.com/heyu123/p/14844284.html
运行环境:win11,devC++编译器
运行结果

相关代码

点击查看代码

#include<iostream>
#include<stdlib.h>
#include<conio.h>
using namespace std;

int map[8][8]={
    {1,1,1,1,1,1,1,1},//0 空地 
    {1,0,0,0,1,0,0,1},//1 墙 
    {1,0,1,0,1,4,3,1},//3 目的地 
    {1,0,0,0,0,4,3,1},//4 箱子 
    {1,0,1,0,1,4,3,1},//5 人 
    {1,0,0,0,1,0,0,1},//7 箱子和目的地重合 
    {1,1,1,1,1,5,0,1},//8 人和目的地 
    {0,0,0,0,1,1,1,1}};

void GamePaint()
{
    //输出
    for(int i=0;i<8;i++)
    {
        for(int j=0;j<8;j++)
        {
            switch(map[i][j])
            {
                case 0:printf("  ");break;
                case 1:printf("■");break;
                case 3:printf("");break;
                case 4:printf("□");break;
                case 5:printf("");break;
                case 7:printf("");break;
                case 8:printf("");break;
            } 
        }
        cout<<endl; 
    } 
}

void GamePlay()
{
    int r,c;//人的下标
    int flag=0;
    for(r=0;r<8;r++)
     {
         for(c=0;c<8;c++)
        {if(map[r][c]==5||map[r][c]==8)
            {
             flag=1;
              break;
            }
        }
     if(flag)
     break;
     }
     cout<<"人的下标:"<<r<<" "<<c;
     char key;
     key=getch();
     switch(key)
     {
         case 'w': 
         if(map[r-1][c]==0||map[r-1][c]==3)//人的前面是空地或目的地 
         { map[r-1][c]+=5; //人来+5
           map[r][c]-=5;//人走-5 
            }  
        else if(map[r-1][c]==4||map[r-1][c]==7)//人前面是箱子或箱子加目的地
        {
              if(map[r-2][c]==0||map[r-2][c]==3)//人前面的前面是空地或目的地 
              {
                  map[r-2][c]+=4;
                  map[r-1][c]+=1;
                  map[r][c]-=5;
              } 
        }         
            
             break;
         case 's':
             if(map[r+1][c]==0||map[r+1][c]==3)//人的后面是空地或目的地 
         { map[r+1][c]+=5; //人来+5
           map[r][c]-=5;//人走-5 
            }  
        else if(map[r+1][c]==4||map[r+1][c]==7)//人后面是箱子或箱子加目的地
        {
              if(map[r+2][c]==0||map[r+2][c]==3)//人后面的前面是空地或目的地 
              {
                  map[r+2][c]+=4;
                  map[r+1][c]+=1;
                  map[r][c]-=5;
              } 
        }         
              break;
         case 'a':
             if(map[r][c-1]==0||map[r][c-1]==3)//人的左面是空地或目的地 
         { map[r][c-1]+=5; //人来+5
           map[r][c]-=5;//人走-5 
            }  
        else if(map[r][c-1]==4||map[r][c-1]==7)//人左面是箱子或箱子加目的地
        {
              if(map[r][c-2]==0||map[r][c-2]==3)//人左面的左面是空地或目的地 
              {
                  map[r][c-2]+=4;
                  map[r][c-1]+=1;
                  map[r][c]-=5;
              } 
        } 
          break;
          
         case 'd': 
             if(map[r][c+1]==0||map[r][c+1]==3)//人的右面是空地或目的地 
         { map[r][c+1]+=5; //人来+5
           map[r][c]-=5;//人走-5 
            }  
        else if(map[r][c+1]==4||map[r][c+1]==7)//人右面是箱子或箱子加目的地
        {
              if(map[r][c+2]==0||map[r][c+2]==3)//人右面的右面是空地或目的地 
              {
                  map[r][c+2]+=4;
                  map[r][c+1]+=1;
                  map[r][c]-=5;
              } 
        }      
         break;
     }
}

int main()
{   while(1)//让下面语句不断运行
    {system("cls");//清屏函数
    GamePaint();
    GamePlay();
    }
    return 0;
}

二,问题分析
主要问题
1.人物与箱子字符大小不统一(可能因不同环境而异?),导致地图错位,无法实现预期效果
2.游戏中使用的地图是固定的,缺乏多样性
3.游戏中的用户交互(如人物移动、箱子推动等)虽然通过键盘控制实现,但可能缺乏直观的视觉反馈或操作提示
4.缺少“悔棋”设计,走错一步可能导致整局重开
5.游戏中可能缺乏自动保存或手动保存进度的功能


三,程序重构
1.像素大小不一: 等特殊字符与代表空地的“ ”的小不同,导致地图错位。我们可以通过重新调试字符成功达到预期

函数GamPaint

void GamePaint() {
    for (int i = 0; i < 8; i++) {
        for (int j = 0; j < 8; j++) {
            switch (map[i][j]) {
                case 0: cout<<"  "; break;
                case 1: cout<<"■ "; break;
                case 3: cout<<" "; break;
                case 4: cout<<"□ "; break;
                case 5: cout<<" "; break;
                case 7: cout<<" "; break;
                case 8: cout<<" "; break;
            }
        }
        cout << endl;
    }
}

2.撤销上一步操作:我们需要用vector记录每一步的操作,通过一个栈来存储每一步的map状态。以便在按下按键(暂定为使用“f”键)时可以重新打印数组,从而回退到上一步的状态

  • SaveState():将当前的map状态保存到history中
  • Undo():撤销上一步操作,恢复到上一步的map状态
  • GamePlay():在每次操作后调用SaveState()保存当前状态,并在按下“f”键时调用Undo()撤销操作

map状态记录与调用相关函数

void SaveState()
{
    vector<vector<int>> currentMap(8, vector<int>(8));
    for(int i=0;i<8;i++)
    {
        for(int j=0;j<8;j++)
        {
            currentMap[i][j] = map[i][j];
        }
    }
    history.push_back(currentMap);
}

void Undo()
{
    if(history.size() > 1)
    {
        history.pop_back(); // 移除当前状态
        vector<vector<int>> previousMap = history.back(); // 获取上一步的状态
        for(int i=0;i<8;i++)
        {
            for(int j=0;j<8;j++)
            {
                map[i][j] = previousMap[i][j];
            }
        }
    }
}

void GamePlay()//游戏实现函数,调用以上两个函数已完成“悔棋”功能
{
        case 'f':     // 撤销上一步操作
             Undo();
             return;
}

3.引导性与地图单一性:通过增加游戏初始界面,基于用户选择与基本引导来优化用户体验;同时,丰富游戏地图,并在初始化期间,根据用户选择调用不同的地图

  • test1234:存储了多个地图,用户可以通过选择编号切换地图
  • StartGame():用户初始界面,给予用户选择与引导
  • CheckWin():判断游戏是否结束的函数

引导与地图相关函数

void StartGame()
{
    int a = 0;
    cout << "——欢迎游玩推箱子小游戏——" << endl; 
    cout << " (按下wasd控制,f上一步)" << endl;
    cout << "——  输入编号进入游戏  ——" << endl; 
    cout << "——      1.simple      ——" << endl; 
    cout << "——      2.castle      ——" << endl; 
    cout << "——      3.tight       ——" << endl; 
    cout << "——      4.double      ——" << endl; 
    cin >> a;
    switch (a) {
        case 1: memcpy(map, test1, sizeof(test1)); break;
        case 2: memcpy(map, test2, sizeof(test2)); break;
        case 3: memcpy(map, test3, sizeof(test3)); break;
        case 4: memcpy(map, test4, sizeof(test4)); break;
        default: memcpy(map, test1, sizeof(test1)); break;
    }
}


bool CheckWin()
{
    for(int i = 0; i < 8; i++)
    {
        for(int j = 0; j < 8; j++)
        {
            if(map[i][j] == 3) // 如果还有目的地
            {
                return false;
            }
        }
    }
    return true;
}

四,二次开发项目测试

全部代码

#include<iostream>
#include<stdlib.h>
#include<conio.h>
#include<vector>
#include<cstring> //  memcpy函数
#include<windows.h> // Sleep函数
using namespace std;

int map[8][8]; // 当前地图

int test4[8][8] = { // double 
    {1,1,1,1,1,1,1,1},
    {1,0,0,0,1,0,0,1},
    {1,0,1,0,1,4,3,1},
    {1,0,0,0,0,4,3,1},
    {1,0,1,0,1,4,3,1},
    {1,0,0,0,1,0,0,1},
    {1,1,1,1,1,5,0,1},
    {0,0,0,0,1,1,1,1}
};    

int test2[8][8] = { // castle
    {0,0,1,1,1,1,0,0},
    {0,0,1,3,3,1,0,0},
    {0,1,1,0,3,1,1,0},
    {0,1,0,0,4,3,1,0},
    {1,1,0,4,0,0,1,1},
    {1,0,0,1,4,4,0,1},
    {1,0,0,5,0,0,0,1},
    {1,1,1,1,1,1,1,1}
};

int test1[8][8] = { // simple
    {1,1,1,1,1,1,1,1},
    {1,0,4,0,0,0,3,1},
    {1,0,0,0,0,0,0,1},
    {1,0,3,0,4,0,0,1},
    {1,0,0,0,4,0,0,1},
    {1,0,3,0,0,0,0,1},
    {1,0,0,0,0,0,5,1},
    {1,1,1,1,1,1,1,1}
};

int test3[8][8] = { // tight
    {0,0,1,1,1,1,0,0},
    {0,0,1,5,0,1,0,0},
    {1,1,1,0,4,1,0,0},
    {1,0,4,4,3,1,1,0},
    {1,0,4,3,3,0,1,0},
    {1,0,4,3,3,0,1,0},
    {1,0,0,0,1,1,1,0},
    {1,1,1,1,1,0,0,0}
};

vector<vector<vector<int> > > history; // 用于存储每一步的map状态

void GamePaint()
{
    // 输出
    for(int i = 0; i < 8; i++)
    {
        for(int j = 0; j < 8; j++)
        {
            switch(map[i][j])
            {
                case 0: cout << "  "; break;
                case 1: cout << "■ "; break;
                case 3: cout << " "; break;
                case 4: cout << "□ "; break;
                case 5: cout << " "; break;
                case 7: cout << " "; break;
                case 8: cout << " "; break;
            } 
        }
        cout << endl; 
    } 
}

void SaveState()
{
    vector<vector<int> > currentMap(8, vector<int>(8));
    for(int i = 0; i < 8; i++)
    {
        for(int j = 0; j < 8; j++)
        {
            currentMap[i][j] = map[i][j];
        }
    }
    history.push_back(currentMap);
}

void Undo()
{
    if(history.size() > 1)
    {
        history.pop_back(); // 移除当前状态
        vector<vector<int> > previousMap = history.back(); // 获取上一步的状态
        for(int i = 0; i < 8; i++)
        {
            for(int j = 0; j < 8; j++)
            {
                map[i][j] = previousMap[i][j];
            }
        }
    }
}

bool CheckWin()
{
    for(int i = 0; i < 8; i++)
    {
        for(int j = 0; j < 8; j++)
        {
            if(map[i][j] == 3) // 如果还有目的地
            {
                return false;
            }
        }
    }
    return true;
}

void GamePlay()
{
    int r, c; // 人的下标
    int flag = 0;
    for(r = 0; r < 8; r++)
    {
        for(c = 0; c < 8; c++)
        {
            if(map[r][c] == 5 || map[r][c] == 8)
            {
                flag = 1;
                break;
            }
        }
        if(flag)
            break;
    }
    cout << "人的下标:" << r << " " << c;
    char key;
    key = getch();
    switch(key)
    {
        case 'w': 
            if(map[r-1][c] == 0 || map[r-1][c] == 3)  
            { 
                map[r-1][c] += 5; // 人来+5
                map[r][c] -= 5; // 人走-5 
            }  
            else if(map[r-1][c] == 4 || map[r-1][c] == 7) 
            {
                if(map[r-2][c] == 0 || map[r-2][c] == 3)  
                {
                    map[r-2][c] += 4;
                    map[r-1][c] += 1;
                    map[r][c] -= 5;
                } 
            }         
            break;
        case 's':
            if(map[r+1][c] == 0 || map[r+1][c] == 3) 
            { 
                map[r+1][c] += 5; // 人来+5
                map[r][c] -= 5; // 人走-5 
            }  
            else if(map[r+1][c] == 4 || map[r+1][c] == 7) 
            {
                if(map[r+2][c] == 0 || map[r+2][c] == 3)  
                {
                    map[r+2][c] += 4;
                    map[r+1][c] += 1;
                    map[r][c] -= 5;
                } 
            }         
            break;
        case 'a':
            if(map[r][c-1] == 0 || map[r][c-1] == 3)  
            { 
                map[r][c-1] += 5; // 人来+5
                map[r][c] -= 5; // 人走-5 
            }  
            else if(map[r][c-1] == 4 || map[r][c-1] == 7) 
            {
                if(map[r][c-2] == 0 || map[r][c-2] == 3)  
                {
                    map[r][c-2] += 4;
                    map[r][c-1] += 1;
                    map[r][c] -= 5;
                } 
            } 
            break;
        case 'd': 
            if(map[r][c+1] == 0 || map[r][c+1] == 3) 
            { 
                map[r][c+1] += 5; // 人来+5
                map[r][c] -= 5; // 人走-5 
            }  
            else if(map[r][c+1] == 4 || map[r][c+1] == 7) 
            {
                if(map[r][c+2] == 0 || map[r][c+2] == 3)  
                {
                    map[r][c+2] += 4;
                    map[r][c+1] += 1;
                    map[r][c] -= 5;
                } 
            }      
            break;
        case 'f': // 撤销上一步操作
            Undo();
            return;
    }
    SaveState(); // 保存当前状态
}

void StartGame()
{
    int a = 0;
    cout << "——欢迎游玩推箱子小游戏——" << endl; 
    cout << " (按下wasd控制,f上一步)" << endl;
    cout << "——  输入编号进入游戏  ——" << endl; 
    cout << "——      1.simple      ——" << endl; 
    cout << "——      2.castle      ——" << endl; 
    cout << "——      3.tight       ——" << endl; 
    cout << "——      4.double      ——" << endl; 
    cin >> a;
    switch (a) {
        case 1: memcpy(map, test1, sizeof(test1)); break;
        case 2: memcpy(map, test2, sizeof(test2)); break;
        case 3: memcpy(map, test3, sizeof(test3)); break;
        case 4: memcpy(map, test4, sizeof(test4)); break;
        default: memcpy(map, test1, sizeof(test1)); break;//输错默认进行图1
    }
}

int main()
{   
    StartGame(); // 开始界面 
    SaveState(); // 保存初始状态
    while(1) // 让下面语句不断运行
    {
        system("cls"); 
        GamePaint();
        if(CheckWin())
        {
            cout << "恭喜你,游戏成功!!!!" << endl;
            break;
        }
        GamePlay();
    }
    return 0;
}

项目截图
图片[1]-C++推箱子小游戏——个人分析与部分功能重构-牛翰网
图片[2]-C++推箱子小游戏——个人分析与部分功能重构-牛翰网


五,项目总结与反思

  • 使用一个容器(vector<vector<vector >>)来存储每一步的 map 状态,且map状态保存与恢复需要确保每次保存的状态是独立的,避免引用问题;
  • 通过这次二次开发,我学到了如何设计“状态管理”、“目标检测”等功能。这些设计模式可以推广到其他应用程序中,具有很高的通用性。通过尝试逆向开发,我也以用户的视角注意到了更多作为开发者容易忽视的部分,逆向软件工程的本质是从需求出发,分析问题,设计解决方案,从而不断地优化和改进。

来源链接:https://www.cnblogs.com/123456789syf/p/18737535

© 版权声明
THE END
支持一下吧
点赞14 分享
评论 抢沙发
头像
请文明发言!
提交
头像

昵称

取消
昵称表情代码快捷回复

    暂无评论内容