C语言100行代码实现推箱子

1 C语言100行代码实现推箱子

1.1 概述

 C语言是很好入门编程的一个语言,它拥有着很好的移植性,基本上所有的平台都支持C语言编程。有些C语言基础的你,是不是也很想做一个项目来检验一下自己的学习成果呢?那小游戏推箱子确实是一个很不错的选择。让我们一起动手,写一个属于自己的推箱子小游戏吧!

1.2 知识点

  本文读者可以学习到:

  • 多维数组
  • 基础指针
  • 简单函数
  • 循环结构

2 构思游戏结构

2.1 数字标签

  在推箱子游戏中,所出现的物体有 : 1. 墙 2. 人 3. 空气 4. 箱子 5. 目标 6. 箱子和目标重合 7. 人和目标重合 共7种物体。为了方便在程序中表达他们,我们需要给他们都打上数字标签。

#define AIR 0
#define BOX 1
#define MAN 2
#define WALL 3
#define AIM 4
#define BOX_AIM 5
#define MAN_AIM 6

  细心的读者不难发现,我们的数字标签不是随便打的,而是有一定规律的。BOX_AIM刚刚好就等于 BOX+AIM ; 同样的 MAN_AIM 也刚刚好就等于 MAN + AIM ; 而 AIR 也正好为 0 ,这使得无论什么加上AIR 都会等于那个东西 。这样可以给书写代码带来极大的方便,也可以让代码的运行效率带来质的飞跃。

2.2 地图

  在推箱子游戏中,丰富的地图给用户带来了不一样的游戏体验。我们需要一个容器来存放地图。而数组确实是一个不错的选择。推箱子是一个二维游戏,所以我们可以选择一个二维数组来存放我们游戏的地图。

#define _X 15
#define _Y 10
int map_[ _X ][ _Y ] = {
	{3,3,3,3,3,3,3,3,3,3},
	{3,0,0,0,0,0,0,0,4,3},
	{3,0,1,3,0,0,0,0,0,3},
	{3,0,0,3,0,0,0,0,3,3},
	{3,0,0,3,0,0,0,0,4,3},
	{3,0,0,0,4,3,0,0,3,3},
	{3,0,0,0,3,3,0,0,0,3},
	{3,0,0,0,0,3,0,0,0,3},
	{3,0,0,0,0,3,3,0,0,3},
	{3,0,0,3,0,0,0,0,0,3},
	{3,0,0,0,1,0,3,0,0,3},
	{3,0,0,0,0,3,3,0,0,3},
	{3,0,0,0,0,0,3,1,0,3},
	{3,2,0,0,0,0,3,0,0,3},
	{3,3,3,3,3,3,3,3,3,3},
	};

 因为地图在说有地方都会使用到,所以我们在这里将map_定义为全局变量,放在函数的外面,并且为了方便文档的修改,用了两个宏定义_X , _Y来定义地图的大小。

2.3 流程图

  现在所有需要的东西都差不多准备好了,我们应该在写程序之前,构思好流程图,以保证我们的思路清晰,书写程序不容易犯错。绘制流程图可以有效的避免在书写程序的时候出现一些不必要的麻烦。
绘制流程图 :
在这里插入图片描述
 流程图绘制完毕了,该写程序了!!!???总感觉少了什么,感觉这游戏有始没有终啊!所以经过略微的改进以后,得到了下面的流程图:
在这里插入图片描述
  这样游戏就 “有始有终” 了!

3 书写程序

  现在程序流程都差不多构建好了;我们该书写程序了

3.1 书写主函数与全局变量

  根据流程图,我们书写的主函数如下:

#define MAN 2
#define WALL 3
#define BOX 1
#define AIR 0
#define AIM 4
#define MAN_AIM 6
#define BOX_AIM 5
#define _X 15
#define _Y 10

int map_[ _X ][ _Y ] = {
	{3,3,3,3,3,3,3,3,3,3},
	{3,0,0,0,0,0,0,0,4,3},
	{3,0,1,3,0,0,0,0,0,3},
	{3,0,0,3,0,0,0,0,3,3},
	{3,0,0,3,0,0,0,0,4,3},
	{3,0,0,0,4,3,0,0,3,3},
	{3,0,0,0,3,3,0,0,0,3},
	{3,0,0,0,0,3,0,0,0,3},
	{3,0,0,0,0,3,3,0,0,3},
	{3,0,0,3,0,0,0,0,0,3},
	{3,0,0,0,1,0,3,0,0,3},
	{3,0,0,0,0,3,3,0,0,3},
	{3,0,0,0,0,0,3,1,0,3},
	{3,2,0,0,0,0,3,0,0,3},
	{3,3,3,3,3,3,3,3,3,3},
	};
int *place_man;		// 人的位置
int all_aim = 0;	// 目标的数量

void Display();	// 更新屏幕
void Key();		// 获取用户操作并更新地图
void Init();		// 初始化
char Win();		// 判断现在是否胜利

int main(){
	Init();
	while ( Win() ) {
		Display();
		Key();
	} 
	return 0;
}

  让我们来仔细分析一下这段代码,全局变量和宏定义就不过多的解释 , 让我们把目光转义到main函数。这个函数由各个子函数组成, 只把核心流程包括在内,其他功能的实现都使用子函数来实现,不会显得主函数长,阅读困难。阅读起来越简单,修改起Bug也会更简单;主函数以Init()函数开始,以Win()函数结尾。没有一行代码是多余的。可读性相对较高。主函数写完了,我们该去逐一实现其他的函数 ;

3.2 子函数的书写

3.2.1 Display()

Display()函数的作用是更新屏幕,清屏并打印地图。首先我们得知道这个函数要完成的任务实际上有两个 :

  1. 清屏
  2. 打印地图

所涉及知识点:

  1. cmd 命令在 C语言的实现
  2. 用循环遍历多维数值
  清屏的实现

  清屏的实现实际上很多新手都很懵逼,清屏怎么搞啊?实际上如果有些cmd基础的同学就知道控制台清屏有一个命令叫做cls 。我们最简单的实现方式就是直接调用控制台命令cls就可以了。但是cmd 的命令怎么在C语言使用呢?实际上C语言在Windows平台上有一个接口在头文件windows.h里面 ,函数是 system(const char *_Command)。这样子清屏就可以使用 system("cls");实现了。

  打印地图的实现

  打印屏幕的话我们只需要遍历整个地图,并将他们逐个打印出来就可以了,因为这个地图是二维的,所以我们打印地图的时候就可以使用两个循环来便利整个地图;

  Display(); 函数代码如下:
void Display(){
	system("cls");
	for(int i = 0; i < _X; i ++){
		for(int j = 0; j < _Y; j ++){
			printf( "%d", map_[i][j] );
		}
		printf("\n");
	}
}

  这样就轻松实现了清屏并打印新的地图的功能;

3.2.2 Key()

Key()函数的作用是获取用户操作并根据操作更新地图。首先我们得知道这个函数要完成的任务实际上也有两个 :

  1. 获取用户操作
  2. 根据用户操作更新地图

所涉及知识点:

  1. 指针
  2. swtich语句
 获取用户按键的实现

  我们要获取用户的操作,如按w代表上,按s代表下,按a代表左,按d代表右。如果是用scanf()函数的话,那么输入完成后就需要按回车来确定。在游戏中,这样子的操作必然是一件愚蠢的事。所以我们必须要有一个方法来获取用户的输入并且不用按回车。是的,C语言有这样的方法:getch()这个函数是一个不回显函数,当用户按下某个字符时,函数自动读取,无需按回车,有的C语言命令行程序会用到此函数做游戏,但是这个函数并非标准函数,要注意移植性! 这个函数在conio.h 这个文件中。

 根据用户操作更新地图的实现

  我们获取到了用户所要进行的操作,我们就需要根据用户的操作来更新地图了,这一个功能的实现可以说是比较难的,同学们可得认真理解了。用户可以有4种不同的操作,每种操作都对应着不同的效果。而这种情况switch无疑是一个很好的选择。还记得全局变量中有一个前面还没有用到的变量吗?int *place_man; // 人的位置 是的,就是这一句place_man是一个int类型的指针,这个指针指向了角色,也就是人。玩家的操作就是控制这个人的,所以我们需要一个指针保存下这个人的位置,而根据玩家操作进行更新地图。如果玩家要像右走的话,我们首先得获取到人右边的东西是什么,所以这里还需要一个指针,这个指针指向玩家需要移动的位置。如果这个位置是空气、目标的话,人就可以走过去。如果是墙的话人就不能过去。如果是箱子或者是和目标重合的箱子的话就复杂一些了,我们需要获取箱子将要移动的地方的位置。如果箱子要移动地方的位置上是空气或者目标的话,就可以人推着箱子过去,如果是箱子、墙或者目标和箱子重合的话,人就不能推着箱子过去。
  我们整理一下,所以说我们除了需要有一个指针指向人以外,还需要两个指针,分别指向人要移动方向的两个位置。并且根据这两个位置对包括人在内的三个位置进行操作。
绘制流程图如下:
在这里插入图片描述

  Key(); 函数代码如下:
void Key(){
	char key;
	int *ft;
	int *sd;
	
	key = getch();
	
	switch (key) {
		case 'w':
		case 'W':
			ft=place_man - _Y;
			sd=place_man - 2 * _Y;
			break;
		case 's':
		case 'S':
			ft=place_man + _Y;
			sd=place_man + 2 * _Y;
			break;
		case 'a':
		case 'A':
			ft=place_man - 1;
			sd=place_man - 2;
			break;
		case 'd':
		case 'D':
			ft=place_man + 1;
			sd=place_man + 2;
			break;
		default :
			return;
	}
	
	switch (*ft) {
		case WALL:
			return;
			break;
		case AIR:
		case AIM:
			*ft += MAN;
			*place_man = *place_man - MAN;
			place_man = ft;
			return;
			break;
		default :
			break;
	}
	
	switch (*sd) {
		case BOX:
		case BOX_AIM:
		case WALL:
			return;
			break;
		case AIR:
		case AIM:
			*ft -= BOX;
			*ft += MAN;
			*sd += BOX;
			*place_man -= MAN;
			place_man = ft;
			return;
			break;
		default :
			return;
	}
}

3.2.3 Win()

Win()函数的功能就比较简单了,只需要实现判断箱子是否都在目标上BOX_AIM 的数量是否与"all_aim"相等即可。

所涉及知识点:

  1. 函数
  Win(); 函数代码如下:
char Win(){
	int boxAimNum=0;
	for(int i=0; i < _X; i++){
		for(int j=0; j < _Y; j++){
			if(BOX_AIM == map_[i][j]){
				boxAimNum++;
			}
		}
	}
	if(boxAimNum == all_aim){
		printf("YOU ARE WINNING");
		return 1;
	} else {
		return 0 ;
	}
}

3.2.4 Init()

Init()函数的功能是为整个程序做准备,我们有两个目标:1.获取到人的位置 2. 获取到共有多少个目标

所涉及知识点:

  1. 没有什么知识点,都讲过了,注意不要遗漏就好了。
  Init(); 函数代码如下:
void Init(){
	for(int i=0;i<_X;i++){
		for(int j=0;j<_Y;j++){
			if( map_[i][j] == MAN || map_[i][j] == MAN_AIM ){
				place_man = &map_[i][j];
			}
			if( map_[i][j] == AIM || map_[i][j] == BOX_AIM || map_[i][j] == MAN_AIM){
				all_aim ++;
			}
		}
	}
}

3.3 整理代码

现在推箱子功能就实现了,我们把代码整理好并编译,就可以开始玩了。
整理代码如下 :

#include <stdio.h>
#include <conio.h>
#include <windows.h>

#define MAN 2
#define WALL 3
#define BOX 1
#define AIR 0
#define AIM 4
#define MAN_AIM 6
#define BOX_AIM 5
#define _X 15
#define _Y 10

int map_[ _X ][ _Y ] = {
	{3,3,3,3,3,3,3,3,3,3},
	{3,0,0,0,0,0,0,0,4,3},
	{3,0,1,3,0,0,0,0,0,3},
	{3,0,0,3,0,0,0,0,3,3},
	{3,0,0,3,0,0,0,0,4,3},
	{3,0,0,0,4,3,0,0,3,3},
	{3,0,0,0,3,3,0,0,0,3},
	{3,0,0,0,0,3,0,0,0,3},
	{3,0,0,0,0,3,3,0,0,3},
	{3,0,0,3,0,0,0,0,0,3},
	{3,0,0,0,1,0,3,0,0,3},
	{3,0,0,0,0,3,3,0,0,3},
	{3,0,0,0,0,0,3,1,0,3},
	{3,2,0,0,0,0,3,0,0,3},
	{3,3,3,3,3,3,3,3,3,3},
	};
int *place_man;
int all_aim = 0;

void Display();
void Key();
void Init();
char Win();

int main(){
	Init();
	while(1){
		Display();
		Win();
		Key();
	}
	
	
}

void Init(){
	for(int i=0;i<_X;i++){
		for(int j=0;j<_Y;j++){
			if( map_[i][j] == MAN || map_[i][j] == MAN_AIM ){
				place_man = &map_[i][j];
			}
			if( map_[i][j] == AIM || map_[i][j] == BOX_AIM || map_[i][j] == MAN_AIM){
				all_aim ++;
			}
		}
	}
}

void Key(){
	char key;
	int *ft;
	int *sd;
	
	key = getch();
	
	switch (key) {
		case 'w':
		case 'W':
			ft=place_man - _Y;
			sd=place_man - 2 * _Y;
			break;
		case 's':
		case 'S':
			ft=place_man + _Y;
			sd=place_man + 2 * _Y;
			break;
		case 'a':
		case 'A':
			ft=place_man - 1;
			sd=place_man - 2;
			break;
		case 'd':
		case 'D':
			ft=place_man + 1;
			sd=place_man + 2;
			break;
		default :
			return;
	}
	
	switch (*ft) {
		case WALL:
			return;
			break;
		case AIR:
		case AIM:
			*ft += MAN;
			*place_man = *place_man - MAN;
			place_man = ft;
			return;
			break;
		default :
			break;
	}
	
	switch (*sd) {
		case BOX:
		case BOX_AIM:
		case WALL:
			return;
			break;
		case AIR:
		case AIM:
			*ft -= BOX;
			*ft += MAN;
			*sd += BOX;
			*place_man -= MAN;
			place_man = ft;
			return;
			break;
		default :
			return;
	}
	
}

void Display(){
	system("cls");
	for(int i=0;i<_X;i++){
		for(int j=0;j<_Y;j++){
			printf( "%d",map_[i][j] );
		}
		printf("\n");
	}
}

char Win(){
	int boxAimNum=0;
	for(int i=0; i < _X; i++){
		for(int j=0; j < _Y; j++){
			if(BOX_AIM == map_[i][j]){
				boxAimNum++;
			}
		}
	}
	if(boxAimNum == all_aim){
		printf("YOU ARE WINNING");
		return 1;
	} else {
		return 0 ;
	}
}

3.4 小小美化

  我们可以通过修改 Display 函数以达到美化的目的
  美化后Display():

void Display(){
	system("cls");
	for(int i=0;i<_X;i++){
		for(int j=0;j<_Y;j++){
			switch( map_[i][j] ){
				case WALL:
					printf("M  ");
					break;
				case MAN:
					printf("!  ");
					break;
				case AIR:
					printf("   ");
					break;
				case AIM:
					printf("*  ");
					break;
				case BOX:
					printf("X  ");
					break;
				case MAN_AIM:
					printf("!  ");
					break;
				case BOX_AIM:
					printf("$  ");
					break;
				default :
					printf("   ")
					break;
			}
		}
		printf("\n");
	}
}

4联系作者

本文作者为 > 【谢玄.】 Mr-XieXuan < 于 2022/7/7/3:00 发布于 CSDN 。

E-mail: [ Mr_Xie_@outlook.com ]
GitHub: [ https://github.com/MR-XieXuan }
个人私站: [ https://main.mrxie.xyz/ ]

 如果本文对您有帮助的话,可以给本文点一个赞👍或者是收藏本文📧。也可以点击关注Follow我。你的每一个赞可以给作者非常大的鼓励。
 如果遇到困难,欢迎联系作者,你可以私聊作者或者添加作者QQ、发送电子邮件向作者寻求帮助。也可以在下方评论区向大家提问。你的问题如果在评论区被解决也可以给其他遇到同样问题的一个参考。
作者不易,期待你的关注❤。

  • 9
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

谢玄.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值