当前位置:   article > 正文

用C语言写个控制台扫雷游戏(附完整代码)_控制台窗口游戏

控制台窗口游戏

前言

  扫雷游戏大家应该都玩过,虽然win10以后更新了,但还是win7版的好用,在代码写烦了来上几把换换脑子还是不错滴。

  下面用c语言模仿win7版的写一个控制台扫雷游戏。


一、对控制台的控制

  游戏中要对控制台的大小、文字颜色、光标大小、光标位置、鼠标输入等需要进行一些设置,下面列出所需要的一些函数。

1.控制台大小

  控制台大小通过mode命令设置。这个命令中的大小是指行和列的字符数而非像素。

//设置控制台大小
void SetSize(unsigned uCol, unsigned uLine)
{
    char cmd[64];
    sprintf(cmd, "mode con cols=%d lines=%d", uCol, uLine);
    system(cmd);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

2.控制台颜色

  控制台颜色设置详情可查看: 用C代码设置Windows控制台颜色

//更改文字颜色
// color为每一种颜色所代表的数字,范围是0~15
void setColor(WORD color)
{
    HANDLE HOutput = GetStdHandle(STD_OUTPUT_HANDLE); //获取标准输出的句柄
    SetConsoleTextAttribute(HOutput, color);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

3.控制台光标样式

  更改光标样式要用到结构体CONSOLE_CURSOR_INFO 和控制台API函数SetConsoleCursorInfo,通过这个结构体和函数可以设置光标的可见性和大小。

// 设置光标样式
void SetCursorType(BOOL bVisible)
{
    HANDLE HOutput = GetStdHandle(STD_OUTPUT_HANDLE);
    CONSOLE_CURSOR_INFO curInfo;
    curInfo.bVisible = bVisible; //更改光标显示状态,TRUE显示光标,FALSE隐藏光标
    curInfo.dwSize = 100;        // 光标大小1到100之间。 范围从完全填充单元格到单元底部的水平线条。
    SetConsoleCursorInfo(HOutput, &curInfo);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

4.控制台光标位置

  这个函数用的比较多,用来定位要输出的字符的位置。

//设置光标位置,起点从1开始
void MoveCursorTo(int nCols, int nRows)
{
    COORD crdLocation = {nCols, nRows};
    HANDLE HOutput = GetStdHandle(STD_OUTPUT_HANDLE); //获取标准输出的句柄
    SetConsoleCursorPosition(HOutput, crdLocation); //设置光标位置
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

5.获取控制台光标位置字符

  这个函数用的比较多,用来定位要输出的字符的位置。

/// 提取出窗口中第x行y列的位置的字符
/// @return 返回指定位置显示的字符,空白处返回空格
char GetCursorStr(int x, int y)
{
    COORD pos = {x, y};
    char str = 0;
    DWORD read;
    // HANDLE HOutput = GetStdHandle(STD_OUTPUT_HANDLE); //获取标准输出的句柄
    ReadConsoleOutputCharacterA(HOutput, &str, 1, pos, &read);
    return str;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

6.控制台显示位置

  通过API函数MoveWindow设置窗口的位置。在设置窗口位置之前,要先取得控制台窗口大小和屏幕大小,通过计算得出屏幕居中的位置。

//居中显示控制台
BOOL MoveConsoleWindow()
{
    //获取屏幕大小
    HDC hdc = GetDC(NULL);
    int cx = GetDeviceCaps(hdc, DESKTOPHORZRES); //窗口左侧位置
    int cy = GetDeviceCaps(hdc, DESKTOPVERTRES); //窗口顶部位置
    ReleaseDC(NULL, hdc);

    //获取控制台大小
    HWND HOutput = GetConsoleWindow();
    RECT rect;
    GetWindowRect(HOutput, &rect);

    int w = rect.right - rect.left; //窗口宽度
    int h = rect.bottom - rect.top; //窗口高度
    cx = (cx - w) / 2;
    cy = (cy - h) / 2;
    return MoveWindow(HOutput, cx, cy, w, h, TRUE); 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

7.控制台鼠标操作设置

  除了上面,还需要对控制台的鼠标操作进行屏蔽。


#define DISABLE_QUICK_EDIT_MODE 0x01
#define DISABLE_INSERT_MODE 0x02
#define DISABLE_MOUSE_INPUT 0x03
#define DISABLE_ALL (DISABLE_QUICK_EDIT_MODE | DISABLE_INSERT_MODE | DISABLE_MOUSE_INPUT)

//执行控制台相关设置
void CloseConsoleMode(UINT uTag)
{
    HANDLE HInput = GetStdHandle(STD_INPUT_HANDLE);//这里用标准输入设备
    DWORD mode;
    GetConsoleMode(HInput, &mode);
    if (uTag & DISABLE_QUICK_EDIT_MODE)
        mode &= ~ENABLE_QUICK_EDIT_MODE; //移除快速编辑模式
    if (uTag & DISABLE_INSERT_MODE)
        mode &= ~ENABLE_INSERT_MODE; //移除插入模式
    if (uTag & DISABLE_MOUSE_INPUT)
        mode &= ~ENABLE_MOUSE_INPUT; //移除鼠标输入
    SetConsoleMode(HInput, mode);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

  

二、游戏界面

1.游戏难度选择界面

  win版的扫雷有3种难度和自定义难度,这里通过选择界面选择游戏难度。
先看看效果
选择界面_自定义

1.1 基本定义

  定义结构体保存游戏的格子数量和地雷数量。


#define CONSOLEWIDTH 80  //控制台宽度
#define CONSOLEHEIGHT 40 //控制台高度
#define DEFCOLOR 0x7     //控制台默认文字颜色

typedef struct _point
{
    short x;
    short y;
} Point;

//格子大小
typedef struct _MapSize
{
    Point MapSize; //格子数量
    short Mine_Count; //地雷数量
} Map_Size;

//按照win扫雷定义尺寸和雷数
Map_Size defMapSize[3] = {{{9, 9}, 10}, {{16, 16}, 40}, {{30, 16}, 99}};

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

1.2显示标题信息

  • 使用setColor更改文字颜色
  • sprintf给buf输出字符宽度控制居中显示。
  • 显示输出后文字颜色改回默认颜色
void PrintName()
{
    system("cls");
    setColor(0x2);
    char buf[100];
    int l = (CONSOLEWIDTH - 20) / 2 + 20;
    sprintf(buf, "\n%%%ds\n", l);
    printf(buf, "欢迎来到扫雷小游戏");
    sprintf(buf, "%%%ds\n", (CONSOLEWIDTH - 16) / 2 + 16);
    printf(buf, "-- by Apull --");
    setColor(DEFCOLOR);
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

1.3显示难度选项

  • 不同难度使用不同颜色显示
  • 游戏界面中横向格子之间用空格隔开,因此在选定难度后,横向格子数量需要翻倍,更改为MapSize.MapSize.x * 2 - 1
//难度选择
void Choose()
{
    MapSize = defMapSize[0];
    PrintName();
    printf("\t\t选择游戏难度:\n");
    
    setColor(0xA);
    printf("\t\t  1、初级:%dx%d,共%d个地雷\n", defMapSize[0].MapSize.x, defMapSize[0].MapSize.y, defMapSize[0].Mine_Count);
    setColor(0xE);
    printf("\t\t  2、中级:%dx%d,共%d个地雷\n", defMapSize[1].MapSize.x, defMapSize[1].MapSize.y, defMapSize[1].Mine_Count);
    setColor(0xc);
    printf("\t\t  3、高级:%dx%d,共%d个地雷\n", defMapSize[2].MapSize.x, defMapSize[2].MapSize.y, defMapSize[2].Mine_Count);
    setColor(0x9);
    printf("\t\t  4、自定义难度\n");
    setColor(DEFCOLOR);
    printf("\t\t  0、退出\n");
    printf("\t\t输入你的选择(回车默认选择1): ");
    
    char ch;
    BOOL inputOK = TRUE;
    while (inputOK)
    {
        ch = _getch();
        switch (ch)
        {
        case '1':
        case Key_ENTER:
            MapSize = defMapSize[0];
            inputOK = FALSE;
            break;
        case '2':
            MapSize = defMapSize[1];
            inputOK = FALSE;
            break;
        case '3':
            MapSize = defMapSize[2];
            inputOK = FALSE;
            break;
        case '4':
            printf("\n");
            MapSize.MapSize.y = OptMines("\t\t  高度:", 9, 24);
            MapSize.MapSize.x = OptMines("\t\t  宽度:", 9, 30);
            MapSize.Mine_Count = OptMines("\t\t  雷数:", 10, 668);
            inputOK = FALSE;
            break;
        case '0':
        case Key_ESC:
            EXIT(0);
            break;
        default:
            break;
        }
    }
    MapSize.MapSize.x = MapSize.MapSize.x * 2 - 1;
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57

1.4 选择自定义难度

  • 为了防止输入错误后的无限循环,这里使用字符串接收输入,再转换成int类型。
  • 设定地雷数量是所有格子数量的20%
  • 输入错误使用红色文字提示
//自定义难度
int OptMines(char *str, int min, int max)
{
    int x;
    char buf[20] = {0};
    if (max > 30) // max > 30 表示输入的是雷数
    {
        max = (int)(MapSize.MapSize.x * MapSize.MapSize.y * 0.2); //地雷数量是所有格子数的20%
    }
    while (TRUE)
    {
        printf(str);
        printf("(%d-%d):", min, max);
        gets_s(buf, 20);

        if (buf[0] == '0')
            EXIT(0);
        x = atoi(buf);
        if (x < min || x > max)
        {
            setColor(0x04);
            printf("\t\t输入超出范围,请重新输入。\n");
            setColor(DEFCOLOR);
        }
        else
            return x;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

2 游戏界面

游戏界面

2.1 基本定义

2.1.1 定义地雷格子结构

  结构体struct _mine中定义一个格子要显示的字符、周围8个方向上地雷数量以及格子本身的状态。

//定义地雷格子状态
enum Mine_STATU
{
    HIDDEN_SAFE, 
    VISIBLE_SAFE,
    HIDDEN_MINE
};

//地雷格子定义
typedef struct _mine
{
    char ch;
    char hasMines;
    enum Mine_STATU statu;
} Mine;

#define FLAG '@'		//标记的地雷
#define MAPNORMAL 'o'	//正常格子
#define MINEICO '*'		//地雷

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
2.1.2 填充地雷格子

  使用二维数组minefield存储每个格子,根据难度选择给二维数组分配内存大小。

Mine **minefield = NULL;    //格子数组

//生成格子数组,并填充显示字符
Mine **initMapArr()
{
    Mine **minefield = (Mine **)calloc(MapSize.MapSize.y, sizeof(Mine *));
    if (minefield == NULL)
    {
        printf("内存分配错误!");
        EXIT(-1);
    }
    for (int i = 0; i < MapSize.MapSize.y; i++)
    {
        minefield[i] = (Mine *)calloc(MapSize.MapSize.x, sizeof(Mine));
        if (minefield == NULL)
        {
            printf("内存分配错误!");
            EXIT(-1);
        }
        for (int j = 0; j < MapSize.MapSize.x; j++)
        {

            minefield[i][j].ch = (j % 2) ? ' ' : MAPNORMAL;
        }
    }
    return minefield;
}


  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
2.1.3 产生地雷
  • 使用动态数组MinesPos保存地雷所在位置
  • 用随机数产生地雷
  • 检索重复项,有重复的则重新生成地雷。
Point *MinesPos = NULL;     //地雷所在位置
Point *FindMinesPos = NULL; //标记的地雷所在位置
int MineCount = 0;          //地雷总数

//产生地雷
void init_Mine(Point *point, int MineCount)
{
    Point pot;
    for (int i = 0, j; MineCount > i;)
    {
        do
        {
            pot.x = rand() % MapSize.MapSize.x;
        } while (pot.x % 2 != 0);

        pot.y = rand() % MapSize.MapSize.y;
        for (j = 0; j < i; j++)
        {
            if (point[j].x == pot.x && point[j].y == pot.y)
                break;
        }
        if (i == j)
        {
            point[i].x = pot.x;
            point[i].y = pot.y;
            i++;
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
2.1.4 将地雷放置到格子中

  数组MinesPos中保存着地雷的坐标,直接在格子中把这些坐标标记为地雷。

//写入雷的位置
void init_Map(Mine **minefield, Point *point, int MineCount)
{
    for (int i = 0; i < MineCount; i++)
    {
        minefield[point[i].y][point[i].x].statu = HIDDEN_MINE;
        minefield[point[i].y][point[i].x].hasMines = -1;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
2.1.5 计算周围地雷数量
  • 按行列扫描所有格子
  • 对一个格子的周围8个位置的地雷数量进行统计
  • 将统计数量保存到格子的hasMines属性中
//计算周围地雷数量
void calMine(Mine **minefield)
{
    int count;
    for (int i = 0; i < MapSize.MapSize.y; i++)
    {
        for (int j = 0; j < MapSize.MapSize.x; j += 2)
        {
            if (minefield[i][j].statu == HIDDEN_MINE)
                continue;

            count = 0;
            if (i > 0 && j > 1 && minefield[i - 1][j - 2].statu == HIDDEN_MINE) //左上角
                count++;
            if (i > 0 && minefield[i - 1][j].statu == HIDDEN_MINE) //上面
                count++;
            if (i > 0 && j < MapSize.MapSize.x - 2 && minefield[i - 1][j + 2].statu == HIDDEN_MINE) //右上角
                count++;

            if (j < MapSize.MapSize.x - 1 && minefield[i][j + 2].statu == HIDDEN_MINE) //右面
                count++;
            if (i < MapSize.MapSize.y - 1 && j < MapSize.MapSize.x - 2 && minefield[i + 1][j + 2].statu == HIDDEN_MINE) //右下角
                count++;

            if (i < MapSize.MapSize.y - 1 && minefield[i + 1][j].statu == HIDDEN_MINE) //下面
                count++;
            if (i < MapSize.MapSize.y - 1 && j > 0 && minefield[i + 1][j - 2].statu == HIDDEN_MINE) //左下角
                count++;

            if (j > 0 && minefield[i][j - 2].statu == HIDDEN_MINE) //左面
                count++;
                
            minefield[i][j].hasMines = count;
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
2.1.6 其他变量

  

Point StatuRow;             //状态显示行位置
clock_t start_t, end_t;     //记录游戏时间
//格子范围,限定地雷显示和操作的范围
int top = 0, left = 0, right = 0, bottom = 0;
  • 1
  • 2
  • 3
  • 4

2.2 显示游戏介绍

  显示游戏介绍,并返回显示的行数。

// 输出游戏介绍
int intro()
{
    int line = 3; //加上之前的标题信息2行
    line += 7;
    StatuRow.y = line - 2;
    StatuRow.x = left;

    PrintName(); //显示标题信息
    printf("\t\t游戏方法介绍:\n");
    
    setColor(0xE);
    printf("\t\t  W、S、A、D/↑、↓、←、→");
    setColor(DEFCOLOR);
    printf(":控制光标上下左右移动\n");
    
    setColor(0xE);
    printf("\t\t  PageUP、PageDown、Home、End");
    setColor(DEFCOLOR);
    printf(":控制光标跳到四边位置\n");
    
    setColor(0xE);
    printf("\t\t  空格");
    setColor(DEFCOLOR);
    printf(":打开当前格子");
    
    setColor(0xE);
    printf("  R");
    setColor(DEFCOLOR);
    printf(":回到选择界面");
    
    setColor(0xE);
    printf("  ESC");
    setColor(DEFCOLOR);
    printf(":结束游戏\n");
    
    ShowStatu();
    printf("\n\n");
    
    return line;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41

2.3 显示游戏状态

  显示游戏信息和游戏时间,光标移动到要输出的位置,输出内容覆盖就内容,完成状态刷新。
  在输出状态的时候要移动光标,游戏过程中会看到光标跳到状态显示位置后有会跳回格子中,为避免这种情况,在开始输出状态时使用SetCursorType隐藏光标,输出完毕后再显示光标

//显示游戏状态
void ShowStatu()
{
    static int total_t = 0;
    end_t = clock();
    total_t = (end_t - start_t) / CLOCKS_PER_SEC;
    SetCursorType(FALSE);
    MoveCursorTo(StatuRow.x, StatuRow.y);
    printf("\t\t共有雷数:%-3d  剩余雷数:%-3d  用时:%ds   ", MapSize.Mine_Count, MineCount, total_t);
    SetCursorType(TRUE);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

2.4 显示地雷格子

  • 根据上面显示的信息行数得到地图格子要显示的起始行
  • 根据上面基本定义的宽度CONSOLEWIDTH计算居中显示的起始列
  • 每一行光标移动到左边起始列后输出行

//确定绘制地雷格子范围
void DrawMine()
{
    left = right = bottom = 0;
    top = intro();
    drawMap(minefield);

    right = MapSize.MapSize.x + left;
    bottom += MapSize.MapSize.y + top - 1;
    MoveCursorTo(left, top);
    start_t = clock(); //开始计时
}

//绘制地雷格子和外框,设置各边范围
void drawMap(Mine **minefield)
{
    int l = (CONSOLEWIDTH - MapSize.MapSize.x) / 2 - 4;
    if (l % 2 == 0)
        l++;
    int t = top;
    MoveCursorTo(l, t++);

    printf("┌");
    for (int i = 0; i <= MapSize.MapSize.x; i++)
        printf("─");
        
    printf("┐\n");
    MoveCursorTo(l, t++);
    // 输出中间部分
    for (int i = 0; i < MapSize.MapSize.y; i++)
    {
        printf("│ ");
        for (int j = 0; j < MapSize.MapSize.x; j++)
        {
            printf("%c", minefield[i][j].ch);
        }
        printf("│\n");
        MoveCursorTo(l, t++);
    }
    printf("└");
    for (int i = 0; i <= MapSize.MapSize.x; i++)
        printf("─");
        
    printf("┘\n");

    top += 1;
    left = l + 2;
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50

  至此地雷界面绘制基本完成,下面来看看操作部分。
  

三、游戏操作

  

1 游戏按键输入

1.1 定义按键

  定义出后面要用到达按键变量

//按键定义
enum KB_KEY
{
    Key_FN = 0xE0,       // 功能键标志
    Key_Up = 0x48,       // 向上方向键   
    Key_Down = 0x50,     // 向下方向键
    Key_Left = 0x4b,     // 向左方向键
    Key_Right = 0x4d,    // 向右方向键
    Key_Home = 0x47,     // Home键
    Key_End = 0x4f,      // End键
    Key_PageUp = 0x49,   // PageUp键
    Key_PageDown = 0x51, // PageDown键
    Key_ESC = 0x1B,      // ESC键
    Key_ENTER = 0xD,     // 回车键
    Key_SPACE = 0x20,    // 空格键
    Key_A = 'A',
    Key_a = 'a',
    Key_D = 'D',
    Key_d = 'd',
    Key_W = 'W',
    Key_w = 'w',
    Key_S = 'S',
    Key_s = 's',
    Key_R = 'R',
    Key_r = 'r'
} KEY;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

1.2 读取按键输入

  • 需要注意的是方向键的输入,方向键是先输入0xE0或0,再输入对应的键值,因此输入方向键的输入需要读取2次才能确定。
  • 使用rowcol记录当前光标位置,列使用奇数位,因此列的移动要±2。
  • 检测移动后光标会不会超出现实范围,如果有超出情况则取消移动。
  • 回车 标记地雷
  • 空格 点开地雷格子
  • R 结束游戏回到选择界面
  • ESC 退出游戏
int row = top; //行
int col = left; //列

char ch;
int chInput = 0;
int isGame = 1;  //游戏继续标志
int x, y;
while (isGame)
{
	if (_kbhit() != 0) //当_kbhit返回值不为0的时候,代表用户有按键按下。
	{
		chInput = _getch();
		if (chInput == Key_FN || chInput == 0) // 方向键读2次
			chInput = _getch();

		switch (chInput) //控制操作
		{
		case 'a':
		case 'A':
		case Key_Left:
			col -= 2;
			if (col <= left)
				col = left;
			break;
		case 'd':
		case 'D':
		case Key_Right:
			col += 2;
			if (col >= right)
				col = right - 1;
			break;
		case 'w':
		case 'W':
		case Key_Up:
			row--;
			if (row <= top)
				row = top;
			break;
		case 's':
		case 'S':
		case Key_Down:
			row++;
			if (row >= bottom)
				row = bottom;
			break;
		case Key_SPACE: //空格点开
			...
			break;
		case Key_ENTER: //标记地雷
			...
			break;
		case Key_Home: //跳到第一列
			col = left;
			break;
		case Key_End: //跳到最后一列
			col = right - 1;
			break;
		case Key_PageUp: //跳到第一行
			row = top;
			break;
		case Key_PageDown: //跳到最后一行
			row = bottom;
			break;
		case Key_ESC: //退出游戏
			isGame = 0;
			break;
		case Key_R:
		case Key_r: //复位到选择界面
			init();
			DrawMine();
			break;
		default: //其他输入字符不动作
			break;
		}

		MoveCursorTo(col, row);
	}
	Sleep(150);
	ShowStatu();
	MoveCursorTo(col, row);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81

2 标记地雷

  • 回车 标记地雷。
  • 读取光标处的字符,光标处是未点开格子则标记地雷。
  • 标记的地雷坐标保存到FindMinesPos数组中。
  • 如果该位置已标记则取消标记,同时从FindMinesPos数组中删除。
  • 标记后判断所有的地雷是否都已标记,如果都已标记则判赢
  • 标记后光标右移到下一个地雷格子
case Key_ENTER: //标记地雷
	if (flagMine(row, col))
		GameOver(WIN_GAME);
	col += 2;
	if (col >= right)
		col = right - 1;
	break;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
//标记地雷
BOOL flagMine(int row, int col)
{
    char ch;
    BOOL iswin = FALSE;
    ch = GetCursorStr(col, row); //获取光标处字符
    if (ch == FLAG && MineCount < MapSize.Mine_Count) //切换已标记
    {
        FindMinesPos[MineCount].x = FindMinesPos[MineCount].y = -1;
        setColor(DEFCOLOR);
        putchar(MAPNORMAL);
        MineCount++;
    }
    else if (ch == MAPNORMAL && MineCount > 0)
    {
        MineCount--;
        FindMinesPos[MineCount].x = col - left;
        FindMinesPos[MineCount].y = row - top;

        setColor(0xC);
        putchar(FLAG);

        if (MineCount == 0)
        {
            int i;
            for (i = 0; i < MapSize.Mine_Count; i++) // 确定是否标记了所有的雷
            {
                if (findMine4Pos(FindMinesPos[i].x, FindMinesPos[i].y) == FALSE)
                    break;
            }
            iswin = i == MapSize.Mine_Count;
        }
    }

    setColor(DEFCOLOR);
    ShowStatu(); //更新标记的雷数
    MoveCursorTo(col, row);
    return iswin;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

3 点开地雷操作

3.1 点开地雷格子

  • 空格 点开地雷格子
  • 读取光标位置字符,如果是已标记地雷则结束点开操作。
  • 如果点开的位置是地雷,游戏结束,显示游戏结束画面
  • 点开位置安全,则自动点开相连的安全区域。
  • 自动点开后判断是否一点开所有安全区,判断胜负。
  • 点开后光标右移到下一个地雷格子
case Key_SPACE: //空格点开
	x = col - left;
	y = row - top;
	ch = GetCursorStr(col, row);
	if (ch == FLAG) //忽略点开已标记地雷
		break;

	if (minefield[y][x].statu == HIDDEN_MINE) //点开雷,游戏结束
	{
		isGame = GameOver(LOSS_GAME);
		row = top;
		col = left;
		continue;
	}
	else //自动点开连续的安全区
	{
		SetCursorType(FALSE);
		OpenMine(y, x);
		SetCursorType(TRUE);
		if (isWin())
		{
			isGame = GameOver(WIN_GAME);
			row = top;
			col = left;
			continue;
		}
	}

	col += 2;
	if (col >= right)
		col = right - 1;
	break;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

3.2 自动展开安全区域

  • 递归对当前格子周围的安全区域进行展开操作
  • 递归到边界或已点开区域终止
  • 点开时使用不用的颜色显示格子周围地雷数。
  • 格子周围地雷数为0的显示为空格’ ’
//自动打开安全区
void OpenMine(int row, int col)
{
    int x = 0;
    x = col + left;
    int y = 0;
    y = row + top;
    char ch = 0;

    if (row < 0 || row >= MapSize.MapSize.y)
        return;
    if (col < 0 || col >= MapSize.MapSize.x)
        return;

    if (minefield[row][col].statu == VISIBLE_SAFE)
        return;

    if (minefield[row][col].hasMines == 0)
        ch = ' ';
    else if (minefield[row][col].hasMines > 0)
        ch = minefield[row][col].hasMines + '0';
    else
        return;

    if (minefield[row][col].hasMines >= 0)
    {
        MoveCursorTo(x, y);
        int n = minefield[row][col].hasMines;
        switch (n)
        {
        case 1:
            setColor(0x9); //淡蓝色
            break;
        case 2:
            setColor(0x2); //绿色
            break;
        case 3:
            setColor(0xC); //淡红色
            break;
        case 4:
            setColor(0x1); //蓝色
            break;
        case 5:
            setColor(0x4); //红色
            break;
        case 6:
            setColor(0xD); //淡紫色
            break;
        case 7:
            setColor(0x5); //紫色
            break;
        case 8:
            setColor(0x6); //黄色
            break;
        }
        putchar(ch);
        setColor(DEFCOLOR);
        minefield[row][col].ch = ch;
        minefield[row][col].statu = VISIBLE_SAFE;
        if (ch != ' ')
            return;
    }

    if (row > 1)
        OpenMine(row - 1, col);
    if (col < MapSize.MapSize.x)
        OpenMine(row, col + 2);
    if (row < MapSize.MapSize.y)
        OpenMine(row + 1, col);
    if (col >= 2 && col % 2 == 0)
        OpenMine(row, col - 2);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72

四、游戏胜负

1 胜负判定

  • 游戏过程中如果点开了地雷,则直接判负。
  • 比对所有未点开的地雷格子和地雷数组的坐标,如果刚好符合则判赢。
//检查标记地雷是否都正确
BOOL isWin()
{
    BOOL iswin = FALSE;
    int cnt = 0;
    for (int i = 0; i < MapSize.MapSize.y; i++)
    {
        for (int j = 0; j < MapSize.MapSize.x; j++)
        {
            if (minefield[i][j].ch == MAPNORMAL)
            {
                if (findMine4Pos(j, i) == FALSE)
                {
                    i = MapSize.MapSize.y + 1;
                    break;
                }
                cnt++;
            }
        }
    }

    iswin = cnt == MapSize.Mine_Count;

    return iswin;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

2 胜负界面

  • 外部函数判断胜负后传递胜负结果变量给GameOver函数。
  • 根据参数使用不同颜色显示不同提示。
  • 光标定位到地雷格子往下2行位置输出胜负信息。
  • 根据提示返回是否继续游戏
//胜负结果变量
enum GAME_RESULT
{
    WIN_GAME,
    LOSS_GAME
};

//游戏结束画面
BOOL GameOver(enum GAME_RESULT statu)
{
    MoveCursorTo(0, bottom + 2);
    if (statu == WIN_GAME)
    {
        // system("color 2F");//改变背景为绿色
        showMine(TRUE);
        setColor(0x02);
        printf("\t    恭喜,你找出了所有的地雷!\n");
    }
    if (statu == LOSS_GAME)
    {
        // system("color 4F"); //改变背景为红色
        showMine(FALSE);
        setColor(0x04);
        printf("\t    哦吼,踩到雷了!\n");
    }
    setColor(DEFCOLOR);

    BOOL rst = FALSE;
    char ch;
    printf("\n\t  是否重新开始游戏(Y/N): ");
    fflush(stdin);
    do
    {
        ch = _getch();
        if (ch == 'Y' || ch == 'y' || ch == Key_ENTER)
        {
            init();
            DrawMine();
            rst = TRUE;
            break;
        }
        else if (ch == 'N' || ch == 'n' || ch == Key_ESC)
        {
            rst = FALSE;
            break;
        }
    } while (TRUE);

    return rst;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50

  胜利界面
胜利界面

  
  失败界面
失败界面


五、总结

  没有总结
  

  完整代码已上传到GitCode,需要的移步 https://gitcode.net/apull/Mines_Console
  
  

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号