(声明:凡人小严初识C语言,概念性的知识就不讲了,注重实践理解)
规则:玩家在规定的‘n×n’雷盘上进行排雷,玩家随机输入对应雷盘的格子坐标。若此格为雷,游戏失败;若此格无雷,则显示周围八格雷的总数,直至标记所有雷,游戏结束。(若标记点数多于系统雷数,无法获胜)
任务一:初始化雷盘
任务二:打印雷盘
任务三:随机安置雷
任务四:显示周围八格雷的总数
任务五:实现简单的清图
任务六:具有标记雷并计数的功能
(隐藏任务:若玩家捣乱或误输入已经处理过的格子的坐标,提醒玩家此格已经处理过)
任务七:进行‘9×9’与‘16×16’模式的简单转换
注意:以下均为9×9扫雷
代码截取,完整代码及完整注释在文章结尾。
步骤一:简单构建扫雷游戏菜单界面
利用scanf函数与switch函数,我们就可以简单地输入特定数字来进入不同的界面。紧接着结合goto语句,我们就可以进行界面返回。(由于扫雷信息量大,借助系统函数清理一下)
int flat = 1;
void menu()
{
printf("······扫雷······\n\n");
printf(" ① play \n\n");
printf(" ② mode \n\n");
printf(" ③ exit \n\n");
Sleep(1000);
printf("···请输入对应序号····\n\n");
}
void mode()
{
printf("请输入对应序号\n\n");
printf("① 9×9模式(标出10个雷,游戏胜利) 系统默认\n\n");
printf("② 16×16模式(标出40个雷,游戏胜利)\n\n");
scanf("%d", &flat);
}
void game()
{
char Player[11][11] = { 0 };//为什么是11,步骤五会提及
char System[11][11] = { 0 };
Initialize_Thunder_Disk( Player , '*');//初始化玩家显示数组
Initialize_Thunder_Disk( System , '0');//初始化系统雷盘数组
Randomly_Place_Mines(System);//随机放雷
printf_Thunder_Disk(Player);//打印玩家显示数组
printf_Thunder_Disk(System);//打印系统雷盘数组 可以选择不打印
Mine_clearance(System, Player);//扫雷函数集总
}
int main()
{
int n = 0;//操作菜单序号
again://菜单打印完后 跳转点
while (scanf("%d", &n ) != EOF)//反复游戏
{
switch (n)//构建各个选项跳转界面
{
case 0:
system("cls");//清理屏幕
agains://游戏结束返回菜单 模式选择后 跳转
menu();//打印游戏菜单界面
goto again;
case 1:
if (flat == 1)//9×9模式
game();
else//16×16 模式 有兴趣可以玩 就是有点费眼睛
game1();
goto agains;
case 2:
mode();//模式选择
goto agains;
case 3:
return 0;
default:
printf("请输入对应的序号\n\n");
goto again;
}
}
return 0;
}
步骤二:初始化雷盘
为了简化代码,我们尽量使用函数,反复调用。雷盘是一个n×n的平面,我们可以用二维数组储存雷盘。但此时,涉及到一个问题,每一个数组元素所储存的信息是单一的。而扫雷游戏进行时,玩家是不能看到雷所在的格子,即系统要隐藏雷点。这个时候,我们就可以创建两个二维数组,一个数组储存雷点并保存在后台(我们选用‘1’为雷点,‘0’为非雷点),一个数组作为显示画面(这里我们选择‘*’作为显示单位)。
void Initialize_Thunder_Disk( char array[][11], char CHAR )//初始化雷盘
{
int i = 0;
for (i = 0; i <= 10; i++)
{
int j = 0;
for (j = 0; j <= 10; j++)
{
array[i][j] = CHAR;
}
}
}
步骤三:随机放置雷
既然是随机,那自然需要运用rand()函数产生随机数,由于rand()函数随机数生成取决于srand()种子函数的种子取值,所以我们将种子关联为时间函数。但也有几率存在两次的随机值相同,所以我们需要利用if语句判断。
void Randomly_Place_Mines(char array[][11])//随机放雷
{
int i = 0;
srand((unsigned)time(NULL));
for (i = 0; i <= 9; i++)//9×9的模式为10个雷
{
int p = rand() % 9 + 1;
int s = rand() % 9 + 1;
if (array[p][s] != '1')//这里不是‘1’,我们放雷
array[p][s] = '1';
else//这里是雷点,我们需要重新安置第i颗雷
i--;
}
}
步骤四:打印雷盘
这里其实与初始化雷盘类似,但我们要考虑玩家感受,我们需要打印出坐标轴以及优化画面效果
void printf_Thunder_Disk( char array[][11])//打印雷盘
{
int i = 0;
int j = 0;
for (j = 0; j <= 9; j++)
{
printf("%d ", j);//打印列坐标轴
}
printf("\n\n");
for (i = 1; i <= 9; i++)
{
printf("%d ", i);//打印行坐标轴
for (j = 1; j <= 9; j++)
{
printf("%c ", array[i][j]);
}
printf("\n\n");
}
printf("请输入row = ___ , col = ___ ; 数字间用空格隔开\n\n");
}
这里展示一下9×9与16×16的画面效果
步骤五:显示周围八格雷数
每当我们输入一个格子坐标时,若此格无雷,则我们要利用雷盘信息,算出此格周围八格的雷数,以告知玩家。但,这里会涉及到一个问题,如果此格是边缘点,那么将凑不齐八格。此时,我们选择扩大数组,将9×9改为11×11,玩家显示数组大小放在其中心,刚好多外围多了一环。8 + 1 = 9,我们就可以利用九宫格的坐标完成一次循环来计算雷数
int Display_The_Number_Of_Surrounding_Mines(int row,int col,char array[][11])//显示周围雷数
{
int i = 0, j = 0, sum = 0;
for (i = row - 1; i <= row + 1; i++)
{
for (j = col - 1; j <= col + 1; j++)
{
sum += array[i][j];
}
}
return sum - 8 * '0';//因为是雷盘,所以我们可以利用函数返回值传输到玩家所看的显示盘
}
步骤六:简单清图
在步骤五的显示基础下,我们可以知道:当此格无雷的情况下,玩家可以看到周围八格的总雷数。那么,当周围八格的总雷数为0,我们就可以将周围八格,各个格子为基本单位,继续显示他们的周围八格的总雷数。这里我们就简单运用一下显示函数嵌套代入九宫格循环。
void Recursive_Clearing(int row , int col,char arr[][11],char array[][11])//清图
{
int i = 0, j = 0;
for (i = row - 1; i <= row + 1; i++)
{
for (j = col - 1; j <= col + 1; j++)
{
arr[i][j] = Display_The_Number_Of_Surrounding_Mines(i, j, array);
}
}
}
步骤七:标记雷
当玩家通过周围雷数判断出雷点位置,此时就可以进行标记或撤回错误雷点。我们可以借助函数形参来判断是否标记(撤回)。此刻,也涉及到玩家可能会输入偏离预期的坐标,此时,我们就要借助if语句来判断了。当提前工作准备完毕,我们也要开始游戏结束点的准备了。我所做的扫雷结束点:玩家标记点数与系统雷点数相同,且坐标也相同,则游戏胜利。那么当玩家标记点数超过系统雷数,系统要进行提醒。
void Mark_Mine(char array[][11],char arr[][11],int mode)
{
int row = 0;
int col = 0;
againt://错误输入返回点
printf("玩家请输入对应标点坐标 数字间用空格隔开\n\n");
scanf("%d %d", &row, &col);
if (mode == 1 && arr[row][col] == '*')//此格是未显示周围雷数点,且未标记
{
arr[row][col] = '!';//雷点标记符号
count++;//标记点统计
if (arr[row][col] == '!' && array[row][col] == '1')//标记点与雷点坐标相同
count_ture++;//准确雷点标记点统计
}
else if (mode == 1 && arr[row][col] != '*')//此时进行雷点标记,错误输入其他已显示坐标点
{
printf("此格已做过处理,请重新输入雷点坐标\n\n");
goto againt;
}
else if(mode == 2 && arr[row][col] == '!')//标记雷点撤回
arr[row][col] = '*';
else if (mode == 2 && arr[row][col] != '!')//此时进行标记雷点撤回,错误输入其他非标记雷点坐标
{
printf("此格为非所标雷点,请重新输入想取消的雷点坐标\n\n");
goto againt;
}
}
步骤八:扫雷函数集总
扫雷功能任务基本完成,现在就是进行逻辑预演,进行组装。首先,输入坐标,若此格无雷,则游戏进行下一步;若此格有雷,则游戏结束;其次,游戏继续,打印上次输入坐标后的显示数组(显示周围雷数与清图);接着,提示玩家是否需要标记雷点(或撤回);最后,进行游戏结束点的判断。
void Mine_clearance(char array[][11], char arr[][11])//扫雷
{
int col = 0;
int row = 0;
int mode = 0;
while (scanf("%d %d", &row, &col) != EOF)
{
if (array[row][col] == '1')
{
printf("很遗憾,此格有雷。\n\n");
break;
}
else
arr[row][col] = Display_The_Number_Of_Surrounding_Mines(row, col, array);//显示周围雷数
if (Display_The_Number_Of_Surrounding_Mines(row, col, array) == '0')//清图
Recursive_Clearing(row, col, arr, array);
system("cls");//清屏
printf_Thunder_Disk(arr);//打印玩家显示数组
printf_Thunder_Disk(array);//打印系统雷盘数组,若要游玩,可以选择不打印
printf("玩家是否要标雷,标雷:请输 1 ;不标雷:请输 0 ;若要取消所标雷:请输 2 ;\n\n");
scanf("%d", &mode);
if (mode == 1)
{
Mark_Mine(array,arr,1);
printf_Thunder_Disk(arr);
printf_Thunder_Disk(array);
}
else if(mode == 2)
{
Mark_Mine(array,arr, 2);
printf_Thunder_Disk(arr);
printf_Thunder_Disk(array);
}
printf("游戏继续\n\n");
if (count == 10 && count_ture == 10)//游戏结束点
{
printf("winner\n\n");
break;
}
else if (count > 10)
printf("请取消多余的雷标点,如果你标记的雷点多于系统雷数,无法获胜哟\n\n");
}
}
以上便是9×9扫雷模式的初步实现,并不会很完美,但对于初学者,我认为已经够用了。
希望我的思路对你有作用。
注意:若要游玩,请严格按照提示来,还有输入数个数和大小时,不要少输或多输,不然会影响代码运行,但不至于崩,只是会无效。
以下为完整代码及完整注释:
1.游戏头文件,函数声明
game.h
#pragma once
#include<stdio.h>
#include <stdlib.h>
#include <time.h>
//9×9
void Initialize_Thunder_Disk(char array[][11], char CHAR);
void printf_Thunder_Disk(char array[][11]);
void Randomly_Place_Mines(char array[][11]);
void Mine_clearance(char array[][11], char arr[][11]);
int Display_The_Number_Of_Surrounding_Mines(int row, int col, char array[][11]);
void Recursive_Clearing(int row, int col, char arr[][11], char array[][11]);
void Mark_Mine(char array[][11],char arr[][11], int mode);
//16×16
void Initialize_Thunder_Disk16(char array[][18], char CHAR);
void printf_Thunder_Disk16(char array[][18]);
void Randomly_Place_Mines16(char array[][18]);
void Mine_clearance16(char array[][18], char arr[][18]);
int Display_The_Number_Of_Surrounding_Mines16(int row, int col, char array[][18]);
void Recursive_Clearing16(int row, int col, char arr[][18], char array[][18]);
void Mark_Mine16(char array[][18], char arr[][18], int mode);
2.游戏函数编写文件
game.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"
int count_ture = 0;
int count = 0;
void Initialize_Thunder_Disk( char array[][11], char CHAR )//初始化雷盘
{
int i = 0;
for (i = 0; i <= 10; i++)
{
int j = 0;
for (j = 0; j <= 10; j++)
{
array[i][j] = CHAR;
}
}
}
void printf_Thunder_Disk( char array[][11])//打印雷盘
{
int i = 0;
int j = 0;
for (j = 0; j <= 9; j++)
{
printf("%d ", j);//打印列坐标轴
}
printf("\n\n");
for (i = 1; i <= 9; i++)
{
printf("%d ", i);//打印行坐标轴
for (j = 1; j <= 9; j++)
{
printf("%c ", array[i][j]);
}
printf("\n\n");
}
printf("请输入row = ___ , col = ___ ; 数字间用空格隔开\n\n");
}
void Randomly_Place_Mines(char array[][11])//随机放雷
{
int i = 0;
srand((unsigned)time(NULL));
for (i = 0; i <= 9; i++)//9×9的模式为10个雷
{
int p = rand() % 9 + 1;
int s = rand() % 9 + 1;
if (array[p][s] != '1')//这里不是‘1’,我们放雷
array[p][s] = '1';
else//这里是雷点,我们需要重新安置第i颗雷
i--;
}
}
void Mine_clearance(char array[][11], char arr[][11])//扫雷
{
int col = 0;
int row = 0;
int mode = 0;
while (scanf("%d %d", &row, &col) != EOF)
{
if (array[row][col] == '1')
{
printf("很遗憾,此格有雷。\n\n");
break;
}
else
arr[row][col] = Display_The_Number_Of_Surrounding_Mines(row, col, array);//显示周围雷数
if (Display_The_Number_Of_Surrounding_Mines(row, col, array) == '0')//清图
Recursive_Clearing(row, col, arr, array);
system("cls");//清屏
printf_Thunder_Disk(arr);//打印玩家显示数组
printf_Thunder_Disk(array);//打印系统雷盘数组,若要游玩,可以选择不打印
printf("玩家是否要标雷,标雷:请输 1 ;不标雷:请输 0 ;若要取消所标雷:请输 2 ;\n\n");
scanf("%d", &mode);
if (mode == 1)
{
Mark_Mine(array,arr,1);
printf_Thunder_Disk(arr);
printf_Thunder_Disk(array);
}
else if(mode == 2)
{
Mark_Mine(array,arr, 2);
printf_Thunder_Disk(arr);
printf_Thunder_Disk(array);
}
printf("游戏继续\n\n");
if (count == 10 && count_ture == 10)//游戏结束点
{
printf("winner\n\n");
break;
}
else if (count > 10)
printf("请取消多余的雷标点,如果你标记的雷点多于系统雷数,无法获胜哟\n\n");
}
}
int Display_The_Number_Of_Surrounding_Mines(int row,int col,char array[][11])//显示周围雷数
{
int i = 0, j = 0, sum = 0;
for (i = row - 1; i <= row + 1; i++)
{
for (j = col - 1; j <= col + 1; j++)
{
sum += array[i][j];
}
}
return sum - 8 * '0';//因为是雷盘,所以我们可以利用函数返回值传输到玩家所看的显示盘
}
void Recursive_Clearing(int row , int col,char arr[][11],char array[][11])//清图
{
int i = 0, j = 0;
for (i = row - 1; i <= row + 1; i++)
{
for (j = col - 1; j <= col + 1; j++)
{
arr[i][j] = Display_The_Number_Of_Surrounding_Mines(i, j, array);
}
}
}
void Mark_Mine(char array[][11],char arr[][11],int mode)
{
int row = 0;
int col = 0;
againt://错误输入返回点
printf("玩家请输入对应标点坐标 数字间用空格隔开\n\n");
scanf("%d %d", &row, &col);
if (mode == 1 && arr[row][col] == '*')//此格是未显示周围雷数点,且未标记
{
arr[row][col] = '!';//雷点标记符号
count++;//标记点统计
if (arr[row][col] == '!' && array[row][col] == '1')//标记点与雷点坐标相同
count_ture++;//准确雷点标记点统计
}
else if (mode == 1 && arr[row][col] != '*')//此时进行雷点标记,错误输入其他已显示坐标点
{
printf("此格已做过处理,请重新输入雷点坐标\n\n");
goto againt;
}
else if(mode == 2 && arr[row][col] == '!')//标记雷点撤回
arr[row][col] = '*';
else if (mode == 2 && arr[row][col] != '!')//此时进行标记雷点撤回,错误输入其他非标记雷点坐标
{
printf("此格为非所标雷点,请重新输入想取消的雷点坐标\n\n");
goto againt;
}
}
// 16 思路与上面一样 我就不注释了 就是改了一下数组大小 以及雷数
void Initialize_Thunder_Disk16(char array[][18], char CHAR)//初始化雷盘
{
int i = 0;
for (i = 0; i <= 17; i++)
{
int j = 0;
for (j = 0; j <= 17; j++)
{
array[i][j] = CHAR;
}
}
}
void printf_Thunder_Disk16(char array[][18])//打印雷盘
{
int i = 0;
int j = 0;
for (j = 0; j <= 16; j++)
{
printf("%3d ", j);
}
printf("\n\n");
for (i = 1; i <= 16; i++)
{
printf("%3d ", i);
for (j = 1; j <= 16; j++)
{
printf(" %c ", array[i][j]);
}
printf("\n\n");
}
printf("请输入row = ___ , col = ___ ; 数字间用空格隔开\n\n");
}
void Randomly_Place_Mines16(char array[][18])//随机放雷
{
int i = 0;
srand((unsigned)time(NULL));
for (i = 0; i <= 39; i++)
{
int p = rand() % 16 + 1;
int s = rand() % 16 + 1;
if (array[p][s] != '1')
array[p][s] = '1';
else
i--;
}
}
void Mine_clearance16(char array[][18], char arr[][18])//扫雷
{
int col = 0;
int row = 0;
int mode = 0;
while (scanf("%d %d", &row, &col) != EOF)
{
if (array[row][col] == '1')
{
printf("很遗憾,此格有雷。\n\n");
break;
}
else
arr[row][col] = Display_The_Number_Of_Surrounding_Mines16(row, col, array);
if (Display_The_Number_Of_Surrounding_Mines16(row, col, array) == '0')
Recursive_Clearing16(row, col, arr, array);
system("cls");
printf_Thunder_Disk16(arr);
printf_Thunder_Disk16(array);
printf("玩家是否要标雷,标雷:请输 1 ;不标雷:请输 0 ;若要取消所标雷:请输 2 ;\n\n");
scanf("%d", &mode);
if (mode == 1)
{
Mark_Mine16(array, arr, 1);
printf_Thunder_Disk16(arr);
printf_Thunder_Disk16(array);
}
else if (mode == 2)
{
Mark_Mine16(array, arr, 2);
printf_Thunder_Disk16(arr);
printf_Thunder_Disk16(array);
}
printf("游戏继续\n\n");
if (count == 40)
{
printf("winner\n\n");
break;
}
else if (count > 40)
printf("请取消多余的雷标点,如果你标记的雷点多于系统雷数,无法获胜哟\n\n");
}
}
int Display_The_Number_Of_Surrounding_Mines16(int row, int col, char array[][18])//显示周围雷数
{
int i = 0, j = 0, sum = 0;
for (i = row - 1; i <= row + 1; i++)
{
for (j = col - 1; j <= col + 1; j++)
{
sum += array[i][j];
}
}
return sum - 8 * '0';
}
void Recursive_Clearing16(int row, int col, char arr[][18], char array[][18])//清图
{
int i = 0, j = 0;
for (i = row - 1; i <= row + 1; i++)
{
for (j = col - 1; j <= col + 1; j++)
{
arr[i][j] = Display_The_Number_Of_Surrounding_Mines16(i, j, array);
}
}
}
void Mark_Mine16(char array[][18], char arr[][18], int mode)
{
int row = 0;
int col = 0;
againt:
printf("玩家请输入对应标点坐标 数字间用空格隔开\n\n");
scanf("%d %d", &row, &col);
if (mode == 1 && arr[row][col] == '*')
{
arr[row][col] = '!';
count++;
if (arr[row][col] == '!' && array[row][col] == '1')
count_ture++;
}
else if (mode == 1 && arr[row][col] != '*')
{
printf("此格已做过处理,请重新输入雷点坐标\n\n");
goto againt;
}
else if (mode == 2 && arr[row][col] == '!')
arr[row][col] = '*';
else if (mode == 2 && arr[row][col] != '!')
{
printf("此格为非所标雷点,请重新输入想取消的雷点坐标\n\n");
goto againt;
}
}
3.运行文件
run.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"
#include<Windows.h>
int flat = 1;//游戏模式变量
void menu()
{
printf("······扫雷······\n\n");
printf(" ① play \n\n");
printf(" ② mode \n\n");
printf(" ③ exit \n\n");
Sleep(1000);
printf("···请输入对应序号····\n\n");
}
void menu0()
{
printf("···············作者:凡人小严···············\n\n");
Sleep(1000);
printf("·············制作周期:4-18---4-21············· \n\n");
Sleep(1000);
printf("················游戏规则··················\n\n");
printf("···玩家在规定的‘n×n’雷盘上进行排雷,玩家随机输入对应雷盘的格子坐标··\n\n");
printf("·············若此格为雷,游戏失败···············\n\n");
printf("·········若此格无雷,则显示周围八格雷的总数············\n\n");
printf("············直至标记所有雷,游戏结束··············\n\n");
printf("············请输入 0 进入游戏界面 ···············\n");
}
void mode()
{
printf("请输入对应序号\n\n");
printf("① 9×9模式(标出10个雷,游戏胜利) 系统默认\n\n");
printf("② 16×16模式(标出40个雷,游戏胜利)\n\n");
scanf("%d", &flat);
}
void game()
{
char Player[11][11] = { 0 };//为什么是11,步骤五会提及
char System[11][11] = { 0 };
Initialize_Thunder_Disk(Player, '*');//初始化玩家显示数组
Initialize_Thunder_Disk(System, '0');//初始化系统雷盘数组
Randomly_Place_Mines(System);//随机放雷
printf_Thunder_Disk(Player);//打印玩家显示数组
printf_Thunder_Disk(System);//打印系统雷盘数组 可以选择不打印
Mine_clearance(System, Player);//扫雷函数集总
}
void game1()
{
char Player16[18][18] = { 0 };
char System16[18][18] = { 0 };
Initialize_Thunder_Disk16(Player16, '*');
Initialize_Thunder_Disk16(System16, '0');
Randomly_Place_Mines16(System16);
printf_Thunder_Disk16(Player16);
printf_Thunder_Disk16(System16);
Mine_clearance16(System16, Player16);
}
int main()
{
int n = 0;//操作菜单序号
menu0();
again://菜单打印完后 跳转点
while (scanf("%d", &n) != EOF)//反复游戏
{
switch (n)//构建各个选项跳转界面
{
case 0:
system("cls");//清理屏幕
agains://游戏结束返回菜单 模式选择后 跳转
menu();//打印游戏菜单界面
goto again;
case 1:
if (flat == 1)//9×9模式
game();
else//16×16 模式 有兴趣可以玩 就是有点费眼睛
game1();
goto agains;
case 2:
mode();//模式选择
goto agains;
case 3:
return 0;
default:
printf("请输入对应的序号\n\n");
goto again;
}
}
return 0;
}
由于vs2022不支持gcc,没法使用变长数组,16×16只能分开写了,也没有学到什么能够实时改变数组长度的办法。所以代码有点长。