项目实现 - 推箱子
一、项目需求
规则如下:
箱子只能推动而不能拉动。
如果箱子前一格是地板或箱子目的地,则可以推动一个箱子往前走一格,如果箱子已经在箱子目的地则不能再推动。
推箱子的小人可以从箱子目的地上经过。
小人可以将目的地上的箱子推开。
注意不要把箱子推到死角上,不然就无法再推动它了。
所有箱子都成功推到箱子目的地,游戏结束,过关成功!
二、项目实现
地图初始化
这里的 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; }
参考资料: