✍ 个人博客:https://blog.csdn.net/Newin2020?spm=1011.2415.3001.5343
📚 专栏目标:C++项目集合
📣 专栏定位:为刚学完C++或还在学习C++过程中的小伙伴提供练手的项目,帮助大家快速熟悉C++语法。
❤️ 如果有收获的话,欢迎点赞👍收藏📁,您的支持就是我创作的最大动力💪
📝 参考视频:B 站 c++ 推箱子项目教程
🎏 唠叨唠叨:在这个专栏里,我将会整理一些有趣的C++项目来给大家练手~
项目实现 - 推箱子
一、项目需求
规则如下:
- 箱子只能推动而不能拉动。
- 如果箱子前一格是地板或箱子目的地,则可以推动一个箱子往前走一格,如果箱子已经在箱子目的地则不能再推动。
- 推箱子的小人可以从箱子目的地上经过。
- 小人可以将目的地上的箱子推开。
- 注意不要把箱子推到死角上,不然就无法再推动它了。
- 所有箱子都成功推到箱子目的地,游戏结束,过关成功!
二、项目实现
地图初始化
这里的 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
//...
推箱子控制
难点:
- 小人可以从箱子目的地上经过。
- 小人可以将箱子从目的地上推开。
实现上面两个需求我们可以设置额外的两个变量:
- 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_USER
和 DB_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
调用地图转换接口(另外,记得将该文件中其它用到 LINE
和 COLUMN
的地方改成从数据库获取到的行数 level.map_row
和 level.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