扫雷小游戏
设计:
1.固定棋盘大小为9*9,你也可以直接修改ROW和COL的值,改变棋盘的大小;
2.ROWS和COLS是为了判断棋盘边缘的格子的雷的情况而设立的,比ROW和COL多2
3.WINE是专门为游戏中的幸运版设计的,大小为棋盘大小-1
4.WINES是扫雷游戏中雷的个数,可以修改
5.本扫雷小游戏包含标记和扩展
6.扫雷幸运版就是只有一个无雷格子,如果你觉得自己是天选之子就试试吧
注:可以跟着目录梳理
首先为测试代码:
#define _CRT_SECURE_NO_WARNINGS
#include"game.h"
void menu() {
printf("---------------------------------\n");
printf("---- 1.play 0.exit ----\n");
printf("---------------------------------\n");
}
void menu_game() {
printf("------------------------------------------\n");
printf("---- 1.正常的扫雷游戏 ----\n");
printf("---- 2.扫雷幸运版(一个无雷区) ----\n");
printf("---- 0.退出游戏 ----\n");
printf("------------------------------------------\n");
}
void game1(){
printf("开始扫雷游戏\n");
char mine[ROWS][COLS] = { 0 };
char show[ROWS][COLS] = { 0 };
//初始化
InitBoard(mine, ROWS, COLS, '0');
InitBoard(show, ROWS, COLS, '*');
//设置雷
SetMine(mine, ROW, COL,MINES);
//打印
//DisplayBoard(mine, ROW, COL);
DisplayBoard(show, ROW, COL);
//排查雷
FindMine(mine, show, ROW, COL,MINES);
}
void game2(){
printf("开始幸运版\n");
char mine[ROWS][COLS] = { 0 };
char show[ROWS][COLS] = { 0 };
//初始化
InitBoard(mine, ROWS, COLS, '0');
InitBoard(show, ROWS, COLS, '*');
//设置雷
SetMine(mine, ROW, COL,MINE);
//打印
//DisplayBoard(mine, ROW, COL);
DisplayBoard(show, ROW, COL);
//排查雷
FindMine(mine, show, ROW, COL,MINE);
}
void game() {
int input = 0;
menu_game();
printf("请选择你想玩的类型:");
scanf("%d", &input);
switch (input) {
case 1:
game1();
break;
case 2:
game2();
break;
case 0:
break;
default:
printf("请重新输入");
break;
}
}
int main() {
//设置随机种子
srand((unsigned int)time(NULL));
int input = 0;
do {
menu();
printf("请输入:");
scanf("%d", &input);
switch (input) {
case 1:
game();
break;
case 0:
break;
default:
printf("请重新选择!\n");
break;
}
} while (input);
return 0;
}
接着是函数声明:
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
#define MINE 80
#define MINES 10
//初始化
void InitBoard(char board[ROWS][COLS],int rows,int cols,char a);
//设置雷
void SetMine(char board[ROWS][COLS], int row, int col,int mines);
//打印
void DisplayBoard(char board[ROWS][COLS], int row, int col);
//查找雷
void FindMine(char mine[ROWS][COLS],char show[ROWS][COLS],int row,int col,int mines);
最后是函数实现:
1.初始化:
将传入的数组全部设置为传入的字符,行列也要传,是为了修改棋盘大小时也不会影响到初始化
//初始化
void InitBoard(char board[ROWS][COLS], int rows, int cols, char a) {
int i = 0;
int j = 0;
for (i = 0; i < rows; i++) {
for (j = 0; j < cols; j++) {
board[i][j] = a;
}
}
}
2.设置雷
判断随机到的雷之前是否之前埋过,如果埋过则重新随机。
//设置雷
void SetMine(char board[ROWS][COLS], int row, int col,int mines) {
int count = mines;
do {
int x = rand() % row + 1;
int y = rand() % col + 1;
if (board[x][y] == '0') {
board[x][y] = '1';
count--;
}
} while (count);
}
3.打印
1. 二维数组的打印方式
2. 额外打印了行列的数,方便玩家输入坐标
//打印
void DisplayBoard(char board[ROWS][COLS], int row, int col) {
int i = 0;
int j = 0;
printf("--------------------\n");
for (i = 0; i <= row; i++) {
printf("%d ", i);
}
printf("\n");
for (i = 1; i <= row; i++) {
printf("%d ", i);
for (j = 1; j <= col; j++) {
printf("%c ", board[i][j]);
}
printf("\n");
}
printf("--------------------\n");
}
4.查找雷
游戏的主体部分
1. 查看玩家输入的坐标是否合法,即是否在棋盘内
2. 查看玩家输入的坐标是否之前输入过
3. 查看玩家输入的坐标是否埋雷了,若有雷,则表示被炸死并退出游戏,若没埋雷则继续以下步骤
4. 查看玩家所选坐标周围是否有雷,若无雷则修改显示棋盘为0,同时往周围扩展,直到遇到的周围有雷,将该处的坐标内容修改为周围雷的个数。若有雷,则将该处的坐标内容修改为周围雷的个数。
5. 打印棋盘情况
6. 统计一下目前棋盘上已经开了多少个格子,为后续判断输赢做准备
7. 询问玩家是否进行标记行动,可标记,取消标记和不标记,退出标记后继续游戏
8. 重复以上步骤,直至玩家被炸死或者赢得胜利
//查找雷
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col,int mines) {
int x = 0; //横轴的坐标
int y = 0; //竖轴的坐标
int win = 0; //判断输赢
int input = 0; //选择输入
int count = 0; //标记的个数
while (win < row*col-mines) { //如果玩家打开的个数小于空格的个数
printf("请输入坐标:");
scanf("%d%d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col) { //判断坐标是否合法
if (show[x][y] == '*' || show[x][y] == '#') { //判断坐标是否被使用过
if (mine[x][y] == '1') { //判断是否开出雷
printf("很抱歉,你被炸死了\n");
DisplayBoard(mine, ROW, COL);
break;
}
else {
//扩展
ExpandFindMine(mine, show, ROW, COL, x, y);
DisplayBoard(show, ROW, COL); //打印
win = IsWin(show, ROW, COL); //获得打开的个数
count = IsMark(show, ROW, COL,mines,count,win); //标记,返回已标记个数
}
}
else {
printf("该坐标已经输过,请重新输入\n");
}
}
else {
printf("该坐标不符合要求,请重新输入\n");
}
}
if (win == row * col - mines) { //判断是否打开了全部的空白
DisplayBoard(mine, ROW, COL);
printf("恭喜你找到了全部的雷\n");
if (mines == MINE) { //幸运版模式的判断
printf("恭喜你运气超过99%%的人\n");
}
}
}
扩展:
采用递归的方式
1. 判断坐标是否符合要求
2. 判断该坐标周围雷的个数,若不为0,则改变展示棋盘上的字符,结束这次的递归,若为0,则先将该位置上的棋盘上的字符改为‘0’,然后继续递归
//扩展找雷
void ExpandFindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col, int x, int y) {
if (x >= 1 && x <= row && y >= 1 && y <= col) {
int count = RoundMine(mine, x, y);
if (count) {
show[x][y] = count + '0';
}
else {
if (show[x][y] == '*' || show[x][y] == '#') {
show[x][y] = '0';
for (int i = x - 1; i <= x + 1; i++) {
for (int j = y - 1; j <= y + 1; j++) {
ExpandFindMine(mine, show, ROW, COL, i, j);
}
}
}
}
}
}
判断周围雷的个数:
探查该坐标周围是否有雷,根据之前的设定,埋雷的格子为‘1’,没埋雷的为‘0’,故我们可以采用加法来更好地判断周围是否有雷,只需将周围的数加起来即可,加起来的个数就是雷的个数。
注:我们设置的是字符数组,相加的时候要每个-‘0’,才能得到数字,将得到的数字加起来就是我们需要的雷的个数。
//查找周围是否有雷
int RoundMine(char mine[ROWS][COLS], int x, int y) {
int sum = 0;
for (int i = x - 1; i <= x + 1; i++) {
for (int j = y - 1; j <= y + 1; j++) {
sum += mine[i][j] - '0';
}
}
return sum;
}
标记:
//是否标记
int IsMark(char board[ROWS][COLS], int row, int col,int mines,int count,int win) {
int input = 0;
if (count < mines && win < row * col - mines) { //判断是否进行标记询问
do {
menu_mark();
printf("请选择:");
scanf("%d", &input);
switch (input) {
case 1:
Mark(board, ROW, COL); //标记
count++;
break;
case 2:
CancelMark(board, ROW, COL); //取消标记
count--;
break;
case 0:
printf("请继续游戏\n"); //不标记
break;
default:
printf("请重新输入\n");
break;
}
} while (input);
}
else if (count == mines && win < row * col - mines) { //判断是否达到最大标记数
do {
menu_mark();
printf("请选择:");
scanf("%d", &input);
switch (input) {
case 1:
printf("你的标记已经达到最大数量\n");
break;
case 2:
CancelMark(board, ROW, COL);
count--;
break;
case 0:
printf("请继续游戏\n");
break;
default:
printf("请重新输入\n");
break;
}
} while (input);
}
else if (count == 0 && win < row * col - mines) { //判断是否为标记
do {
menu_mark();
printf("请选择:");
scanf("%d", &input);
switch (input) {
case 1:
Mark(board, ROW, COL);
break;
case 2:
printf("你未标记不可进行消除标记操作\n");
break;
case 0:
printf("请继续游戏\n");
break;
default:
printf("请重新输入\n");
break;
}
} while (input);
}
return count;
}
标记目录
//标记目录
void menu_mark() {
printf("--------------------------------------\n");
printf("---- 1.进行标记 ----\n");
printf("---- 2.取消标记 ----\n");
printf("---- 0.不标记 ----\n");
printf("--------------------------------------\n");
}
1.标记
标记就是将展示棋盘上的‘*’字符改为其他都字符,就可以
1.注意判断坐标是否符合要求
2.判断该坐标之前是否标记过,或者是已经开了的格子,即是否为‘*’,若为‘*’才可以修改,为其他都不可修改
//标记
void Mark(char board[ROWS][COLS], int row, int col) {
int x = 0;
int y = 0;
while (1) {
printf("请输入想标记的坐标:");
scanf("%d%d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col) {
if (board[x][y] == '*') {
board[x][y] = '#';
DisplayBoard(board, ROW, COL);
break;
}
else {
printf("该坐标不可标记,请重新输入\n");
}
}
}
}
2.取消标记
标记就是将展示棋盘上的你设置的特殊字符改为'*',就可以
1.注意判断坐标是否符合要求
2.判断该坐标之前是否标记过,即是否为你设置的特殊字符,若为你设置的特殊字符才可以修改,为其他都不可修改
//取消标记
void CancelMark(char board[ROWS][COLS], int row, int col) {
int x = 0;
int y = 0;
while (1) {
printf("请输入取消标记的坐标:");
scanf("%d%d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col) {
if (board[x][y] == '#') {
board[x][y] = '*';
DisplayBoard(board, ROW, COL);
break;
}
else {
printf("该坐标不可标记,请重新输入\n");
}
}
}
}
判断输赢
判断输赢就是看你的展示棋盘上玩家已经打开了的格子的个数,如果个数为总格子数-埋雷数,就可以判断玩家赢
//判断输赢
int IsWin(char board[ROWS][COLS], int row, int col) {
int i = 0;
int j = 0;
int count = 0;
for (i = 1; i <= row; i++) {
for (j = 1; j <= col; j++) {
if (board[i][j] != '*' && board[i][j] != '#') {
count++;
}
}
}
return count;
}