当前位置:首页 > 编程学习 > 用C语言写个控制台扫雷游戏

用C语言写个控制台扫雷游戏

编程学习2022-11-131480

logo

前言

扫雷游戏大家应该都玩过,虽然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);
}

2.控制台颜色

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


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

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);
}

4.控制台光标位置

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


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

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;
}

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); 
}

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.游戏难度选择界面

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显示标题信息

  • 使用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.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.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;
    }
}

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 '*'  //地雷

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;
}

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++;
        }
    }
}

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;
    }
}

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;
        }
    }
}

2.1.6 其他变量

Point StatuRow;      //状态显示行位置
clock_t start_t, end_t;  //记录游戏时间

//格子范围,限定地雷显示和操作的范围
int top = 0, left = 0, right = 0, bottom = 0;

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;
}

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);
}

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 游戏按键输入

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 读取按键输入

  • 需要注意的是方向键的输入,方向键是先输入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);
}

2 标记地雷

  • 回车标记地雷。

  • 读取光标处的字符,光标处是未点开格子则标记地雷。

  • 标记的地雷坐标保存到FindMinesPos数组中。

  • 如果该位置已标记则取消标记,同时从FindMinesPos数组中删除。

  • 标记后判断所有的地雷是否都已标记,如果都已标记则判赢。

  • 标记后光标右移到下一个地雷格子


case Key_ENTER: //标记地雷
    if (flagMine(row, col))
        GameOver(WIN_GAME);
        
    col += 2;
    if (col >= right)
        col = right - 1;
    break;


//标记地雷
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;
}

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;

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 胜负判定

  • 游戏过程中如果点开了地雷,则直接判负。

  • 比对所有未点开的地雷格子和地雷数组的坐标,如果刚好符合则判赢。


//检查标记地雷是否都正确
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;
}

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;
}



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



扫描二维码推送至手机访问。

版权声明:本文由海阔天空发布,如需转载请注明出处。

本文链接:https://www.apull.net/html/20221113195039.html

分享给朋友:
返回列表

上一篇:用C代码设置Windows控制台颜色

没有最新的文章了...

相关文章

VB获取光驱盘符

VB获取光驱盘符

VB获取光驱盘符Option Explicit Private Declare Function GetDriveType Lib "kernel32" Alias "GetDriveTypeA" _ (ByVal nDrive As String) As Long 'GetLogicalDriveStrings-->获取一个字串,其中包含了当前所有逻辑驱动器的根驱动器路径 Private Declare Function GetLogicalDriveStri...

制作网页28个常用小代码

制作网页28个常用小代码

1、oncontextmenu="window.event.returnValue=false" 将彻底屏蔽鼠标右键<table border oncontextmenu=return(false)><td>no</table>  可用于Table2、<body onselectstart="return false"> 取消选取、防止复制3、onpaste="retu...

计算机蓝屏代码的含义

计算机蓝屏代码的含义

0 0x0000 作业完成。1 0x0001 不正确的函数。2 0x0002 系统找不到指定的档案。3 0x0003 系统找不到指定的路径。4 0x0004 系统无法开启档案。5 0x0005 拒绝存取。6 0x0006 无效的代码。7 0x0007 储存体控制区块已毁。8 0x0008 储存体空间不足,无法处理这个指令。9 0x0009 储存体控制区块地址无效。10 0x000A 环境不正确。11 0x000B 尝试加载一个格式错误的程序。12 0x000C 存取码错误。1...

在ASP中访问和更新Cookies集合

在ASP中访问和更新Cookies集合

  Cookies的值比ASP其他集合(例如Form和ServerVariables)的值要复杂得多。Cookie是一小块由浏览器存贮在客户端系统上的文本,且随同每次请求发往它们应用于的域中的服务器。  ASP使得应用cookie较为容易,可以从Request对象的Cookies集合中获得所有随同请求发出的cookie值,并可创建或修改cookie,通过Response对象的Cookies集合发回给用户。  Cookie包含可用两种方式构造的信息,单值cookie提供其值给代...

发表评论

访客

看不清,换一张

◎欢迎参与讨论,请在这里发表您的看法和观点。