一、 问题
c语言实现扫雷游戏,非雷位置可以递归展开,且为保证游戏体验,用户第一次扫雷时不能踩中雷。
二、解决思路
-
扫雷游戏大家都玩过,具体规则我也就不再赘述。要实现扫雷游戏,我们至少得需要两个棋盘,一张给用户展示,让用户输入在这张棋盘上进行扫雷,另一张为埋雷棋盘,这张棋盘上埋藏若干个雷,将用户输入的坐标与这张棋盘上对应的坐标进行对比,如果是雷,则游戏结束,如果不是雷则继续。
-
程序需要实现的功能以及实现该功能的函数。
-
menu函数,指引用户做出选择。
-
game函数,游戏从这里开始。
-
setmine函数,实现埋雷,并找到一个没有雷的坐标,用于保护用户第一次不能踩中雷。
-
show函数,展示棋盘。
-
minecount函数,计算该位置周围雷的个数。
-
open函数,对非雷位置进行递归展开。
-
judge函数,判断用户是否扫雷成功。
三、代码实现
- 第一步,编写game.h头文件,把需要用到的函数声明及一些宏定义写在里面。
#ifndef __GAME_H__
#define __GAME_H__
#include <stdio.h>
#include <windows.h>
#include <time.h>
#pragma warning(disable:4996)
#define ROW 12
#define COL 12
#define MINENUM 20
void menu();
void setmine(char mineboard[][COL],int * x, int * y);
void show(char board[][COL]);
int minecount(char mineboard[][COL], int x, int y);
void game();
void open(char mineboard[][COL], char sweepboard[][COL], int x, int y);
int judge(char sweepboard[][COL]);
#endif
- 第二步,编写main函数,从这里调用函数
#include "game.h"
int main()
{
menu();
system("pause");
return 0;
}
- 第三步,编写game.c,把需要用到的函数都写在里面。
- menu函数
void menu() //menu函数,指引用户做出选择。
{
int quit = 1;
while (quit) {
printf("###############################\n");
printf("####### 欢迎来到扫雷 ########\n");
printf("###############################\n");
printf("#####1:play ####\n");
printf("#####2:exit ####\n");
printf("###############################\n");
printf("###############################\n");
int k = 0;
scanf("%d", &k);
switch (k) {
case 1:
game();//输入1,调用game函数,游戏开始。
break;
case 2:
quit = 0;
printf("Bye bye!\n");//输入0,程序运行结束。
break;
default:
printf("输入有误,请重新输入:\n");
break;
}
}
}
- game函数
void game()// game函数,游戏开始。
{
int flag = 1;
int x0 = 0;
int y0 = 0;
char sweepboard[ROW][COL];//第一个二维数组,扫描棋盘,用于给用户展示。为方便计算
// 定义数组为12 * 12,实际用到的是10 * 10的棋盘,后续棋盘均指10 * 10棋盘。
char mineboard[ROW][COL];//第二个二维数组,埋雷棋盘,用于埋雷。
memset(sweepboard, '*', sizeof(sweepboard));//扫描棋盘初始化均为‘*’
memset(mineboard, '0', sizeof(mineboard));//埋雷棋盘初始化均为‘0’
setmine(mineboard, &x0, &y0);//调用setmine函数,系统随机埋雷,整型变量x0和y0用于传回一个非雷的坐标。
while (1) {
system("cls");
show(sweepboard); //show函数,展示棋盘。
printf("请输入坐标:");
int x = 0;
int y = 0;
scanf("%d %d", &x, &y);
if (x < 1 || x > 10 || y < 1 || y > 10) {
printf("输入坐标有误,请重新输入!\n");
Sleep(1500);
continue;
}
if (sweepboard[x][y] != '*') {
printf("该位置已被扫过,请重新输入!\n");
Sleep(1500);
continue;
}
if (mineboard[x][y] == '1') {
if (flag == 1) {
mineboard[x][y] = '0';
mineboard[x0][y0] = '1';
flag = 0;
}//用户踩到雷了,如果是第一次,就与x0,y0坐标进行交换。
else {
printf("很遗憾,你输了!\n");
Sleep(1500);
printf("雷达:(1代表有雷,0代表没雷)\n");
Sleep(1500);
show(mineboard);//游戏结束,输出埋雷棋盘,让用户明白输在哪里。
break;
}
}
open(mineboard, sweepboard, x, y);//若没有踩到雷,调用open函数,判定是否需要展开。
if (judge(sweepboard) == 1) { //调用judge函数,判定是否扫雷成功。
printf("恭喜你,扫雷成功!\n");
break;
}
flag = 0;//用户第一次扫完,不在有第一次不可能踩中雷的保护。
}
}
- setmine函数
void setmine(char mineboard[][COL],int * x, int * y )//setmine函数,开始埋雷。
{
int times = MINENUM;//循环执行次数,即雷的个数。
srand((unsigned long)time(NULL));
while (times) {
int x = rand() % (ROW - 2) + 1;
int y = rand() % (COL - 2) + 1;
if (mineboard[x][y] != '1') {
mineboard[x][y] = '1';
times--;
}
}
while (1) {
*x = rand() % (ROW - 2) + 1;
*y = rand() % (COL - 2) + 1;
if (mineboard[*x][*y] == '0') {
break;
}
}//在埋雷棋盘中找到一个随机非雷坐标。
}
- show函数
void show(char board[][COL])//show函数,展示棋盘。
{
int i = 1;
int j = 0;
printf(" ");//调整列标位置。
for (; i < 11; i++) {
printf("%d ", i);//输出列标。
}
printf("\n");
for (i = 1; i < 11; i++) {
printf(" ----------------------------------------\n");//输出每一行之间分割线。
printf("%2d|", i);//输出行标。
for (j = 1; j < 11; j++) {
printf(" %c |", board[i][j]);//输出棋盘元素。
}
printf("\n");
}
printf(" ----------------------------------------\n");//输出最后一行分割线。
}
- minecount函数
int minecount(char mineboard[][COL], int x, int y)//minecount函数,统计该坐标周围雷的个数。
{
int c = mineboard[x - 1][y - 1] + mineboard[x - 1][y] + mineboard[x - 1][y + 1] +\
mineboard[x][y - 1] + mineboard[x][y + 1] + mineboard[x + 1][y - 1] +\
mineboard[x + 1][y] + mineboard[x + 1][y + 1] - 8 * '0';//棋盘是字符型的,把该坐标周围8个坐标里的字符加起来
//再减去8 * '0',计算时用的是acsii值,所得结果就是雷的个数。
return c;
}
- open函数
void open(char mineboard[][COL], char sweepboard[][COL], int x, int y)//open函数,判断是否需要递归式展开。
{
if (!minecount(mineboard, x, y))//调用minecount函数,计算周围雷的个数,若为0,则需要展开。
{
sweepboard[x][y] = ' ';
int i = 0;
int j = 0;
for (i = x - 1; i <= x + 1; i++)
{
for (j = y - 1; j <= y + 1; j++)
{
if (sweepboard[i][j] == '*' &&\
i > 0 && i < 11 && j > 0 && j < 11)//若该位置未被扫过且在棋盘范围内则继续递归调用open函数。
open(mineboard, sweepboard, i, j);
}
}
}
else {
sweepboard[x][y] = minecount(mineboard, x, y) + '0';//若不需要展开,则为该位置赋值周围雷的个数,
//棋盘是字符型的,需要加'0'才是对应数字的字符。
}
}
- judge函数
int judge(char sweepboard[][COL]) //judge函数,判定用户是否扫雷成功。
{
int i = 1;
int j = 1;
int count = 0;
for (; i <= ROW - 2; i++) {
for (j = 1; j <= COL - 2; j++) {
if (sweepboard[i][j] == '*') {
count++;
}//遍历棋盘元素,若该位置未被扫过,则记录下来。
if (count > MINENUM) {
return 0;
}//若未被扫过的个数大于雷的个数,则游戏继续。
}
}
return 1;
}
四、运行结果
- 开始界面:
- 扫雷过程:
空格部分就是递归展开的部分。 - 游戏结束:
五,思考
还是多动手吧,写的过程中好多小错误。如循环时不记得给循环变量赋值。还有就是这个程序还可以优化一下,比如做成闯关模式,或者让用户自己选择棋盘大小和雷的个数。应该会很有意思。