目录
3.1 创建游戏主体文件test.c
3.2 创建存放头文件的文件game.h
1.配置运行环境
众所周知,“扫雷”是个极其经典的游戏,相信大家都玩过。下面开始教你用C语言简单实现扫雷游戏。
在开始之前推荐大家先去看看我写的 前一篇博客(链接点这里 —>) [C语言] [游戏] 三子棋 写了 8950字 超详细哟 所以在本文中有些重复的细节部分我可能不会说得太详细,不然字数会好多(^ ^ 捂脸.jpg)。
写之前还是先简单介绍扫雷的游戏规则:玩家输入要排查雷的坐标,如果该位置埋了雷那么游戏结束玩家挑战失败;如果该位置没雷,那么该位置会显示其周围一圈(与其最相邻的共8个位置)内一共含有的雷的个数,就这样一直排雷一直排雷直到所有非雷位置都被排查完,那么恭喜你挑战成功。
这个扫雷代码,我也同样分为 game.h \ game.c \ test.c 三个文件,分别写自定义函数的声明、定义以及整个程序的实现。然后大家记得一边写一边测试,这样有错误的话改起来不费力。
为了方便下文说明。我们把“操作面”形象的称为“棋盘”。
扫雷代码的实现思路
先来分析一下扫雷游戏的组成成分和动作:游戏开始和结束的提示、棋盘、电脑埋雷、打印棋盘、玩家排雷、判断输赢。
主函数
int main() { int input = 0; srand((unsigned int)time(NULL)); do { menu(); printf("请选择->"); scanf("%d", &input); { switch (input) { case 1: printf("扫雷\n"); game(); break; case 0: printf("游戏结束\n"); break; default: printf("选择错误,重新选择\n"); } } }while (input); }
游戏开始和结束的提示
同样的为了方便玩家退出游戏和提高游戏体验感这里需要写一个函数 menu 让玩家选择是否开始游戏。
void menu() { printf("*********************\n"); printf("*******1.play********\n"); printf("*********************\n"); printf("*******0.exit********\n"); printf("*********************\n"); }
接着写出游戏的开始和结束执行代码,先整好最大的一个逻辑,将游戏细节的实现放入game函数里。
void game() { char mine[ROWS][COLS]; char show[ROWS][COLS]; InitBoard(mine, ROWS, COLS,'0'); InitBoard(show, ROWS, COLS,'*'); DisplayBoard(show, ROW, COL); SetMine(mine, ROW, COL); FindMine(mine, show, ROW, COL); }
棋盘
玩家的操作是输入坐标,所以对应的我们也应该使用二维数组。 我们把 0 表示无雷 , 1 表示 雷 ,‘ * ’ 表示未被排查,数字字符表示(已排查)排查出该位置周围一圈内含雷的数量。
这里把 0 表示无雷 , 1 表示 雷 选择统一使用数字是因为方便,比起使用多种特殊字符,人们更习惯用有顺序 的数字字符进行替换记忆。而且便于计算排查出的雷的信息。
使用‘ * ’ 表示未被排查,是为了保持游戏的神秘感,直观上的视觉冲击强烈暗示我们该位置情况未知。若使用数字则和雷的信息混乱,小白玩家可能会吐槽太丑,然后直接退出游戏。(游戏体验感 游戏体验感)若用空格那么玩家可能会认为没有雷要排,就是没有那种该位置放了东西的感觉。而且将满屏的 ‘ * ’ 都消灭的 成就感 也是玩家需要的。(嘿嘿)
那么这里还需注意我们得创建两个数组,一个埋雷,一个排雷,是吧。
这里你可能想问,我只用一个数组,把一个特殊字符当雷,其它位置直接放排查雷 的信息不可以吗? 那么你这么打印给玩家看呢?只有一个数组,那么打印就会把雷也打印出来,但玩家需看到整个棋盘才可以选择坐标。所以我们要创建 mine数组仅给电脑操作(埋雷),对玩家隐藏。show数组负责打印出来给玩家排雷。
那么show数组必须和mine数组完全一一对应存放对应坐标的排查信息。也就是show数组得和mine数组一样大小。
想好了两个棋盘,但是棋盘大小呢?真的是你所看到的 9 x 9 吗?
因为排雷排的是坐标周围一圈,那么那些边缘坐标具体怎么排?是和中间坐标一样直接整个一圈来判断? 那么在 9 x 9 中会判断到这些边缘坐标的边缘(1,5)—> (0,5) 显然没有(0,5)这个坐标,这时候就会出现数组的越界访问,编译器直接报错。
若是对这些边缘坐标特殊化判断,那么会有 在行的边缘和列的边缘的坐标,又会出现不同的特殊判断情况,明显更麻烦。
所以更好的方法就是扩大一圈,上下左右各多一行一列。变成 11 x 11 。这样就可以对原来的(1,5)这样的坐标使用一样的一整圈排查的函数,每个元素用一样的操作进行判断,会精简一些,只要打印的时候注意不显示出这些扩大了的范围就好。
void InitBoard(char board[ROWS][COLS], int rows, int cols,char set) { int i = 0; for (i = 0; i < rows; i++) { int j = 0; for (j = 0; j < cols; j++) { board[i][j] = set; } } } void DisplayBoard(char board[ROWS][COLS], int row, int col) { int i = 0; printf("-------扫雷游戏-----------\n"); int j = 0; for (j = 0; j <= col; j++) printf("%d ", j); printf("\n"); for(i = 1; i <=row; i++) { printf("%d ", i); int j = 0; for (j = 1; j <= col; j++) { printf("%c ", board[i][j]); } printf("\n"); } }
电脑埋雷
先定义 MINE 表示雷的个数,使用随机数(不确定性大),所以埋的时候得判断该位置是否已被埋。用随机值。
void SetMine(char board[ROWS][COLS], int row, int col) { int count = EASY_COUNT; while (count) { int x = rand() % row + 1; int y = rand() % col + 1; if (board[x][y] == '0') { board[x][y] = '1'; count--; } } }
玩家排雷、判断输赢
接下来判断游戏结束,要么赢,要么被炸。结束后都要把mine打印出来让玩家看看所有雷的位置。
被炸简单,直接跳出循环就好。其实赢也一样,赢就是避开雷排完所有位置。如果一直没被炸,排完了怎么跳出循环呢?
在这里唯一可控的就是排的次数,因为棋盘大小已固定 9x9 减去我们埋好的 10 个雷 最多可以排 row * col - MINE(这里是71)次。所以设置个变量 win ,每排一次加一,直到排到最大次数后循环结束。
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col) { int x = 0; int y = 0; int win = 0; while (win < row * col - EASY_COUNT) { printf("请输入要排查的坐标:>"); scanf("%d %d", &x, &y); if (x >= 1 && x <= row && y >= 1 && y <= col) { if (mine[x][y] == '1') { printf("很遗憾,你被炸死了\n"); DisplayBoard(mine, ROW, COL); break; } else { int count = GetMineCount(mine, x, y); show[x][y] = count + '0'; DisplayBoard(show, ROW, COL); win++; } } else { printf("坐标非法,重新输入\n"); } } if (win == row * col - EASY_COUNT) { printf("恭喜你,排雷成功\n"); DisplayBoard(mine, ROW, COL); } }
利用下列程序可查找周围雷的个数
int GetMineCount(char mine[ROWS][COLS], int x, int y) { return (mine[x - 1][y] + mine[x - 1][y - 1] + mine[x][y - 1] + mine[x + 1][y - 1] + mine[x + 1][y] + mine[x + 1][y + 1] + mine[x][y + 1] + mine[x - 1][y + 1] - 8 * '0'); }
总代码
game.h
#pragma once #include <stdio.h> #include <stdlib.h> #include <time.h> #define EASY_COUNT 10 #define ROW 9 #define COL 9 #define ROWS ROW+2 #define COLS COL+2 void InitBoard(char board[ROWS][COLS], int rows, int cols, char set); void DisplayBoard(char board[ROWS][COLS], int row, int col); void SetMine(char board[ROWS][COLS], int row, int col); void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
game.c
#define _CRT_SECURE_NO_WARNINGS 1 #include"game.h" void InitBoard(char board[ROWS][COLS], int rows, int cols,char set) { int i = 0; for (i = 0; i < rows; i++) { int j = 0; for (j = 0; j < cols; j++) { board[i][j] = set; } } } void DisplayBoard(char board[ROWS][COLS], int row, int col) { int i = 0; printf("-------扫雷游戏-----------\n"); int j = 0; for (j = 0; j <= col; j++) printf("%d ", j); printf("\n"); for(i = 1; i <=row; i++) { printf("%d ", i); int j = 0; for (j = 1; j <= col; j++) { printf("%c ", board[i][j]); } printf("\n"); } } void SetMine(char board[ROWS][COLS], int row, int col) { int count = EASY_COUNT; while (count) { int x = rand() % row + 1; int y = rand() % col + 1; if (board[x][y] == '0') { board[x][y] = '1'; count--; } } } int GetMineCount(char mine[ROWS][COLS], int x, int y) { return (mine[x - 1][y] + mine[x - 1][y - 1] + mine[x][y - 1] + mine[x + 1][y - 1] + mine[x + 1][y] + mine[x + 1][y + 1] + mine[x][y + 1] + mine[x - 1][y + 1] - 8 * '0'); } void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col) { int x = 0; int y = 0; int win = 0; while (win < row * col - EASY_COUNT) { printf("请输入要排查的坐标:>"); scanf("%d %d", &x, &y); if (x >= 1 && x <= row && y >= 1 && y <= col) { if (mine[x][y] == '1') { printf("很遗憾,你被炸死了\n"); DisplayBoard(mine, ROW, COL); break; } else { int count = GetMineCount(mine, x, y); show[x][y] = count + '0'; DisplayBoard(show, ROW, COL); win++; } } else { printf("坐标非法,重新输入\n"); } } if (win == row * col - EASY_COUNT) { printf("恭喜你,排雷成功\n"); DisplayBoard(mine, ROW, COL); } }
test.c
#define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> #include"game.h" void menu() { printf("*********************\n"); printf("*******1.play********\n"); printf("*********************\n"); printf("*******0.exit********\n"); printf("*********************\n"); } void game() { char mine[ROWS][COLS]; char show[ROWS][COLS]; InitBoard(mine, ROWS, COLS,'0'); InitBoard(show, ROWS, COLS,'*'); DisplayBoard(show, ROW, COL); SetMine(mine, ROW, COL); FindMine(mine, show, ROW, COL); } int main() { int input = 0; srand((unsigned int)time(NULL)); do { menu(); printf("请选择->"); scanf("%d", &input); { switch (input) { case 1: printf("扫雷\n"); game(); break; case 0: printf("游戏结束\n"); break; default: printf("选择错误,重新选择\n"); } } }while (input); }
效果图
收尾
要对将要写的东西有足够的了解,关于它的基本组成成分和操作。把这些预想一下,还要想想可能发生的小意外。就像这个扫雷,如果定义的二维数组是 9x9 那么问题太多,你得想到给它增加一圈却不打印这样的小办法。