整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

C语言开发:一篇文章带你还原童年,扫雷游戏完整源码分

C语言开发:一篇文章带你还原童年,扫雷游戏完整源码分析

必屏幕前的你,肯定玩过windows系统自带的那个游戏,扫雷

回想当年,我根本没看懂这个游戏是怎么玩的

比起扫雷,三维弹球对我更有吸引力

跑题了

本篇博客就让我们一起来试试,如何通过C语言代码,制作出一个“扫雷游戏se”

1.游戏程序主函数

在编写这类游戏代码时,我们要用到的主函数基本是一致的

扫雷游戏的主函数和猜数字游戏的主函数相差很小

void menu()//简易目录
{
  printf("***************************\n");
  printf("**** 1. play   0. exit*****\n");
  printf("***************************\n");
}

int main()
{
  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);
  return 0;
}?

2.游戏实现原理

想写好一串代码,首先我们要知道扫雷游戏需要通过什么方式来实现

我们需要一个9x9的棋盘,用于生成我们的雷以及玩家的游玩

在c语言中当然无法直接产生这样的画面

但我们可以同符号*或者#来代替网格,用1和0来表示有无雷

如果我们只生成一个棋盘,那1和0会直接显示出来,达不到隐藏的效果

所以我们需要用二维数组生成两个棋盘,一个用于存放雷,一个用于玩家的游玩

	char mine[ROWS][COLS];//雷区布置
	char show[ROWS][COLS];//玩家看到的界面 

扫雷游戏我们使用头文件+源文件的形式撰写代码

这样写代码的优点在于后续我们可以直接通过更改.h文件中的数组,从而更改我们的格子大小

如: 改成12x12的游玩界面,改变雷区布雷个数等等

所以我们需要在game.h中定义这些符号

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#define ROW 9
#define COL 9

#define ROWS ROW+2
#define COLS COL+2

同时我们要在主函数的最上面引用这个自己写的头文件

只要把库函数头文件放入game.h文件,在其他源文件中只需引用game.h

不需要再次引用<stdio.h>、<stdlib.h>之类

#include 'game.h'

棋盘大小为什么需要11x11?

你可能注意到了,在生成数组的时候,我使用了ROWS,其值为ROW+2

我们最终展示的只是9x9的游戏界面,但生成的棋盘其实是11x11的

这是因为我们需要在mine数组中实现扫描雷区的操作

玩过扫雷游戏的你肯定知道:在你点击一个格子的时候,如果这个格子不是雷

它会显示一个数字,告诉你它周围的8个格子中有几颗雷

如图所示:

在C语言中,我们可以用函数统计周围8个格子中雷’1’的个数

但是如果你来到边缘,那就出现问题了

如果我们想统计边缘的格子周边有几颗雷,就会遇到这种溢出数组的情况

此时代码会报错

为了避免这个问题,我们可以在原来9x9的基础上在周围加一圈空白的格子

也就是代码所示的ROW(行)COL(列)都要+2的情况

#define ROW 9
#define COL 9

#define ROWS ROW+2
#define COLS COL+2

游戏过程

这里简单梳理一下我们的游戏过程

(1)玩家选择开始游戏

(2)生成两个棋盘,一个放置雷\扫描雷,一个向玩家展示游戏界面

(3)玩家输入坐标,选择排雷位置

(4)有雷–>玩家被炸死,游戏结束;无雷–>显示周边有几颗雷,游戏继续

(5)所有雷被排出,游戏胜利


3.游戏代码实现

接下来就进入我们的游戏代码部分

3-1.初始化和打印

	//初始化扫雷
	InitBoard(mine, ROWS, COLS, '0');
	InitBoard(show, ROWS, COLS, '*');

	//打印扫雷
	DisplayBoard(mine, ROW, COL);
	DisplayBoard(show, ROW, COL); 

我们需要初始化两个棋盘,其中雷区初始化为0(0代表无雷),展示区初始化为’*’,用?代替界面

同时我们打印这两个棋盘,查看初始化效果

因为这是我们的自定义函数,所以需要在.h文件中定义函数,在另外一个.c文件中包含函数的实现

//初始化棋盘
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set);
//打印
void DisplayBoard(char board[ROWS][COLS], int row, int col);

初始化函数和打印函数比较简单,使用for语句达成我们的需求

void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)
{
	int i=0;
	int j=0;
	for (i=0; i < rows; i++)
	{
		for (j=0; j < cols; j++)
		{
			board[i][j]=set;
		}
	}
}

void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
	int i=0;
	int j=0;
	printf("------扫雷游戏------\n");
	//打印列号
	for (i=0; i <=col; i++)
	{
		printf("%d ", i);
	}
	printf("\n");
	for (i=1; i <=row; i++)//只打印中心的99方格
	{
		printf("%d ", i);//打印行号
		for (j=1; j <=col; j++)//只打印中心的99方格
		{
			printf("%c ", board[i][j]);
		}
		printf("\n");
	}
	printf("--------------------\n");
}

需要注意的是我们的最后打印棋盘的时候是从i=1开始的,这样就能避开添加的空白边缘区域,只打印中心的99方格

同时我们添加了列号和行号,这样能让玩家清除的知道自己应该输入什么坐标

3-2.布置雷区

	//布置雷
	SetMine(mine, ROW, COL); 

同样的,我们需要在game.h中定义这个函数

//布置地雷
void SetMine(char mine[ROWS][COLS], int row, int col);

在game.c中写入自定义函数的实现

//放置雷
void SetMine(char  mine[ROWS][COLS], int row, int col)
{
  int count=EASY_COUNT;
  while (count)
  {
    int x=rand() % row + 1;
    int y=rand() % col + 1;
    if (mine[x][y]=='0')
    {
      mine[x][y]='1';
      count--;
    }
  }
}?

这里面出现了一个前面没有提到的变量,EASY_COUNT

本来这个位置只是个10

但如果我们想更改布雷个数,那每次都需要更改这里的10,后面的代码中也需要更改,非常麻烦

所以我们改为使用一个自定义变量,在game.h中定义这个变量的值

#define EASY_COUNT 10

这个值就代表我们布置雷的个数了

3-3.玩家排查雷

//在主函数中引用这个函数
	FindMine(mine,show, ROW, COL);//需要把mine数组中排查的雷放入show

//在game.h中定义这个函数
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS],int row, int col);

因为我们需要把mine数组中排查出的雷的个数放入show数组中打印出来

所以这里我们需要把两个数组都传送过去

1.输入排查的坐标

2.检查坐标处是不是雷

·是雷 -boom!炸死 -游戏结束

·不是雷 -统计坐标周围有几个雷-存储排雷的信息到show数组,游戏继续

void FindMine(char mine[ROWS][COLS], char show[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 (mine[x][y]=='1')
      {
        printf("很遗憾,你被炸死了\n");
        DisplayBoard(mine, ROW, COL);
        break;
      }
      else
      {
        //不是雷的情况下,统计坐标周围有几个雷
        int count=get_mine_count(mine, x, y);
        show[x][y]=count + '0';
      }
    }
    else
    {
      printf("坐标错误,请重新输入 \n");
    }
  }
}

注意,这里面我们需要添加一个代码来判断坐标合法性

我们的棋盘是9x9,玩家要是输入一个(99,99)的坐标,那肯定不在数组中的,是无效的

我们需要提醒玩家他输错了

‘0’的作用

show[x][y]=count + '0';

你可能会对这行代码感到疑惑

为什么要在count后面+上一个‘0’?

这里就和我们ascii码表有关了

因为我们初始化数组和布置雷的时候,我们给数组传入的都是1和0这两个符号,并不是数字!

但是在show数组中我们需要给玩家显示一个数字的字符

这里面我们提供的是1的字符,并不是1它本身

而我们在计算周边雷的个数的时候,传回来的是一个具体的数字

观察表格,你会发现数字和对应的字符中间,都差了48

而48恰好是字符’0’对应的ASCII码值

所以我们需要用count加上字符’0’,以此在界面中向玩家展示周边8格有几颗雷

3-4. 系统扫描雷

如何把玩家选择的格子周边的雷扫描出来呢?

设玩家的选择的坐标为x和y

我们只需要把这些坐标全部在二维数组中键入,就能逐个扫描出雷的个数

//统计雷的个数
static int get_mine_count(char mine[ROWS][COLS], int x, int y)
{
  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';
}

这里因为我们扫描出来的也是‘1’的字符,系统中是字符1的ascii码值49

所以我们需要减去8个字符‘0’,这样就能得到雷的个数的数字

(然后在之前的那个函数中接受,count+‘0’,在show数组中显示)

这个函数是在玩家排查雷的函数之前的

因为在主函数中我们不需要使用这个自定义函数,所以不需要在game.h中定义

我们想让它只在game.c中生效,所以用static修饰它

static的作用

1.修饰局部变量

2.修饰全局变量

3.修饰函数

上面的代码其实还少了一个东西

我们需要判断玩家什么时候胜利——雷区的0(无雷方块)全部被玩家找出,玩家就胜利了

	int win=0;
	while (win< row * col - EASY_COUNT) 

这里我们需要更改的是whlie函数

其中 row * col - EASY_COUNT 指方格总数减去雷的个数,得到的是无雷方块的个数

玩家每成功排除一个无雷方块,win就会加一个数字

	else
	{
		//不是雷的情况下,统计坐标周围有几个雷
		int count=get_mine_count(mine, x, y);
		show[x][y]=count + '0';
		//显示排查出来的信息
		DisplayBoard(show, ROW, COL);
        win++;
	} 

当win达到无雷方块个数的时候,whlie循环就会停止

随后我们判断玩家是否胜利,如果胜利,就打印棋盘,让玩家知道雷的位置

	if (win==row * col - EASY_COUNT)
	{
		printf("恭喜你,游戏胜利!\n");
		DisplayBoard(mine, ROW, COL);
	} 

这里必须要判断,因为你失败了也是会跳出循环的!

到此,我们的扫雷代码就是完成了

4.查看结果

这里我作弊,将雷的个数设置为80并打印出布置雷之后的棋盘

输入最后一个雷的位置,系统提示我们游戏胜利

输入雷的坐标后,也会提示你被炸死了

到这里我们可以确认代码是编写成功了!

对啦对啦!另外的话为了帮助大家,轻松,高效学习C语言/C++,我给大家分享我收集的资源,从最零基础开始的教程到C语言项目案例,帮助大家在学习C语言的道路上披荆斩棘!可以来我粉丝群领取哦~

编程学习书籍分享:

编程学习视频分享:

整理分享(多年学习的源码、项目实战视频、项目笔记,基础入门教程)最重要的是你可以在群里面交流提问编程问题哦!

对于C/C++感兴趣可以关注小编在后台私信我:【编程交流】一起来学习哦!可以领取一些C/C++的项目学习视频资料哦!已经设置好了关键词自动回复,自动领取就好了!

经以为扫雷很难,根本没有什么思路。昨晚想了想,其实一步一步分解开来还是可以做的。于是乎,今天就写了一个。本来想用pygame做一个图形界面,可是太懒了不想做了,就用最简单的方法实现了。

扫雷有几个要点:

  • 你扫的第一块不能有雷。也就是说当你操作之后才会随机产生雷。
  • 空白展开,也就是说一个格子,周边没有雷那么上面是没有数字。如果你玩的一个非常的方阵的话,总不能一直一个个的点格子。如果你点到空白的格子,与之相连的空白格子要同时展开。对我来说这个挺难的,我花了很多的时间。
  • 扫雷,每块格子上的数字表示是这个格子周围一圈(不在边缘的话有八个格子)的雷的数量。我觉得和上一个问题有联系。你要解决二维数组中的某个数与周边的关系。不能超过列表的索引范围。
  • 还记得右键鼠标可以标记吗,这个小功能当然不能少了。
from random import randint

class Lawn():
    def __init__(self, cols, rows, mines_num):
        self.mines_map=[[0 for row in range(rows)] for col in range(cols)]
        self.lawn_map=[['■' for row in range(rows)] for col in range(cols)]
        self.cols, self.rows=cols, rows
        self.mines_num=mines_num
        self.game_active=True

    def initialize(self, col, row):
        self.plant_mines(col, row)
        self.cells_neighbor_mines()

    def sweep_mine(self, col, row):
        if self.game_active:
            self.initialize(col, row)
            self.game_active=False
        self.lawn_map[col][row]=str(self.mines_map[col][row])
        self.check_res(col, row)
        self.unfold_nomine_place(col, row)

    def flag(self, col, row):
        if self.lawn_map[col][row]=='#':
            self.lawn_map[col][row]='?'
        elif self.lawn_map[col][row]=='?':
            self.lawn_map[col][row]='■'
        elif self.lawn_map[col][row]=='■':
            self.lawn_map[col][row]='#'
        
    def unfold_nomine_place(self, col, row):
        check_list, checked_list=[(col, row)], []
        while check_list:
            deal_list, stack=[], []
            i, j=check_list[0][0], check_list[0][1]
            self.cell_neighbor(deal_list, check_list[0][0], check_list[0][1])
            self.get_cell_neighbor(stack, deal_list)
            checked_list.append(check_list.pop(0))
            if self.lawn_map[i][j]=='0':
                for k in range(len(stack)):
                    x, y=stack[k][0], stack[k][1]
                    if self.lawn_map[x][y] in '#?':
                        continue
                    self.lawn_map[x][y]=str(self.mines_map[x][y])
                    if (x, y) not in checked_list and (x, y) not in check_list:
                        check_list.append((x, y))
               
    def show_underlawn(self):
        for col in range(self.cols):
            print(self.mines_map[col])

    def show(self):
        for col in range(self.cols):
            print(self.lawn_map[col])

    def plant_mines(self, col, row):
        deal_list=[] 
        self.cell_neighbor(deal_list, col, row)
        deal_list.append((col, row))
        mines_count=self.mines_num
        while mines_count > 0:
            mine_x, mine_y=randint(0, self.cols - 1), randint(0, self.rows - 1)
            if self.mines_map[mine_x][mine_y] !=9:
                self.mines_map[mine_x][mine_y]=9
                mines_count -=1
            for i in range(len(deal_list)):
                x, y=deal_list[i][0], deal_list[i][1]
                if self.mines_map[x][y] !=0:
                    self.mines_map[x][y]=0
                    mines_count +=1

    def cells_neighbor_mines(self):
        for col in range(self.cols):
            for row in range(self.rows):
                if self.mines_map[col][row]==9:
                    deal_list, stack=[], []
                    self.cell_neighbor(deal_list, col, row)         
                    self.get_cell_neighbor(stack, deal_list)
                    for i in range(len(stack)):
                        x, y=stack[i][0], stack[i][1]
                        if self.mines_map[x][y] !=9:
                            self.mines_map[x][y] +=1

    def cell_neighbor(self, deal_list, col, row):
        for x in range(col - 1, col + 2):
            for y in range(row - 1, row + 2):
                if x !=col or y !=row:
                    deal_list.append((x,y))
 
    def get_cell_neighbor(self, stack, deal_list):
        for i in range(len(deal_list)):
            if deal_list[i][0] not in range(self.cols) or deal_list[i][1] not in range(self.rows):
                continue
            else:
                stack.append(deal_list[i])

    def check_res(self, col , row):
        count=0
        if self.mines_map[col][row]==9:
            print('你踩到雷了!!!')
        for i in range(self.cols):
            for j in range(self.rows):
                if self.lawn_map[i][j] in '#?■':
                    count +=1              
        if count==self.mines_num:
            print('你胜利了!!!')

def play(col, row, mode=0):
    if mode==0:
        lawn.sweep_mine(col, row)
    else:
        lawn.flag(col, row)


col, row, num=eval(input('请输入方阵的大小,地雷的数量【例如:8,8,10。8X8的方阵,10个地雷。】:'))
lawn=Lawn(col, row, num)
while True:
    lawn.show()
    lawn.show_underlawn()
    col,row,mode=eval(input('请输入要铲多少行多少列的地方,并且加一个数。如果最后一个数非0,表示鼠标右键【插旗】:'))
    play(col, row, mode)

小编还准备了一下项目资料 有:视频教学 和源码 有需要关注评论区留言或者

们先和大家说好,本次C语言开发的扫雷游戏是通过Easy X实现的,但是很多和我一样的新手,一开始不知道Easy X是什么,到时源码拿过去写之后,运行报错。Easy X是很多和我一样的新手在学习的时候用到的一个绘图工具,毕竟都不想天天对着一个黑漆漆的控制台,有需要的小伙伴可以关注笔者,进群领取哦~

同样这个扫雷的小游戏是很多和我一样新手学习中所制作的一个小项目,仅当练手。厉害的大佬肯定有用win32或者QT实现的,但是小萌新现在还不会这么高端,仅限新手!

在分享源码之前,我先来给大家展示一下咱们这个项目完成之后是什么样的一个效果:

扫雷游戏效果图

接下来,就正式给大家分享这个项目的源码,每一处都会有较为详细的注释,就不给大家一一说明了,直接上源码了!

项目头文件以及参数设置:


编写函数初始化游戏:①随机生成的个数:


②遍历数组,进行判断:


③加密格子,设置图片:


二、绘制游戏界面


三、处理鼠标消息


四、遍历打开空白格:


五、游戏判定:


六、主函数:

希望本篇源码文章对你有帮助,另外本项目需要图形库插件和图片素材哦!大家做之前可以先去准备好这些东西,图片可以百度,当然可以找笔者直接领取的!

学习C/C++编程知识,想要成为一个更加优秀的程序员,或者你学习C/C++的时候有难度,可以关注+私信小编【C/C++编程】笔者的C语言C++零基础编程学习圈,里面不仅有学习视频和文件源码,还有更多志同道合的朋友,欢迎转行也学习编程的伙伴,和大家一起交流成长会比自己琢磨更快哦!


上一篇:Bootstrap 基础
下一篇:CSS 链接