在线试玩
游戏介绍
- 在控制台中实现扫雷游戏
- 通过选择菜单决定是否进行游戏
- 游戏棋盘默认为9*9
- 雷的个数默认为10
- 可以排查雷
- 排查到非雷位置时,会显示周围雷的数量
- 排查的位置是雷时,雷会爆炸,游戏结束
- 当排查完所有非雷的位置后,游戏结束
显示雷的数量的范围:
游戏分析
首先,棋盘默认是9*9的正方形,所以可以用二维数组的方式来代替棋盘。同时,因为技术的限制,我们可以用输入坐标的方式来来代替鼠标的点击。
接着,在放置雷和表示雷的位置时,需要使用一个二维数组来储存各个位置的信息。为了方便起见,我们可以用0表示非雷,用1表示雷。当目标位置为非雷的时候,我们要计算周围一圈8个位置上雷的总数,并显示在该位置。例如当我输入的坐标是(3,5)的时候,该坐标所在位置并不是雷,然后经计算它周围雷的数量为1,所以该点打印1。
这时,问题出现。如果一个二维数组存储雷的信息,当一个坐标点不是雷的时候,我们将该点信息变为周围雷的数量,那么在之后的操作中,我们就需要对该点的信息进行判断,这样会带来不必要的麻烦。因此,我们可以创建两个二维数组,一个用来储存雷的信息,一个用来进行屏幕上的输出。
还有一个问题:在计算非雷点周围雷的个数时,如果该点在边缘,那么计算周围8个点的方法会导致下标越界。所以我们可以将棋盘扩大一圈,如图所示:
这样便解决了越界问题。为了统一,我们也可以将打印的数组扩大一圈。
代码实现
C语言程序从main函数开始,我们编写代码也从main函数开始向外扩散。
首先,因为每次开始游戏都需要再次开启程序过于麻烦,所以首先需要一个循环。
在循环内部,游戏开始需要一个菜单来让玩家选择开始或者结束游戏。因此,先打印一个菜单。我们可以创建一个菜单函数:
void menu(void)
{
printf("***********************\n");
printf("******* 1. play *******\n");
printf("******* 0. exit *******\n");
printf("***********************\n");
}
看到菜单时我们进行选择,然后根据选择的值执行相应的操作,这里可以用到switch语句。同时,我们还需考虑用户输入的值并不在两个选择的范围内,这时就需要重新输入,但不需要重新打印菜单,因此可以使用goto语句。代码如下:
int main(void)
{
int input;
do
{
menu();
printf("请进行选择:");
start:
scanf("%d", &input);
switch (input)
{
case 1:
printf("扫雷游戏\n");
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("输入无效,请重新选择:");
goto start;
break;
}
} while (input);
return 0;
}
然后我们可以运行一下来检验所写代码是否有误。接下来我们可以编写game函数,来实现扫雷游戏。
首先,为了便于修改,我们可以使用预处理指令来确定行和列。
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
#define NUM 10
其中,row代表行,col代表列,num则代表雷的数量。
然后,定义两个二维数组。
char mine[ROWS][COLS] = { '\0' }, show[ROWS][COLS] = { '\0' };
接着,对两个数组进行初始化。为了神秘,我们可以让初打印的棋盘是‘*’,刚开始雷的数组元素全是字符0。方便起见,我们可以让mine数组也为char类型,这样我们可以使用一个函数进行初始化:
void Init(char board[ROWS][COLS], int rows, int cols, char set)
{
int i, j;
for (i = 0; i < rows; i++)
{
for (j = 0; j < cols; j++)
board[i][j] = set;
}
}
接下来打印棋盘,我们也可以写一个函数。由于打印的棋盘是中间的9*9,所以代表行和列的便是row和col,同时为了能够很容易地找到坐标,我们可以在第一行打印上0-9,第一列也打印上0-9:
void Print(char board[ROWS][COLS], int row, int col)
{
int i, j;
//打印列数
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");
}
}
运行效果如下:
打印棋盘后,我们开始放置雷。放置雷的坐标需要随机,所以使用rand函数(当然也要srand设置种子),代码如下:
void Setmine(char board[ROWS][COLS], int row, int col, int n)
{
srand((unsigned)time(NULL));
while (n)
{
int x = rand() % row + 1;
int y = rand() % col + 1;
if (board[x][y] != '1')
{
board[x][y] = '1';
n--;
}
}
}
接着开始我们的扫雷环节:
首先,我们需要找到所有非雷的位置,同时一般来说我们无法确定自己能否成功,所以使用while循环非常合适。
接下来,我们需要检验用户输入的坐标是否有效(横纵坐标0-9),与此同时也需考虑用户重复输入一个有效坐标的可能。
最后我们需要计算用户输入的坐标周围雷的数量。
至于跳出循环(结束游戏),只有两个方法,
- 找的坐标是雷
- 游戏胜利(设置变量win统计寻找的坐标数量,直到win == row*col-num)
代码如下:
//计算周围雷的数量
int Countmine(char mine[ROWS][COLS], int x, int y)
{
int i, j, sum = 0;
for (i = x - 1; i <= x + 1; i++)
{
for (j = y - 1; j <= y + 1; j++)
if ('1' == mine[i][j])
sum++;
}
return sum;
}
//扫雷
void Sweepmine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x, y, win = 0;
while (1)
{
printf("请输入要排查的坐标:");
scanf("%d%d", &x, &y);
if (show[x][y] != '*')
{
printf("该坐标已被排查,无需再查。\n");
continue;
}
if (x >= 1 && x <= ROW && y >= 1 && y <= COL)
{
if (mine[x][y] != '1')
{
int count = Countmine(mine, x, y);
show[x][y] = count + '0';
Print(show, ROW, COL);
win++;
}
else
{
printf("很遗憾,游戏失败。\n");
Print(mine, ROW, COL);
break;
}
}
else
printf("输入坐标无效,");
if (win == row * col - NUM)
{
printf("恭喜你,找到所以非雷的坐标。\n");
Print(mine, ROW, COL);
break;
}
}
}
最终代码
game.h
#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 NUM 10
//初始化棋盘
void Init(char board[ROWS][COLS], int rows, int cols, char set);
//打印棋盘
void Print(char board[ROWS][COLS], int row, int col);
//放置雷
void Setmine(char board[ROWS][COLS], int row, int col, int n);
//计算周围雷的数量
int Countmine(char mine[ROWS][COLS], int x, int y);
//扫雷
void Sweepmine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
game.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
//初始化棋盘
void Init(char board[ROWS][COLS], int rows, int cols, char set)
{
int i, j;
for (i = 0; i < rows; i++)
{
for (j = 0; j < cols; j++)
board[i][j] = set;
}
}
//打印棋盘
void Print(char board[ROWS][COLS], int row, int col)
{
int i, j;
//打印列数
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");
}
}
//放置雷
void Setmine(char board[ROWS][COLS], int row, int col, int n)
{
srand((unsigned)time(NULL));
while (n)
{
int x = rand() % row + 1;
int y = rand() % col + 1;
if (board[x][y] != '1')
{
board[x][y] = '1';
n--;
}
}
}
//计算周围雷的数量
int Countmine(char mine[ROWS][COLS], int x, int y)
{
int i, j, sum = 0;
for (i = x - 1; i <= x + 1; i++)
{
for (j = y - 1; j <= y + 1; j++)
if ('1' == mine[i][j])
sum++;
}
return sum;
}
//扫雷
void Sweepmine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x, y, win = 0;
while (1)
{
printf("请输入要排查的坐标:");
scanf("%d%d", &x, &y);
if (show[x][y] != '*')
{
printf("该坐标已被排查,无需再查。\n");
continue;
}
if (x >= 1 && x <= ROW && y >= 1 && y <= COL)
{
if (mine[x][y] != '1')
{
int count = Countmine(mine, x, y);
show[x][y] = count + '0';
Print(show, ROW, COL);
win++;
}
else
{
printf("很遗憾,游戏失败。\n");
Print(mine, ROW, COL);
break;
}
}
else
printf("输入坐标无效,");
if (win == row * col - NUM)
{
printf("恭喜你,找到所以非雷的坐标。\n");
Print(mine, ROW, COL);
break;
}
}
}
test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
void menu(void);
void game(void);
int main(void)
{
int input;
do
{
menu();
printf("请进行选择:");
start:
scanf("%d", &input);
switch (input)
{
case 1:
game();
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("输入无效,请重新选择:");
goto start;
break;
}
} while (input);
return 0;
}
void menu(void)
{
printf("***********************\n");
printf("******* 1. play *******\n");
printf("******* 0. exit *******\n");
printf("***********************\n");
}
void game(void)
{
char mine[ROWS][COLS] = { '\0' }, show[ROWS][COLS] = { '\0' };
//初始化棋盘
Init(mine, ROWS, COLS, '0');
Init(show, ROWS, COLS, '*');
//打印棋盘
//Print(mine, ROW, COL);
Print(show, ROW, COL);
//放置雷
Setmine(mine, ROW, COL, NUM);
//Print(mine, ROW, COL);
//扫雷
Sweepmine(mine, show, ROW, COL);
}
为了代码的整洁,我将代码的声明,定义,主函数的实现分别放在3个文件里。如有问题请不吝评论。