1. 程式人生 > >C++ 小遊戲之推箱子

C++ 小遊戲之推箱子

做完C的貪吃蛇遊戲後,感覺還不錯,剛好記得在HDU上做過一道關於推箱子游戲的演算法題目,即雙BFS。

所以我決定來做做C++的小遊戲推箱子,由於剛學C++,對C++還是不很熟練,但是思路還是很清楚的,

編寫程式碼還是很舒服的。!

現在晒晒我的程式碼和詳細解釋,希望多交流~!

//*******************************************************
//********************小小推箱子*************************
//*******************************************************
//**************版權所有***2011.9.24***鹹魚**************
//*******************************************************
//*****************如寫的不好,請見諒*********************
//************每次執行程式的地圖是不同的*****************
//*******************************************************
/*
Sokoban.h:  類定義   Sokoban.c:  類成員函式實現
Use_Sokoban.c:  主函式
請用VC6(別編譯器的也行)先執行Use_Sokoban.c檔案,要編譯該檔案一下,
再點Project-> Add To Project-> Files 選擇Sokoban.c檔案,
即將Sokoban.c載入到工程裡,最後執行就OK拉。
*/

//*******************************************************
Sokoban.h
//*******************************************************
#ifndef SOKOBAN_H_  //防止檔案重複包含
#define SOKOBAN_H_
#include <queue>
using std::queue;
//每一步的資料型別
struct node 
{
 int bx, by; //箱子的座標
 int px, py; //人的座標
};
//推箱子類
class Sokoban 
{
private:
 enum {L = 15, H = 7};
 char GameMap[H][L]; //地圖
 int Pex, Pey;  //人的位置
 int Boxx, Boxy;  //箱子的位置
    int Succeed, Prove; //是否成功到目的地, 是否可玩性
    int dx[4], dy[4]; //方向陣列
protected:
 char Empty;
 char People;
 char Box;
 char Block;
 char Target;
 int dir;  //記錄按鍵方向
 node s, e;
public:
 Sokoban();  //構建函式
 ~Sokoban() {} //解構函式,即為inline
 //地圖初始化函式
 void Initial();
 //箱子路勁驗證函式,引數為箱子座標(bx,by),人座標(px,py)
 void Box_Bfs(int bx, int by, int px, int py);
 //人路勁驗證函式,人所到的目的地(ex,ey)
 bool People_Bfs(int ex, int ey);
 //地圖重新整理函式
 void Show();
 //按鍵判斷函式
 void Button();
 //箱子人移動函式
 void Move();
 //驗證越界函式
 bool Check(int x, int y); 
};
#endif
//*******************************************************
Sokoban.cpp
//*******************************************************
#include "Sokoban.h"
#include <cstring>
#include <cstdlib>
#include <ctime>
#include <iostream>
#include <conio.h>
using std::cout;
using std::endl;
Sokoban::Sokoban()  //構建函式即對變數初始化
{
 dir = -1;
 Succeed = Prove = 0;
 memset(GameMap, '.', sizeof(GameMap));
 Empty = '.';
 People = 'P';
 Box = '#';
 Block = '*';
 Target = 'T';
 //方向依次為上右下左
 dx[0] = -1;  dx[1] = 0;  dx[2] = 1;  dx[3] = 0;  
 dy[0] = 0;   dy[1] = 1;  dy[2] = 0;  dy[3] = -1;
    //隨機種子,使程式每次執行時所產生的隨機數不同
 srand(time(0));
}
//地圖初始化函式
void Sokoban::Initial()
{
 int count = 0, x, y;
 //對地圖中隨機產生25個阻礙物
 while(count != 25)
 {
  x = rand()%H;
  y = rand()%L;
        if(GameMap[x][y] == Empty)
  {
   GameMap[x][y] = Block;
      count++;
  }
 }
 while(true) //隨機產生人開始的位置
 {
  x = rand()%H;
  y = rand()%L;
     if(GameMap[x][y] == Empty)
  {
   GameMap[x][y] = People;
   Pex = x;
   Pey = y;
   break;
  }
 }
 while(true) //隨機產生箱子開始的位置
 {
  x = rand()%H;
  y = rand()%L;
  //不讓箱子在地圖的邊界處
     if(GameMap[x][y] == Empty && x != 0 && y != 0
  && x != H-1 && y != L-1)
  {
   GameMap[x][y] = Box;
   Boxx = x;
   Boxy = y;
   break;
  }
 }
 while(true) //隨機產生目標的位置
 {
  x = rand()%H;
  y = rand()%L;
     if(GameMap[x][y] == Empty)
  {
   GameMap[x][y] = Target;
   break;
  }
 }
 //對遊戲地圖檢查是否可將箱子推到目的地,即判斷遊戲可玩性
 Sokoban::Box_Bfs(Boxx, Boxy, Pex, Pey); 
 //如遊戲不可玩,即再隨機產生地圖
 if(!Prove)
 {
        memset(GameMap, '.', sizeof(GameMap));
  Sokoban::Initial();
 }
 else
  Sokoban::Show();
}
//箱子路勁驗證函式
//用BFS演算法對箱子驗證是否可到目的地
void Sokoban::Box_Bfs(int bx, int by, int px, int py)
{
 queue<node>_Box; //建立箱子佇列
 //visit對上一步走到下一步的記錄,防止箱子走重複路勁
 //visit[i][j][z][k]表示箱子從點(i,j)到點(z,k)
 //visit[][][][]為0時表示為走過,1時表示已走過
 int visit[H][L][H][L];
    
 memset(visit, 0, sizeof(visit)); //visit陣列初始化 
 s.bx = bx;  s.by = by;  //將起始的箱子、人位置放入佇列
 s.px = px;  s.py = py;
 _Box.push(s);
 int pe_x, pe_y;
 while(!_Box.empty()) //佇列為空時跳出
    {
        s = _Box.front();
        _Box.pop();
      
        if(GameMap[s.bx][s.by] == Target)  //到達目的地
        {
            Prove = 1;
            break;
        }
        for(int i = 0; i < 4; i++)
        {
            e.bx = s.bx + dx[i];  e.by = s.by + dy[i];
          
            switch(i) //人推箱子的位置
            {
            case 0:  pe_x = s.bx + dx[2]; pe_y = s.by + dy[2]; break;
            case 1:  pe_x = s.bx + dx[3]; pe_y = s.by + dy[3]; break;
            case 2:  pe_x = s.bx + dx[0]; pe_y = s.by + dy[0]; break;
            case 3:  pe_x = s.bx + dx[1]; pe_y = s.by + dy[1]; break;
            }
 
   //驗證箱子和人的位置的合法性
            if(!Check(e.bx, e.by) || !Check(pe_x, pe_y)
            || GameMap[e.bx][e.by] == Block || GameMap[pe_x][pe_y] == Block
            || visit[s.bx][s.by][e.bx][e.by] )
                continue;
 
   //如人可推箱子即進入佇列
            if(Sokoban::People_Bfs(pe_x, pe_y)) 
            {
    //儲存人推箱子後的位置
                e.px = pe_x;  e.py = pe_y;
                _Box.push(e);
                visit[s.bx][s.by][e.bx][e.by] = 1; //箱子路勁的標記
            }
        }
 }
}
 
//人路勁驗證函式
//用BFS演算法對人驗證是否可推箱子
bool Sokoban::People_Bfs(int ex, int ey)
{
 queue<node>_People;
    node t, end;
 //visit陣列對人的路勁進行標記,0為未走過,1為走過
    int visit[H][L]; 
 //visit陣列初始化為0
 memset(visit, 0, sizeof(visit));
    t.px = s.px;  t.py = s.py;  //人初始位置進入佇列
    _People.push(t);
    visit[t.px][t.py] = 1;
   
    while(!_People.empty()) //對立為空時跳出
    {
        t = _People.front();
        _People.pop();
        if(t.px == ex && t.py == ey)  //人可到達(ex,ey)該點
   return 1;
        for(int i = 0; i < 4; i++)
        {
            end.px = t.px + dx[i];  end.py = t.py + dy[i];
   //檢查人的位置合法性
            if(!Check(end.px, end.py) || GameMap[end.px][end.py] == Block
   || GameMap[end.px][end.py] == Box || visit[end.px][end.py])
                 continue;
   //進入佇列
            _People.push(end);
            visit[end.px][end.py] = 1; //記錄
        }
    }
    return 0;
}
 
//地圖重新整理函式
void Sokoban::Show()
{
 int i, j;
 while(true)
 {  
     //每半秒重新整理一次地圖
        clock_t  s = clock();
  while(clock() - s < CLOCKS_PER_SEC/2)
   ;
  //先判斷按鍵在移動
  Sokoban::Button();  
  Sokoban::Move();
  system("cls");
  for(i = 0; i < H; i++)
  {
   for(j = 0; j < L; j++)
      cout << GameMap[i][j];
      cout << endl;
  }
      cout << endl;
  
  cout << "\n**********************************" << endl;
  cout << "*     小小C++語言推箱子游戲      *" << endl;
  cout << "*     遊戲規則:                  *" << endl;
  cout << "*     P: 人        #: 箱子       *" << endl;
        cout << "*     *: 障礙物    T: 目的地     *" << endl;
  cout << "**********************************" << endl;
  cout << "*       每次遊戲地圖不一樣       *" << endl;
  cout << "*    人將箱子推到目的地即過關    *" << endl;
  cout << "*所給地圖,一定可過關,請慎重移箱子*" << endl;
  cout << "*   箱子無路可走時,機器不會提示  *" << endl;
  cout << "**********************************" << endl;
  //箱子成功到達目的地
  if(Succeed)
  {
   cout << "\n       ^_^  >_<" << endl;
   cout << "恭喜過關成功! 再來一盤吧" << endl;
   getchar();
   break;
  }
 }
}
 
//按鍵判斷函式
void Sokoban::Button()
{
 int key;
 if(kbhit() != 0) //檢查當前是否有鍵盤輸入,若有則返回一個非0值,否則返回0
 { 
  while(kbhit() != 0)  //可能存在多個按鍵,要全部取完,以最後一個為主
      key = getch(); //將按鍵從控制檯中取出並儲存到key中
  switch(key)
  {  
   //上
   case 72:  dir = 0;
          break;
   //右
            case 77:  dir = 1;     
          break;
            //下
   case 80:  dir = 2; 
          break;
   //左
   case 75:  dir = 3; 
          break;
  }
 }
}
 
//人推箱子移動函式
void Sokoban::Move()
{
 int x, y;
 //有按鍵時
 if(dir != -1) 
 {
  //人所推向的位置座標
  x = Pex + dx[dir];  y = Pey + dy[dir];
  //人所推位置為空,即走向該位置
  if(Check(x, y) && GameMap[x][y] == '.')
  {
   GameMap[Pex][Pey] = '.';  //人的位置改變
   GameMap[x][y] = 'P';
   Pex = x;  Pey = y;
   dir = -1;  //按鍵記錄為無即-1
  }
  else //人所推位置為箱子,即將箱子推向該方向的前面這點
   if(Check(x, y) && GameMap[x][y] == '#'
   && Check(x+dx[dir], y+dy[dir])
   && GameMap[ x+dx[dir] ][ y+dy[dir] ] == '.')
   {
    GameMap[Boxx][Boxy] = '.';  //箱子的位置改變
    GameMap[x+dx[dir] ][ y+dy[dir] ] = '#';
    Boxx = x + dx[dir];  Boxy = y + dy[dir];
              
    GameMap[Pex][Pey] = '.';  //人的位置改變
       GameMap[x][y] = 'P';
       Pex = x;  Pey = y;
    dir = -1;
   }
   else  //將箱子推向該方向的前面這點為目的地
    if(Check(x, y) && GameMap[x][y] == '#'
       && Check(x+dx[dir], y+dy[dir])
       && GameMap[ x+dx[dir] ][ y+dy[dir] ] == 'T')
    {
     GameMap[Boxx][Boxy] = '.';  //箱子的位置改變
        GameMap[x+dx[dir] ][ y+dy[dir] ] = '#';
        Boxx = x + dx[dir];  Boxy = y + dy[dir];
              
        GameMap[Pex][Pey] = '.';  //人的位置改變
           GameMap[x][y] = 'P';
           Pex = x;  Pey = y;
        dir = -1;
     Succeed = 1;  //記錄成功到達目的地
    }
 }
}
 
//判斷越界情況
bool Sokoban::Check(int x, int y)
{
 if(x < 0 || x >= H || y < 0 || y >= L)
        return 0;
    else
        return 1;
}
//*************************************************
Use_Sokoban.cpp
//*************************************************
#include <iostream>
#include "Sokoban.h"
using namespace std;
 
int main()
{
 Sokoban s;
 s.Initial();
 return 0;
}