提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
一、框架
我们小时候玩的经典扫雷有以下基本功能(可自行跳转到想看功能)
1.选择难度
2.布置雷及初始化游戏界面
3.输出游戏界面
4.选择挖开地面,标?,标!或者取消标记
5.挖开地面
6.标!与标?与取消标记
7.点击时自行消去明显地块
8.判断输赢
基础模式的运行流程:
首先是对输出棋盘的初始化,地面的初始化(即放置雷),关于雷数数组的初始化,然后展示棋盘。而后正式游戏,由于我们是循环进行游戏,所以我们选择int来调控是否进行下一局游戏
//初级模式
int beginnermode() {
char num[ROWS][COLS]; //存放周围雷的信息
char ground[ROWS][COLS];//存放雷的位置
char show[ROWS][COLS]; //展示的数组
initshow(show);
initground(ground);
initshowout(ground);
initnum(num,ground);
display(show);
game(num, ground, show);
}
二、各项功能具体实现
1.选择难度
由于我们在一次游戏中只选择一次,所以我们可以在此处一同输出规则
//打印菜单
int menu() {
int temp;
printf("输入0结束游戏,1为初级模式,2为中级模式,为高级模式\n");
scanf_s("%d", &temp);
return temp;
}
而具体的难度选择可以通过switch语句实现(此处只写了基础难度,其他可通过更改define实现)同时我们应让玩家可以随时结束游戏,也可以在玩一局之后自行选择是否继续游玩,所以加上一个while循环(此处的srand是用于后面放置雷时使用)
int temp = menu();
if (temp == 0) printf("游戏结束\n");
srand((unsigned int)time(NULL));
while (temp!=0) {
printf("游戏规则:#表示未探测地,*表示雷\n");
switch (temp)
{
//初级模式
case 1:
if(beginnermode())
break;
//中级模式
/*case 2:
mediummode();
break;
//高级模式
case 3:
advancedmode();
break;*/
default:
printf("输入错误请重新输入\n");
}
}
2.布置雷及初始化游戏界面
布置雷时应该是随机的,所以使用rand函数。rand
函数的原型在stdlib.h
头文件中,形式为int rand(void)
。它不需要传入参数,会返回一个范围在0
到RAND_MAX
之间的伪随机整数。(可自行跳过讲解)
//基本用法示例
#include <stdio.h>
#include <stdlib.h>
int main() {
// 生成一个随机数
int randomNumber = rand();
printf("随机数:%d\n", randomNumber);
return 0;
}
通常我们需要生成在某个特定范围内的随机数,可以通过取余和加法运算来实现。例如,要生成一个在[a, b]
范围内的随机数,可以使用以下公式:rand() % (b - a + 1) + a
。示例代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main() {
// 使用当前时间作为随机数种子
srand((unsigned int)time(NULL));
// 生成1到100之间的随机数
int a = 1;
int b = 100;
int randomNumberInRange = rand() % (b - a + 1) + a;
printf("1到100之间的随机数:%d\n", randomNumberInRange);
return 0;
}
rand
函数生成的是伪随机数,每次程序运行时,如果不设置随机数种子,生成的随机数序列是固定的。为了让每次运行生成的随机数序列不同,通常会使用time
函数来设置随机数种子,time
函数在time.h
头文件中。示例如下
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main() {
// 使用当前时间作为随机数种子
srand((unsigned int)time(NULL));
// 生成1到100之间的随机数
int a = 1;
int b = 100;
int randomNumberInRange = rand() % (b - a + 1) + a;
printf("1到100之间的随机数:%d\n", randomNumberInRange);
return 0;
}
然后使用rand放置雷,使用WINNUM来调控雷的数量(使用宏可增强代码可读性与健壮性)。在定义ground时为了在初始化num数组时防止溢出定义为ROWS*COLS(ROWS=ROW+2)
//初始化ground
void initground(char ground[ROWS][COLS]) {
int count = 0, x, y;
for (int i = 1; i <= ROW; i++) {
for (int j = 1; j <= COL; j++) {
ground[i][j] = ' ';
}
}
for (; count < WINNUM;) {
x = rand() % ROW + 1;
y = rand() % COL + 1;
if (ground[x][y] == ' ') {
ground[x][y] = '*';
++count;
}
}
}
现在进行打印数组的初始化。由于是通过输入坐标行动,所以应输出行列以便选择,将show数组边单独初始化,然后再初始化棋盘,使功能更独立。而在定义show数组时为了方便也定义为ROWS*COLS
//初始化show数组
void initshow(char show[ROWS][COLS]) {
initshowout(show);
initshowin(show);
}
//初始化show边界
void initshowout(char show[ROWS][COLS]) {
for (int i = 0; i <= COL; i++) {
show[i][0] = i + '0';
show[0][i] = i + '0';
}
}
//初始化show内部
void initshowin(char show[ROWS][COLS]) {
for (int i = 1; i <= ROW; ++i) {
for (int j = 1; j <= COL + 1; ++j) {
show[i][j] = '#';
}
}
}
对于num,需要根据ground初始化,而得到的等于0的可以赋值为’ ‘,同时定义num为char便于赋值给show
//初始化num
void initnum(char num[ROWS][COLS], char ground[ROWS][COLS]) {
for (int i = 1; i <= ROW; i++)
{
for (int j = 1; j <= COL; j++) {
num[i][j] = '0';
for (int I = i - 1; I <= i + 1; I++)
{
for (int J = j - 1; J <= j + 1; J++) {
if (ground[I][J] == '*') num[i][j] += 1;
}
}
}
}
for (int i = 1; i <= ROW; i++)
{
for (int j = 1; j <= COL; j++) {
if (num[i][j] == '0') num[i][j] = ' ';
}
}
}
3.输出游戏界面
//展示游戏界面
void display(char show[ROWS][COLS]) {
for (int i = 0; i <= ROW; i++)
{
for (int j = 0; j <= COL; j++) {
if (show[i][j] == '0') show[i][j] = ' ';
}
}
for (int i = 0; i <= ROW; i++)
{
for (int j = 0; j <= COL; j++) {
printf("%c ", show[i][j]);
}
printf("\n");
}
}
这里为了方便可以顺便把查看雷的程序写了
//展示雷
void displaymine(char ground[ROWS][COLS]) {
for (int i = 0; i <= ROW; i++)
{
for (int j = 0; j <= COL; j++) {
printf("%c ", ground[i][j]);
}
printf("\n");
}
}
4.选择结束游戏,挖开地面,标?,标!或者取消标记
玩家应可以随时结束游戏,所以添加了结束游戏的选项,同1.选择难度时使用switch与while
,同样使用int型反映玩家选择对地面进行怎样的操作。每次操作后检查输赢(通过remain与check函数)
//选择结束游戏或挖或标记!或标记?或取消标记
int chose(void) {
int num;
printf("请选择0结束游戏,1挖掘,2标记!,3标记?,4取消标记\n");
scanf_s("%d", &num);
return num;
}
void game(char num[ROWS][COLS], char ground[ROWS][COLS], char show[ROWS][COLS]){
int temp, count = 0, remain = ROW * COL;
while (1) {
temp = chose();
switch (temp)
{
case 0:
return 1;
case 1:
if (mine(show, ground, num, &remain)) {
display(show);
//displaymine(ground); 用于检查
return 1;
}
display(show);
displaymine(ground);
break;
case 2:
excalmationmark(show, ground, &count);
display(show);
//displaymine(ground);
break;
case 3:
questionmark(show);
display(show);
//displaymine(ground);
break;
case 4:
cancelmark(show);
display(show);
break;
default:
printf("输入错误,请重新输入\n");
break;
}
if (remain == WINNUM || check(show, ground, count)) {
printf("你赢了\n");
return 1;
}
}
}
5.挖开地面
在挖掘后有以下情况
(1)雷 (2)空,但是周围仍有雷(3)空,且周围没有雷
如果为(1)就直接游戏结束,而游戏结束通过返回1表示,故mine函数为int型
如果为(2)那么该位置就应该显示周围的雷数目,所以把show替换为num
如果为(3)那么就应把周围的地块清除,直到旁边有雷的地块,这时要替换show为’ ‘,而初始化num时已经把对应num赋值为’ ‘了,所以和(2)操作统一,但是还要调用expand函数
//挖掘
int mine(char show[ROWS][COLS], char ground[ROWS][COLS], char num[ROWS][COLS],int *remain) {
int x, y;
printf("请输入要挖的坐标,用空格隔开\n");
scanf_s("%d %d", &x, &y);
if (x > 0 && y > 0 && x <= ROW && y <= COL) {
if (ground[x][y] == '*') {
show[x][y] = '*';
display(show);
printf("你输了\n");
return 1;
}
else {
show[x][y] = num[x][y];
expand(show, num);
initshowout(show);
(*remain)--;
}
return 0;
}
else {
printf("输入错误,请重新输入\n");
return mine(show, ground,num,remain);
}
}
6.标!与标?与取消标记
标记!时不可以超过WINNUM个(在基础模式为10),所以要加上判断,使用count来传递是否已满(一定用指针,否则count无效)。并且已经挖开的地块不能标记。所以有
//标记!
void excalmationmark(char show[ROWS][COLS], char ground[ROWS][COLS], int *count) {
if (*count == WINNUM) {
printf("已标%d个!,请重新选择\n",WINNUM);
}
else {
int x, y;
printf("请输入要标记!的坐标,用空格隔开\n");
scanf_s("%d %d", &x, &y);
if (x > 0 && y > 0 && x <= ROW && y <= COL) {
if (show[x][y] == '#' || show[x][y] == '?' || show[x][y] == '!')
{
show[x][y] = '!';
(*count)++;
}
else printf("该位置已挖开,不能标记\n");
}
else {
printf("输入错误,请重新输入\n");
}
}
}
而另外两个函数与!函数基本相同,甚至更简单,简单复制删除即可
//标记?
void questionmark(char show[ROWS][COLS]) {
int x, y;
printf("请输入要标记?坐标,用空格隔开\n");
scanf_s("%d %d", &x, &y);
if (x > 0 && y > 0 && x <= ROW && y <= COL) {
if (show[x][y] == '#' || show[x][y] == '?' || show[x][y] == '!')
{
show[x][y] = '?';
}
else printf("该位置已挖开,不能标记\n");
}
else {
printf("输入错误,请重新输入\n");
}
}
//取消标记
void cancelmark(char show[ROWS][COLS]) {
int x, y;
printf("请输入要取消标记的坐标,用空格隔开\n");
scanf_s("%d %d", &x, &y);
if (x > 0 && y > 0 && x <= ROW && y <= COL) {
if (show[x][y] == '#' || show[x][y] == '?' || show[x][y] == '!')
{
show[x][y] = '#';
}
else printf("该位置已挖开,不能取消标记\n");
}
else {
printf("输入错误,请重新输入\n");
}
}
7.点击时自行消去明显地块
当玩家选择情况(3)时show中出现了’ ‘,那么就把该地块周围show全部赋值为num,递归调用即可(狗头)。其实这样会溢出。把递归改为遍历所有的’ ‘,当第一次过后出现新的’ ‘时就可以显示其周围的地块,实现功能。为防止溢出,把遍历和赋值两个功能分为两个函数
//拓展雷区
void expand(char show[ROWS][COLS], char num[ROWS][COLS]) {
for (int i = 1; i <= ROW; i++) {
for (int j = 1; j <= COL; j++)
{
if (show[i][j] == ' ') {
for (int I = i - 1; I <= i + 1 && I<ROW + 1; I++)
{
for (int J = j - 1; J <= j + 1&&J<COL+1; J++) {
show[I][J] = num[I][J];
if (show[I][J] == ' ') EXPAND(show, num,I,J);
}
}
}
}
}
}
void EXPAND(char show[ROWS][COLS], char num[ROWS][COLS], int i, int j) {
for (int I = i - 1; I <= i + 1 && I < ROW + 1; I++)
{
for (int J = j - 1; J <= j + 1 && J < COL + 1; J++) {
show[I][J] = num[I][J];
}
}
}
8.判断输赢
判断输已在前面实现,而不通过标!获胜的判断通过剩余的地块可以轻易实现(在4中已有简单描述),所以只需写出通过标!获胜的判断函数。当标!的地块均为雷时获胜,而只有当标了WINNUM个!时才有可能获胜,所以前面count还可以在此作用
//判断赢
int check(char show[ROWS][COLS], char ground[ROWS][COLS], int count) {
if (count == WINNUM) {
for (int i = 1; i <= ROW; i++) {
for (int j = 0; j <= COL; j++)
{
if (show[i][j] == '!') {
if (ground[i][j]!= '*') {
return 0;
}
}
}
}
return 1;
}
else return 0;
}
总结
以下是全部代码的整合,读者可自行获取。本人初学,水平有限,望指正或建议。
game.h
#pragma once #include<stdio.h> #include<time.h> #include<stdlib.h> #define _CRT_SECURE_NO_WARNINGS #define ROW 9 #define COL 9 #define ROWS ROW+2 //防止越界 #define COLS COL+2 #define WINNUM 10 //打印菜单 int menu(); //初级模式 int beginnermode(); //正式游戏 void game(char num[ROWS][COLS], char ground[ROWS][COLS], char show[ROWS][COLS]); //初始化show void initshow(char show[ROWS][COLS]); //初始化show边界 void initshowout(char show[ROWS][COLS]); //初始化show内部 void initshowin(char show[ROWS][COLS]); //初始化ground void initground(char ground[ROWS][COLS]); //初始化num void initnum(char num[ROWS][COLS], char ground[ROWS][COLS]); //展示游戏界面 void display(char show[ROWS][COLS]); //展示雷 void displaymine(char ground[ROWS][COLS]); //选择挖或标记或不确定 int chose(void); //挖掘 int mine(char show[ROWS][COLS], char ground[ROWS][COLS], char num[ROWS][COLS],int *remain); //标记! void excalmationmark(char show[ROWS][COLS], char ground[ROWS][COLS],int *count); //标记? void questionmark(char show[ROWS][COLS]); //取消标记 void cancelmark(char show[ROWS][COLS]); //拓展雷区 void expand(char show[ROWS][COLS], char num[ROWS][COLS]); void EXPAND(char show[ROWS][COLS], char num[ROWS][COLS],int i,int j); //判断赢 int check(char show[ROWS][COLS], char ground[ROWS][COLS], int count);
test.c
#include"game。h.h" #define _CRT_SECURE_NO_WARNINGS int main() { int temp = menu(); if (temp == 0) printf("游戏结束\n"); srand((unsigned int)time(NULL)); while (temp!=0) { printf("游戏规则:#表示未探测地,*表示雷\n"); switch (temp) { //初级模式 case 1: if(beginnermode()) break; //中级模式 /*case 2: mediummode(); break; //高级模式 case 3: advancedmode(); break;*/ default: printf("输入错误请重新输入\n"); } } return 0; }
game.c
#include"game。h.h" #define _CRT_SECURE_NO_WARNINGS //打印菜单 int menu() { int temp; printf("输入0结束游戏,1为初级模式,2为中级模式,为高级模式\n"); scanf_s("%d", &temp); return temp; } //初级模式 int beginnermode() { char num[ROWS][COLS]; //存放周围雷的信息 char ground[ROWS][COLS];//存放雷的位置 char show[ROWS][COLS]; //展示的数组 initshow(show); initground(ground); initshowout(ground); initnum(num,ground); display(show); game(num, ground, show); } // void game(char num[ROWS][COLS], char ground[ROWS][COLS], char show[ROWS][COLS]){ int temp, count = 0, remain = ROW * COL; while (1) { temp = chose(); switch (temp) { case 0: return 1; case 1: if (mine(show, ground, num, &remain)) { display(show); //displaymine(ground); 用于检查 return 1; } display(show); displaymine(ground); break; case 2: excalmationmark(show, ground, &count); display(show); //displaymine(ground); break; case 3: questionmark(show); display(show); //displaymine(ground); break; case 4: cancelmark(show); display(show); break; default: printf("输入错误,请重新输入\n"); break; } if (remain == WINNUM || check(show, ground, count)) { printf("你赢了\n"); return 1; } } } //初始化show数组 void initshow(char show[ROWS][COLS]) { initshowout(show); initshowin(show); } //初始化show边界 void initshowout(char show[ROWS][COLS]) { for (int i = 0; i <= COL; i++) { show[i][0] = i + '0'; show[0][i] = i + '0'; } } //初始化show内部 void initshowin(char show[ROWS][COLS]) { for (int i = 1; i <= ROW; ++i) { for (int j = 1; j <= COL + 1; ++j) { show[i][j] = '#'; } } } //初始化ground void initground(char ground[ROWS][COLS]) { int count = 0, x, y; for (int i = 1; i <= ROW; i++) { for (int j = 1; j <= COL; j++) { ground[i][j] = ' '; } } for (; count < WINNUM;) { x = rand() % ROW + 1; y = rand() % COL + 1; if (ground[x][y] == ' ') { ground[x][y] = '*'; ++count; } } } //初始化num void initnum(char num[ROWS][COLS], char ground[ROWS][COLS]) { for (int i = 1; i <= ROW; i++) { for (int j = 1; j <= COL; j++) { num[i][j] = '0'; for (int I = i - 1; I <= i + 1; I++) { for (int J = j - 1; J <= j + 1; J++) { if (ground[I][J] == '*') num[i][j] += 1; } } } } for (int i = 1; i <= ROW; i++) { for (int j = 1; j <= COL; j++) { if (num[i][j] == '0') num[i][j] = ' '; } } } //展示游戏界面 void display(char show[ROWS][COLS]) { for (int i = 0; i <= ROW; i++) { for (int j = 0; j <= COL; j++) { if (show[i][j] == '0') show[i][j] = ' '; } } for (int i = 0; i <= ROW; i++) { for (int j = 0; j <= COL; j++) { printf("%c ", show[i][j]); } printf("\n"); } } //展示雷 void displaymine(char ground[ROWS][COLS]) { for (int i = 0; i <= ROW; i++) { for (int j = 0; j <= COL; j++) { printf("%c ", ground[i][j]); } printf("\n"); } } //选择结束或挖或标记!或标记?或取消标记 int chose(void) { int num; printf("请选择0结束游戏,1挖掘,2标记!,3标记?,4取消标记\n"); scanf_s("%d", &num); return num; } //挖掘 int mine(char show[ROWS][COLS], char ground[ROWS][COLS], char num[ROWS][COLS],int *remain) { int x, y; printf("请输入要挖的坐标,用空格隔开\n"); scanf_s("%d %d", &x, &y); if (x > 0 && y > 0 && x <= ROW && y <= COL) { if (ground[x][y] == '*') { show[x][y] = '*'; display(show); printf("你输了\n"); return 1; } else { show[x][y] = num[x][y]; expand(show, num); initshowout(show); (*remain)--; } return 0; } else { printf("输入错误,请重新输入\n"); return mine(show, ground,num,remain); } } //标记! void excalmationmark(char show[ROWS][COLS], char ground[ROWS][COLS], int *count) { if (*count == WINNUM) { printf("已标%d个!,请重新选择\n",WINNUM); } else { int x, y; printf("请输入要标记!的坐标,用空格隔开\n"); scanf_s("%d %d", &x, &y); if (x > 0 && y > 0 && x <= ROW && y <= COL) { if (show[x][y] == '#' || show[x][y] == '?' || show[x][y] == '!') { show[x][y] = '!'; (*count)++; } else printf("该位置已挖开,不能标记\n"); } else { printf("输入错误,请重新输入\n"); } } } //标记? void questionmark(char show[ROWS][COLS]) { int x, y; printf("请输入要标记?坐标,用空格隔开\n"); scanf_s("%d %d", &x, &y); if (x > 0 && y > 0 && x <= ROW && y <= COL) { if (show[x][y] == '#' || show[x][y] == '?' || show[x][y] == '!') { show[x][y] = '?'; } else printf("该位置已挖开,不能标记\n"); } else { printf("输入错误,请重新输入\n"); } } //取消标记 void cancelmark(char show[ROWS][COLS]) { int x, y; printf("请输入要取消标记的坐标,用空格隔开\n"); scanf_s("%d %d", &x, &y); if (x > 0 && y > 0 && x <= ROW && y <= COL) { if (show[x][y] == '#' || show[x][y] == '?' || show[x][y] == '!') { show[x][y] = '#'; } else printf("该位置已挖开,不能取消标记\n"); } else { printf("输入错误,请重新输入\n"); } } //拓展雷区 void expand(char show[ROWS][COLS], char num[ROWS][COLS]) { for (int i = 1; i <= ROW; i++) { for (int j = 1; j <= COL; j++) { if (show[i][j] == ' ') { for (int I = i - 1; I <= i + 1 && I<ROW + 1; I++) { for (int J = j - 1; J <= j + 1&&J<COL+1; J++) { show[I][J] = num[I][J]; if (show[I][J] == ' ') EXPAND(show, num,I,J); } } } } } } void EXPAND(char show[ROWS][COLS], char num[ROWS][COLS], int i, int j) { for (int I = i - 1; I <= i + 1 && I < ROW + 1; I++) { for (int J = j - 1; J <= j + 1 && J < COL + 1; J++) { show[I][J] = num[I][J]; } } } //判断赢 int check(char show[ROWS][COLS], char ground[ROWS][COLS], int count) { if (count == WINNUM) { for (int i = 1; i <= ROW; i++) { for (int j = 0; j <= COL; j++) { if (show[i][j] == '!') { if (ground[i][j]!= '*') { return 0; } } } } return 1; } else return 0; }