数据结构课程设计-推箱子

1. 题目描述

推箱子的游戏规则是扮演工人的玩家,以“推”的方式推动箱子。玩家可以在没有阻碍物(如墙壁等的阻碍物)的情况下,向上、下、左、右的方向移动,将箱子移动到指定位置,当箱子都处于指定位置上时,即可过关。

地图上有若干个箱子,当玩家移动箱子时,需要满足以下条件:

⑴ 箱子只能以“推”的方式移动,不能以“拉”的方式移动,推到墙壁的箱子,玩家就不可以背对墙壁,把箱子拉回到空处。但如果玩家推至墙壁后,垂直墙壁的两侧没有阻碍物,则玩家可以朝这两个不同的方向推动箱子。

⑵ 一旦箱子被移动到角落,玩家没有任何方法再移动这个箱子。

⑶ 玩家不可同时推动两个或以上的箱子。假设玩家面前有一个箱子,箱子的正前方又有一个箱子,则这两个箱子是不能被推动的。

2. 设计要求

⑴ 在内存中,设计数据结构存储游戏数据。

⑵ 满足推箱子游戏的游戏规则。

3. 数据结构设计

    在游戏运行时,需要将关卡信息加载到内存中,供玩家游玩,为了记录关卡的内容,需要一个数据结构与之对应,可以设计以下结构来存储关卡信息。

    在这里我们人为规定,关卡中用‘#’来表示墙、用’.’表示空地、用’P’表示玩家、用’B’表示箱子、用’G’表示目标位置。

    struct LevelType

    {

       int width;               // 关卡宽度

       int height;              // 关卡高度

       char map[20][21];          // 关卡内容,宽和高最高为20

       }

       其中,因为关卡是一个二维平面,对于平面上的任一点可以通过一个二元组来表示其位置,可以设计以下结构来表示一个点。同时又因为要对点进行判断相等或不等,所以需要用到运算符重载。

    struct PointType

    {

        int x;

       int y;

       bool operator==(const PointType& p)

       {

          if(p.x==this->x&&p.y==this->y)

             return true;

          return false;

       }

       bool operator!=(const PointType& p)

       {

           return !(*this==p);

       }

    }  

4. 算法设计

对于该款游戏,我们需要以下函数提供基本功能:

LoadLevelToGame:载入关卡数据到游戏中的函数

Update:负责绘制更新游戏的画面函数

PlayerMove:处理玩家移动的函数

CheckPlayerWin:判断玩家是否取得胜利的函数

较为重要的函数PlayerMove负责处理玩家的移动,算法设计如下:

其中的playerPos为前文定义的PointType类型,表示玩家的位置。

PlayerMove:处理玩家的移动

输入:玩家的移动方向,本例以向下移动举例

输出:无

   1. 如果玩家的前进位置为空地:

          1.1. 玩家的位置向下移动一格;

          1.2. playerPos.x++,算法结束;

   2. 如果玩家的前进位置为墙:忽略本次输入,结束算法;

   3. 如果玩家的前进位置是箱子:

          3.1. 判断箱子的的前进位置是墙或箱子:忽略本次输入,结束算法;

          3.2. 判断箱子的前进位置是否为空地:

                 3.2.1. 箱子的位置向下移动一格;

                 3.2.2. 玩家的位置向下移动一格;

                 3.3.3. playerPos.x++,结束算法;

       其中CheckPlayerWin函数判断玩家是否胜利,也就是判断所有目标位置上方是否均为箱子,算法设计如下:

其中status的类型是char类型的二维数组,表示当前关卡的每一格状态。

其中level的类型是前文定义的LevelType类型,表示当前关卡的关卡信息。

CheckPlayerWin:判断玩家是否胜利

输入:无

输出:玩家是否胜利

   1. isWin = true;

   2. 逐行逐列判断,行号为i,i从0至level.height-1,列号为j,j从0至level.width-1:

          2.1. 若status [i][j]为目标位置并且上方无箱子:isWin=False;

   3. 如果isWin==true:输出玩家胜利;

   4. 如果isWin==false:输出玩家还未取得胜利;

   3.2. 返回选择关卡界面,算法结束;

4. 玩家当前未获得胜利,算法结束;

5. 测试样例

      

其中‘#’是墙、用’.’是空地、用’P’是玩家、用’B’是箱子、用’G’是目标位置。

5 5

#####

#P#.#

#...#

#.BG#

#####

6 9

..####...

###..####

#.....B.#

#.#..#B.#

#.G.G#P.#

#########

6. 代码实现

// 编译器版本 g++8.1.0

// 编译参数 -Weffc++ -Wextra -Wall -std=c++11

#include <cstdio>

#include <cstdlib>

#include <conio.h>

/**

 * 常量的定义

**/

// 关卡最大高度和宽度

const int MAXHEIGHT = 20;

const int MAXWIDTH = 20;

// 代表位移的四个常量(对应上下左右)

const int DX[4]= {-1,1,0,0};

const int DY[4]= {0,0,-1,1};

/**

 * 数据类型定义

**/

// 坐标数据类型

struct PointType

{

    int x;

    int y;

    bool operator==(const PointType& p)

    {

        if(p.x==this->x&&p.y==this->y)

            return true;

        return false;

    }

    bool operator!=(const PointType& p)

    {

        return !(*this==p);

    }

};

// 关卡数据类型

struct LevelType

{

    int height; // 关卡的高度,应当小于MAXHEIGHT

    int width;  // 关卡的宽度,应当小于MAXWIDTH

    char levelMap[MAXHEIGHT][MAXWIDTH];

    // 关卡每格数据,'P'代表玩家、'B'代表箱子,'G'代表目标点,'#'代表墙,'.'代表空地

};

/**

 * 全局变量定义

**/

LevelType Glevel[2]={

    {5,5,{

    "#####",

    "#P#.#",

    "#...#",

    "#.BG#",

    "#####"}},

    {6,9,{

    "..####...",

    "###..####",

    "#.....B.#",

    "#.#..#B.#",

    "#.G.G#P.#",

    "#########"}},

};  // 关卡的定义

int GlevelSelect;       // 当前正在游玩的关卡

LevelType GnowLevel;    // 游戏正在运行中的关卡状态

PointType Gplayer;      // 玩家的位置信息

/**

 * 载入关卡数据至游戏中并初始化玩家位置

**/

void LoadLevelToGame(LevelType & level)

{

    GnowLevel = level;

    for(int i=0; i<GnowLevel.height; i++)

        for(int j=0; j<GnowLevel.width; j++)

            if(GnowLevel.levelMap[i][j]=='P')

                Gplayer= {i,j};

    return;

}

/**

 * 更新游戏画面

**/

void Update()

{

    // 清空显示画面

    system("cls");

    // 绘制提示信息

    printf("w:Up s:Down a:Left d:Right\n");

    printf("r:Restart q:Quit\n\n");

    // 绘制关卡信息

    for(int i=0; i<GnowLevel.height; i++)

        printf("%s\n",GnowLevel.levelMap[i]);

    return;

}

/**

 * 选择关卡

**/

void LevelSelect()

{

    // 绘制提示信息

    system("cls");

    printf("Please choose level.\n");

    printf("0=Easy 1=Hard\n\n");

    printf("Level:");

    scanf("%d",&GlevelSelect);

    // 载入关卡数据

    LoadLevelToGame(Glevel[GlevelSelect]);

    // 更新画面

    Update();

    return;

}

/**

 * 更新游戏胜利画面

**/

void UpdateWin()

{

    // 清空显示画面

    system("cls");

    // 绘制提示信息

    printf("YOU WIN!!!\n");

    // 等待玩家确认

    system("pause");

    return;

}

/**

 * 游戏初始化

**/

void InitGame()

{

    // 初始化显示画面高度和宽度

    system("mode con cols=50 lines=20");

    return;

}

/**

 * 玩家移动函数

 * type:移动类型,与上文位移常量对应,0=上,1=下,2=左,3=右

**/

void PlayerMove(int type)

{

    // 玩家下一格的前进方向

    PointType toPoint = {Gplayer.x+DX[type],Gplayer.y+DY[type]};

    // 玩家当前格位置与触碰到箱子的位置,便于恢复地图状态

    PointType beforePlayer = Gplayer;

    switch(GnowLevel.levelMap[toPoint.x][toPoint.y])

    {

    case '.':

    case 'G':

        // 如果下一格是空地或目标点,直接前进

        Gplayer=toPoint;

        break;

    case '#':

        // 如果下一格是墙,忽略输入

        break;

    case 'B':

        // 如果下一格是箱子,对箱子在进行一次判断

        PointType boxToPoint = {toPoint.x+DX[type],toPoint.y+DY[type]};

        switch(GnowLevel.levelMap[boxToPoint.x][boxToPoint.y])

        {

        case '.':

        case 'G':

            // 如果下一格是空地或目标点,直接前进

            GnowLevel.levelMap[boxToPoint.x][boxToPoint.y]='B';

            // 玩家覆盖该位置

            Gplayer=toPoint;

            break;

            // 其他状况忽略输入

        }

        break;

    }

    // 更新地图上玩家位置

    GnowLevel.levelMap[Gplayer.x][Gplayer.y]='P';

    // 恢复玩家之前踩着的格子状态,仅存在目标点需要处理

    if(beforePlayer!=Gplayer)

    {

        if(Glevel[GlevelSelect].levelMap[beforePlayer.x][beforePlayer.y]=='G')

            GnowLevel.levelMap[beforePlayer.x][beforePlayer.y]='G';

        else

            GnowLevel.levelMap[beforePlayer.x][beforePlayer.y]='.';

    }

    return;

}

/**

 * 获取用户输入

**/

void GetPlayerInput()

{

    int input=getch();

    switch(input)

    {

    case 'w':

        PlayerMove(0);

        break;

    case 's':

        PlayerMove(1);

        break;

    case 'a':

        PlayerMove(2);

        break;

    case 'd':

        PlayerMove(3);

        break;

    case 'r':

        LoadLevelToGame(Glevel[GlevelSelect]);

        break;

    case 'q':

        exit(0);

        break;

    default:

        break;

    }

    return;

}

/**

 * 判断玩家胜利

**/

bool CheckPlayerWin()

{

    bool isWin=true;

    for(int i=0; i<GnowLevel.height; i++)

        for(int j=0; j<GnowLevel.width; j++)

            if(Glevel[GlevelSelect].levelMap[i][j]=='G'&&GnowLevel.levelMap[i][j]!='B')

                isWin=false;

    return isWin;

}

/**

 * 程序入口

**/

int main()

{

    // 初始化游戏

    InitGame();

    while(true)

    {

        // 加载关卡界面

        LevelSelect();

        // 游戏开始

        while(true)

        {

            // 获取用户输入并执行相关功能函数

            GetPlayerInput();

            // 更新画面

            Update();

            // 判断玩家是否胜利

            if(CheckPlayerWin())

            {

                // 显示胜利画面

                UpdateWin();

                // 返回到关卡选择界面

                break;

            }

        }

    }

    return 0;

}

7. 思考题

⑴ 游戏的所有关卡信息放置于内存当中,造成了不必要的资源浪费。如何通过文件读取的方式,将关卡数据保存在外存中,每当玩家游玩时从外存中读取关卡数据至内存,降低内存资源的占用?

⑵ 在玩家游玩推箱子游戏时,可能会出现误操作或者是想反悔的情况,重新游玩关卡会导致不必要的时间浪费。需要使用何种数据结构来实现“上一步”的操作?又如何实现该功能呢?

       ⑶ 在目前的游戏程序中,需要玩家自己意识到该局游戏目前的状态无解,需要重新开始游戏。请思考并设计算法,使得本局游戏无解的时候主动提醒玩家游戏失败,重新开始游戏。

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值