C language Sokoban game

Disclaimer: This article is a blogger original article, follow the CC 4.0 BY-SA copyright agreement, reproduced, please attach the original source link and this statement.
This link: https://blog.csdn.net/ZackSock/article/details/101645494

It has long been thought of doing a little game, but never had the chance to do it. Today nothing else, a fight. The process is still very smooth, the code is not very difficult. Today to share with you ~

I. INTRODUCTION

开发语言:C语言
开发工具:Dev-C++ 5.11
日期:2019年9月28日
作者:ZackSock

Do not say too much superfluous words, and look at renderings:
Here Insert Picture Description
characters in the game, boxes, walls, balls are composed of characters. By wasd keys to move, then the rule is a rule Sokoban, I will say no more.

Second, code implementation

About the code, I speak as meticulous. I hope you can understand ~

(1) Method list

//主函数
void main();

//初始化一些数据
initData();

//在控制台上打印地图
drawMap();

//向上移动
moveUp();

//向左移动
moveLeft()

//向下移动
moveDown()

//向右移动
moveRight();

These methods are by definition, but the intention is very clear, it initData may not know the specific use, but no major problems. The only problem is, on the lower left and right of the order may be a death sentence for some OCD patients, ha ha.

(2) the list of parameters

For convenience, I include and macro definitions are also placed among the list of parameters

//导入函数库
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>

//宏定义
#define WIDTH 8
#define HEIGHT 8

//定义地图数组,二维数组有两个维度,而地图也是二维的矩形
int map[HEIGHT][WIDTH] = {
	{0, 0, 1, 1, 1, 0, 0, 0},
	{0, 0, 1, 4, 1, 0, 0, 0},
	{0, 0, 1, 0, 1, 1, 1, 1},
	{1, 1, 1, 3, 0, 3, 4, 1},
	{1, 4, 0, 3, 2, 1, 1, 1},
	{1, 1, 1, 1, 3, 1, 0, 0},
	{0, 0, 0, 1, 4, 1, 0, 0},
	{0, 0, 0, 1, 1, 1, 0, 0} 
};

//人的位置,在二维地图中,我们可以用坐标表示一个人的位置,就好比经纬度
int x, y;

//箱子的个数,推箱子肯定要有箱子嘛。
int boxs;

Here few parameters, wherein the transverse as x, vertical to Y , then further specified here something about the map:

/**
*	0	表示空
*	1	表示墙
*	2	表示人
*	3	表示箱子
*	4	表示目的地(球)
*	5	表示已完成的箱子
*/

(3) Analysis of the specific function

Next we analyze one by one function.

1, main function
int main(int argc, char *argv[]) {
	char direction;		//存储键盘按的方向 
	initData();			//初始化一些数据
	
	//开始游戏的循环,这里是个死循环,每按一次按钮循环一次
	while(1){
		//每次循环的开始清除屏幕
		system("cls");
		//绘画地图
		drawMap();

		//判断,当boxs的数量0时,!0为真,然后走break跳出循环(结束游戏) 
		if(!boxs){
			break;
		}
		
		//键盘输入方向,这里使用getch,因为getch读取字符不会显示在屏幕上
		direction = getch();
		
		//用switch判断用户输入的方向
		switch(direction){
			case 'w':
				//按w时,调用向上移动函数
				moveUp();
				break;
			case 'a':
				//按a时,调用向左移动函数
				moveLeft(); 
				break;
			case 's':
				moveDown();
				break;
			case 'd':
				moveRight();
				break; 
		}
	}  
	//当跳出循环时,运行该语句,游戏结束
	printf("恭喜你完成游戏!※");
	return 0;
}

I say something about the process, out of the loop nothing special. initData () just a few simple initialization data, do not be too concerned about. Process cycle generally is as follows:

  • Clear screen
  • Mapping
  • Determine whether the end of the game
  • Button pressed by the user for feedback

Into the body of the loop, to clear the screen, and then draw the map, and then determine whether to end the game. It may not quite understand this order, here we do not consider the problem to determine the end of the game. We clear the screen and draw a map together, referred to as " redraw the map ", and the game is over judgment is not considered, then the flow is reduced to " redraw the map + respond to user actions ." Is simply, users click the button, I change the map.

InitData (2)

void initData(){
	int i, j;
	
	//加载数据时让用户等待,一般情况加载数据比较快
	printf("游戏加载中,请稍后........."); 
	
	//遍历地图中的数据
	for(i = 0; i < HEIGHT; i++){
		for(j = 0; j < WIDTH; j++){
			//遍历到2(人)时,记录人的坐标。x, y是前面定义的全局变量
			if(map[i][j] == 2){
				x = j;
				y = i;
			} 
			//遍历到3时,箱子的数目增加。boxs是前面定义的全局变量 
			if(map[i][j] == 3){
				boxs++;
			}
		}
	} 
}

This method is very simple, that is to traverse the map and location of the person and the number of initialization box. One thing to note here is that in the end the inner loop is the outer loop is still WIDTH WIDTH. Here Insert Picture Description
As shown, during traversal. The outer loop controls the number of rows, i.e. HEIGHT. So the inner loop should be WIDTH.

3, drawMap ()

void drawMap(){
	int i, j;
	for(i = 0; i < WIDTH; i++){
		for(j = 0; j < HEIGHT; j++){
			switch(map[i][j]){
				case 0:
					printf("  ");
					break;
				case 1:
					printf("■");
					break;
				case 2:
					printf("♀");
					break;
				case 3:
					printf("◆");
					break;
				case 4:
					printf("●");
					break;
				case 5:
					printf("★");
					break; 
			}
		}
		printf("\n");
	}
}

There is also very simple, the variable element in the map, and then judges the content to be outputted through the switch. Each cycle is then completed once the inner wrap.

4、moveUp()

This function is a bit more content, I want to talk about is probably thinking:

向上移有两种情况
1、前面为空白
	这种情况有两个步骤1)将人当前的位置设置为空白(0),2)再讲人前面的位置设置为人(22、前面为箱子
	当前面为箱子时有三种情况
	1、箱子前面为空白
		移动人和箱子,这个操作有三个步骤1)将人当前位置设置为空(02)将箱子位置设置为人(23)将箱子前面设置为箱子(32、箱子前面为墙
		这种情况不需要做任何操作
	3、箱子前面为终点
		这种情况有四个个步骤1)将人的位置设置为空(02)将箱子的位置设置为人(23)将终点位置设置为★(54)箱子boxs的数量减一
3、前面为墙
	这种情况最简单,不需要做任何操作
4、前面为终点
	我这里没有考虑太多,这种情况不做操作。(如果更换地图的话可能需要修改代码)

Specific code as follows, I have resolved all written in the comments which:

void moveUp(){
	//定义变量存放人物上方的坐标
	int ux, uy; 
	
	//当上方没有元素时,直接return	(其实人不可能在边缘)
	if(y == 0){
		return;
	}
	
	//记录上方坐标,x为横,y为纵,所有ux = x, uy = y - 1;
	ux = x;
	uy = y - 1; 
	
	//上方为已完成的箱子
	if(map[uy][ux] == 5){
		return;
	} 
	//假设上方为墙,直接return,这个和上面的判断可以合在一起,这里为了看清楚分开写 
	if(map[uy][ux] == 1){
		return;
	}
	
	//假设上方为箱子
	if(map[uy][ux] == 3){
		//判断箱子上方是否为墙 
		if(map[uy - 1][ux] == 1){
			return;
		}
		
		//判断箱子上方是否为终点
		if(map[uy - 1][ux] == 4){
			//将箱子上面内容赋值为5★ 
			map[uy - 1][ux] = 5;
			map[uy][ux] = 0;
					
			//箱子的数目减1	
			boxs--; 
		}else{
			//移动箱子
			map[uy - 1][ux] = 3;
		}
	}
	//当上面几种return的情况都没遇到,人肯定会移动,移动操作如下
	map[y][x] = 0;
	map[uy][ux] = 2;
	//更新人的坐标
	y = uy; 
} 

This is a direction, the direction of the other issues to be considered also as before, I would not go into details.

6, moveLeft ()

When substantially all here and above, the recording is left coordinates, it should be lx = x - 1.

void moveLeft(){
	//定义变量存放人物左边的坐标
	int lx, ly; 
	
	//当左边没有元素时,直接return	
	if(x == 0){
		return;
	}
	
	//记录左边坐标
	lx = x - 1;
	ly = y; 
	
	//左边为已完成方块
	if(map[ly][lx] == 5){
		return;
	} 
	
	//假设左边为墙,直接return 
	if(map[ly][lx] == 1){
		return;
	}
	
	//假设左边为箱子
	if(map[ly][lx] == 3){
		//判断箱子左边是否为墙 
		if(map[ly][lx - 1] == 1){
			return;
		}
		
		//判断箱子左边是否为球
		if(map[ly][lx - 1] == 4){
			//将箱子左边内容赋值为5★ 
			map[ly][lx - 1] = 5;
			map[ly][lx] = 0;
		
			//箱子的数目减1 
			boxs--; 
		}else{
			//移动箱子 
			map[ly][lx - 1] = 3; 
		}
	}
	map[y][x] = 0;
	map[ly][lx] = 2;
	x = lx; 
}

7、moveDown()

Here when determining the boundary, the judgment is y == HEIGHT - 1.

void moveDown(){
	//定义变量存放人物下方的坐标
	int dx, dy; 
	
	//当下方没有元素时,直接return	
	if(y == HEIGHT - 1){
		return;
	}
	
	//记录下方坐标
	dx = x;
	dy = y + 1; 
	
	//下方为已完成方块
	if(map[dy][dx] == 5){
		return;
	} 
	
	//假设下方为墙,直接return 
	if(map[dy][dx] == 1){
		return;
	}
	
	//假设下方为箱子
	if(map[dy][dx] == 3){
		//判断箱子下方是否为墙 
		if(map[dy + 1][dx] == 1){
			return;
		}
		
		//判断箱子下方是否为球
		if(map[dy + 1][dx] == 4){
			//将箱子下面内容赋值为5★ 
			map[dy + 1][dx] = 5;
			map[dy][dx] = 0;
			
			//箱子的数目减1 
			boxs--; 
		}else{
			//移动箱子
			map[dy + 1][dx] = 3; 
		}
	}
	map[y][x] = 0;
	map[dy][dx] = 2;
	y = dy; 
}

8, moveRight ()

There is also nothing special to say:

void moveRight(){
	//定义变量存放人物右边的坐标
	int rx, ry; 
	
	//当右边没有元素时,直接return	
	if(x == WIDTH - 1){
		return;
	}
	
	//记录右边坐标
	rx = x + 1;
	ry = y; 
	
	//右边为已完成方块
	if(map[ry][rx] == 5){
		return;
	} 
	
	//假设右边为墙,直接return 
	if(map[ry][rx] == 1){
		return;
	}
	
	//假设右边为箱子
	if(map[ry][rx] == 3){
		//判断箱子右边是否为墙 
		if(map[ry][rx + 1] == 1){
			return;
		}
		
		//判断箱子左边是否为球
		if(map[ry][rx + 1] == 4){
			//将箱子右边内容赋值为5★ 
			map[ry][rx + 1] = 5;
			map[ry][rx] = 0;
			
			//箱子的数目减1 
			boxs--; 
		}else{
			//移动箱子 
			map[ry][rx + 1] = 3; 
		}
	}
	map[y][x] = 0;
	map[ry][rx] = 2;
	x = rx; 
}

Third, the summary

Now look back at the beginning of the operation steps

  • Clear screen
  • Mapping
  • Determine whether the end of the game
  • Button pressed by the user for feedback

Here the game is ended is determined to redraw the image sent to the back, as when the user feedback only changed data in the map, in fact, a final push to the box end of the image has not been displayed, so after the redrawing then determine whether to end the game.

There are a lot of redundant code that places the one hand, we want to better understand, and partly out of lazy. Haha, the code up and running no problem, I'll upload the source code and source code, are interested can lower down, or directly copy the code to run is not the problem.
Baidu cloud connection as follows:
link: https://pan.baidu.com/s/1pwEKt3VTKmssDgU6dLx0pg extraction code: 4o9v

Guess you like

Origin blog.csdn.net/ZackSock/article/details/101645494