今天来介绍如何用C语言写一个简单的小游戏,叫做“小老鼠推箱子”。虽然游戏的编写过程不复杂,但是我觉得能够从中找到自己对于编程的不足和完善自己的编程思维是最重要的。游戏代码不多,所以直接写在一个c文件中。本人小白,有不足之处还望指教


游戏介绍
下图是游戏开始界面,$是小老鼠,#是墙,O是终点,当小老鼠把所有箱子推进终点就代表游戏通过。
在这里插入图片描述
游戏思维
游戏地图用一个二维数组去存储。数组中不同的值代表不同的对象(老鼠、墙、路等)。当小老鼠在移动时,数组中的值也会随之改变,但是为了在游戏无法进行下去时可以从新开始游戏,所以需要另一个数组去保留地图的初始状态。也就是说需要两个二维数组,一个始终不改变,一个用来记录实时的状态。
小老鼠在移动时会遇到两种情况,即可移动和不可移动。在编写代码时,只需要将可移动的情况写完成,那么就不需要去判断不可移动的状态。可移动的情况有下面两种:

  1. 小老鼠面前什么也没有(面前是路或者是终点)。
  2. 小老鼠前面是箱子,此时分为两种情况(箱子前面是路或者箱子前面是终点)。

这时候,应当思考的是如何根据二维数组的值去判断上述情况,以及当小老鼠移动和推动箱子时,二维数组的值是如何变化的。

#include <stdio.h>
#include "get_keyboard.h"
//地图7行8列   行[0-6]  列[0-7]
//0 路
//1 墙
//2 箱子
//3 终点
//4 小老鼠
//7 小老鼠站在终止上
//5 箱子到达终点上

int g_boards[7][8] =
{
    {0,1,1,1,1,1,1,0},
    {0,1,0,0,0,0,1,1},
    {1,3,0,1,1,2,0,1},
    {1,0,3,3,2,0,0,1},
    {1,0,0,1,2,0,0,1},
    {1,0,0,4,0,1,1,1},
    {1,1,1,1,1,0,0,0}
}, boards[7][8]={};
//记录小老鼠在移动中的位置
int row = 0;
int col = 0;
int cnt = 0;//箱子个数,用来判断游戏是否结束。

用0~4表示地图上面有的小老鼠等物体,值得提的是对于箱子和小老鼠来说,他们移动时都可能会在终点上,那么他们移入终点和移开终点时的情况需要分别去判断,为了能够避免过多的判断,当它们移入终点时,终点所在位置的值就等于它们的值加上终点的值。同理,当它们移开终点时,终点所在位置的值就等于终点的值减去它们的值。这样就不用去判断小老鼠和箱子在不在终点上了。所以,小老鼠在终点上的值是7。

代码详解
这里先从main()函数开始思考。当游戏开始时,需要先将地图初始化,即把用来记录实时状态的地图初始化成游戏开始时的地图(就是前面提的的两个地图),所以这里需要init()函数。初始化后就是开始游戏,游戏过程中需要获取方向键,这些在start()函数中完成。之后,在这些函数中再去思考是否需要去写其他函数补足功能。

//初始化地图
void init()
{
	for(int i=0; i<7*8; i++)
	{	//这里是为了当你按下enter键时,能够重置游戏,重置游戏时需要重新初始化地图
		boards[i/8][i%8]=g_boards[i/8][i%8];
		if(4==boards[i/8][i%8])	//这里还需要记录老鼠的位置
		{
			row=i/8;
			col=i%8;
		}
	}
}

//打印地图
void print()
{
	for(int i=0; i<7; i++)
	{
		for(int j=0; j<8; j++)
		{
			switch(boards[i][j])
			{
				case 0:
					printf(" ");break;	//空格代表路径
				case 1:
					printf("#");break;	//打印墙
				case 2:
				case 5:
					printf("@");break; 	//打印箱子
				case 3:
					printf("O");break;	//打印终点
				case 4:
				case 7:
					printf("$");break;	//打印小老鼠
			}
		}
		printf("\n");
	}
}

//推箱子
void move(int nrow, int  ncol, int nnrow, int nncol)
{
	if(0==boards[nrow][ncol] || 3==boards[nrow][ncol])	//如果小老鼠前面是路或者终点,说明可以移动
	{
		boards[nrow][ncol] +=4;//小老鼠进入这个位置了
		boards[row][col] -=4;//小老鼠离开这个位置了
		//移动过后小老鼠的位置也改变了
		row = nrow;
		col = ncol;
	}
	else if(2==boards[nrow][ncol] || 5==boards[nrow][ncol])	//如果小老鼠前面是箱子
	{
		if(0==boards[nnrow][nncol] || 3==boards[nnrow][nncol])	//如果箱子前面是路或者终点则可以移动
		{
			boards[nnrow][nncol] +=2;
			boards[nrow][ncol] -=2-4;
			//这里其实是boards[nrow][ncol]=boards[nrow][ncol]-2+4
			//减去2是因为箱子移走了,加上4是因为小老鼠进入了
			boards[row][col] -=4;
			row=nrow;
			col=ncol;
		}
	}
	for(int i=0; i<7; i++)	//统计游戏是否结束,当箱子都在终点上时就结束了
	{
		for(int j=0; j<8; j++)
		{
			if(5==boards[i][j])
			{
				cnt++;
			}
			if(3==cnt)
			{
				printf("通关!\n");
				exit(0);//通关退出游戏
			}
		}
	}
	cnt=0;	//如果游戏没有结束,下次还是需要从0统计
}

//开始游戏
void start()
{
	while(1)
	{
		print();//游戏开始时需要打印地图,每次从键盘上获得方向移动后也需要打印地图
		int dir=get_keyboard();	//从键盘获取方向
		system("clear");	//下次打印地图时,先将屏幕清空
		switch(dir)	//每次都需要判断小老鼠前面和前面的前面的坐标的状态,这样可以在move()
					//函数中统一各个方向的写法
		{
			case KEY_UP: move(row-1, col, row-2, col);break;
			case KEY_DOWN: move(row+1, col, row+2, col);break;
			case KEY_LEFT: move(row, col-1, row, col-2);break;
			case KEY_RIGHT: move(row, col+1, row, col+2);break;
			case KEY_ENTER:
					init();break; //按enter则从新开始游戏
			case KEY_q:
					exit(0);//如果按q则退出游戏
		}
	}
}

int main()
{
	//游戏初始化
	init();
	system("clear");	//系统命令,用来清空屏幕

	//开始游戏
	start();

	return 0;
}

好了,这就是今天的内容。这里需要的用来获取键盘的头文件直接粘贴在下面,也可以从我的资源下载。

#ifndef GETCH_H
#define GETCH_H
#include <stdio.h>
#include <termios.h>
#include <stdlib.h>
#include <unistd.h>

typedef enum KEYBOARD
{
	KEY_UP    		= 183,
	KEY_DOWN  		= 184,
	KEY_RIGHT 		= 185,
	KEY_LEFT  		= 186,
	KEY_BACKSPACE	= 127,
	KEY_ENTER 		= 10,
	KEY_0           = 48,
	KEY_1           = 49,
	KEY_2           = 50,
	KEY_3           = 51,
	KEY_4           = 52,
	KEY_5           = 53,
	KEY_6           = 54,
	KEY_7           = 55,
	KEY_8           = 56,
	KEY_9           = 57,
	KEY_A           = 65,
	KEY_B           = 66,
	KEY_C           = 67,
	KEY_D           = 68,
	KEY_E           = 69,
	KEY_F           = 70,
	KEY_G           = 71,
	KEY_H           = 72,
	KEY_I           = 73,
	KEY_J           = 74,
	KEY_K           = 75,
	KEY_L           = 76,
	KEY_M           = 77,
	KEY_N           = 78,
	KEY_O           = 79,
	KEY_P           = 80,
	KEY_Q           = 81,
	KEY_R           = 82,
	KEY_S           = 83,
	KEY_T           = 84,
	KEY_U           = 85,
	KEY_V           = 86,
	KEY_W           = 87,
	KEY_X           = 88,
	KEY_Y           = 89,
	KEY_Z           = 90,
	KEY_a           = 97,
	KEY_b           = 98,
	KEY_c           = 99,
	KEY_d           = 100,
	KEY_e           = 101, 
	KEY_f           = 102,
	KEY_g           = 103,
	KEY_h           = 104,
	KEY_i           = 105,
	KEY_j           = 106,
	KEY_k           = 107,
	KEY_l           = 108,
	KEY_m           = 109,
	KEY_n           = 110,
	KEY_o           = 111,
	KEY_p           = 112,
	KEY_q           = 113,
	KEY_r           = 114,
	KEY_s           = 115,
	KEY_t           = 116,
	KEY_u           = 117,
	KEY_v           = 118,
	KEY_w           = 119,
	KEY_x           = 120,
	KEY_y           = 121,
	KEY_z           = 122
}KEYBOARD;

//此函数能立即从键盘不回显的接收数据
static int get_keyboard(void)
{
	//接收系统调用的执行结果
	int ret = 0;
	//存储终端设备的配置信息
	struct termios old;
	//通过系统调用获取终端的配置信息
	ret=tcgetattr(STDIN_FILENO,&old);
	if(0 > ret)
	{
		perror("tcgetattr");
		return -1;
	}
	//初始化新的终端配置信息
	struct termios new = old;
	//取消回显并立即获取
	new.c_lflag &= ~(ICANON|ECHO);
	//设置新的终端配置信息
	ret= tcsetattr(STDIN_FILENO,TCSANOW,&new);
	if(0 > ret)
	{
		perror("tcsetattr");
		return -2;
	}
	//在新的模式下从终端获取数据
	int key_value = 0;
	do
	{
		key_value += getchar();
	//由于和系统对FILE结构体的实现各不相同
	//linux系统 while(stdin->_IO_read_end - stdin->_IO_read_ptr);
	//OS系统 while(stdin->_r);
	}while(stdin->_IO_read_end - stdin->_IO_read_ptr);
	//还原终端的配置信息
	ret = tcsetattr(STDIN_FILENO,TCSANOW,&old);
	if(0 > ret)
	{
		perror("tcsetattr");
		return -3;
	}
	//返回获取到的数据
	return key_value;
}
#endif//GETCH_H

Logo

更多推荐