【C++项目实现】推箱子(含数据库实现)

本文涉及的产品
云数据库 RDS MySQL Serverless,0.5-2RCU 50GB
简介: 【C++项目实现】推箱子(含数据库实现)

项目实现 - 推箱子

一、项目需求


规则如下:


箱子只能推动而不能拉动。

如果箱子前一格是地板或箱子目的地,则可以推动一个箱子往前走一格,如果箱子已经在箱子目的地则不能再推动。

推箱子的小人可以从箱子目的地上经过。

小人可以将目的地上的箱子推开。

注意不要把箱子推到死角上,不然就无法再推动它了。

所有箱子都成功推到箱子目的地,游戏结束,过关成功!




5cf435076c7047ee9d7af00e62aa47fe.png


二、项目实现

地图初始化


这里的 x y 坐标和数学中的定义不太一样,这里的 x 坐标往下是增大,y 坐标往右是增大。


另外,下面代码中为了方便理解,暂时没有引入数据库,所以地图是用二维数组在程序中保存起来,后续用到数据库后会从数据库中读取地图信息(代码和数据库那里可能会比较混,但是在最后全部代码中只会用到数据库版本的代码)。

#include<graphics.h>
#include<iostream>
#include<stdlib.h>
#include<string>
using namespace std
#define RATIO 61  //地图中每个小方块的大小
#define SCREEN_WIDTH 960  //背景宽度
#define SCREEN_HEIGHT 768 //背景高度
#define LINE 9      //地图行数
#define COLUMN 12   //地图列数
#define START_X 100   //地图开始的横坐标
#define START_Y 150   //地图开始的纵坐标
enum _PROPS {
  WALL, //墙:0
  FLOOR,  //地板:1
  BOX_DES,//箱子目的地:2
  MAN,  //小人:3
  BOX,  //箱子:4
  HIT,  //箱子命中目标:5
  ALL   //道具数量:6
};
struct _POS {
  int x;  //小人所在的二维数组的行
  int y;  //小人所在的二维数组的列
};
IMAGE images[ALL];  //存储道具图标
struct _POS man;
/**************************************
* 游戏地图
* 墙:0   地板:1   箱子目的地:2
* 小人:3    箱子:4   箱子命中目标:5
**************************************/
int map[LINE][COLUMN] = {
  {0,0,0,0,0,0,0,0,0,0,0,0},
  {0,1,0,1,1,1,1,1,1,1,0,0},
  {0,1,4,1,0,2,1,0,2,1,0,0},
  {0,1,0,1,0,1,0,0,1,1,1,0},
  {0,1,0,2,0,1,1,4,1,1,1,0},
  {0,1,1,1,0,3,1,1,1,4,1,0},
  {0,1,2,1,1,4,1,1,1,1,1,0},
  {0,1,0,0,1,0,1,1,0,0,1,0},
  {0,0,0,0,0,0,0,0,0,0,0,0},
};
int main()
{
  IMAGE bg_img; //存储背景
  //初始化背景
  initgraph(SCREEN_WIDTH, SCREEN_HEIGHT);
  loadimage(&bg_img, _T("blackground.bmp"), SCREEN_WIDTH, SCREEN_HEIGHT, true);
  putimage(0, 0, &bg_img);
  //加载道具图标
  loadimage(&images[WALL], _T("wall.bmp"), RATIO, RATIO, true);
  loadimage(&images[FLOOR], _T("floor.bmp"), RATIO, RATIO, true);
  loadimage(&images[BOX_DES], _T("des.bmp"), RATIO, RATIO, true);
  loadimage(&images[MAN], _T("man.bmp"), RATIO, RATIO, true);
  loadimage(&images[BOX], _T("box.bmp"), RATIO, RATIO, true);
  loadimage(&images[HIT], _T("box.bmp"), RATIO, RATIO, true);
  //打印道具图标
  for (int i = 0; i < LINE; i++) {
    for (int j = 0; j < COLUMN; j++) {
            //统计目标箱子数
      if (map[i][j] == BOX_DES) nums_hit++;
      //获取小人初始位置
      if (map[i][j] == MAN) {
        man.x = i;
        man.y = j;
      }
      putimage(START_X + j * RATIO, START_Y + i * RATIO, &images[map[i][j]]);
    }
  }
  return 0;
}

热键控制

热键定义:左=>a 下=>s 上=>w 右=>d 退出=>q

#include<conio.h>
//控制键上、下、左、右控制方向,'q'退出
#define KEY_UP 'w'
#define KEY_LEFT 'a'
#define KEY_RIGHT 'd'
#define KEY_DOWN 's'
#define KEY_QUIT 'q'
//游戏控制方向
enum _DIRECTION
{
  UP,
  DOWN,
  LEFT,
  RIGHT 
};
//主函数
//...
  //游戏环节
  bool quit = false;
  do {
    if (_kbhit()) {//玩家按键
      char ch = _getch();
      if (ch == KEY_UP) {
        gameControl(UP);
      }
      else if (ch == KEY_DOWN) {
        gameControl(DOWN);
      }
      else if (ch == KEY_LEFT) {
        gameControl(LEFT);
      }
      else if (ch == KEY_RIGHT) {
        gameControl(RIGHT);
      }
      else if (ch == KEY_QUIT) {
        quit = true;
      }
            if (isGameOver()) {
        quit = true;
        gameOverScence(&bg_img);
      }
    }
    Sleep(100);
  } while (quit == false); //!quit
//...


推箱子控制

难点:

  1. 小人可以从箱子目的地上经过。
  2. 小人可以将箱子从目的地上推开。

实现上面两个需求我们可以设置额外的两个变量:

  • nums_hit —— 用于记录当前还剩多少箱子没推到目的地上
  • last —— 用于记录小人上次待在什么地方(FLOOR/BOX_DES),初始化为 FLOOR
/******************************************
* 改变并打印当前图标在地图中的位置
* 输入:pos - 当前下标
*   prop - 当前图标
* 输出:void
******************************************/
void changeMap(struct _POS& pos, enum _PROPS prop){
  map[pos.x][pos.y] = prop;
  putimage(START_X + pos.y * RATIO, START_Y + pos.x * RATIO, &images[prop]);
}
/******************************************
* 判断当前位置是否越界
* 输入:pos 当前下标
* 输出:bool
******************************************/
bool isValid(struct _POS pos){
  if (map[pos.x][pos.y] == WALL)  return false;
  if (pos.x < 0 && pos.x >= LINE && pos.y < 0 && pos.y >= COLUMN)
    return false;
  return true;
}
/******************************************
* 实现游戏四个方向的控制(上、下、左、右)
* 输入:direct - 人前进的方向
* 输出:void
******************************************/
void gameControl(enum _DIRECTION direct)
{
  struct _POS next_pos = man;
  struct _POS next_next_pos = man;
    //获取变化后的坐标
  switch (direct) {
  case UP:
    next_pos.x--;
    next_next_pos.x -= 2;
    break;
  case DOWN:
    next_pos.x++;
    next_next_pos.x += 2;
    break;
  case LEFT:
    next_pos.y--;
    next_next_pos.y -= 2;
    break;
  case RIGHT:
    next_pos.y++;
    next_next_pos.y += 2;
    break;
  }
  if (isValid(next_pos) && map[next_pos.x][next_pos.y] == FLOOR){ //如果小人前面是地板
    changeMap(next_pos, MAN);
    changeMap(man, last);
    last = FLOOR;
    man = next_pos;
  } 
  else if (isValid(next_pos) && map[next_pos.x][next_pos.y] == BOX_DES) { //如果小人前面是箱子目的地
    changeMap(man, last);
    //小人可以经过HIT,但不能改变其在地图上的值
    putimage(START_X + next_pos.y * RATIO, START_Y + next_pos.x * RATIO, &images[MAN]);
    man = next_pos;
    last = BOX_DES;
  }
  else if (isValid(next_next_pos) && (map[next_pos.x][next_pos.y] == BOX || map[next_pos.x][next_pos.y] == HIT)) { //如果小人前面是箱子或者已经在目的地上的箱子
    //如果要将箱子从目的地上推开,要用更新last变量,并且nums_hit要加1
    bool is_hit = false;
    //如果箱子推不动,就直接退出,以免更改last变量导致下次移动出现bug
    if (!(map[next_next_pos.x][next_next_pos.y] == FLOOR || map[next_next_pos.x][next_next_pos.y] == BOX_DES))
      return;
        //last需要在最后更新,防止覆盖之前的数据,因为接下来还要更新箱子和小人的位置
    if (map[next_pos.x][next_pos.y] == HIT) {
      is_hit = true;
      nums_hit++;
    }
    if (map[next_next_pos.x][next_next_pos.y] == FLOOR) {
      changeMap(next_next_pos, BOX);
      changeMap(next_pos, MAN);
      changeMap(man, last);
      man = next_pos;
    }
    else if (map[next_next_pos.x][next_next_pos.y] == BOX_DES) {
      changeMap(next_next_pos, HIT);
      changeMap(next_pos, MAN);
      changeMap(man, last);
      man = next_pos;
      nums_hit--;
    }
    //更新小人上次访问的位置
    if (is_hit) last = BOX_DES;
    else last = FLOOR;
  }
}

游戏结束

将判断游戏是否结束以及游戏结束的通关场景封装成函数。

/*****************************************************
* 判断游戏是否结束
* 输入:无
* 输出:bool - true表示游戏结束,false表示游戏未结束
******************************************************/
bool isGameOver(){
  if (nums_hit != 0)  return false;
  return true;
}
/******************************************
* 游戏结束通关场景
* 函数里参数:
* DT_CENTER - 水平居中
* DT_VCENTER - 垂直居中
* DT_SINGLELINE - 文字显示在一行
* 输入:bg - 图片变量指针
* 输出:无
******************************************/
void gameOverScence(IMAGE* bg) {
  putimage(0, 0, bg);
  settextcolor(WHITE);
  RECT rec = { 0,0,SCREEN_WIDTH,SCREEN_HEIGHT };  //定义一个矩形
  settextstyle(20, 0, _T("宋体"));    //设置字的大小与风格
  drawtext(_T("恭喜您~\n游戏成功通关!"), &rec, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
}

三、数据库实现

数据库表设计

用户表


字段名 类型 是否为空 默认值 主、外键 备注
id int(11) NOT 1,自增长 PK 用户 id
username varchar(64) NOT 用户名:英文字符、数字和特殊符号的组合
password varchar(32) NOT 密码:英文字符、数字和特殊符号的组合, 8-16 位
level_id int 1 当前关卡,关联 Levels 表中的 id
-- 用户表
create table users(
    id int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT,
    username varchar(64) NOT NULL UNIQUE,
    password varchar(32) NOT NULL,
    level_id int default 1
);

关卡表


字段名 类型 是否为空 默认值 主、外键 备注
id int NOT 1 PK 游戏关卡序号,从 1 开始
name varchar(64) NOT 地图名称
map_row int NOT 地图二维组的总行数
map_column int NOT 地图二维组的总列数


map_data varchar(4096) NOT 地图数据,二维数组对应的行列式,多行以|分开,列以逗号分隔,最大接近 45×45 的地图
next_level_id int 0 下一关的关卡 id ,0 代表通关



初始化信息

-- 创建一个用户
insert into users values(1000, 'gdx', md5('123456gdx'), 1);
insert into users values(2, 'rock', md5('rock1234'), 2);
-- 创建一个关卡
insert into levels values(1, '牛刀小试', 9, 12, 
'0,0,0,0,0,0,0,0,0,0,0,0|0,1,0,1,1,1,1,1,1,1,0,0|0,1,4,1,0,2,1,0,2,1,0,0|0,1,0,1,0,1,0,0,1,1,1,0|0,1,0,2,
0,1,1,4,1,1,1,0|0,1,1,1,0,3,1,1,1,4,1,0|0,1,2,1,1,4,1,1,1,1,1,0|0,1,0,0,1,0,1,1,0,0,1,0|0,0,0,0,0,0,0,0,0,0,
0,0',0);
insert into levels values(2, '再接再厉', 12, 13, '0,0,0,0,0,0,0,0,0,0,0,0,0|0,1,1,1,1,1,1,1,0,0,0,0,0|0,1,1,4,1,1,1,4,0,0,0,0,0|0,0,0,0,1,1,1,1,0,0,0,0,0|0,0,0,0,1,4,1,1,0,0,0,0,0|0,0,0,0,1,1,1,4,1,0,0,0,0|0,2,2,1,4,1,0,0,1,0,0,0,0|0,2,2,1,1,4,0,0,1,1,1,1,0|0,2,2,1,4,3,1,1,1,1,1,1,0|0,2,0,0,0,4,0,0,0,1,0,1,0|0,2,0,0,0,1,1,1,1,1,0,0,0|0,0,0,0,0,0,0,0,0,0,0,0,0|', 0);


登录验证

database.h

定义用户信息的结构体,并定义从数据库获取用户信息的接口。

#pragma once
#include<string>
using namespace std;
typedef struct _userinfo {
  int id;       //用户id
  string username;  //用户名
  string passwd;    //密码
  int level_id;   //关卡id
}userinfo;
bool fetch_user_info(userinfo& user);


database.cpp

实现获取用户信息的接口,并将数据库连接的功能也封装成一个函数调用。

另外,下面 DB_USERDB_USER_PASSWD 要换成自己本地数据库的用户和密码,不然无法连接到本地数据库。

#include "database.h"
#include <mysql.h>
#include <stdio.h>
#define DB_NAME "box_man"
#define DB_HOST "127.0.0.1"
#define DB_PORT 3306
#define DB_USER "root"
#define DB_USER_PASSWD "123456"
static int debug = 1;
static bool connect_db(MYSQL& mysql);
/******************************************
* 功能:通过用户名和密码获取用户信息
* 输入:
* user - 用户信息结构体
* 返回值:
* 获取成功返回ture,失败返回false
******************************************/
bool fetch_user_info(userinfo& user) {
  MYSQL mysql;
  MYSQL_RES* res; //查询结果集
  MYSQL_ROW row;  //记录结构体
  char sql[256];
  bool ret = false;
  //1.连接到数据库
  if (connect_db(mysql) == false) {
    return false;
  }
  //2.根据用户名和密码获取用户信息(id, level_id)
  snprintf(sql, 256, "select id, level_id from users where username='%s' and password=md5('%s')", user.username.c_str(), user.passwd.c_str());
  ret = mysql_query(&mysql, sql); //成功返回0
  if (ret) {
    printf("数据库查询出错,%s 错误原因:%s\n", sql, mysql_error(&mysql));
    mysql_close(&mysql);
    return false;
  }
  //3.获取结果
  res = mysql_store_result(&mysql);
  row = mysql_fetch_row(res);
  if (row == NULL) {
    mysql_free_result(res);
    mysql_close(&mysql);
    return false;
  }
  user.id = atoi(row[0]);
  user.level_id = atoi(row[1]);
  if(debug) printf("userif: %d  level_id: %d\n", user.id, user.level_id); //打印id
  //4.返回结果
  //释放结果集
  mysql_free_result(res);
  //关闭数据库
  mysql_close(&mysql);
  return true;
}
bool connect_db(MYSQL& mysql) {
  //1.初始化数据库句柄
  mysql_init(&mysql);
  //2.设置字符编码
  mysql_options(&mysql, MYSQL_SET_CHARSET_NAME, "gbk");
  //3.连接数据库
  if (mysql_real_connect(&mysql, DB_HOST, DB_USER, DB_USER_PASSWD, DB_NAME, DB_PORT, NULL, 0) == NULL) {
    printf("数据库连接出错,错误原因:%s\n", mysql_error(&mysql));
    return false;
  }
  return true;
}


box_man.cpp

将数据库登陆验证封装成一个函数进行调用,并在主函数中调用相关函数。

#include "database.h"
//数据库登录验证功能
bool login(userinfo& user) {
  int times = 0;
  bool ret = false;
  do {
    cout << "输入用户名:";
    cin >> user.username;
    cout << "请输入密码:";
    cin >> user.passwd;
    //返回bool,成功返回true,失败返回false
    ret = fetch_user_info(user);
    times++;
    if (times >= MAX_RETRY_TIMES) {
      break;
    }
    if (ret == false) {
      cout << "登录失败,请重新输入!" << endl;
    }
  } while (!ret);
  return ret;
}
int main(){
    //用户身份验证
  userinfo user;
  if (!login(user)) {
    cout << "登陆失败,请重新登录!" << endl;
    ::system("pause");
    exit(-1);
  }
  else {
    cout << "用户" << user.id << ",您当前所在的关卡是:level-" << user.level_id << endl;
    cout << "您已登陆成功,请开始您的表演!" << endl;
    ::system("pause");
  }
    //...
}


获取关卡

database.h

定义关卡信息结构体,并定义从数据库获取关卡信息的接口。

typedef struct _levelinfo {
  int id;      //关卡的id
  string name;   //关卡的名字
  int map_row;   //地图总行数
  int map_column;  //地图总列数
  string map_data; //二维地图数据
  int next_level;  //下一关卡的id
}levelinfo;
bool fetch_level_info(levelinfo& level, int level_id);


database.cpp

实现从数据库获取关卡信息的接口。

/*********************************************************
* 功能:根据关卡id获取完整的关卡信息(如:地图,下一关等)
* 输入:
* level - 保存关卡信息的结构体变量
* level_id - 要获取详细关卡信息的关卡id
* 返回值:
* 获取成功返回ture,失败返回false
*********************************************************/
bool fetch_level_info(levelinfo& level, int level_id){
  MYSQL mysql;
  MYSQL_RES* res; //查询结果集
  MYSQL_ROW row;  //记录结构体
  char sql[256];
  bool ret = false;
  //1.连接到数据库
  if (connect_db(mysql) == false) {
    return false;
  }
  //2.根据关卡id查询数据库获取关卡地图信息
  snprintf(sql, 256, "select name, map_row, map_column, map_data, next_level_id from levels where id=%d;", level_id);
  ret = mysql_query(&mysql, sql); //成功返回0
  if (ret) {
    printf("数据库查询出错,%s 错误原因:%s\n", sql, mysql_error(&mysql));
    mysql_close(&mysql);
    return false;
  }
  //3.获取结果
  res = mysql_store_result(&mysql);
  row = mysql_fetch_row(res);
  if (row == NULL) {
    mysql_free_result(res);
    mysql_close(&mysql);
    return false;
  }
  level.id = level_id;
  level.name = row[0];
  level.map_row = atoi(row[1]);
  level.map_column = atoi(row[2]);
  level.map_data = row[3];
  level.next_level = atoi(row[4]);
  if(debug) printf("level id: %d  name: %s map row: %d  map column: %d map data: %s next level: %d\n", level.id, level.name.c_str(), level.map_row, level.map_column, level.map_data.c_str(), level.next_level);
  //4.返回结果
  //释放结果集
  mysql_free_result(res);
  //关闭数据库
  mysql_close(&mysql);
  return true;
}

box_man.cpp

调用从数据库获取关卡信息的接口。

int main(){
    levelinfo level;
  bool ret = false;
    //...
    //根据用户所在的关卡id获取关卡数据
  ret = fetch_level_info(level, user.level_id);
  if (!ret) {
    cout << "获取关卡数据失败,请重试!" << endl;
    ::system("pause");
    exit(-1);
  }
  ::system("pause");
    //...
}


地图适配


前面提到地图暂时使用二维数组保存的,现在有了数据库所以可以从数据库中读取。但是地图的大小可变,故我们可以先将地图的行数和列数定义到一个最大值 48 ,这个 48×48 的二维数组足矣存下我们的所有地图。


每次读取地图后都用一个借口将得到的数据转换到二维数组 map 中,然后通过得到的行数和列数对这个二维数组进性读取使用,也就是说这个二维数组中的空间不一定完全用得到,可根据读入的地图信息用相应的一部分。


database.h

我们可以将行数和列数最大值以及地图转换接口定义到该头文件中。

#define LINE 48     //地图行数
#define COLUMN 48   //地图列数
bool transform_map_db2array(levelinfo& level, int map[LINE][COLUMN]);

database.cpp

实现地图转换功能。

/*********************************************************
* 功能:将从数据库读到的地图数据转换到二维数组中
* 输入:
* level - 保存关卡信息的结构体变量
* map - 存储地图数据的二维数组
* 返回值:
* 获取成功返回ture,失败返回false
*********************************************************/
bool transform_map_db2array(levelinfo& level, int map[LINE][COLUMN]) {
  if (level.map_row > LINE || level.map_column > COLUMN) {
    printf("地图过大,请重新设置!\n");
    return false;
  }
  if (level.map_data.length() < 1) {
    printf("地图数据有误,请重新设置!\n");
    return false;
  }
  int start = 0, end = 0;
  int row = 0, column = 0;
  do {
    //找到一行数据的末尾
    end = level.map_data.find('|', start);
    //判断是否已经读到字符串结尾
    if (end < 0)  end = level.map_data.length();
    //如果起始位置大于等于终止位置,直接退出
    if (start >= end) break;
    //截取一行数据
    string line = level.map_data.substr(start, end - start);
    printf("get line: %s\n", line.c_str());
    //对行数据进行解析
    char* next_token = NULL;
    char* item = strtok_s((char*)line.c_str(), ",", &next_token);
    column = 0;
    while (item && column < level.map_column) {
      printf("%s ", item);
      map[row][column] = atoi(item);
      column++;
      item = strtok_s(NULL, ",", &next_token);
    }
    //检查列数是否设置正确
    if (column < level.map_column) {
      printf("地图列数少于设定,%d(need:%d),终止!\n", column, level.map_column);
      return false;
    }
    printf("\n");
    row++;
    //如果行数过多,则直接截取
    if (row >= level.map_row) {
      break;
    }
    //读取下一行
    start = end + 1;
  } while (1);
  if (row < level.map_row) {
    printf("地图行数少于设定,%d(need:%d),终止!", row, level.map_row);
    return false;
  }
  return true;
}

box_man.cpp

调用地图转换接口(另外,记得将该文件中其它用到 LINECOLUMN 的地方改成从数据库获取到的行数 level.map_rowlevel.map_column ,最终版会呈现在最后的全部代码板块)。

int main(){
    //...
      //把数据库中的地图转换到map中
  ret = transform_map_db2array(level, map);
  if (!ret) {
    cout << "地图数据转换失败,请重试!" << endl;
    ::system("pause");
    exit(-1);
  }
    //...
}

下一关跳转

database.h

定义用户下一关关卡 id 更新接口。

bool update_user_level(userinfo& user, int next_level_id);


database.cpp

实现用户下一关关卡 id 更新功能。

/******************************************
* 功能:更新用户下一关信息
* 输入:
* user - 用户信息结构体
* next_level_id - 下一关的id
* 返回值:
* 更新成功返回ture,失败返回false
******************************************/
bool update_user_level(userinfo& user, int next_level_id)
{
  MYSQL mysql;
  MYSQL_RES* res; //查询结果集
  MYSQL_ROW row;  //记录结构体
  char sql[256];
  bool ret = false;
  //1.连接到数据库
  if (connect_db(mysql) == false) {
    return false;
  }
  //2.根据用户id更新下一关的level_id
  snprintf(sql, 256, "update users set level_id=%d where id=%d;", next_level_id, user.id);
  ret = mysql_query(&mysql, sql);
  if (ret) {
    printf("数据库更新出错,%s 错误原因:%s\n", sql, mysql_error(&mysql));
    mysql_close(&mysql);
    return false;
  }
  return true;
}


box_man.cpp

更改游戏结束通关场景函数接口,这里将其划分成两个函数,一个是用于除最后一关通关后的通关画面打印,一个是用于通关所有关卡后的画面打印。


另外,还需要在主函数里加上 do…while 循环使用户完成当前关卡后能够跳到下一关,下面代码显示主要部分,其它代码细节会放到最后全部代码板块。

/******************************************
* 游戏结束通关场景
* 函数里参数:
* DT_CENTER - 水平居中
* DT_VCENTER - 垂直居中
* DT_SINGLELINE - 文字显示在一行
* 输入:bg - 图片变量指针
* 输出:无
******************************************/
//通往下一关场景
void gameNextScence(IMAGE* bg) {
  putimage(0, 0, bg);
  settextcolor(WHITE);
  RECT rec = { 0,0,SCREEN_WIDTH,SCREEN_HEIGHT };  //定义一个矩形
  settextstyle(20, 0, _T("宋体"));    //设置字的大小与风格
  drawtext(_T("恭喜您~\n此关挑战成功,任意键跳转到下一关!"), &rec, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
  ::system("pause");
  cleardevice();  
}
//通关场景
void gameOverScence(IMAGE* bg) {
  putimage(0, 0, bg);
  settextcolor(WHITE);
  RECT rec = { 0,0,SCREEN_WIDTH,SCREEN_HEIGHT };  //定义一个矩形
  settextstyle(20, 0, _T("宋体"));    //设置字的大小与风格
  drawtext(_T("恭喜您~\n通关成功!有缘再会!"), &rec, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
}
int main(){
    //...
    if (isGameOver()) {
        if (level.next_level < 1) {
            gameOverScence(&bg_img);
            quit = true;
            break;
        }
        gameNextScence(&bg_img);
        //更新用户下一关的管求爱信息
        if (update_user_level(user, level.next_level)) {
            user.level_id = level.next_level;
        }
        break;
    }
    //...
}


四、全部代码

不包含数据库代码

box_man.h

#pragma once
#include<graphics.h>  
#include<iostream>
#include<stdlib.h>
#include<string>
#include<conio.h>
using namespace std;
#define RATIO 61  //地图中每个小方块的大小
#define SCREEN_WIDTH 960  //背景宽度
#define SCREEN_HEIGHT 768 //背景高度
#define LINE 9      //地图行数
#define COLUMN 12   //地图列数
#define START_X 100   //地图开始的横坐标
#define START_Y 150   //地图开始的纵坐标
//控制键上、下、左、右控制方向,'q'退出
#define KEY_UP 'w'
#define KEY_LEFT 'a'
#define KEY_RIGHT 'd'
#define KEY_DOWN 's'
#define KEY_QUIT 'q'
enum _PROPS {
  WALL, //墙:0
  FLOOR,  //地板:1
  BOX_DES,//箱子目的地:2
  MAN,  //小人:3
  BOX,  //箱子:4
  HIT,  //箱子命中目标:5
  ALL   //道具数量:6
};
//游戏控制方向
enum _DIRECTION
{
  UP,
  DOWN,
  LEFT,
  RIGHT
};
struct _POS {
  int x;  //小人所在的二维数组的行
  int y;  //小人所在的二维数组的列
};
IMAGE images[ALL];  //存储道具图标
struct _POS man;  //小人在数组中的位置
int nums_hit; //表示需要放的箱子数量
enum _PROPS last = FLOOR; //记录小人上次待的位置是什么


box_man.cpp

#include "box_man.h"
/**************************************
* 游戏地图
* 墙:0   地板:1   箱子目的地:2
* 小人:3    箱子:4   箱子命中目标:5
**************************************/
int map[LINE][COLUMN] = {
  {0,0,0,0,0,0,0,0,0,0,0,0},
  {0,1,0,1,1,1,1,1,1,1,0,0},
  {0,1,4,1,0,2,1,0,2,1,0,0},
  {0,1,0,1,0,1,0,0,1,1,1,0},
  {0,1,0,2,0,1,1,4,1,1,1,0},
  {0,1,1,1,0,3,1,1,1,4,1,0},
  {0,1,2,1,1,4,1,1,1,1,1,0},
  {0,1,0,0,1,0,1,1,0,0,1,0},
  {0,0,0,0,0,0,0,0,0,0,0,0},
};
/******************************************
* 改变并打印当前图标在地图中的位置
* 输入:pos - 当前下标
*   prop - 当前图标
* 输出:void
******************************************/
void changeMap(struct _POS& pos, enum _PROPS prop){
  map[pos.x][pos.y] = prop;
  putimage(START_X + pos.y * RATIO, START_Y + pos.x * RATIO, &images[prop]);
}
/******************************************
* 判断当前位置是否越界
* 输入:pos 当前下标
* 输出:bool
******************************************/
bool isValid(struct _POS pos){
  if (map[pos.x][pos.y] == WALL)  return false;
  if (pos.x < 0 && pos.x >= LINE && pos.y < 0 && pos.y >= COLUMN)
    return false;
  return true;
}
/******************************************
* 实现游戏四个方向的控制(上、下、左、右)
* 输入:direct - 人前进的方向
* 输出:void
******************************************/
void gameControl(enum _DIRECTION direct)
{
  struct _POS next_pos = man;
  struct _POS next_next_pos = man;
  switch (direct) {
  case UP:
    next_pos.x--;
    next_next_pos.x -= 2;
    break;
  case DOWN:
    next_pos.x++;
    next_next_pos.x += 2;
    break;
  case LEFT:
    next_pos.y--;
    next_next_pos.y -= 2;
    break;
  case RIGHT:
    next_pos.y++;
    next_next_pos.y += 2;
    break;
  }
  if (isValid(next_pos) && map[next_pos.x][next_pos.y] == FLOOR){
    changeMap(next_pos, MAN);
    changeMap(man, last);
    last = FLOOR;
    man = next_pos;
  }
  else if (isValid(next_pos) && map[next_pos.x][next_pos.y] == BOX_DES) {
    changeMap(man, last);
    //小人可以经过HIT,但不能改变其在地图上的值
    putimage(START_X + next_pos.y * RATIO, START_Y + next_pos.x * RATIO, &images[MAN]);
    man = next_pos;
    last = BOX_DES;
  }
  else if (isValid(next_next_pos) && (map[next_pos.x][next_pos.y] == BOX || map[next_pos.x][next_pos.y] == HIT)) {
    //如果要将箱子从目的地上推开,要用更新last变量,并且nums_hit要加1
    bool is_hit = false;
    //如果箱子推不动,就直接退出,以免更改last变量导致下次移动出现bug
    if (!(map[next_next_pos.x][next_next_pos.y] == FLOOR || map[next_next_pos.x][next_next_pos.y] == BOX_DES))
      return;
    if (map[next_pos.x][next_pos.y] == HIT) {
      is_hit = true;
      nums_hit++;
    }
    if (map[next_next_pos.x][next_next_pos.y] == FLOOR) {
      changeMap(next_next_pos, BOX);
      changeMap(next_pos, MAN);
      changeMap(man, last);
      man = next_pos;
    }
    else if (map[next_next_pos.x][next_next_pos.y] == BOX_DES) {
      changeMap(next_next_pos, HIT);
      changeMap(next_pos, MAN);
      changeMap(man, last);
      man = next_pos;
      nums_hit--;
    }
    //更新小人上次访问的位置
    if (is_hit) last = BOX_DES;
    else last = FLOOR;
  }
}
/*****************************************************
* 判断游戏是否结束
* 输入:无
* 输出:bool - true表示游戏结束,false表示游戏未结束
******************************************************/
bool isGameOver(){
  if (nums_hit != 0)  return false;
  return true;
}
/******************************************
* 游戏结束通关场景
* 函数里参数:
* DT_CENTER - 水平居中
* DT_VCENTER - 垂直居中
* DT_SINGLELINE - 文字显示在一行
* 输入:bg - 图片变量指针
* 输出:无
******************************************/
void gameOverScence(IMAGE* bg) {
  putimage(0, 0, bg);
  settextcolor(WHITE);
  RECT rec = { 0,0,SCREEN_WIDTH,SCREEN_HEIGHT };  //定义一个矩形
  settextstyle(20, 0, _T("宋体"));    //设置字的大小与风格
  drawtext(_T("恭喜您~\n游戏成功通关!"), &rec, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
}
int main()
{
  IMAGE bg_img; //存储背景
  nums_hit = 0; //初始化目标箱子数量
  //初始化背景
  initgraph(SCREEN_WIDTH, SCREEN_HEIGHT);
  loadimage(&bg_img, _T("blackground.bmp"), SCREEN_WIDTH, SCREEN_HEIGHT, true);
  putimage(0, 0, &bg_img);
  //加载道具图标
  loadimage(&images[WALL], _T("wall.bmp"), RATIO, RATIO, true);
  loadimage(&images[FLOOR], _T("floor.bmp"), RATIO, RATIO, true);
  loadimage(&images[BOX_DES], _T("des.bmp"), RATIO, RATIO, true);
  loadimage(&images[MAN], _T("man.bmp"), RATIO, RATIO, true);
  loadimage(&images[BOX], _T("box.bmp"), RATIO, RATIO, true);
  loadimage(&images[HIT], _T("box.bmp"), RATIO, RATIO, true);
  //打印道具图标
  for (int i = 0; i < LINE; i++) {
    for (int j = 0; j < COLUMN; j++) {
      //统计目标箱子数
      if (map[i][j] == BOX_DES) nums_hit++;
      //获取小人初始位置
      if (map[i][j] == MAN) {
        man.x = i;
        man.y = j;
      }
      putimage(START_X + j * RATIO, START_Y + i * RATIO, &images[map[i][j]]);
    }
  }
  //游戏环节
  bool quit = false;
  do {
    if (_kbhit()) {//玩家按键
      char ch = _getch();
      if (ch == KEY_UP) {
        gameControl(UP);
      }
      else if (ch == KEY_DOWN) {
        gameControl(DOWN);
      }
      else if (ch == KEY_LEFT) {
        gameControl(LEFT);
      }
      else if (ch == KEY_RIGHT) {
        gameControl(RIGHT);
      }
      else if (ch == KEY_QUIT) {
        quit = true;
      }
      if (isGameOver()) {
        quit = true;
        gameOverScence(&bg_img);
      }
    }
    Sleep(100);
  } while (quit == false); //!quit
  system("pause");
  closegraph(); //释放资源
  return 0;
}


包含数据库代码

database.h

#pragma once
#include<string>
using namespace std;
#define LINE 48     //地图行数
#define COLUMN 48   //地图列数
typedef struct _userinfo {
  int id;       //用户id
  string username;  //用户名
  string passwd;    //密码
  int level_id;   //关卡id
}userinfo;
typedef struct _levelinfo {
  int id;      //关卡的id
  string name;   //关卡的名字
  int map_row;   //地图总行数
  int map_column;  //地图总列数
  string map_data; //二维地图数据
  int next_level;  //下一关卡的id
}levelinfo;
bool fetch_user_info(userinfo& user);
bool update_user_level(userinfo& user, int next_level_id);
bool fetch_level_info(levelinfo& level, int level_id);
bool transform_map_db2array(levelinfo& level, int map[LINE][COLUMN]);


database.cpp

#include "database.h"
#include <mysql.h>
#include <stdio.h>
#define DB_NAME "box_man"
#define DB_HOST "127.0.0.1"
#define DB_PORT 3306
#define DB_USER "root"
#define DB_USER_PASSWD "123456"
static int debug = 1;
static bool connect_db(MYSQL& mysql);
/******************************************
* 功能:通过用户名和密码获取用户信息
* 输入:
* user - 用户信息结构体
* 返回值:
* 获取成功返回ture,失败返回false
******************************************/
bool fetch_user_info(userinfo& user) {
  MYSQL mysql;
  MYSQL_RES* res; //查询结果集
  MYSQL_ROW row;  //记录结构体
  char sql[256];
  bool ret = false;
  //1.连接到数据库
  if (connect_db(mysql) == false) {
    return false;
  }
  //2.根据用户名和密码获取用户信息(id, level_id)
  snprintf(sql, 256, "select id, level_id from users where username='%s' and password=md5('%s')", user.username.c_str(), user.passwd.c_str());
  ret = mysql_query(&mysql, sql); //成功返回0
  if (ret) {
    printf("数据库查询出错,%s 错误原因:%s\n", sql, mysql_error(&mysql));
    mysql_close(&mysql);
    return false;
  }
  //3.获取结果
  res = mysql_store_result(&mysql);
  row = mysql_fetch_row(res);
  if (row == NULL) {
    mysql_free_result(res);
    mysql_close(&mysql);
    return false;
  }
  user.id = atoi(row[0]);
  user.level_id = atoi(row[1]);
  if(debug) printf("userif: %d  level_id: %d\n", user.id, user.level_id); //打印id
  //4.返回结果
  //释放结果集
  mysql_free_result(res);
  //关闭数据库
  mysql_close(&mysql);
  return true;
}
/******************************************
* 功能:更新用户下一关信息
* 输入:
* user - 用户信息结构体
* next_level_id - 下一关的id
* 返回值:
* 更新成功返回ture,失败返回false
******************************************/
bool update_user_level(userinfo& user, int next_level_id)
{
  MYSQL mysql;
  MYSQL_RES* res; //查询结果集
  MYSQL_ROW row;  //记录结构体
  char sql[256];
  bool ret = false;
  //1.连接到数据库
  if (connect_db(mysql) == false) {
    return false;
  }
  //2.根据用户id更新下一关的level_id
  snprintf(sql, 256, "update users set level_id=%d where id=%d;", next_level_id, user.id);
  ret = mysql_query(&mysql, sql);
  if (ret) {
    printf("数据库更新出错,%s 错误原因:%s\n", sql, mysql_error(&mysql));
    mysql_close(&mysql);
    return false;
  }
  return true;
}
/*********************************************************
* 功能:根据关卡id获取完整的关卡信息(如:地图,下一关等)
* 输入:
* level - 保存关卡信息的结构体变量
* level_id - 要获取详细关卡信息的关卡id
* 返回值:
* 获取成功返回ture,失败返回false
*********************************************************/
bool fetch_level_info(levelinfo& level, int level_id){
  MYSQL mysql;
  MYSQL_RES* res; //查询结果集
  MYSQL_ROW row;  //记录结构体
  char sql[256];
  bool ret = false;
  //1.连接到数据库
  if (connect_db(mysql) == false) {
    return false;
  }
  //2.根据关卡id查询数据库获取关卡地图信息
  snprintf(sql, 256, "select name, map_row, map_column, map_data, next_level_id from levels where id=%d;", level_id);
  ret = mysql_query(&mysql, sql); //成功返回0
  if (ret) {
    printf("数据库查询出错,%s 错误原因:%s\n", sql, mysql_error(&mysql));
    mysql_close(&mysql);
    return false;
  }
  //3.获取结果
  res = mysql_store_result(&mysql);
  row = mysql_fetch_row(res);
  if (row == NULL) {
    mysql_free_result(res);
    mysql_close(&mysql);
    return false;
  }
  level.id = level_id;
  level.name = row[0];
  level.map_row = atoi(row[1]);
  level.map_column = atoi(row[2]);
  level.map_data = row[3];
  level.next_level = atoi(row[4]);
  if(debug) printf("level id: %d  name: %s map row: %d  map column: %d map data: %s next level: %d\n", level.id, level.name.c_str(), level.map_row, level.map_column, level.map_data.c_str(), level.next_level);
  //4.返回结果
  //释放结果集
  mysql_free_result(res);
  //关闭数据库
  mysql_close(&mysql);
  return true;
}
//连接数据库
bool connect_db(MYSQL& mysql) {
  //1.初始化数据库句柄
  mysql_init(&mysql);
  //2.设置字符编码
  mysql_options(&mysql, MYSQL_SET_CHARSET_NAME, "gbk");
  //3.连接数据库
  if (mysql_real_connect(&mysql, DB_HOST, DB_USER, DB_USER_PASSWD, DB_NAME, DB_PORT, NULL, 0) == NULL) {
    printf("数据库连接出错,错误原因:%s\n", mysql_error(&mysql));
    return false;
  }
  return true;
}
/*********************************************************
* 功能:将从数据库读到的地图数据转换到二维数组中
* 输入:
* level - 保存关卡信息的结构体变量
* map - 存储地图数据的二维数组
* 返回值:
* 获取成功返回ture,失败返回false
*********************************************************/
bool transform_map_db2array(levelinfo& level, int map[LINE][COLUMN]) {
  if (level.map_row > LINE || level.map_column > COLUMN) {
    printf("地图过大,请重新设置!\n");
    return false;
  }
  if (level.map_data.length() < 1) {
    printf("地图数据有误,请重新设置!\n");
    return false;
  }
  int start = 0, end = 0;
  int row = 0, column = 0;
  do {
    //找到一行数据的末尾
    end = level.map_data.find('|', start);
    //判断是否已经读到字符串结尾
    if (end < 0)  end = level.map_data.length();
    //如果起始位置大于等于终止位置,直接退出
    if (start >= end) break;
    //截取一行数据
    string line = level.map_data.substr(start, end - start);
    printf("get line: %s\n", line.c_str());
    //对行数据进行解析
    char* next_token = NULL;
    char* item = strtok_s((char*)line.c_str(), ",", &next_token);
    column = 0;
    while (item && column < level.map_column) {
      printf("%s ", item);
      map[row][column] = atoi(item);
      column++;
      item = strtok_s(NULL, ",", &next_token);
    }
    //检查列数是否设置正确
    if (column < level.map_column) {
      printf("地图列数少于设定,%d(need:%d),终止!\n", column, level.map_column);
      return false;
    }
    printf("\n");
    row++;
    //如果行数过多,则直接截取
    if (row >= level.map_row) {
      break;
    }
    //读取下一行
    start = end + 1;
  } while (1);
  if (row < level.map_row) {
    printf("地图行数少于设定,%d(need:%d),终止!", row, level.map_row);
    return false;
  }
  return true;
}


box_man.h

#pragma once
#include<graphics.h>  
#include<iostream>
#include<stdlib.h>
#include<string>
#include<conio.h>
using namespace std;
#define RATIO 61  //地图中每个小方块的大小
#define SCREEN_WIDTH 960  //背景宽度
#define SCREEN_HEIGHT 768 //背景高度
//#define LINE 9      //地图行数
//#define COLUMN 12   //地图列数
#define START_X 80    //地图开始的横坐标
#define START_Y 30    //地图开始的纵坐标
//控制键上、下、左、右控制方向,'q'退出
#define KEY_UP 'w'
#define KEY_LEFT 'a'
#define KEY_RIGHT 'd'
#define KEY_DOWN 's'
#define KEY_QUIT 'q'
#define MAX_RETRY_TIMES 4
enum _PROPS {
  WALL, //墙:0
  FLOOR,  //地板:1
  BOX_DES,//箱子目的地:2
  MAN,  //小人:3
  BOX,  //箱子:4
  HIT,  //箱子命中目标:5
  ALL   //道具数量:6
};
//游戏控制方向
enum _DIRECTION
{
  UP,
  DOWN,
  LEFT,
  RIGHT
};
struct _POS {
  int x;  //小人所在的二维数组的行
  int y;  //小人所在的二维数组的列
};
IMAGE images[ALL];  //存储道具图标
struct _POS man;  //小人在数组中的位置
int nums_hit; //表示需要放的箱子数量
enum _PROPS last = FLOOR; //记录小人上次待的位置是什么


box_man.cpp

#include "box_man.h"
#include "database.h"
/**************************************
* 游戏地图
* 墙:0   地板:1   箱子目的地:2
* 小人:3    箱子:4   箱子命中目标:5
**************************************/
int map[LINE][COLUMN] = { 0 };
//int map[LINE][COLUMN] = {
//  {0,0,0,0,0,0,0,0,0,0,0,0},
//  {0,1,0,1,1,1,1,1,1,1,0,0},
//  {0,1,4,1,0,2,1,0,2,1,0,0},
//  {0,1,0,1,0,1,0,0,1,1,1,0},
//  {0,1,0,2,0,1,1,4,1,1,1,0},
//  {0,1,1,1,0,3,1,1,1,4,1,0},
//  {0,1,2,1,1,4,1,1,1,1,1,0},
//  {0,1,0,0,1,0,1,1,0,0,1,0},
//  {0,0,0,0,0,0,0,0,0,0,0,0},
//};
/******************************************
* 改变并打印当前图标在地图中的位置
* 输入:pos - 当前下标
*   prop - 当前图标
* 输出:void
******************************************/
void changeMap(struct _POS& pos, enum _PROPS prop) {
  map[pos.x][pos.y] = prop;
  putimage(START_X + pos.y * RATIO, START_Y + pos.x * RATIO, &images[prop]);
}
/******************************************
* 判断当前位置是否越界
* 输入:pos 当前下标
* 输出:bool
******************************************/
bool isValid(struct _POS pos, levelinfo level) {
  if (map[pos.x][pos.y] == WALL)  return false;
  if (pos.x < 0 && pos.x >= level.map_row && pos.y < 0 && pos.y >= level.map_column)
    return false;
  return true;
}
/******************************************
* 实现游戏四个方向的控制(上、下、左、右)
* 输入:direct - 人前进的方向
* 输出:void
******************************************/
void gameControl(enum _DIRECTION direct, levelinfo level)
{
  struct _POS next_pos = man;
  struct _POS next_next_pos = man;
  switch (direct) {
  case UP:
    next_pos.x--;
    next_next_pos.x -= 2;
    break;
  case DOWN:
    next_pos.x++;
    next_next_pos.x += 2;
    break;
  case LEFT:
    next_pos.y--;
    next_next_pos.y -= 2;
    break;
  case RIGHT:
    next_pos.y++;
    next_next_pos.y += 2;
    break;
  }
  if (isValid(next_pos, level) && map[next_pos.x][next_pos.y] == FLOOR) {
    changeMap(next_pos, MAN);
    changeMap(man, last);
    last = FLOOR;
    man = next_pos;
  }
  else if (isValid(next_pos, level) && map[next_pos.x][next_pos.y] == BOX_DES) {
    changeMap(man, last);
    //小人可以经过HIT,但不能改变其在地图上的值
    putimage(START_X + next_pos.y * RATIO, START_Y + next_pos.x * RATIO, &images[MAN]);
    man = next_pos;
    last = BOX_DES;
  }
  else if (isValid(next_next_pos, level) && (map[next_pos.x][next_pos.y] == BOX || map[next_pos.x][next_pos.y] == HIT)) {
    //如果要将箱子从目的地上推开,要用更新last变量,并且nums_hit要加1
    bool is_hit = false;
    //如果箱子推不动,就直接退出,以免更改last变量导致下次移动出现bug
    if (!(map[next_next_pos.x][next_next_pos.y] == FLOOR || map[next_next_pos.x][next_next_pos.y] == BOX_DES))
      return;
    if (map[next_pos.x][next_pos.y] == HIT) {
      is_hit = true;
      nums_hit++;
    }
    if (map[next_next_pos.x][next_next_pos.y] == FLOOR) {
      changeMap(next_next_pos, BOX);
      changeMap(next_pos, MAN);
      changeMap(man, last);
      man = next_pos;
    }
    else if (map[next_next_pos.x][next_next_pos.y] == BOX_DES) {
      changeMap(next_next_pos, HIT);
      changeMap(next_pos, MAN);
      changeMap(man, last);
      man = next_pos;
      nums_hit--;
    }
    //更新小人上次访问的位置
    if (is_hit) last = BOX_DES;
    else last = FLOOR;
  }
}
/*****************************************************
* 判断游戏是否结束
* 输入:无
* 输出:bool - true表示游戏结束,false表示游戏未结束
******************************************************/
bool isGameOver() {
  if (nums_hit != 0)  return false;
  return true;
}
/******************************************
* 游戏结束通关场景
* 函数里参数:
* DT_CENTER - 水平居中
* DT_VCENTER - 垂直居中
* DT_SINGLELINE - 文字显示在一行
* 输入:bg - 图片变量指针
* 输出:无
******************************************/
//通往下一关场景
void gameNextScence(IMAGE* bg) {
  putimage(0, 0, bg);
  settextcolor(WHITE);
  RECT rec = { 0,0,SCREEN_WIDTH,SCREEN_HEIGHT };  //定义一个矩形
  settextstyle(20, 0, _T("宋体"));    //设置字的大小与风格
  drawtext(_T("恭喜您~\n此关挑战成功,任意键跳转到下一关!"), &rec, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
  ::system("pause");
  cleardevice();  
}
//通关场景
void gameOverScence(IMAGE* bg) {
  putimage(0, 0, bg);
  settextcolor(WHITE);
  RECT rec = { 0,0,SCREEN_WIDTH,SCREEN_HEIGHT };  //定义一个矩形
  settextstyle(20, 0, _T("宋体"));    //设置字的大小与风格
  drawtext(_T("恭喜您~\n通关成功!有缘再会!"), &rec, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
}
//数据库登录验证功能
bool login(userinfo& user) {
  int times = 0;
  bool ret = false;
  do {
    cout << "输入用户名:";
    cin >> user.username;
    cout << "请输入密码:";
    cin >> user.passwd;
    //返回bool,成功返回true,失败返回false
    ret = fetch_user_info(user);
    times++;
    if (times >= MAX_RETRY_TIMES) {
      break;
    }
    if (ret == false) {
      cout << "登录失败,请重新输入!" << endl;
    }
  } while (!ret);
  return ret;
}
//初始化背景
void init_graph(IMAGE& bg_img) {
  nums_hit = 0; //初始化目标箱子数量
  //初始化背景
  initgraph(SCREEN_WIDTH, SCREEN_HEIGHT);
  loadimage(&bg_img, _T("blackground.bmp"), SCREEN_WIDTH, SCREEN_HEIGHT, true);
  putimage(0, 0, &bg_img);
  //加载道具图标
  loadimage(&images[WALL], _T("wall_right.bmp"), RATIO, RATIO, true);
  loadimage(&images[FLOOR], _T("floor.bmp"), RATIO, RATIO, true);
  loadimage(&images[BOX_DES], _T("des.bmp"), RATIO, RATIO, true);
  loadimage(&images[MAN], _T("man.bmp"), RATIO, RATIO, true);
  loadimage(&images[BOX], _T("box.bmp"), RATIO, RATIO, true);
  loadimage(&images[HIT], _T("box.bmp"), RATIO, RATIO, true);
}
int main()
{
  //用户身份验证
  userinfo user;
  levelinfo level;
  bool ret = false;
  if (!login(user)) {
    cout << "登陆失败,请重新登录!" << endl;
    ::system("pause");
    exit(-1);
  }
  else {
    cout << "用户" << user.id << ",您当前所在的关卡是:level-" << user.level_id << endl;
    cout << "您已登陆成功,请开始您的表演!" << endl;
    ::system("pause");
  }
  //初始化游戏背景
  IMAGE bg_img; //存储背景
  init_graph(bg_img);
  bool quit = false;
  do {
    //根据用户所在的关卡id获取关卡数据
    ret = fetch_level_info(level, user.level_id);
    if (!ret) {
      cout << "获取关卡数据失败,请重试!" << endl;
      ::system("pause");
      exit(-1);
    }
    //把数据库中的地图转换到map中
    ret = transform_map_db2array(level, map);
    if (!ret) {
      cout << "地图数据转换失败,请重试!" << endl;
      ::system("pause");
      exit(-1);
    }
    //打印道具图标
    for (int i = 0; i < level.map_row; i++) {
      for (int j = 0; j < level.map_column; j++) {
        //统计目标箱子数
        if (map[i][j] == BOX_DES) nums_hit++;
        //获取小人初始位置
        if (map[i][j] == MAN) {
          man.x = i;
          man.y = j;
        }
        putimage(START_X + j * RATIO, START_Y + i * RATIO, &images[map[i][j]]);
      }
    }
    //游戏环节
    do {
      if (_kbhit()) {//玩家按键
        char ch = _getch();
        if (ch == KEY_UP) {
          gameControl(UP, level);
        }
        else if (ch == KEY_DOWN) {
          gameControl(DOWN, level);
        }
        else if (ch == KEY_LEFT) {
          gameControl(LEFT, level);
        }
        else if (ch == KEY_RIGHT) {
          gameControl(RIGHT, level);
        }
        else if (ch == KEY_QUIT) {
          quit = true;
        }
        if (isGameOver()) {
          if (level.next_level < 1) {
            gameOverScence(&bg_img);
            quit = true;
            break;
          }
          gameNextScence(&bg_img);
          //更新用户下一关的管求爱信息
          if (update_user_level(user, level.next_level)) {
            user.level_id = level.next_level;
          }
          break;
        }
      }
      Sleep(100);
    } while (quit == false); //!quit
  } while (quit == false);
  ::system("pause");
  closegraph(); //释放资源
  return 0;
}


参考资料:

https://www.bilibili.com/video/BV1SL4y1M7yL/?spm_id_from=333.337.search-card.all.click&vd_source=12c90255e5a0009cd588cada10859bd5

目录
相关文章
|
1月前
|
API 数据库 C语言
【C/C++ 数据库 sqlite3】SQLite C语言API返回值深入解析
【C/C++ 数据库 sqlite3】SQLite C语言API返回值深入解析
170 0
|
1月前
|
开发工具 C语言 C++
CMake构建大型C/C++项目:跨平台设计与高级应用(二)
CMake构建大型C/C++项目:跨平台设计与高级应用
43 0
|
1月前
|
设计模式 测试技术 编译器
C++项目中打破循环依赖的锁链:实用方法大全(一)
C++项目中打破循环依赖的锁链:实用方法大全
79 0
|
16天前
|
存储 算法 Linux
【实战项目】网络编程:在Linux环境下基于opencv和socket的人脸识别系统--C++实现
【实战项目】网络编程:在Linux环境下基于opencv和socket的人脸识别系统--C++实现
40 6
|
9天前
|
SQL 监控 druid
Druid数据库连接池简介及应用推广(老项目翻出来做下记录)
Druid数据库连接池简介及应用推广(老项目翻出来做下记录)
|
25天前
C/C++test两步完成CMake项目静态分析
通过将C/C++test集成到CMake项目中,并根据项目的需要进行配置,可以在两步内完成CMake项目的静态分析。这样可以帮助开发人员及时发现并修复潜在的代码问题,提高代码质量和可靠性。
8 0
|
1月前
|
IDE 算法 编译器
快速掌握陌生C++项目的科学与心理学策略
快速掌握陌生C++项目的科学与心理学策略
58 0
|
1月前
|
敏捷开发 安全 API
C/C++ 工程师面试:如何精彩展示你的项目经验并获得高分
C/C++ 工程师面试:如何精彩展示你的项目经验并获得高分
73 0
|
1月前
|
消息中间件 存储 算法
【C/C++ 泡沫精选面试题04】在实际项目中,多进程和多线程如何选择?
【C/C++ 泡沫精选面试题04】在实际项目中,多进程和多线程如何选择?
43 1
|
1月前
|
编译器 持续交付 项目管理
CMake构建大型C/C++项目:跨平台设计与高级应用(三)
CMake构建大型C/C++项目:跨平台设计与高级应用
44 0

热门文章

最新文章