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

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

编程学习2022-11-1327660

logo 用C语言写个控制台扫雷游戏  编程 技术 电脑 C++ 第1张

前言

扫雷游戏大家应该都玩过,虽然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种难度和自定义难度,这里通过选择界面选择游戏难度。

先看看效果

选择界面_自定义 用C语言写个控制台扫雷游戏  编程 技术 电脑 C++ 第2张



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 游戏界面

游戏界面 用C语言写个控制台扫雷游戏  编程 技术 电脑 C++ 第3张



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


胜利界面 用C语言写个控制台扫雷游戏  编程 技术 电脑 C++ 第4张

失败界面 用C语言写个控制台扫雷游戏  编程 技术 电脑 C++ 第5张


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



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

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

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

分享给朋友:
返回列表

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

没有最新的文章了...

相关文章

代码迷惑技术如何保护Java免遭逆向工程

代码迷惑技术如何保护Java免遭逆向工程

很少有问题比程序员遇到不访问无法利用的源代码就无法解决的漏洞更令人沮丧的了。你是否在通过一个在线开源库修补代码,或正在调用常用操作系统例行程序;你可能每周都要花时间处理不是由你编写,因而也无法访问其源代码的代码。因为Java字节码包含许多和原始代码相同的信息,所以很容易对Java类文件执行逆向工程。另外,Java程度以其“一旦编写,随处运行”特性而闻名。虽然并非Java语言的专利,但代码反编译从未在Java开发者之中得到如此公开或普遍地利用。反编译的对...

 C++ string类常用函数

C++ string类常用函数

string类的构造函数:string(const char *s);    //用c字符串s初始化 string(int n,char  c);     //用n个字符c初始化此外,string类还支持默认构造函数和复制构造函数,如string s1;string  s2="hello";都是正确的写法。...

致面向对象技术初学者的一封公开信

致面向对象技术初学者的一封公开信

 致面向对象技术初学者的一封公开信 Alistair Cockburn 著(1996 年2 月),袁峰 译介绍 首先我要解释一下为什么会写这封公开信。这似乎已经成了一种习惯,但这个步骤还是需要的。过去6 年中, 我曾经无数次地在饭店、酒吧、旅店大厅等各种地方以同一种方式度过愉快而漫长的夜晚:和同样追求真理、光明和智慧的伙伴一起探讨面向对象的真谛。现在,我已经可以回答很多当年我遇到的问题。这些同样的问题也在困扰着我的一位新同事,在一家饭店里,我花了整整一个晚上和他讨...

制作网页28个常用小代码

制作网页28个常用小代码

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

发表评论

访客

看不清,换一张

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