1. 程式人生 > 程式設計 >C++實現掃雷遊戲(控制檯不閃屏版)

C++實現掃雷遊戲(控制檯不閃屏版)

之前寫了一個C++ 的控制檯掃雷小遊戲,但由於過度使用system("cls")刷屏,導致閃屏,因此重寫了一個改善的不閃屏版本,並把邏輯重新捋了一遍。

map.h

#ifndef MAP_H_
#define MAP_H_
 
#define MAX_WID 18
#define MAX_LEN 32
#define UP_EDGE 1    //上邊界
#define LEFT_EDGE 1   //左邊界
#define RIGHT_EDGE _len //右邊界
#define DOWN_EDGE _wid  //下邊界
 
struct Position {    //用於表示位置
  short x;
  short y;
};
 
struct MapInfo {    //表示掃雷圖的資訊
  int n;       //-1表示地雷,0表示空格,1~8表示雷數
  bool flag;     //是否已經被開啟
};
 
void gotoxy(short,short);    //游標移動函式
 
class Map {
private:
  int _len,_wid;        //圖的長寬
  int _mines,_blanks;      //雷數和空格數
  Position pos;          //游標位置
  MapInfo data[MAX_WID][MAX_LEN]; //地圖
 
public:
  void ChooseMode();       //選擇遊戲模式,初級,中級,高階
  void Draw();          //畫出地圖
  void InitMap();         //初始化地圖資訊
  void SetMine();         //設定地雷
  void SetNum();         //根據周圍地雷數計算數字
  void Move();          //負責移動
  void OpenBlock();        //開啟方塊
  void OpenAll();         //如果觸雷則全部開啟
  void Play();          //提供遊戲操作介面
  bool IfWin();          //判斷輸贏
  bool IfLose(); 
  // void show();
};
 
#endif

map類的實現

map.cpp

#include "map.h"
#include <iostream>
#include <cstdio>
#include <cstdlib>   //提供隨機函式,rand(), srand()
#include <ctime>    //提供time()函式
#include <conio.h>   //提供不回顯的輸入函式getch()
#include <windows.h>  //提供system()內命令
 
#define GOTOXY( pos ) gotoxy( 2 * (pos).x - 1,(pos).y - 1 )
#define POSITION_POS _wid+1   //遊戲資訊的位置,這裡是位置資訊
#define POSITION_BLANKS _wid+2 //空格數位置
#define POSITION_TIMES _wid+3  //時間顯示位置
#define POSITION_SITUATION _wid+4 //輸贏狀態位置
 
using std::cin;
using std::cout;
 
void gotoxy(short x,short y) {  //自行百度
  COORD pos = { x,y };
  HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
  SetConsoleCursorPosition(hOut,pos);
}
 
 
void Map::ChooseMode() {
  system("cls");      //清屏
  cout << "Please choose the mode\n";     
  cout << "1 : Beginner\n";
  cout << "2 : Intermediate\n";
  cout << "3 : Expert\nYour mode: ";
 
  char mode;
  cin >> mode;
  while (mode != '1' && mode != '2' && mode != '3') {  //只接受 1, 2, 3
    cout <<"\nWrong mode,please input 1,2,or 3\nYour mode: ";
    cin >> mode;
  }
  switch (mode) {  //根據模式改變地圖資訊
    case '1': _len = _wid = 8; _mines = 10; break;
    case '2': _len = _wid = 16; _mines = 40; break;
    default : _len = 30; _wid = 16; _mines = 99; 
  }
  _blanks = _len * _wid - _mines;  //更新空格數
}
 
 
void Map::Draw() {  //畫出地圖
  system("cls");
  SetConsoleOutputCP(437);   //自行百度,否則無法顯現方塊而是顯現?
  for (int i = 1; i <= _wid; i++) {
    printf("|");
    for (int j = 1; j <= _len; j++)
      printf("%c",219);    //219是方塊
    printf("|\n");
  }
  gotoxy(0,POSITION_POS);
  printf("Position: ( %2d,%2d )\n",pos.x,pos.y);
  printf("Blanks: %2d",_blanks);
  GOTOXY(pos);   //檢視 map.h 內說明
}
 
 
void Map::InitMap() {
  for (int i = 0; i <= _wid+1; i++)   //從0 ~ _wid+1 是因為可以假設地圖邊界的空格存在,且為0,後面計算
    for (int j = 0; j <= _len+1; j++) {
      data[i][j].flag = false;   //設定為沒有被開啟
      data[i][j].n = 0;      //全部設為空格
    }
  pos.x = pos.y = 1;
}
 
 
void Map::SetMine() {
  int mines = _mines;
  int x,y;
  Move();    //先執行move(),避免第一個空就觸雷
  srand(time(NULL));
  while (mines) { 
    x = rand() % _wid + 1;
    y = rand() % _len + 1;
    if (0 == data[x][y].n && (x != pos.x && y != pos.y)) {  //後面的條件可以避免第一個開啟的空被設定為地雷,避免第一步就觸雷
      data[x][y].n = -1;    //雷 設為 -1
      data[x][y].flag = true;  //設為雷的格子 flag 置為 true
      mines--;
    }
  }
}
 
 
void Map::SetNum() {
  for (int i = 1; i <= _wid; i++) {
    for (int j = 1; j <= _len; j++) {  //逐個計算格子周圍的 8 個格子的雷數
      if (-1 == data[ i ][ j ].n) continue;
      if (-1 == data[i-1][j-1].n) data[i][j].n++;
      if (-1 == data[i-1][ j ].n) data[i][j].n++;
      if (-1 == data[i-1][j+1].n) data[i][j].n++;
      if (-1 == data[ i ][j-1].n) data[i][j].n++;
      if (-1 == data[ i ][j+1].n) data[i][j].n++;
      if (-1 == data[i+1][j-1].n) data[i][j].n++;
      if (-1 == data[i+1][ j ].n) data[i][j].n++;
      if (-1 == data[i+1][j+1].n) data[i][j].n++;
    }
  }
  OpenBlock();  //與SetMine()配套,這時才正好開啟使用者要開啟的第一個空,避免第一步就觸雷
}
 
 
void Map::Move() {
  char mv;
  while (1) {
    mv = getch();
    if (mv == ' ') break;  //如果是 ‘ '(空格),那麼就結束移動,開啟方塊
    if (mv != 'w' && mv != 'a' && mv != 's' && mv != 'd') continue; //移動只接受 w a s d 四個鍵
    switch (mv) {
      case 'w': 
        if (pos.y != UP_EDGE)  pos.y--;
        break;
      case 's':
        if (pos.y != DOWN_EDGE) pos.y++;
        break;
      case 'a':
        if (pos.x != LEFT_EDGE) pos.x--;
        break;
      default :
        if (pos.x != RIGHT_EDGE) pos.x++;
    }
    gotoxy(12,POSITION_POS); //12,可以不用重新輸入覆蓋已經存在的 "Position: ",而是接著輸入
    printf("%2d,%2d",pos.y);
    GOTOXY(pos); //回到使用者所指的位置
  }
}
 
 
#define IF_IN_EDGE(p) ((p.x >= LEFT_EDGE && p.x <= RIGHT_EDGE) && (p.y >= UP_EDGE && p.y <= DOWN_EDGE)) //判斷是否越界
#define IF_PUSHIN_STACK(p) (data[p.y][p.x].flag == false && IF_IN_EDGE(p))     //判斷是否入棧,條件是:不越界且格子未被開啟
#define PUSHIN_STACK(p) { stack[++top] = p; data[p.y][p.x].flag = true; _blanks--; } //入棧,並設定為已開啟,並減少空格數,用於定是否獲勝
 
void Map::OpenBlock() {
  if (data[pos.y][pos.x].flag == true) return ;  //如果格子開啟過,就跳出函式
  int num,top = 0;
  Position stack[_len * _wid << 1];  //棧,用於存位置
  Position temp;
  stack[top] = pos;
  data[pos.y][pos.x].flag = true;  //要開啟的第一個格子設定為開啟
  _blanks--;  
  while (top != -1) {
    temp = stack[top--];
    GOTOXY(temp);
    num = data[temp.y][temp.x].n;
    if (0 == num) {
      printf(" ");      //如果是0,那麼輸出空格,並且判斷一下週圍8個是否要開啟,如果不是地雷就開啟
      temp.y--; temp.x--;
      if (IF_PUSHIN_STACK(temp)) PUSHIN_STACK(temp)  //格子周圍8個都要判斷
      temp.x++;                    //因為空格周圍的都要開啟
      if (IF_PUSHIN_STACK(temp)) PUSHIN_STACK(temp)
      temp.x++;
      if (IF_PUSHIN_STACK(temp)) PUSHIN_STACK(temp)
      temp.y++;
      if (IF_PUSHIN_STACK(temp)) PUSHIN_STACK(temp)
      temp.y++;
      if (IF_PUSHIN_STACK(temp)) PUSHIN_STACK(temp)
      temp.x--;
      if (IF_PUSHIN_STACK(temp)) PUSHIN_STACK(temp)
      temp.x--;
      if (IF_PUSHIN_STACK(temp)) PUSHIN_STACK(temp)
      temp.y--;
      if (IF_PUSHIN_STACK(temp)) PUSHIN_STACK(temp)
    }
    else {
      printf("%d ",num);  //不是0,也即不是空格,不存在連開
    }
  }
  gotoxy(8,POSITION_BLANKS);  //更新空格數
  printf("%2d",_blanks);
  GOTOXY(pos);  //回到使用者所指的位置
}
 
 
void Map::OpenAll() {
  SetConsoleOutputCP(437);
  gotoxy(0,0);
  for (int i = 1; i <= _wid; i++) {
    printf("|");
    for (int j = 1; j <= _len; j++) {
      switch (data[i][j].n) {
        case 0 : printf("%c",219); break;
        case -1: printf("* "); break;
        default: printf("%d ",data[i][j].n);
      }
    } printf("|\n");
  }
  GOTOXY(pos);
  printf("X");
}
 
 
void Map::Play() {
  char op;
  float end,start;
 
  start = clock();  //計時用
  while (!IfWin()) {  //如果還未獲勝
    Move();  //當 Move() 跳出即代表使用者輸入空格
    if (IfLose()) { OpenAll(); break; } //如果觸雷則跳出,並開啟全圖
    OpenBlock();  //開格子
  }
  end = clock();  //計時用
  gotoxy(0,POSITION_TIMES);
  printf("Times: %.2f s\n\n",(end-start)/CLK_TCK);
}
 
 
bool Map::IfWin() {
  return _blanks == 0;
}
 
 
bool Map::IfLose() {
  return -1 == data[pos.y][pos.x].n;
}

主函式

mineweeper.cpp

#include <cstdio>
#include <cstdlib>
#include <conio.h>
#include "map.h"
 
int main() {
  Map game;
  char ch;
  while (1) {
    game.ChooseMode();  //模式選擇
    game.InitMap();   //初始化
    game.Draw();     //畫出地圖
    game.SetMine();   //佈置地雷
    game.SetNum();    //計算數字
    game.Play();     //掃雷
    if (game.IfWin())   //判定輸贏
      printf("You Win\n");
    else
      printf("You Lose\n");
    printf("\nInput q to quit or c to continue : ");  //是否繼續
    ch = getch();
    while (ch != 'q' && ch != 'c') {
      ch = getch();
    }
    if (ch == 'q') break;
  }
  system("cls");
  printf("~Bye~\n\n");
  system("pause");
  return 0;

遊戲截圖

C++實現掃雷遊戲(控制檯不閃屏版)

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