C語言實(shí)現(xiàn)推箱子游戲的代碼示例
很早就想過做點(diǎn)小游戲了,但是一直沒有機(jī)會(huì)動(dòng)手。今天閑來無事,動(dòng)起手來。過程還是蠻順利的,代碼也不是非常難。今天給大家分享一下~
一、介紹
開發(fā)語言:C語言
開發(fā)工具:Dev-C++ 5.11
日期:2019年9月28日
作者:ZackSock
也不說太多多余的話了,先看一下效果圖:
游戲中的人物、箱子、墻壁、球都是字符構(gòu)成的。通過wasd鍵移動(dòng),規(guī)則的話就是推箱子的規(guī)則,也就不多說了。
二、代碼實(shí)現(xiàn)
關(guān)于代碼方面,我盡可能講的細(xì)致。希望大家可以理解~
(1)方法列表
//主函數(shù) void main(); //初始化一些數(shù)據(jù) initData(); //在控制臺(tái)上打印地圖 drawMap(); //向上移動(dòng) moveUp(); //向左移動(dòng) moveLeft() //向下移動(dòng) moveDown() //向右移動(dòng) moveRight();
這幾個(gè)方法都顧名思義,而且用意也非常明確,就initData可能不知道具體用處,但是沒有什么大問題。唯一的問題就是,上左下右的順序可能會(huì)逼死幾個(gè)強(qiáng)迫癥患者,哈哈。
(2)參數(shù)列表
為了方便,我把include和宏定義也放到參數(shù)列表當(dāng)中
//導(dǎo)入函數(shù)庫 #include <stdio.h> #include <stdlib.h> //宏定義 #define WIDTH 8 #define HEIGHT 8 //定義地圖數(shù)組,二維數(shù)組有兩個(gè)維度,而地圖也是二維的矩形 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} }; //人的位置,在二維地圖中,我們可以用坐標(biāo)表示一個(gè)人的位置,就好比經(jīng)緯度 int x, y; //箱子的個(gè)數(shù),推箱子肯定要有箱子嘛。 int boxs;
這里參數(shù)不多,其中橫為x,縱為y,另外這里再規(guī)定一下map的一些東西:
/** * 0 表示空 * 1 表示墻 * 2 表示人 * 3 表示箱子 * 4 表示目的地(球) * 5 表示已完成的箱子 */
(3)函數(shù)具體分析
接下來我們一個(gè)一個(gè)函數(shù)來分析。
1、main函數(shù)
int main(int argc, char *argv[]) { char direction; //存儲(chǔ)鍵盤按的方向 initData(); //初始化一些數(shù)據(jù) //開始游戲的循環(huán),這里是個(gè)死循環(huán),每按一次按鈕循環(huán)一次 while(1){ //每次循環(huán)的開始清除屏幕 system("cls"); //繪畫地圖 drawMap(); //判斷,當(dāng)boxs的數(shù)量0時(shí),!0為真,然后走break跳出循環(huán)(結(jié)束游戲) if(!boxs){ break; } //鍵盤輸入方向,這里使用getch,因?yàn)間etch讀取字符不會(huì)顯示在屏幕上 direction = getch(); //用switch判斷用戶輸入的方向 switch(direction){ case 'w': //按w時(shí),調(diào)用向上移動(dòng)函數(shù) moveUp(); break; case 'a': //按a時(shí),調(diào)用向左移動(dòng)函數(shù) moveLeft(); break; case 's': moveDown(); break; case 'd': moveRight(); break; } } //當(dāng)跳出循環(huán)時(shí),運(yùn)行該語句,游戲結(jié)束 printf("恭喜你完成游戲!※"); return 0; }
我大概說一下流程,循環(huán)外面沒有什么特別的。initData()只是一些簡單數(shù)據(jù)的初始化,不需要太在意。循環(huán)中大致流程如下:
- 清除屏幕
- 繪制地圖
- 判斷游戲是否結(jié)束
- 對(duì)用戶按下的按鈕進(jìn)行反饋
進(jìn)入循環(huán)體,先清除屏幕,再繪制地圖,然后再判斷游戲是否結(jié)束??赡艽蠹覍?duì)這個(gè)順序不是很理解,這里我們先不考慮判斷游戲結(jié)束的問題。我們把清屏和繪制地圖合在一起,簡稱“重繪地圖”,而游戲結(jié)束的判斷先不考慮,那么流程就簡化為“重繪地圖 + 響應(yīng)用戶的操作”。簡單來說就是,用戶按一下按鈕,我改變一下地圖。
2、initData()
void initData(){ int i, j; //加載數(shù)據(jù)時(shí)讓用戶等待,一般情況加載數(shù)據(jù)比較快 printf("游戲加載中,請(qǐng)稍后........."); //遍歷地圖中的數(shù)據(jù) for(i = 0; i < HEIGHT; i++){ for(j = 0; j < WIDTH; j++){ //遍歷到2(人)時(shí),記錄人的坐標(biāo)。x, y是前面定義的全局變量 if(map[i][j] == 2){ x = j; y = i; } //遍歷到3時(shí),箱子的數(shù)目增加。boxs是前面定義的全局變量 if(map[i][j] == 3){ boxs++; } } } }
這個(gè)方法很簡單,就是遍歷地圖,然后初始化人的位置和箱子的個(gè)數(shù)。這里有一點(diǎn)要注意一下,就是到底內(nèi)層循環(huán)是WIDTH還是外層循環(huán)是WIDTH。
如圖,在遍歷過程中。外層循環(huán)控制行數(shù),即HEIGHT。那么內(nèi)層循環(huán)應(yīng)該是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判斷應(yīng)該輸出的內(nèi)容。然后內(nèi)層循環(huán)每走完一次就換行。
4、moveUp()
這個(gè)函數(shù)內(nèi)容有點(diǎn)多,想講一下大概思路:
向上移有兩種情況
1、前面為空白
這種情況有兩個(gè)步驟
(1)將人當(dāng)前的位置設(shè)置為空白(0),
(2)再講人前面的位置設(shè)置為人(2)
2、前面為箱子
當(dāng)前面為箱子時(shí)有三種情況
1、箱子前面為空白
移動(dòng)人和箱子,這個(gè)操作有三個(gè)步驟
(1)將人當(dāng)前位置設(shè)置為空(0)
(2)將箱子位置設(shè)置為人(2)
(3)將箱子前面設(shè)置為箱子(3)
2、箱子前面為墻
這種情況不需要做任何操作
3、箱子前面為終點(diǎn)
這種情況有四個(gè)個(gè)步驟
(1)將人的位置設(shè)置為空(0)
(2)將箱子的位置設(shè)置為人(2)
(3)將終點(diǎn)位置設(shè)置為★(5)
(4)箱子boxs的數(shù)量減一
3、前面為墻
這種情況最簡單,不需要做任何操作
4、前面為終點(diǎn)
我這里沒有考慮太多,這種情況不做操作。(如果更換地圖的話可能需要修改代碼)
具體代碼如下,解析我全寫在注釋里面:
void moveUp(){ //定義變量存放人物上方的坐標(biāo) int ux, uy; //當(dāng)上方?jīng)]有元素時(shí),直接return (其實(shí)人不可能在邊緣) if(y == 0){ return; } //記錄上方坐標(biāo),x為橫,y為縱,所有ux = x, uy = y - 1; ux = x; uy = y - 1; //上方為已完成的箱子 if(map[uy][ux] == 5){ return; } //假設(shè)上方為墻,直接return,這個(gè)和上面的判斷可以合在一起,這里為了看清楚分開寫 if(map[uy][ux] == 1){ return; } //假設(shè)上方為箱子 if(map[uy][ux] == 3){ //判斷箱子上方是否為墻 if(map[uy - 1][ux] == 1){ return; } //判斷箱子上方是否為終點(diǎn) if(map[uy - 1][ux] == 4){ //將箱子上面內(nèi)容賦值為5★ map[uy - 1][ux] = 5; map[uy][ux] = 0; //箱子的數(shù)目減1 boxs--; }else{ //移動(dòng)箱子 map[uy - 1][ux] = 3; } } //當(dāng)上面幾種return的情況都沒遇到,人肯定會(huì)移動(dòng),移動(dòng)操作如下 map[y][x] = 0; map[uy][ux] = 2; //更新人的坐標(biāo) y = uy; }
這是一個(gè)方向的,其它方向要考慮的問題也和前面一樣,我也就不贅述了。
6、moveLeft()
這里大致都和上面一樣,就是在記錄左邊坐標(biāo)時(shí),應(yīng)該應(yīng)該是lx = x - 1。
void moveLeft(){ //定義變量存放人物左邊的坐標(biāo) int lx, ly; //當(dāng)左邊沒有元素時(shí),直接return if(x == 0){ return; } //記錄左邊坐標(biāo) lx = x - 1; ly = y; //左邊為已完成方塊 if(map[ly][lx] == 5){ return; } //假設(shè)左邊為墻,直接return if(map[ly][lx] == 1){ return; } //假設(shè)左邊為箱子 if(map[ly][lx] == 3){ //判斷箱子左邊是否為墻 if(map[ly][lx - 1] == 1){ return; } //判斷箱子左邊是否為球 if(map[ly][lx - 1] == 4){ //將箱子左邊內(nèi)容賦值為5★ map[ly][lx - 1] = 5; map[ly][lx] = 0; //箱子的數(shù)目減1 boxs--; }else{ //移動(dòng)箱子 map[ly][lx - 1] = 3; } } map[y][x] = 0; map[ly][lx] = 2; x = lx; }
7、moveDown()
這里在判斷邊界時(shí),判斷的是 y == HEIGHT - 1。
void moveDown(){ //定義變量存放人物下方的坐標(biāo) int dx, dy; //當(dāng)下方?jīng)]有元素時(shí),直接return if(y == HEIGHT - 1){ return; } //記錄下方坐標(biāo) dx = x; dy = y + 1; //下方為已完成方塊 if(map[dy][dx] == 5){ return; } //假設(shè)下方為墻,直接return if(map[dy][dx] == 1){ return; } //假設(shè)下方為箱子 if(map[dy][dx] == 3){ //判斷箱子下方是否為墻 if(map[dy + 1][dx] == 1){ return; } //判斷箱子下方是否為球 if(map[dy + 1][dx] == 4){ //將箱子下面內(nèi)容賦值為5★ map[dy + 1][dx] = 5; map[dy][dx] = 0; //箱子的數(shù)目減1 boxs--; }else{ //移動(dòng)箱子 map[dy + 1][dx] = 3; } } map[y][x] = 0; map[dy][dx] = 2; y = dy; }
8、moveRight()
這里也沒什么特別說的:
void moveRight(){ //定義變量存放人物右邊的坐標(biāo) int rx, ry; //當(dāng)右邊沒有元素時(shí),直接return if(x == WIDTH - 1){ return; } //記錄右邊坐標(biāo) rx = x + 1; ry = y; //右邊為已完成方塊 if(map[ry][rx] == 5){ return; } //假設(shè)右邊為墻,直接return if(map[ry][rx] == 1){ return; } //假設(shè)右邊為箱子 if(map[ry][rx] == 3){ //判斷箱子右邊是否為墻 if(map[ry][rx + 1] == 1){ return; } //判斷箱子左邊是否為球 if(map[ry][rx + 1] == 4){ //將箱子右邊內(nèi)容賦值為5★ map[ry][rx + 1] = 5; map[ry][rx] = 0; //箱子的數(shù)目減1 boxs--; }else{ //移動(dòng)箱子 map[ry][rx + 1] = 3; } } map[y][x] = 0; map[ry][rx] = 2; x = rx; }
三、總結(jié)
現(xiàn)在再回顧開始的運(yùn)行步驟
- 清除屏幕
- 繪制地圖
- 判斷游戲是否結(jié)束
- 對(duì)用戶按下的按鈕進(jìn)行反饋
這里把判斷游戲是否結(jié)束放到了重繪圖像后面,因?yàn)樵趯?duì)用戶進(jìn)行反饋的時(shí)候只是改變了map中的數(shù)據(jù),實(shí)際上最后一個(gè)箱子推到終點(diǎn)的圖像還沒有顯示出來,所以要在重繪之后再判斷是否結(jié)束游戲。
代碼有很多冗余的地方,一方面是想大家更好的理解,還有一方面出于懶。哈哈,代碼運(yùn)行起來沒有問題,源碼和源程序我會(huì)上傳,有興趣的可以下下來,或者直接復(fù)制代碼運(yùn)行也是沒問題的。
以上就是本文的全部內(nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
用C語言實(shí)現(xiàn)自動(dòng)售貨機(jī)
這篇文章主要為大家詳細(xì)介紹了用C語言實(shí)現(xiàn)自動(dòng)售貨機(jī),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-01-01dev-c++創(chuàng)建lib(靜態(tài)鏈接庫)文件的實(shí)現(xiàn)步驟
本文主要介紹了dev-c++創(chuàng)建lib(靜態(tài)鏈接庫)文件的實(shí)現(xiàn)步驟,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-06-06C++解決大數(shù)組棧內(nèi)存不夠問題的方法分析
這篇文章主要介紹了C++解決大數(shù)組棧內(nèi)存不夠問題的方法,結(jié)合實(shí)例形式對(duì)比分析了C++針對(duì)大數(shù)組棧內(nèi)存不足情況的常見解決方法及其優(yōu)缺點(diǎn),具有一定參考借鑒價(jià)值,需要的朋友可以參考下2018-05-05