用C语言实现扫雷游戏(简易版)

在线试玩

        扫雷游戏网页版 - Minesweeper

游戏介绍

  • 在控制台中实现扫雷游戏
  • 通过选择菜单决定是否进行游戏
  • 游戏棋盘默认为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),与此同时也需考虑用户重复输入一个有效坐标的可能。

        最后我们需要计算用户输入的坐标周围雷的数量。

        至于跳出循环(结束游戏),只有两个方法,

  1. 找的坐标是雷
  2. 游戏胜利(设置变量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个文件里。如有问题请不吝评论。 

 

 

 

 

 

        

            

         

                        

  • 44
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值