1. 程式人生 > 程式設計 >C語言實現簡單推箱子游戲

C語言實現簡單推箱子游戲

使用C語言實現超簡單的推箱子游戲,供大家參考,具體內容如下/p>

感謝您打開了這篇文章,下面我將講述一下推箱子是如何實現的。

另外附贈適配該程式簡單好用 專屬推箱子地圖編輯器 讓您在16 * 16大地圖的條件下也能輕鬆編輯地圖。
連結:地圖編輯器

本程式在沒有檢測到地圖檔案的情況下也能獨自執行!程式碼中儲存了推箱子游戲第一關的標準地圖,讓您在沒有地圖檔案的情況下也能熟悉整個程式的流程!

當然,擁有地圖檔案會也會獲得更好的遊戲體驗,請自行編輯。

廢話不多說!

下面進入技術環節:

C語言版 多功能推箱子

編譯環境: Windows VS2019
其他編譯器,可通過檢視下文的“注意事項”將程式碼更正為其他平臺可正常版本

需求:

控制人物將箱子推至目標中,目標全部完成進入下一關。

思路:

使用二維陣列儲存不同數字,數字包括了地圖中所有的元素,通過按鍵控制人物完成推箱子的操作,達成關卡內的所有目標後,自動進入下一關。

做法:

主要邏輯移動推箱子部分:按下方向鍵後,雙重迴圈找到人物,根據移動方向儲存 人物、人物前面、箱子、箱子前面四大基礎資訊,並通過判斷前方陣列值是否是牆壁、目標等,進行人物移動和箱子移動操作。
具體詳細做法我已經整理到了程式碼註釋當中,以便一一對應檢視。

使用到知識點:

迴圈、二維陣列、讀取檔案

難點:

在人物和箱子移動的同時,有需要注意當人物移動到了未完成目標或已完成目標、箱子移動到了已完成目標的情況,這種情況需要判斷在人物/箱子離開之後,原地又再次變為原元素。

說明:

程式前部分有較多程式碼用於寫出未檢測到檔案的情況邏輯和關卡選擇邏輯,如果要直接檢視核心程式碼請移動到operation();操作人物函式和gbszszhs(char ch);修改二維陣列函式。

注意:

由於編譯器原因,程式中_kbhit()和_getch()函式可能在其他編譯器上編譯會出現錯誤,解決辦法是去掉函式前面的“_”。
同時,要將 檔案開啟函式fopen_s(&fp,FLPA,“r”);更改為fp = fopen(FLPA,“r”);
fcanf_s更改為fcanfscanf_s()更改為scanf

執行效果:

選單選擇:

C語言實現簡單推箱子游戲

遊戲進行:

C語言實現簡單推箱子游戲

程式碼實現:

#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,1,2,3,4,\
};

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();  //獲取一次電腦現在的時間
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,"r");

 if (fp == NULL)
 {
  wjzdwjzjjrqk();  //未找到檔案直接進入第一關情況
  return 0;
 }
 int temp = 0;   //臨時變數用來統計地圖檔案全位元組數

 rewind(fp);    //檔案指標移動到檔案首部

 while (!feof(fp))  //檔案指標還沒有到檔案尾進入迴圈
 {
  fgetc(fp);   //讀字元函式從檔案開頭讀,向後移動檔案指標
  temp ;    //每讀一個字元則臨時變數自增,統計出全檔案有幾個字元
 }
 fclose(fp);    //檔案使用完成關閉檔案
 maxn = temp / BYT 1; //最大關數就是所有字元的數量除以一關的字元數
 return 1;
}



int gkxzhs()     //選擇關卡函式
{
 system("cls");   //輸出關卡選擇提示
 gotoxy(26,8);
 printf("請輸入想要挑戰的關卡:(回車確認)");

 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,"rb");

 if (fp == NULL)
 {
  wjzdwjzjjrqk();   //直接進入第一關函式
  return;     //地圖檔案不存在則直接進入第一關
 }

 //讀檔案進入關卡的程式碼,從第一關進入和選擇特定關從n關進入兩種情況都會執行
   //流程如果用到該函式,檢測已達成的目標和未達成目標的個數一直則呼叫函式,n關數自增1
 int skip = (n - 1) * BYT; //到n關需要跳過的字數 跳過一關需要的字數為512

 fseek(fp,skip,0);   //定位到地圖檔案第skip個位元組處開始讀取
 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找到檔案則繼續執行該函式下面的內容
 }

 else if (chn == '1')
  n = 1;     //如果選擇新遊戲,直接從第一關開始
 else
 {
  gotoxy(34,16);
  printf("請輸入正確的選擇:");
  goto Head;    //選擇錯誤的選單,則重新返回選擇選單的地方
 }

 //選擇1的情況:

 cwjzjrwkdhs();    //從檔案讀取關卡的函式
 detection();    //統計當前關卡的目標數
 tranmap();     //畫出地圖
 scmbxyhs();     //輸出地圖下方文字資訊
}

int detunf;//檢測未完成目標的變數,初始為一個關卡中未完成目標的個數,箱子碰到未完成目標時,自減

void detection()   //檢測當前關中有多少個目標
{
 //detunf
 detunf = 0;    //從0開始統計
 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(); //統計當前關卡目標個數
   scmbxyhs();  //輸出地圖下方的資訊
   opnum = 0,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;  //改變為空地
     else if (mapch[box_y][box_x] == 8) //如果箱子所在的位置是 箱子暫時所在已達成的目標
      mapch[box_y][box_x] = 5;  //改變為已達成的目標

     //箱子的下一步改變:

     if (mapch[boxnext_y][boxnext_x] == 2)  //如果箱子前面的格子是未放箱子的目標
     {
      mapch[boxnext_y][boxnext_x] = 5;  //則該格子變為已放箱子的目標
      detunf--;        //本關目標減1
      gotoxy(50,17);       //輸出本關剩餘目標個數
      printf("%d",detunf);
     }
     else if (mapch[boxnext_y][boxnext_x] == 0) //如果箱子前面是空地      
      mapch[boxnext_y][boxnext_x] = 3;  //則變為普通箱子
     else if (mapch[boxnext_y][boxnext_x] == 5) //如果箱子前面是已經放過箱子的目標
      mapch[boxnext_y][boxnext_x] = 8;  //則變為 箱子暫時所在已達成的目標
    }

    //原地改變
    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; //修改完成後不需要遍歷後面的陣列,直接跳出所有迴圈
   }
  }
 }

 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()      //獲取一次電腦現在的時間
{
 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()裡面的邏輯是比較複雜的,我當時寫這段程式碼的時候也是反反覆覆修改好多次甚至推翻重做才理通順這些邏輯的。

如果對於程式程式碼註釋有我沒寫明白的地方,歡迎在評論區下方留言詢問,如果我看到會盡最大的努力為您解惑。

不足之處:

地圖在螢幕上顯示時容易出錯,需要調整每關字數。原因並不明確。

因為作者對C語言的學習還比較淺薄,程式碼寫到初始化遊戲的兩種模式(有檔案和無檔案)時思維有些混亂,導致程式碼在這一部分有很多的缺陷,但最終程式的效果還是出來了。
但其實對整篇所有程式碼而言最重要的部分還是gbszszhs()函式,只要將這個函式完全理解並熟練掌握了,那麼整個“推箱子”遊戲也就非常簡單了。

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援我們。