目录
前言
前些天偶然想起曾经初中在机房上课划水时玩的小游戏扫雷,便在电脑上找,发现竟没有这个小游戏了,于是便试着用C实现下它。
1.游戏功能
玩家通过菜单选择玩游戏或退出,进入游戏后,玩家可以输入坐标来实现扫雷。当玩家不幸扫中了雷,那么游戏结束返回菜单,当玩家成功避开所有雷,游戏胜利返回菜单。
2.游戏实现
2.1 打印菜单
玩家选择0退出游戏,选择1开始游戏
2.2 建立棋盘
扫雷嘛,肯定需要电脑埋雷,但这个埋雷的棋盘不能被玩家看到,不然就失去了意义。因此,我们建立两个棋盘,一个用来埋雷的棋盘,一个是给玩家玩的棋盘,我现在演示的是9*9的棋盘,如果想要修改棋盘只需修改ROW和COL的值即可。但我们建立的棋盘却要在原来的基础上都加2,即11*11,原因在判断函数模块中。
2.2.1 电脑埋雷棋盘
我们将雷定义为字符‘1’,将没有雷的地方定义为字符‘0’,至于为何这样操作,先埋下个伏笔。棋盘名字为mine。
2.2.2 玩家面对棋盘
我们将所有棋盘上的元素遍历为字符‘*’,棋盘名字为show。
2.3 棋盘初始化
初始化要在思考两个棋盘里该放什么什么元素后才应该做的。因此当我们知道了棋盘中要放什么元素后,初始化函数就很容易实现了。但这又有问题,两个棋盘初始化的元素不同,于是,我们再改初始化函数添加一个参数set,将想要初始化成什么的元素也传过来,于是便得到了它:
(为了方便我将图形调整了90度)
void Initboard(char board[ROWS][COLS], int rows, int cols, char set)
{
int i = 0, j = 0;
for (i = 0; i < rows; i++)
{
for (j = 0; j < cols; j++)
{
board[i][j] = set;
}
}
}
2.4 打印棋盘
为了方便玩家寻找坐标,我们还需要打印一行和一列的序列号,当然这个序列号并不在棋盘中元素中,相当于是在棋盘外围显示,但我们打印的是中间9行9列,因此就会产生序列号在棋盘中的效果
(图形已调整90度)
void Printboard(char board[ROWS][COLS], int row, int col)
{
int j = 0, i = 0;
for (j = 0; j <= col; j++)//打印序列号
{
printf("%d ", j);
}
printf("\n");
for (i = 1; i <= row; i++)
{
printf("%d ", i);
for (j = 1; j <= col; j++)
{
printf("%c ", board[i][j]);//打印棋盘
}
printf("\n");
}
}
2.5 电脑埋雷
实现电脑埋雷又是需要借助rand函数,但rand函数产生的伪随机数(0-32767),且每次在调用之前都会查询是否钓调用过srand(seed)函数,是否给seed赋予了一个值。而srand函数是作为rand的初始化函数,当seed的值相同时将会产生一样的随机值,因此我们又需要借用time函数
因为时间一直在变化,给srand赋值的种子也就不同,由于srand的参数需要为unsigned int型,因此我们还需对返回time强转。
埋的雷我们还需判断其是否越界或者是否在同一个位置布置雷。
(图形已调整90度)
void setmine(char mine[ROWS][COLS], int row, int col)//布置雷场
{
int x = 0, y = 0;
int count = MINE;
while (count)
{
x = rand() % row + 1;
y = rand() % col + 1;
if (x >= 1 && x <= row && y >= 1 && y <= col && mine[x][y] == '0')
{
count--;
mine[x][y] = '1';
}
}
}
2.6 玩家排雷
好了,又到了最经典的压轴模块。
因为这个函模块太过庞大,当一个模块中写了太多代码时我们调试bug时很难发现错误的原因,因此我们分装几个函数进行实现。
2.6.1 判断函数
获取玩家输入坐标后的周围8个格子内是否有雷并返还雷的个数。
如果我们设计的棋盘为9*9,那么返回的数将会混乱,不如设计一个11*11的棋盘,但我们只操作其中的9*9区域。将11*11的mine棋盘遍历为字符‘0’时,就不在影响输入的坐标是否在角落了。
但我们肯定不会把外面这圈打印出来,只要我们心中知道就OK了。
我们查阅ASCII表发现字符‘0’的值为48,字符‘1’的值为49,而在计算机中,字符相加减其实就是ASCII值的相加减。因此字符‘1’减字符‘0’返回的为数字1。于是,我们便设计出:
int Get_mine_count(char mine[ROWS][COLS],int x,int y)//获取周围8个格子是否有雷
{
return mine[x - 1][y - 1] +
mine[x - 1][y] +
mine[x - 1][y + 1] +
mine[x][y - 1] +
mine[x][y + 1] +
mine[x + 1][y - 1] +
mine[x + 1][y] +
mine[x + 1][y + 1] - 8 * '0';
}
2.6.2 扩散函数
当判断函数返回的值为0时,即周围没有雷时,将继续判断该8个格子的周围8个格子,并将原来的8个格子遍历为空格,直到判断的数字不为0时才停止,并将该数字传到该坐标。
当然我们的游戏就没有这么精致了,因为还没有颜色和图形模块。
void Spread(char show[ROWS][COLS],char mine[ROWS][COLS], int x, int y)//传递
{
show[x][y] = ' ';
int i = 0, j = 0, ret = 0;
for (i = x - 1; i <= x + 1; i++)
{
for (j = y - 1; j <= y + 1; j++)
{
if (i > 0 && i <= ROW && j > 0 && j <= COL && mine[i][j] != '1' && show[i][j] == '*')
{
ret = Get_mine_count(mine, i, j);
if (ret != 0)
{
show[i][j] = ret + '0';
}
if (!ret)
{
Spread(show, mine, i, j);
}
else if (show[i][j] == '*')
{
show[i][j] = ' ';
}
}
}
}
}
2.6.3 计算函数
计算show棋盘上不是字符‘*’的个数,并返还个数
int Isblank(char show[ROWS][COLS], int row, int col)//计算已判断完的个数
{
int i = 0, j = 0, n = 0;
for (i = 1; i <= row; i++)
{
for (j = 1; j <= col; j++)
{
if (show[i][j] != '*')n++;
}
}
return n;
}
2.6.4 安全函数
如果玩家第一个排的就是雷,函数将会把该坐标的雷重置为字符‘0’并重新设计一个雷。
测试:将MINE值改为80
if (n==0 && mine[x][y] == '1')
{
mine[x][y] = '0';
int a, b = 0;
while (1)
{
a = rand() % row + 1;
b = rand() % col + 1;
if (a >= 1 && a <= row && b >= 1 && b <= col && mine[a][b] == '0'&&a != x && b != y)
{
mine[a][b] = '1';
break;
}
}
}
2.7 判断输赢
如果不是第一次就踩雷,之后的踩雷将会使游戏结束,当SIZEX-n的值为0时,扫雷成功
void showmine(char mine[ROWS][COLS], char show[ROWS][COLS] ,int row, int col)//玩家所看到的棋盘
{
int x = 0, y = 0;
int n = 0;//记录已排除的个数
while (SIZEX-n)
{
printf("请输入坐标:>");
scanf("%d%d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col )
{
//欧皇测试仪
if (n==0 && mine[x][y] == '1')
{
mine[x][y] = '0';
int a, b = 0;
while (1)
{
a = rand() % row + 1;
b = rand() % col + 1;
if (a >= 1 && a <= row && b >= 1 && b <= col && mine[a][b] == '0'&&a != x && b != y)
{
mine[a][b] = '1';
break;
}
}
}
//坐标合法
//踩雷
if (mine[x][y] == '1')
{
printf("很遗憾,你扫到了雷\n");
Printboard(mine, ROW, COL);
break;
}
else//未踩雷
{
int count = 0;
count= Get_mine_count(mine, x, y)+'0';
if (count == '0')
{
Spread(show, mine, x, y);//递归传递空格
}
show[x][y] = count;
n=Isblank(show, ROW, COL);
system("cls");
Printboard(show, row, col);
}
}
else
{
printf("输入坐标非法,请重新输入!!!\n");
}
}
if (SIZEX - n == 0)
{
printf("恭喜你,扫雷成功\n");
Printboard(mine, ROW, COL);
system("pause");
}
}
3.代码模块
game.h
#pragma once
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
#define MINE 10//80 80用来测试欧皇的
#define SIZEX ROW*COL-MINE
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <time.h>
void Initboard(char board[ROWS][COLS], int row, int col, char set);
void Printboard(char board[ROWS][COLS], int rpws, int cols);
void setmine(char mine[ROWS][COLS], int row, int col);
void showmine(char mine[ROWS][COLS],char show[ROWS][COLS], int row, int col);
int Get_mine_count(char mine[ROWS][COLS], int x, int y);
void Spread(char show[ROWS][COLS],char mine[ROWS][COLS], int x, int y);
game.c
#define _CRT_SECURE_NO_WARNINGS
#include "game.h"
void Initboard(char board[ROWS][COLS], int rows, int cols, char set)
{
int i = 0, j = 0;
for (i = 0; i < rows; i++)
{
for (j = 0; j < cols; j++)
{
board[i][j] = set;
}
}
}
void Printboard(char board[ROWS][COLS], int row, int col)
{
int j = 0, i = 0;
for (j = 0; j <= col; j++)//打印序列号
{
printf("%d ", j);
}
printf("\n");
for (i = 1; i <= row; i++)
{
printf("%d ", i);
for (j = 1; j <= col; j++)
{
printf("%c ", board[i][j]);//打印棋盘
}
printf("\n");
}
}
void setmine(char mine[ROWS][COLS], int row, int col)//布置雷场
{
int x = 0, y = 0;
int count = MINE;
while (count)
{
x = rand() % row + 1;
y = rand() % col + 1;
if (x >= 1 && x <= row && y >= 1 && y <= col && mine[x][y] == '0')
{
count--;
mine[x][y] = '1';
}
}
}
int Get_mine_count(char mine[ROWS][COLS],int x,int y)//获取周围8个格子是否有雷
{
return mine[x - 1][y - 1] +
mine[x - 1][y] +
mine[x - 1][y + 1] +
mine[x][y - 1] +
mine[x][y + 1] +
mine[x + 1][y - 1] +
mine[x + 1][y] +
mine[x + 1][y + 1] - 8 * '0';
}
void Spread(char show[ROWS][COLS],char mine[ROWS][COLS], int x, int y)//传递
{
show[x][y] = ' ';
int i = 0, j = 0, ret = 0;
for (i = x - 1; i <= x + 1; i++)
{
for (j = y - 1; j <= y + 1; j++)
{
if (i > 0 && i <= ROW && j > 0 && j <= COL && mine[i][j] != '1' && show[i][j] == '*')
{
ret = Get_mine_count(mine, i, j);
if (ret != 0)
{
show[i][j] = ret + '0';
}
if (!ret)
{
Spread(show, mine, i, j);
}
else if (show[i][j] == '*')
{
show[i][j] = ' ';
}
}
}
}
}
int Isblank(char show[ROWS][COLS], int row, int col)//计算已判断完的个数
{
int i = 0, j = 0, n = 0;
for (i = 1; i <= row; i++)
{
for (j = 1; j <= col; j++)
{
if (show[i][j] != '*')n++;
}
}
return n;
}
void showmine(char mine[ROWS][COLS], char show[ROWS][COLS] ,int row, int col)//玩家所看到的棋盘
{
int x = 0, y = 0;
int n = 0;//记录已排除的个数
while (SIZEX-n)
{
printf("请输入坐标:>");
scanf("%d%d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col )
{
//欧皇测试仪
if (n==0 && mine[x][y] == '1')
{
mine[x][y] = '0';
int a, b = 0;
while (1)
{
a = rand() % row + 1;
b = rand() % col + 1;
if (a >= 1 && a <= row && b >= 1 && b <= col && mine[a][b] == '0'&&a != x && b != y)
{
mine[a][b] = '1';
break;
}
}
}
//坐标合法
//踩雷
if (mine[x][y] == '1')
{
printf("很遗憾,你扫到了雷\n");
Printboard(mine, ROW, COL);
break;
}
else//未踩雷
{
int count = 0;
count= Get_mine_count(mine, x, y)+'0';
if (count == '0')
{
Spread(show, mine, x, y);//递归传递空格
}
show[x][y] = count;
n=Isblank(show, ROW, COL);
system("cls");
Printboard(show, row, col);
}
}
else
{
printf("输入坐标非法,请重新输入!!!\n");
}
}
if (SIZEX - n == 0)
{
printf("恭喜你,扫雷成功\n");
Printboard(mine, ROW, COL);
system("pause");
}
}
test.c
#define _CRT_SECURE_NO_WARNINGS
#include "game.h"
void game()
{
char mine[ROWS][COLS] = { 0 };//建立一个11*11的棋盘
char show[ROWS][COLS] = { 0 };//
Initboard(mine, ROWS, COLS, '0');
Initboard(show, ROWS, COLS, '*');
//建立有雷的棋盘
setmine(mine, ROW, COL);
//打印棋盘
Printboard(mine, ROW, COL);//玩家不能看到仅用于测试,也可以用来作弊。。。。
Printboard(show, ROW, COL);//玩家能看到的棋盘
//排雷
showmine(mine, show, ROW, COL);
}
void menu()
{
printf("****** 菜单 *****\n");
printf("***** 1.play ****\n");
printf("***** 0.exit ****\n");
}
int main()
{
srand((unsigned)time(NULL));
int input = 0;
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
game();
break;
case 0:
printf("游戏已退出>>>\n");
break;
default:
printf("选择错误,请重新选择:>\n");
break;
}
} while (input);
}
结语
如果该代码有不懂的可以问我,评论区回复。
如有错误也可以指出来我会及时修改的。
谢谢点赞。