C語言實現推箱子游戲

ZackSock發表於2019-09-28

很早就想過做點小遊戲了,但是一直沒有機會動手。今天閒來無事,動起手來。過程還是蠻順利的,程式碼也不是非常難。今天給大家分享一下~

一、介紹

開發語言:C語言
開發工具:Dev-C++ 5.11
日期:2019年9月28日
作者:ZackSock

也不說太多多餘的話了,先看一下效果圖:
在這裡插入圖片描述
遊戲中的人物、箱子、牆壁、球都是字元構成的。通過wasd鍵移動,規則的話就是推箱子的規則,也就不多說了。

二、程式碼實現

關於程式碼方面,我儘可能講的細緻。希望大家可以理解~

(1)方法列表

//主函式
void main();

//初始化一些資料
initData();

//在控制檯上列印地圖
drawMap();

//向上移動
moveUp();

//向左移動
moveLeft()

//向下移動
moveDown()

//向右移動
moveRight();

這幾個方法都顧名思義,而且用意也非常明確,就initData可能不知道具體用處,但是沒有什麼大問題。唯一的問題就是,上左下右的順序可能會逼死幾個強迫症患者,哈哈。

(2)引數列表

為了方便,我把include和巨集定義也放到引數列表當中

//匯入函式庫
#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;

這裡引數不多,其中橫為x,縱為y,另外這裡再規定一下map的一些東西:

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

(3)函式具體分析

接下來我們一個一個函式來分析。

1、main函式
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;
}

我大概說一下流程,迴圈外面沒有什麼特別的。initData()只是一些簡單資料的初始化,不需要太在意。迴圈中大致流程如下:

  • 清除螢幕
  • 繪製地圖
  • 判斷遊戲是否結束
  • 對使用者按下的按鈕進行反饋

進入迴圈體,先清除螢幕,再繪製地圖,然後再判斷遊戲是否結束。可能大家對這個順序不是很理解,這裡我們先不考慮判斷遊戲結束的問題。我們把清屏和繪製地圖合在一起,簡稱“重繪地圖”,而遊戲結束的判斷先不考慮,那麼流程就簡化為“重繪地圖 + 響應使用者的操作”。簡單來說就是,使用者按一下按鈕,我改變一下地圖。

2、initData()

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++;
			}
		}
	} 
}

這個方法很簡單,就是遍歷地圖,然後初始化人的位置和箱子的個數。這裡有一點要注意一下,就是到底內層迴圈是WIDTH還是外層迴圈是WIDTH。在這裡插入圖片描述
如圖,在遍歷過程中。外層迴圈控制行數,即HEIGHT。那麼內層迴圈應該是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");
	}
}

這裡也非常簡單,變數map中的元素,然後通過switch判斷應該輸出的內容。然後內層迴圈每走完一次就換行。

4、moveUp()

這個函式內容有點多,想講一下大概思路:

向上移有兩種情況
1、前面為空白
	這種情況有兩個步驟
	(1)將人當前的位置設定為空白(0),
	(2)再講人前面的位置設定為人(22、前面為箱子
	當前面為箱子時有三種情況
	1、箱子前面為空白
		移動人和箱子,這個操作有三個步驟
		(1)將人當前位置設定為空(0)
		(2)將箱子位置設定為人(2)
		(3)將箱子前面設定為箱子(32、箱子前面為牆
		這種情況不需要做任何操作
	3、箱子前面為終點
		這種情況有四個個步驟
		(1)將人的位置設定為空(0)
		(2)將箱子的位置設定為人(2)
		(3)將終點位置設定為★(5)
		(4)箱子boxs的數量減一
3、前面為牆
	這種情況最簡單,不需要做任何操作
4、前面為終點
	我這裡沒有考慮太多,這種情況不做操作。(如果更換地圖的話可能需要修改程式碼)

具體程式碼如下,解析我全寫在註釋裡面:

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; 
} 

這是一個方向的,其它方向要考慮的問題也和前面一樣,我也就不贅述了。

6、moveLeft()

這裡大致都和上面一樣,就是在記錄左邊座標時,應該應該是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()

這裡在判斷邊界時,判斷的是 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()

這裡也沒什麼特別說的:

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; 
}

三、總結

現在再回顧開始的執行步驟

  • 清除螢幕
  • 繪製地圖
  • 判斷遊戲是否結束
  • 對使用者按下的按鈕進行反饋

這裡把判斷遊戲是否結束放到了重繪影象後面,因為在對使用者進行反饋的時候只是改變了map中的資料,實際上最後一個箱子推到終點的影象還沒有顯示出來,所以要在重繪之後再判斷是否結束遊戲。

程式碼有很多冗餘的地方,一方面是想大家更好的理解,還有一方面出於懶。哈哈,程式碼執行起來沒有問題,原始碼和源程式我會上傳,有興趣的可以下下來,或者直接複製程式碼執行也是沒問題的。
百度雲連線如下:
連結:https://pan.baidu.com/s/1pwEKt3VTKmssDgU6dLx0pg 提取碼:4o9v

相關文章