C語言實現(xiàn)簡單推箱子游戲
使用C語言實現(xiàn)超簡單的推箱子游戲,供大家參考,具體內容如下
感謝您打開了這篇文章,下面我將講述一下推箱子是如何實現(xiàn)的。
另外附贈適配該程序簡單好用 專屬推箱子地圖編輯器 讓您在16 * 16大地圖的條件下也能輕松編輯地圖。
鏈接:地圖編輯器
本程序在沒有檢測到地圖文件的情況下也能獨自運行!代碼中儲存了推箱子游戲第一關的標準地圖,讓您在沒有地圖文件的情況下也能熟悉整個程序的流程!
當然,擁有地圖文件會也會獲得更好的游戲體驗,請自行編輯。
廢話不多說!
下面進入技術環(huán)節(jié):
C語言版 多功能推箱子
編譯環(huán)境: Windows VS2019
其他編譯器,可通過查看下文的“注意事項”將代碼更正為其他平臺可正常版本
需求:
控制人物將箱子推至目標中,目標全部完成進入下一關。
思路:
使用二維數組儲存不同數字,數字包括了地圖中所有的元素,通過按鍵控制人物完成推箱子的操作,達成關卡內的所有目標后,自動進入下一關。
做法:
主要邏輯移動推箱子部分:按下方向鍵后,雙重循環(huán)找到人物,根據移動方向儲存 人物、人物前面、箱子、箱子前面四大基礎信息,并通過判斷前方數組值是否是墻壁、目標等,進行人物移動和箱子移動操作。
具體詳細做法我已經整理到了代碼注釋當中,以便一一對應查看。
使用到知識點:
循環(huán)、二維數組、讀取文件
難點:
在人物和箱子移動的同時,有需要注意當人物移動到了未完成目標或已完成目標、箱子移動到了已完成目標的情況,這種情況需要判斷在人物/箱子離開之后,原地又再次變?yōu)樵亍?/p>
說明:
程序前部分有較多代碼用于寫出未檢測到文件的情況邏輯和關卡選擇邏輯,如果要直接查看核心代碼請移動到operation();操作人物函數和gbszszhs(char ch);修改二維數組函數。
注意:
由于編譯器原因,程序中_kbhit()和_getch()函數可能在其他編譯器上編譯會出現(xiàn)錯誤,解決辦法是去掉函數前面的“_”。
同時,要將 文件打開函數fopen_s(&fp, FLPA, “r”);更改為fp = fopen(FLPA, “r”);
fcanf_s更改為fcanf scanf_s()更改為scanf
運行效果:
菜單選擇:

游戲進行:

代碼實現(xiàn):
#include <stdio.h>
#include <windows.h>
#include <conio.h>
//0代表空地,1代表墻,2代表未達成的目標,3代表箱子,4代表玩家,5代表已放箱子的目標,
//6代表人暫時所在的未達成的目標,7代表人暫時所在的已達成的目標,8代表箱子暫時所在的已達成的目標
#define WH 16 //地圖的寬高
#define BYT 529 //一關需要跳過的字數 因為文件指針定位函數的原因,有時定位可能會不準確,可以通過修改BYT進行適配
#define FLPA "C:\\Users\\ASUS\\Desktop\\推箱子地圖.txt" //需要讀取地圖文件的路徑 游戲之前需進行設置?。?
//找不到路徑將只能進行第一關游戲
//注意:游戲地圖邊界 不可以 當做墻壁使用!
#define INITMAP \
int mapch_init[WH][WH] = { \
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, \
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, \
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, \
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, \
{0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0}, \
{0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, 0}, \
{0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0}, \
{0, 0, 0, 0, 1, 1, 1, 3, 0, 3, 2, 1, 0, 0, 0, 0}, \
{0, 0, 0, 0, 1, 2, 0, 3, 4, 1, 1, 1, 0, 0, 0, 0}, \
{0, 0, 0, 0, 1, 1, 1, 1, 3, 1, 0, 0, 0, 0, 0, 0}, \
{0, 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0}, \
{0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0}, \
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, \
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, \
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, \
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, \
};
int mapch[WH][WH];
//所有函數之間不是獨立和順序的,會互相調用
void HideCursor(); //隱藏光標
void gotoxy(int x, int y);//光標定位
void scmbxyhs(); //輸出地圖下方文字信息函數
void wjzdwjzjjrqk(); //未找到文件直接進入第一關情況
int gkxzhs(); //關卡選擇函數
void cwjzjrwkdhs(); //從文件中進入關卡的函數
void gnxzjm(); //主菜單選擇
void cshhs(); //初始化函數
void tranmap(); //翻譯并畫出地圖
void detection(); //尋找所有該地圖中未完成的目標
void gktgszxhs(); //判斷關卡是否通過,是進行下一關卡
void operation(); //操作人物主要函數***
void gbszszhs(char ch); //改變數組數值函數
int updatetime(); //獲取一次電腦現(xiàn)在的時間
void process(); //主要流程
int main()
{
cshhs(); //初始化函數
process(); //主要流程
return 0;
}
//游戲開始初始化部分
void scmbxyhs() //輸出地圖提示信息函數
{
gotoxy(34, 17);
printf("本關剩余目標數:");
gotoxy(34, 19);
printf("本關已走步數:");
gotoxy(32, 21);
printf("您使用 秒完成了本關!");
gotoxy(49, 19);
printf("0"); //輸出初始的步數0
}
void wjzdwjzjjrqk() //未找到文件直接進入第一關情況
{
system("cls");
printf("地圖文件不存在,\n直接進入第一關");
INITMAP //初始地圖數組
for (int i = 0; i < WH; i ) //如果地圖文件不存在則將初始地圖數組的值賦給需要使用的地圖
for (int j = 0; j < WH; j ) //將初始地圖數組的值復制給當前地圖數組
mapch[i][j] = mapch_init[i][j];
Sleep(2000); //等待兩秒進入第一關
system("cls"); //清屏
tranmap(); //畫出初始地圖
detection(); //目標信息
scmbxyhs(); //輸出地圖下方文字信息
}
int n = 1; //輸入關卡變量**
int maxn = 0; //最大關數
int dczdgshs() //最大關數
{
FILE* fp = NULL; //因為用于提示和限制輸入情況,所以需要得到最大關卡數maxn
fopen_s(&fp, FLPA, "r");
if (fp == NULL)
{
wjzdwjzjjrqk(); //未找到文件直接進入第一關情況
return 0;
}
int temp = 0; //臨時變量用來統(tǒng)計地圖文件全字節(jié)數
rewind(fp); //文件指針移動到文件首部
while (!feof(fp)) //文件指針還沒有到文件尾進入循環(huán)
{
fgetc(fp); //讀字符函數從文件開頭讀,向后移動文件指針
temp ; //每讀一個字符則臨時變量自增,統(tǒng)計出全文件有幾個字符
}
fclose(fp); //文件使用完成關閉文件
maxn = temp / BYT 1; //最大關數就是所有字符的數量除以一關的字符數
return 1;
}
int gkxzhs() //選擇關卡函數
{
system("cls"); //輸出關卡選擇提示
gotoxy(26, 8);
printf("請輸入想要挑戰(zhàn)的關卡:(回車確認)");
if (!dczdgshs()) //得出最大關數函數,返回值為0代表未找到地圖文件,直接進入流程
return 0;
gotoxy(36, 10);
printf("請輸入1- %d ", maxn);//輸出提示最大關數信息
srgk: //重新選擇關卡
gotoxy(41, 12);
scanf_s("%d", &n);
if (n<1 || n>maxn) //對輸入的錯誤關卡信息加以限制
{
gotoxy(36, 12);
printf(" ");
gotoxy(33, 11);
printf("請輸入正確的關數");
goto srgk; //如果輸入錯誤的關卡輸出提示信息并返回到輸入的地方重新輸入
}
return 1; //如果找到了文件就返回1
}
void cwjzjrwkdhs() //從文件中進入關卡,適用于可以找到地圖文件的情況
{
//關數變量n的默認初始化值為1
FILE* fp = NULL;
fopen_s(&fp, FLPA, "rb");
if (fp == NULL)
{
wjzdwjzjjrqk(); //直接進入第一關函數
return; //地圖文件不存在則直接進入第一關
}
//讀文件進入關卡的代碼,從第一關進入和選擇特定關從n關進入兩種情況都會執(zhí)行
//流程如果用到該函數,檢測已達成的目標和未達成目標的個數一直則調用函數,n關數自增1
int skip = (n - 1) * BYT; //到n關需要跳過的字數 跳過一關需要的字數為512
fseek(fp, skip, 0); //定位到地圖文件第skip個字節(jié)處開始讀取
int i, j;
for (i = 0; i < WH; i ) //讀取512個字符
{
for (j = 0; j < WH; j )
{
fscanf_s(fp, "%d ", &mapch[i][j]);//格式讀函數,直接以整數格式讀取數值存入地圖數組mapch中
printf("%d ", mapch[i][j]);
}
printf("\n");
}
fclose(fp); //讀取文件完畢,將文件關閉
system("cls");
}
void gnxzjm() //主菜單頁面選擇
{
system("cls");
gotoxy(39, 8);
printf("推箱子");
gotoxy(34, 10);
printf("輸入1 開始新游戲");
gotoxy(34, 12);
printf("輸入2 選擇關卡");
gotoxy(34, 14);
printf("輸入3 退出游戲");
Head: //用于返回的標簽
gotoxy(34, 17);
char chn=_getch(); //選擇游戲模式
if (chn == '3')
{
system("cls"); //退出游戲則輸出提示信息
gotoxy(34, 12);
printf("歡迎下次光臨");
Sleep(2000);
gotoxy(0, 24);
exit(0); //退出游戲
}
else if (chn == '2')
{
if (!gkxzhs()) //進入關卡選擇
return; //如果關卡選擇函數返回值為0則代表未找到文件,直接跳出初始化函數
//如果返回1找到文件則繼續(xù)執(zhí)行該函數下面的內容
}
else if (chn == '1')
n = 1; //如果選擇新游戲,直接從第一關開始
else
{
gotoxy(34, 16);
printf("請輸入正確的選擇:");
goto Head; //選擇錯誤的菜單,則重新返回選擇菜單的地方
}
//選擇1的情況:
cwjzjrwkdhs(); //從文件讀取關卡的函數
detection(); //統(tǒng)計當前關卡的目標數
tranmap(); //畫出地圖
scmbxyhs(); //輸出地圖下方文字信息
}
int detunf;//檢測未完成目標的變量,初始為一個關卡中未完成目標的個數,箱子碰到未完成目標時,自減
void detection() //檢測當前關中有多少個目標
{
//detunf
detunf = 0; //從0開始統(tǒng)計
int i, j;
for (i = 0; i < WH; i )
for (j = 0; j < WH; j )
if (mapch[i][j] == 2)
detunf ;
gotoxy(50,17); //在提示信息的位置輸出剩余目標信息
printf("%d", detunf);
}
void cshhs() //總初始化函數
{
system("title 推箱子");//控制臺標題
system("mode con cols=84 lines=26");//設置控制臺大小,第一個參數為橫軸,地圖參數32 16
gnxzjm(); //主菜單頁面選擇
HideCursor(); //隱藏光標函數
dczdgshs(); //找到地圖文件的情況下得出最大關數
}
//游戲流程部分
void tranmap() //翻譯地圖
{
gotoxy(26, 1); //輸出地圖時在控制臺中間輸出,地圖最上方空一行
int i, j;
for (i = 0; i < WH; i )
{
for (j = 0; j < WH; j )
{
if (mapch[i][j] == 1)
printf("■");
else if (mapch[i][j] == 2)
printf("★");
else if (mapch[i][j] == 3 || mapch[i][j] == 8)
printf("●");
else if (mapch[i][j] == 4 || mapch[i][j] == 6 || mapch[i][j] == 7)
printf("♀");
else if (mapch[i][j] == 5)
printf("--");
else
printf(" ");
}
gotoxy(26, i 1); //根據數組的y軸更改地圖輸出的y軸,地圖最上方空一行
}
}
int opnum; //每一關行走的步數
int time;
int time_2;
void gktgszxhs() //判斷當前關卡是否通過,是進入下一關
{
if (detunf == 0) //當前關卡目標為0時
{
n ; //關卡變量自增
if (n > maxn) //如果關數n大于了最大關卡數則返回主菜單
{
tranmap(); //畫出地圖
gotoxy(26, 22);
printf("您已通關所有關卡,3秒后返回主菜單!");
Sleep(3000); //等待三秒
opnum = 0, time = updatetime(),time_2 = 0;//行走的步數,本關時間清0,重新獲取當前時間,\
因為需要輸出的time_2是用當前時間-剛開始的時間
gnxzjm(); //主菜單頁面選擇函數
}
else
{
tranmap(); //畫出地圖
Sleep(2000); //等候兩秒
cwjzjrwkdhs(); //當前關卡目標為0時,從文件中向地圖數組中讀入下一關卡的信息
detection(); //統(tǒng)計當前關卡目標個數
scmbxyhs(); //輸出地圖下方的信息
opnum = 0, time = updatetime(),time_2 = 0;//注釋見297行
//按鍵結束之后操控函數會調用畫出地圖函數
}
}
}
int i, j; //找到后的人的坐標
int box_x, box_y; //箱子的坐標
int boxnext_x, boxnext_y; //箱子前面的坐標
int peonext_x, peonext_y; //人前面的坐標
void gbszszhs(char ch) //修改地圖數組值主要函數**
{
for (i = 0; i < WH; i ) //遍歷 i是y軸,j是x軸
{
for (j = 0; j < WH; j )
{
if (mapch[i][j] == 4 || mapch[i][j] == 6 || mapch[i][j] == 7) //找到人的位置
{
if (ch == 'w') // 用于確定不同方向上 箱子、箱子前面、人前面的坐標
{
box_y = i - 1; box_x = j; //箱子當前
boxnext_y = i - 2; boxnext_x = j; //箱子前面
peonext_y = i - 1; peonext_x = j; //人前面
}
else if (ch == 'a')
{
box_y = i; box_x = j - 1;
boxnext_y = i; boxnext_x = j - 2;
peonext_y = i; peonext_x = j - 1;
}
else if (ch == 's')
{
box_y = i 1; box_x = j;
boxnext_y = i 2; boxnext_x = j;
peonext_y = i 1; peonext_x = j;
}
else if (ch == 'd')
{
box_y = i; box_x = j 1;
boxnext_y = i; boxnext_x = j 2;
peonext_y = i; peonext_x = j 1;
}
//排除大的錯誤
if (mapch[box_y][box_x] == 3 || mapch[box_y][box_x] == 8) //如果人的前邊是箱子
if (mapch[boxnext_y][boxnext_x] == 1 || mapch[boxnext_y][boxnext_x] == 3 || mapch[boxnext_y][boxnext_x] == 8)
return; //如果箱子前邊是墻或者是箱子直接結束修改函數
if (mapch[box_y][box_x] == 1) //人的前邊不能是墻
return; //如果人的前邊是墻直接結束修改
opnum ; //每次有效操作步數自增一次,并輸出
gotoxy(49, 19);
printf("%d", opnum);//步數
//箱子改變
if (mapch[box_y][box_x] == 3 || mapch[box_y][box_x] == 8) //如果人的前邊是箱子
{
//箱子原地改變:
if (mapch[box_y][box_x] == 3) //如果箱子所在的位置是 一般箱子
mapch[box_y][box_x] = 0; //改變?yōu)榭盏?
else if (mapch[box_y][box_x] == 8) //如果箱子所在的位置是 箱子暫時所在已達成的目標
mapch[box_y][box_x] = 5; //改變?yōu)橐堰_成的目標
//箱子的下一步改變:
if (mapch[boxnext_y][boxnext_x] == 2) //如果箱子前面的格子是未放箱子的目標
{
mapch[boxnext_y][boxnext_x] = 5; //則該格子變?yōu)橐逊畔渥拥哪繕?
detunf--; //本關目標減1
gotoxy(50, 17); //輸出本關剩余目標個數
printf("%d", detunf);
}
else if (mapch[boxnext_y][boxnext_x] == 0) //如果箱子前面是空地
mapch[boxnext_y][boxnext_x] = 3; //則變?yōu)槠胀ㄏ渥?
else if (mapch[boxnext_y][boxnext_x] == 5) //如果箱子前面是已經放過箱子的目標
mapch[boxnext_y][boxnext_x] = 8; //則變?yōu)?箱子暫時所在已達成的目標
}
//原地改變
if (mapch[i][j] == 6)
mapch[i][j] = 2; //走出去之后原地又變回2 未達成的目標
else if (mapch[i][j] == 7)
mapch[i][j] = 5; //走出去之后原地又變回5 已達成的目標
else if (mapch[i][j] == 4)
mapch[i][j] = 0; //走出去之后原地變回4 人的原型
//人下一步改變
if (mapch[peonext_y][peonext_x] == 0 || mapch[peonext_y][peonext_y] == 3)//如果他的下一步是普通箱子或者空地
mapch[peonext_y][peonext_x] = 4; //人是 普通的人
if (mapch[peonext_y][peonext_x] == 2) //如果他的下一步還是未達成的目標
mapch[peonext_y][peonext_x] = 6; //人是 人暫時所在未達成的目標
if (mapch[peonext_y][peonext_x] == 5) //如果人的下一步是已經達成的目標
mapch[peonext_y][peonext_x] = 7; //人是 人暫時所在已經達成的目標
if (mapch[peonext_y][peonext_x] == 8) //如果人的下一步是 箱子暫時所在已達成的目標
mapch[peonext_y][peonext_x] = 7; //人還是 暫時所在已達成的目標
goto L1; //修改完成后不需要遍歷后面的數組,直接跳出所有循環(huán)
}
}
}
L1: //用于跳出的標簽
gktgszxhs(); //關卡通過則進入下一關
}
void operation() //操作人物函數
{
char ch = _getch(); //接收輸入的方向 _getch()即使接收
switch (ch)
{
case 'w': //向不同方向移動
gbszszhs(ch); //傳遞參數,修改二維數組
break;
case 'a':
gbszszhs(ch);
break;
case 's':
gbszszhs(ch);
break;
case 'd':
gbszszhs(ch);
break;
}
tranmap(); //重新畫出地圖
}
int updatetime() //獲取一次電腦現(xiàn)在的時間
{
int now;
SYSTEMTIME system_time;
GetLocalTime(&system_time);
now = system_time.wMinute * 60 system_time.wSecond;
return now;
}
void process() //主要流程
{
time = updatetime(); //初始時間
while (1)
{
time_2 = updatetime() - time; //每關的時間time_2,值為當前時間減去當前關卡開始的時間
gotoxy(39, 21);
printf("%d s", time_2); //輸出
if(_kbhit())
operation(); //操作人物、修改數值主要函數
Sleep(20); //游戲幀率和手感 休眠時間
}
}
void gotoxy(int x, int y) //光標定位
{
COORD pos = { x,y };
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), pos);
}
void HideCursor() //光標隱藏
{
CONSOLE_CURSOR_INFO cursor_info = { 1, 0 };
SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &cursor_info);
}
主要函數:gbszszhs()里面的邏輯是比較復雜的,我當時寫這段代碼的時候也是反反復復修改好多次甚至推翻重做才理通順這些邏輯的。
如果對于程序代碼注釋有我沒寫明白的地方,歡迎在評論區(qū)下方留言詢問,如果我看到會盡最大的努力為您解惑。
不足之處:
地圖在屏幕上顯示時容易出錯,需要調整每關字數。原因并不明確。
因為作者對C語言的學習還比較淺薄,代碼寫到初始化游戲的兩種模式(有文件和無文件)時思維有些混亂,導致代碼在這一部分有很多的缺陷,但最終程序的效果還是出來了。
但其實對整篇所有代碼而言最重要的部分還是gbszszhs()函數,只要將這個函數完全理解并熟練掌握了,那么整個“推箱子”游戲也就非常簡單了。
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。

