用C++实现一个烧脑的推箱子游戏

2023-10-17 22:50
文章标签 c++ 实现 游戏 箱子 烧脑

本文主要是介绍用C++实现一个烧脑的推箱子游戏,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

前言

一、准备工作 

1.1 工具安装

1.2 前置知识 

二、实现思路

2.1 宏定义

2.2 游戏场景的搭建

2.2.1 地图

2.2.2 箱子坐标

2.2.3 角色坐标

2.3 类的属性与行为的确定

2.4 地图行为的实现

2.4.1 初始化地图数据

2.4.2 绘制地图

2.5 箱子行为的实现

2.5.1 初始化箱子数据

2.5.2 绘制箱子

2.5.3 判断是否胜利

2.6 角色行为的实现

2.6.1 初始化角色数据

2.6.2 绘制角色

2.6.3  角色移动 

三、整体代码

四、游戏下载与安装 

总结


前言

  推箱子游戏最早起源于日本,由电子游戏公司Thinking Rabbit在1982年推出的《Sokoban》。这个游戏的名字来自于日语“倉庫番”,意为“仓库管理员”。游戏在休闲的同时又需要开动我们的脑筋,关卡设计既简单又富有挑战性,能够很好地锻炼我们的逻辑推理能力。下面,让我们用C++来复刻这样一款既简单又烧脑的推箱子游戏吧!

  运行效果演示:

一、准备工作 

1.1 工具安装

1. 安装 Visual Studio

2. 安装 easyx 图形库

1.2 前置知识 

1. C++的相关语法知识。比如二维数组、类、友元、文件读写操作等等。 

2. 熟练使用easyx图形库提供的API。比如贴图、背景音乐、键盘消息等等。

  如果大家不太了解easyx图形库的安装以及使用,可以参考以下内容:

  • easyx图形库一基本绘图与文字绘制_哔哩哔哩_bilibili
  • C语言图形库EasyX安装及基本使用_easyx图形库_@梅哲仁.的博客-CSDN博客

二、实现思路

2.1 宏定义

  在正式写代码之前,我们先要考虑一下哪些数据是在整个代码体系中都有可能会用的上的。在这里,我想到的是窗口的宽度和高度、每一个格子的宽度以及箱子的最大数目。具体代码如下:

//定义窗口宽度和高度
#define W 640
#define H 640
//定义每个格子的宽度
#define SIZE 40
//箱子最大数目
#define MAXNUM 30

2.2 游戏场景的搭建

  我们知道,除了人物和箱子,这个游戏包含了墙体、目的地、空地以及游戏场景以外的背景。那么,这些数据我们应该如何存储呢?我这里采用txt文件来存储这些内容,这样做的好处就是可以动态地调整自己的地图以及创建新的地图和删除已有的地图。

  我们首先建一个文件夹level,将关卡数据存在这个文件夹当中。每个关卡包含有三个文件,分别为“数字_map.txt”、“数字_box.txt”、“数字_role.txt”。比如,第1关的三个文件是1_map.txt、1_box.txt以及1_role.txt,第2关的三个文件是2_map.txt、2_box.txt以及2_role.txt......这样,我们就可以通过输入的数字动态地访问到不同的关卡了。

2f4915ce859f4f24b9d2d53f281bab5c.png

   介绍了每个关卡所包含的三个文件,下面我们就来说说这三个文件分别是用来存储什么数据的,下面以第1关的文件为例。

2.2.1 地图

   在1_map.txt文件中,存储了墙体、目的地、游戏去外面、空地这四个数据。对于特定的游戏数据,我是这样定义的:

//游戏场景定义
#define AIR   '0'						//游戏区外面
#define SPACE '1'						//空地		
#define BOX   '2'						//箱子		
#define WALL  '3'						//墙体		
#define DEST  '4'						//目的地   

54bb3a1552fe4beaa4e4834ab2b09538.png

  在1_map.txt中存储的数据形状为一个矩阵,到时候初始化地图数据的时候就可以将这里面的数据读取到一个二维数组当中,然后再通过二维数组中存储的不同的数据绘制不同的图案。

2.2.2 箱子坐标

  1_box.txt文件中存储的是箱子的坐标,每一行存储的一个箱子的坐标,第一个表示的是x坐标,第二个表示的是y坐标。

86b8df510be349cd936c7427853168a6.png

  可以看到,文件中存储了一个6和一个7。当读取到这两个数后,我们把它们乘上每个格子的宽度SIZE 就能得到箱子最终呈现在界面上的位置。

2.2.3 角色坐标

  1_role.txt文件中存储的是角色初始的坐标,与1_box.txt文件中存储的内容类似,第一行存储的是人物的初始x坐标,第二行存储的是人物的初始y坐标。这两个数分别乘以每个格子的宽度SIZE 就能得到角色最终呈现在界面上的位置。

9247419f573a45c2ae7db10834b43db0.png

2.3 类的属性与行为的确定

  在这个游戏中,我们应该定义三个类,分别为地图类、箱子类和角色类。

  在地图类中,包含的属性为一个从文件当中读取地图数据的二维数组,其行为包括初始化地图数据以及绘制地图。在箱子类中,包含的属性有箱子的数目以及每个箱子的坐标,其行为包括初始化箱子数据、绘制箱子和判断游戏是否胜利。在角色类中,包含的属性为角色的坐标,其行为包括初始化角色数据、绘制角色、移动和判断移动路径上的两个箱子是否靠在一起。具体的代码如下:

//自定义坐标类型
typedef struct {int x;int y;
}locate;//存储目的地,待会用来判断是否胜利
typedef struct {int num;							//目的地的数量locate	dest[MAXNUM];				//每个目的地的坐标
}Dests;Dests d;								//目的地
int level;							    //关卡//定义地图的类
class Map {friend class Person;
public:Map();							    //初始化地图数据void Draw();					    //绘制地图		
private:char map[H / SIZE+1][W / SIZE]; 	//地图数据(不包括人物和箱子)
};//定义箱子的类
class  Box{friend class Person;
public:Box();								//初始化箱子数据void Draw(Dests& d);				//绘制箱子	bool IsWin(Map& m);					//用来判断是否胜利
private:int num;							//箱子的数目locate box[MAXNUM];					//每个箱子的坐标
};//定义角色的类
class Person {
public:Person();							//初始化角色数据void Draw();						//绘制角色void Move(Map& m, Box& b);			//移动bool IsBox(Box& b, int x, int y);	//用来判断移动路径上的两个箱子是否靠在一起
private:int x, y;							//角色的坐标
};

2.4 地图行为的实现

2.4.1 初始化地图数据

  在初始化地图数据的时候,我们需要读取前面所提到的文件当中的数据,然后将这个数据存储到地图的二维数组当中。在全局里,我们还定义了存储目的地的变量,当我们在文件中每读到一个目的地时,我们需要将这个其加入到存储目的地的变量当中去。具体代码如下:

//初始化地图数据
Map::Map() {ifstream ifs;char str[20];sprintf(str, "./level/%d_map.txt", level);ifs.open(str, ios::in);int i = 0;while (ifs >> this->map[i]) {i++;}ifs.close();for (int i = 0; i < H / SIZE; i++) {for (int j = 0; j < W / SIZE; j++) {if (this->map[i][j] == '4') {d.num++;d.dest[d.num - 1].x = j * SIZE;d.dest[d.num - 1].y = i * SIZE;}}}
}

2.4.2 绘制地图

  在这里,我们遍历地图中的二维数组,绘制相应的图片即可。但是,需要注意一点的是,我们这里需要实现一个透明贴图的效果,否则观感会有点差。

  关于透明贴图:1.关于Easyx如何显示透明无背景贴图

  2.easyx图形库-----贴图技巧之透明贴图与位运算(与运算、或运算、异或运算)

  我也在网上找了一份tool.h的头文件来实现透明贴图的效果。tool.h中的具体代码如下:

#pragma once
#include<easyx.h>
void drawImg(int x, int y, IMAGE* src) {DWORD* pwin = GetImageBuffer();DWORD* psrc = GetImageBuffer(src);int win_w = getwidth();int win_h = getheight();int src_w = src->getwidth();int src_h = src->getheight();//计算 贴图的实际长宽int real_w = (x + src_w > win_w) ? win_w - x : src_w;int real_h = (y + src_h > win_h) ? win_h - y : src_h;if (x < 0) { psrc += -x;            real_w -= -x;   x = 0; }if (y < 0) { psrc += (src_w * -y);  real_h -= -y;   y = 0; }//修正贴图起始位置pwin += (win_w * y + x);//实现透明贴图for (int iy = 0; iy < real_h; iy++) {for (int ix = 0; ix < real_w; ix++) {byte a = (byte)(psrc[ix] >> 24);if (a > 100) {pwin[ix] = psrc[ix];}}//换到下一行pwin += win_w;psrc += src_w;}
}

   绘制地图的具体代码如下:

//绘制地图
void Map::Draw() {IMAGE bk;loadimage(&bk, "./bk.jpg", W, H);putimage(0, 0, &bk);for (int i = 0; i < H / SIZE; i++) {for (int j = 0; j < W / SIZE; j++) {int x = j * SIZE;int y = i * SIZE;//空地if (this->map[i][j] == SPACE) {setfillcolor(RGB(210, 210, 210));fillrectangle(x, y, (x + SIZE), (y + SIZE));}//墙体if (this->map[i][j] == WALL) {IMAGE a;loadimage(&a, "./wall.png", SIZE, SIZE);putimage(x, y, &a);}//目的地if (this->map[i][j] == DEST) {setfillcolor(RGB(210, 210, 210));fillrectangle(x, y, (x + SIZE), (y + SIZE));IMAGE a;loadimage(&a, "./dest.png", SIZE, SIZE);drawImg(x, y, &a);//调用"tool.h"头文件当中的接口实现透明贴图}}}
}

2.5 箱子行为的实现

2.5.1 初始化箱子数据

  在初始化箱子数据的时候,我们需要从文件当中读取箱子的坐标。每读取到一个箱子,箱子数量加一,并且需要将这个箱子的坐标存储起来。具体代码如下:

//初始化箱子数据
Box::Box() {this->num = 0;ifstream ifs;char str[20];sprintf(str, "./level/%d_box.txt", level);ifs.open(str, ios::in);char tmp[20];int i = 0;int j = 0;while (ifs >> tmp) {	if (i == 0) {this->box[j].x = atoi(tmp)*SIZE;}if (i == 1) {this->box[j].y = atoi(tmp)*SIZE;}i++;if (i == 2) {i = 0;j++;this->num++;}}ifs.close();
}

2.5.2 绘制箱子

  箱子的绘制分为两种情况,第一种是在空地上的箱子,第二种是在目的地上的箱子,这两个箱子的贴图需要设置成不一样的。我们需要遍历存储目的地数据的全局变量,以此确定一个箱子是否在目的地。具体代码如下:

//绘制箱子
void Box::Draw(Dests& d) {for (int i = 0; i < this->num; i++) {IMAGE a;int status = 0;for (int m = 0; m < d.num; m++) {if (d.dest[m].x == this->box[i].x && d.dest[m].y == this->box[i].y) {loadimage(&a, "./box_ok.png", SIZE, SIZE);drawImg(this->box[i].x, this->box[i].y, &a);status = 1;break;}}if (status == 0) {loadimage(&a, "./box.png", SIZE, SIZE);drawImg(this->box[i].x, this->box[i].y, &a);}}
}

2.5.3 判断是否胜利

  我们用一个局部变量count来计数,将其初始化为0,遍历箱子与目的地的坐标,如果有一个箱子与其中一个目的地重合,就让count加一,当count的值等于箱子的数目时,说明全部箱子都到达了目的地,游戏胜利,否则游戏还未胜利。具体代码如下:

//用来判断是否胜利
bool Box::IsWin(Map& m) {int count = 0;for (int i = 0; i < d.num; i++) {for (int j = 0; j < this->num; j++) {if (this->box[j].x == d.dest[i].x && this->box[j].y == d.dest[i].y) {count++;}}}if (count == d.num) {return true;}return false;
}

2.6 角色行为的实现

2.6.1 初始化角色数据

  在每一关开始时,我们需要从文件当中读取角色的初始坐标,然后将坐标赋值给角色类中的坐标属性。具体代码如下:

//初始化角色数据
Person::Person() {ifstream ifs;char str[20];sprintf(str, "./level/%d_role.txt", level);ifs.open(str, ios::in);char tmp[20];int i = 0;while (ifs >> tmp) {if (i == 0) {this->x = atoi(tmp) * SIZE;}if (i == 1) {this->y = atoi(tmp) * SIZE;}i++;}ifs.close();
}

2.6.2 绘制角色

  我们只需要知道角色的坐标,然后根据坐标将角色的贴图绘制上去即可。不过这里需要注意的是,我们在贴图是也需要用透明贴图,否则影响观感。具体代码如下:

//绘制角色
void Person::Draw() {IMAGE a;loadimage(&a, "./one.png", SIZE, SIZE);drawImg(this->x, this->y, &a);//"tool.h"中的实现透明贴图的接口函数
}

2.6.3  角色移动 

  角色可以移动的情况有:

  1. 角色移动方向是空地。
  2. 角色移动方向是箱子,且箱子在角色的移动方向上既没有墙体也没有箱子。

  举个例子,角色坐标向上移动一格会碰到箱子,这时候我们应该判断角色可不可以向上移动一格。怎么判断呢?我们需要让箱子的坐标也向上移动一格,判断移动后的坐标是否与某个箱子的坐标重合或者与墙体坐标重合,若重合,则角色和箱子不能移动;若不重合,则角色与箱子都能向上移动一格。 角色移动的具体代码如下:

/移动
void Person::Move(Map& m,Box& b) {//获取人物在二维数组中的坐标int j = this->x / SIZE;int i = this->y / SIZE;//用来判断前面是否有箱子int status = 0;//按下移动键if (GetAsyncKeyState('W')) {	for(int n = 0; n < b.num; n++) {//如果前面有箱子if (b.box[n].x == this->x && b.box[n].y == this->y - SIZE) {status = 1;//如果箱子后面不靠墙也不靠箱子if (m.map[i-2][j] != WALL&&!this->IsBox(b,b.box[n].x,b.box[n].y-SIZE)) {b.box[n].y -= SIZE;this->y -= SIZE;}}}//空地移动if ((m.map[i - 1][j] == SPACE || m.map[i - 1][j] == DEST)&&(status!=1)) {this->y -= SIZE;}return;}if (GetAsyncKeyState('S') ) {	for (int n = 0; n < b.num; n++) {//如果前面有箱子if (b.box[n].x == this->x && b.box[n].y == this->y + SIZE) {status = 1;//如果箱子后面不靠墙也不靠箱子if (m.map[i + 2][j] != WALL && !this->IsBox(b, b.box[n].x, b.box[n].y + SIZE)) {b.box[n].y += SIZE;this->y += SIZE;}}}//空地移动if ((m.map[i + 1][j] == SPACE || m.map[i + 1][j] == DEST) && (status != 1)) {this->y += SIZE;}return;}if (GetAsyncKeyState('A')) {for (int n = 0; n < b.num; n++) {//如果前面有箱子if (b.box[n].y == this->y && b.box[n].x == this->x - SIZE) {status = 1;//如果箱子后面不靠墙也不靠箱子if (m.map[i][j-2] != WALL && !this->IsBox(b, b.box[n].x-SIZE, b.box[n].y )) {b.box[n].x -= SIZE;this->x -= SIZE;}}}//空地移动if ((m.map[i][j-1] == SPACE || m.map[i][j-1] == DEST) && (status != 1)) {this->x -= SIZE;}return;}if (GetAsyncKeyState('D')) {for (int n = 0; n < b.num; n++) {//如果前面有箱子if (b.box[n].y == this->y && b.box[n].x == this->x + SIZE) {status = 1;//如果箱子后面不靠墙也不靠箱子if (m.map[i][j + 2] != WALL && !this->IsBox(b, b.box[n].x+SIZE, b.box[n].y )) {b.box[n].x += SIZE;this->x += SIZE;}}}//空地移动if ((m.map[i][j+1] == SPACE || m.map[i][j+1] == DEST) && (status != 1)) {this->x += SIZE;}return;}
}//用来判断移动路径上的两个箱子是否靠在一起
bool Person::IsBox(Box& b, int x, int y) {for (int i = 0; i < b.num; i++) {if (b.box[i].x == x && b.box[i].y == y)return true;}return false;
}

三、整体代码

  box.cpp:

#include<iostream>
using namespace std;
#include<easyx.h>
#include<fstream>
#include<stdio.h>
#include"tool.h"
#include<mmsystem.h>
#pragma comment(lib,"winmm.lib")//游戏场景定义
#define AIR   '0'						//游戏区外面
#define SPACE '1'						//空地		
#define BOX   '2'						//箱子		
#define WALL  '3'						//墙体		
#define DEST  '4'						//目的地    //定义窗口宽度和高度
#define W 640
#define H 640
//定义每个格子的宽度
#define SIZE 40
//箱子最大数目
#define MAXNUM 30//自定义坐标类型
typedef struct {int x;int y;
}locate;//存储目的地,待会用来判断是否胜利
typedef struct {int num;							//目的地的数量locate	dest[MAXNUM];				//每个目的地的坐标
}Dests;Dests d;								//目的地
int level;							    //关卡//定义地图的类
class Map {friend class Person;
public:Map();							    //初始化地图数据void Draw();					    //绘制地图		
private:char map[H / SIZE+1][W / SIZE]; 	//地图数据(不包括人物和箱子)
};//定义箱子的类
class  Box{friend class Person;
public:Box();								//初始化箱子数据void Draw(Dests& d);				//绘制箱子	bool IsWin(Map& m);					//用来判断是否胜利
private:int num;							//箱子的数目locate box[MAXNUM];					//每个箱子的坐标
};//定义角色的类
class Person {
public:Person();							//初始化角色数据void Draw();						//绘制角色void Move(Map& m, Box& b);			//移动bool IsBox(Box& b, int x, int y);	//用来判断移动路径上的两个箱子是否靠在一起
private:int x, y;							//角色的坐标
};int main() {int n;
a:cout << "请输入关卡(1-6):";cin >> n;if (n < 1 || n>6) {cout << "输入错误!"<<endl;goto a;}level = n - 1;initgraph(W, H,0);//背景音乐mciSendString("open bk.mp3", 0, 0, 0);mciSendString("play bk.mp3 repeat", 0, 0, 0);
table://关卡设置level++;if (level == 7) {closegraph();cout << "恭喜通关!" << endl;return 0;}char str[10];sprintf(str, "第%d关", level);Map m;Box box;Person one;//游戏while (1) {one.Move(m,box);BeginBatchDraw();cleardevice();m.Draw();outtextxy(5, 5, str);box.Draw(d);one.Draw();EndBatchDraw();FlushBatchDraw();if (box.IsWin(m)) {//WriteLevel(level);break;}Sleep(80);};d.num = 0;goto table;return 0;
}//初始化地图数据
Map::Map() {ifstream ifs;char str[20];sprintf(str, "./level/%d_map.txt", level);ifs.open(str, ios::in);int i = 0;while (ifs >> this->map[i]) {i++;}ifs.close();for (int i = 0; i < H / SIZE; i++) {for (int j = 0; j < W / SIZE; j++) {if (this->map[i][j] == '4') {d.num++;d.dest[d.num - 1].x = j * SIZE;d.dest[d.num - 1].y = i * SIZE;}}}
}//绘制地图
void Map::Draw() {IMAGE bk;loadimage(&bk, "./bk.jpg", W, H);putimage(0, 0, &bk);for (int i = 0; i < H / SIZE; i++) {for (int j = 0; j < W / SIZE; j++) {int x = j * SIZE;int y = i * SIZE;//空地if (this->map[i][j] == SPACE) {setfillcolor(RGB(210, 210, 210));fillrectangle(x, y, (x + SIZE), (y + SIZE));}//墙体if (this->map[i][j] == WALL) {IMAGE a;loadimage(&a, "./wall.png", SIZE, SIZE);putimage(x, y, &a);}//目的地if (this->map[i][j] == DEST) {setfillcolor(RGB(210, 210, 210));fillrectangle(x, y, (x + SIZE), (y + SIZE));IMAGE a;loadimage(&a, "./dest.png", SIZE, SIZE);drawImg(x, y, &a);//调用"tool.h"头文件当中的接口实现透明贴图}}}
}//初始化箱子数据
Box::Box() {this->num = 0;ifstream ifs;char str[20];sprintf(str, "./level/%d_box.txt", level);ifs.open(str, ios::in);char tmp[20];int i = 0;int j = 0;while (ifs >> tmp) {	if (i == 0) {this->box[j].x = atoi(tmp)*SIZE;}if (i == 1) {this->box[j].y = atoi(tmp)*SIZE;}i++;if (i == 2) {i = 0;j++;this->num++;}}ifs.close();
}//绘制箱子
void Box::Draw(Dests& d) {for (int i = 0; i < this->num; i++) {IMAGE a;int status = 0;for (int m = 0; m < d.num; m++) {if (d.dest[m].x == this->box[i].x && d.dest[m].y == this->box[i].y) {loadimage(&a, "./box_ok.png", SIZE, SIZE);drawImg(this->box[i].x, this->box[i].y, &a);status = 1;break;}}if (status == 0) {loadimage(&a, "./box.png", SIZE, SIZE);drawImg(this->box[i].x, this->box[i].y, &a);}}
}//初始化角色数据
Person::Person() {ifstream ifs;char str[20];sprintf(str, "./level/%d_role.txt", level);ifs.open(str, ios::in);char tmp[20];int i = 0;while (ifs >> tmp) {if (i == 0) {this->x = atoi(tmp) * SIZE;}if (i == 1) {this->y = atoi(tmp) * SIZE;}i++;}ifs.close();
}//绘制角色
void Person::Draw() {IMAGE a;loadimage(&a, "./one.png", SIZE, SIZE);drawImg(this->x, this->y, &a);
}//移动
void Person::Move(Map& m,Box& b) {//获取人物在二维数组中的坐标int j = this->x / SIZE;int i = this->y / SIZE;//用来判断前面是否有箱子int status = 0;//按下移动键if (GetAsyncKeyState('W')) {	for(int n = 0; n < b.num; n++) {//如果前面有箱子if (b.box[n].x == this->x && b.box[n].y == this->y - SIZE) {status = 1;//如果箱子后面不靠墙也不靠箱子if (m.map[i-2][j] != WALL&&!this->IsBox(b,b.box[n].x,b.box[n].y-SIZE)) {b.box[n].y -= SIZE;this->y -= SIZE;}}}//空地移动if ((m.map[i - 1][j] == SPACE || m.map[i - 1][j] == DEST)&&(status!=1)) {this->y -= SIZE;}return;}if (GetAsyncKeyState('S') ) {	for (int n = 0; n < b.num; n++) {//如果前面有箱子if (b.box[n].x == this->x && b.box[n].y == this->y + SIZE) {status = 1;//如果箱子后面不靠墙也不靠箱子if (m.map[i + 2][j] != WALL && !this->IsBox(b, b.box[n].x, b.box[n].y + SIZE)) {b.box[n].y += SIZE;this->y += SIZE;}}}//空地移动if ((m.map[i + 1][j] == SPACE || m.map[i + 1][j] == DEST) && (status != 1)) {this->y += SIZE;}return;}if (GetAsyncKeyState('A')) {for (int n = 0; n < b.num; n++) {//如果前面有箱子if (b.box[n].y == this->y && b.box[n].x == this->x - SIZE) {status = 1;//如果箱子后面不靠墙也不靠箱子if (m.map[i][j-2] != WALL && !this->IsBox(b, b.box[n].x-SIZE, b.box[n].y )) {b.box[n].x -= SIZE;this->x -= SIZE;}}}//空地移动if ((m.map[i][j-1] == SPACE || m.map[i][j-1] == DEST) && (status != 1)) {this->x -= SIZE;}return;}if (GetAsyncKeyState('D')) {for (int n = 0; n < b.num; n++) {//如果前面有箱子if (b.box[n].y == this->y && b.box[n].x == this->x + SIZE) {status = 1; //如果箱子后面不靠墙也不靠箱子if (m.map[i][j + 2] != WALL && !this->IsBox(b, b.box[n].x+SIZE, b.box[n].y )) {b.box[n].x += SIZE;this->x += SIZE;}}}//空地移动if ((m.map[i][j+1] == SPACE || m.map[i][j+1] == DEST) && (status != 1)) {this->x += SIZE;}return;}
}//用来判断移动路径上的两个箱子是否靠在一起
bool Person::IsBox(Box& b, int x, int y) {for (int i = 0; i < b.num; i++) {if (b.box[i].x == x && b.box[i].y == y)return true;}return false;
}//用来判断是否胜利
bool Box::IsWin(Map& m) {int count = 0;for (int i = 0; i < d.num; i++) {for (int j = 0; j < this->num; j++) {if (this->box[j].x == d.dest[i].x && this->box[j].y == d.dest[i].y) {count++;}}}if (count == d.num) {return true;}return false;
}

  tool.h:

#pragma once
#include<easyx.h>
void drawImg(int x, int y, IMAGE* src) {DWORD* pwin = GetImageBuffer();DWORD* psrc = GetImageBuffer(src);int win_w = getwidth();int win_h = getheight();int src_w = src->getwidth();int src_h = src->getheight();//计算 贴图的实际长宽int real_w = (x + src_w > win_w) ? win_w - x : src_w;int real_h = (y + src_h > win_h) ? win_h - y : src_h;if (x < 0) { psrc += -x;            real_w -= -x;   x = 0; }if (y < 0) { psrc += (src_w * -y);  real_h -= -y;   y = 0; }//修正贴图起始位置pwin += (win_w * y + x);//实现透明贴图for (int iy = 0; iy < real_h; iy++) {for (int ix = 0; ix < real_w; ix++) {byte a = (byte)(psrc[ix] >> 24);if (a > 100) {pwin[ix] = psrc[ix];}}//换到下一行pwin += win_w;psrc += src_w;}
}

四、游戏下载与安装 

  由于游戏在运行的过程中需要一些贴图之类的素材,所以我们将整体的代码复制粘贴之后并不能正常运行 。我将游戏的源文件和安装包都整理出来了,具体的获取方法如下:

0584c7d002e646b9a67059eea19afa5a.png

 1. 迅雷云盘

迅雷云盘:推箱子    提取码:r6am

2. CSDN 

  大家如果不想通过云盘下载,也可以在我的博客上找到该资源进行下载。如果暂时没有看到该资源,那么资源可能还在审核当中哦~

f5b3b89419024b609cec87c96bd4b4b6.png

总结

  文章篇幅较长,如果您能耐心地看到这里,那么给自己点一个大大的赞吧!让我们一起探索代码所带来的乐趣,共同进步吧!

e60cd51b83af4135aa2bbfdcfc45141c.jpeg

   如果您觉得本篇文章不错,也请点个免费的赞支持一下博主吖~~

这篇关于用C++实现一个烧脑的推箱子游戏的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/228480

相关文章

Java实现检查多个时间段是否有重合

《Java实现检查多个时间段是否有重合》这篇文章主要为大家详细介绍了如何使用Java实现检查多个时间段是否有重合,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录流程概述步骤详解China编程步骤1:定义时间段类步骤2:添加时间段步骤3:检查时间段是否有重合步骤4:输出结果示例代码结语作

使用C++实现链表元素的反转

《使用C++实现链表元素的反转》反转链表是链表操作中一个经典的问题,也是面试中常见的考题,本文将从思路到实现一步步地讲解如何实现链表的反转,帮助初学者理解这一操作,我们将使用C++代码演示具体实现,同... 目录问题定义思路分析代码实现带头节点的链表代码讲解其他实现方式时间和空间复杂度分析总结问题定义给定

Java覆盖第三方jar包中的某一个类的实现方法

《Java覆盖第三方jar包中的某一个类的实现方法》在我们日常的开发中,经常需要使用第三方的jar包,有时候我们会发现第三方的jar包中的某一个类有问题,或者我们需要定制化修改其中的逻辑,那么应该如何... 目录一、需求描述二、示例描述三、操作步骤四、验证结果五、实现原理一、需求描述需求描述如下:需要在

如何使用Java实现请求deepseek

《如何使用Java实现请求deepseek》这篇文章主要为大家详细介绍了如何使用Java实现请求deepseek功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1.deepseek的api创建2.Java实现请求deepseek2.1 pom文件2.2 json转化文件2.2

python使用fastapi实现多语言国际化的操作指南

《python使用fastapi实现多语言国际化的操作指南》本文介绍了使用Python和FastAPI实现多语言国际化的操作指南,包括多语言架构技术栈、翻译管理、前端本地化、语言切换机制以及常见陷阱和... 目录多语言国际化实现指南项目多语言架构技术栈目录结构翻译工作流1. 翻译数据存储2. 翻译生成脚本

C++初始化数组的几种常见方法(简单易懂)

《C++初始化数组的几种常见方法(简单易懂)》本文介绍了C++中数组的初始化方法,包括一维数组和二维数组的初始化,以及用new动态初始化数组,在C++11及以上版本中,还提供了使用std::array... 目录1、初始化一维数组1.1、使用列表初始化(推荐方式)1.2、初始化部分列表1.3、使用std::

C++ Primer 多维数组的使用

《C++Primer多维数组的使用》本文主要介绍了多维数组在C++语言中的定义、初始化、下标引用以及使用范围for语句处理多维数组的方法,具有一定的参考价值,感兴趣的可以了解一下... 目录多维数组多维数组的初始化多维数组的下标引用使用范围for语句处理多维数组指针和多维数组多维数组严格来说,C++语言没

如何通过Python实现一个消息队列

《如何通过Python实现一个消息队列》这篇文章主要为大家详细介绍了如何通过Python实现一个简单的消息队列,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录如何通过 python 实现消息队列如何把 http 请求放在队列中执行1. 使用 queue.Queue 和 reque

Python如何实现PDF隐私信息检测

《Python如何实现PDF隐私信息检测》随着越来越多的个人信息以电子形式存储和传输,确保这些信息的安全至关重要,本文将介绍如何使用Python检测PDF文件中的隐私信息,需要的可以参考下... 目录项目背景技术栈代码解析功能说明运行结php果在当今,数据隐私保护变得尤为重要。随着越来越多的个人信息以电子形

使用 sql-research-assistant进行 SQL 数据库研究的实战指南(代码实现演示)

《使用sql-research-assistant进行SQL数据库研究的实战指南(代码实现演示)》本文介绍了sql-research-assistant工具,该工具基于LangChain框架,集... 目录技术背景介绍核心原理解析代码实现演示安装和配置项目集成LangSmith 配置(可选)启动服务应用场景