用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); }
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次才能确定。
使用row和col记录当前光标位置,列使用奇数位,因此列的移动要±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