文章目录

  • 简介
  • 主函数部分
    • 菜单函数
    • 随机数函数
    • 游戏函数
    • 初始化函数
    • 展示棋盘函数
  • 头文件
  • 游戏函数及其实现
    • 初始化函数实现
    • 展示棋盘函数实现
    • 计算非雷板块数字函数实现
    • 设雷函数实现
    • 操作(点击)函数实现
    • 展开函数实现
    • 自动标记地雷函数实现
    • 手动标记地雷函数实现
    • 判断胜利函数实现
  • 游戏实际体验画面
  • 结束语

简介

扫雷可以说是一款我们从小玩到大的游戏,他的游戏规则也很简单。

在一块16×30的网格中,点开所有没有雷的网格(雷数默认为99颗)。(其中简单难度为9×9的方格,共10颗雷;中级难度为16×16的方格,共40颗雷) 所有操作依靠鼠标即可完成。左键点开网格,若该网格为雷则游戏结束;若该网格周边八个网格中有雷,则显示雷数,若无雷则直接开启周边网格。可通过鼠标右键标记网格。点击一次右键插旗,表明你确定该网格有雷,在改变插旗状态前该网格无法被点开;在旗上右键将插旗状态改变为问号标记(仅具有标记作用,与普通网格无异);在问号上右键回复初始状态。左右键同时点击,为快速点开周围网格。若插旗数不等于雷数则无法点开;若插旗数等于雷数则直接点开所有未插旗网格(旗插错了游戏结束)。

由于c语言本身的局限性,而且只能够用键盘输入数据以确定要进行操作的坐标,因此在本篇文章中,我们实现了点开数字为零的板块时自动展开的功能;标记雷的功能;以及当非雷板块周围已经确定的雷的数量与自身数字相等时自动标雷等功能,以下为实现代码。

主函数部分

菜单函数

主函数前包括两个菜单函数,第一个为游戏开始时的菜单,决定进行游戏or退出游戏,第二个为决定是否进行标记的菜单,因为实现了标记功能,在进行之前先进行询问。

#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
void menu1(void)
{printf("\n**************扫雷*******************\n");printf("********      1 . play       ********\n");printf("********      0 . exit       ********\n");printf("*************************************\n");
}
void menu2(void)
{printf("********      1 . yes       *********\n");printf("********      0 . no       **********\n");
}

随机数函数

主函数中我们先调用srand函数进行随机设雷,为了使游戏能够重复体验,采用do-while语句进行设计,在一局游戏结束后可选择继续或退出游戏。

int main(void)
{srand((unsigned)time(NULL));int input = 0;do {menu1();scanf("%d",&input);switch (input){case 1:game();break;case 0:printf("退出游戏\n");break;default:printf("输入有误,请重新输入\n");break;}} while (input);return 0;
}

游戏函数

然后便是game函数的设计。首先要创建两个二维数组,其中一个用来设置地雷,另一个则用来展现给玩家。这里我们考虑到在计算非雷板块的数字时需要遍历其周围的八个区域,而在棋盘边上的时候这样计算会出现越界访问的问题,因此我们将实际的数组比游戏中的棋盘的长宽多出来两行,也就是ROWS = ROW + 2,COLS = COL + 2,在后续的头文件中也会体现出来。

void game(void)
{char mineboard[ROWS][COLS] = { 0 };char playerboard[ROWS][COLS] = { 0 };initialization(mineboard, ROWS, COLS, '0');initialization(playerboard, ROWS, COLS, '*');setmine(mineboard, ROWS, COLS);displayboard(playerboard, ROWS, COLS);while (1){if ((move(mineboard, playerboard, ROWS, COLS)) == 0){break;}system("cls");mark_mine(playerboard, ROWS, COLS);displayboard(playerboard, ROWS, COLS);if ((is_win(mineboard, playerboard, ROWS, COLS)) == 0){break;}system("cls");if_mark(playerboard, ROWS, COLS);displayboard(playerboard, ROWS, COLS);}}

初始化函数

随后是两个初始化函数,可以看到初始化函数在传参时,除了数组和行列外,还传了一个字符,这是为了能让两个数组共用一个函数的办法,传参时通过最后一个参数来区别两个数组,省掉了分别初始化的麻烦。

 initialization(mineboard, ROWS, COLS, '0');initialization(playerboard, ROWS, COLS, '*');

展示棋盘函数

然后对于地雷棋盘进行初始化并进行棋盘展示,然后便进入了操作循环(while(1))。分别为操作(也就是鼠标的点击)函数->清屏->自动标记雷区函数->展示棋盘函数->判断是否游戏结束函数->清屏->判断是否进行手动标记函数->展示棋盘函数。

displayboard(playerboard, ROWS, COLS);while (1){if ((move(mineboard, playerboard, ROWS, COLS)) == 0){break;}system("cls");mark_mine(playerboard, ROWS, COLS);displayboard(playerboard, ROWS, COLS);if ((is_win(mineboard, playerboard, ROWS, COLS)) == 0){break;}system("cls");if_mark(playerboard, ROWS, COLS);displayboard(playerboard, ROWS, COLS);}}

到此为止,该游戏的基本逻辑就已经交代清楚,下面介绍头文件以及具体功能的实现。

头文件

#pragma once#include<stdio.h>
#include<time.h>
#include<stdlib.h>
#include<windows.h>#define ROW 10
#define COL 10#define ROWS ROW+2
#define COLS COL+2#define NUMBER_OF_MINES 6void initialization(char board[ROWS][COLS],int row,int col,char sign);void displayboard(char board[ROWS][COLS],int row,int col);void setmine(char board[ROWS][COLS], int row, int col);int move(char mineboard[ROWS][COLS], char playerboard[ROWS][COLS], int row, int col);int is_win(char mineboard[ROWS][COLS], char playerboard[ROWS][COLS], int row, int col);void unfold_zero_area(char mineboard[ROWS][COLS], char playerboard[ROWS][COLS], int row, int col);char get_mine_count(char board[ROWS][COLS], int row, int col);void mark_mine(char board[ROWS][COLS], int row, int col);void if_mark(char board[ROWS][COLS], int row, int col);

可以看出,在头文件中我们主要设置游戏难度,即棋盘的大小以及雷数,以及所有的函数声明,这里面包括在文章前面未出现的函数,如unfold_zero_area函数,也就是实现点0自动展开功能的函数;get_mine_count函数,计算非雷区域数字的函数。

下面介绍各函数的实现细节。

游戏函数及其实现

初始化函数实现

名曰初始化,其实就是遍历数组并赋初值。前面有讲,最后一个字符类型参数决定了用什么符号进行初始化,其中地雷棋盘用‘0’来初始化,玩家棋盘用‘*’来初始化。

void initialization(char board[ROWS][COLS], int row, int col, char sign)
{int i = 0;for (i = 0; i < ROWS; i++){int j = 0;for (j = 0; j < COLS; j++){if (sign == '*')board[i][j] = '*';elseboard[i][j] = '0';}}
}

展示棋盘函数实现

遍历并打印。为了方便玩家使其快速找到坐标,我们在第一行上方&第一列左边加入了坐标显示。如果棋盘变大的话,可以在打印时在%c中间加入数字来使坐标数与棋盘对齐。

void displayboard(char board[ROWS][COLS], int row, int col)
{int i = 0;for (int m = 0; m <= ROW; m++){printf(" %d",m);}printf("\n");for (i = 1; i < ROWS - 1; i++){int j = 0;printf("%2d ", i);for (j = 1; j < COLS - 1; j++){printf("%c ",board[i][j]);}printf("\n");}
}

计算非雷板块数字函数实现

在地雷棋盘上的某个‘0’区算出其周围‘1’的个数,由于字符数组中存储的是字符数字,比如字符‘0’的ASCII码为48,因此在返回时我们要减去8个字符‘0’以保证返回值是数字。

char get_mine_count(char board[ROWS][COLS], int row, int col)
{return (  board[row - 1][col]+ board[row - 1][col - 1]+ board[row][col - 1]+ board[row + 1][col - 1]+ board[row + 1][col]+ board[row + 1][col + 1]+ board[row][col + 1]+ board[row - 1][col + 1] - 8 * '0');
}

设雷函数实现

先对预设雷数进行拷贝,借助随机数函数在未设有雷的地方进行埋雷,然后预设雷数减一,循环此操作直至预设雷数为零。

void setmine(char board[ROWS][COLS], int row, int col)
{int count = NUMBER_OF_MINES;do{int i = rand() % ROW + 1;int j = rand() % COL + 1;if (board[i][j] == '0'){board[i][j] = '1';count--;}} while (count);
}

操作(点击)函数实现

首先输入坐标,然后进行两次判断:①坐标是否越界 ②坐标是否已经排查。在不符合以上两个条件之后判断所点击的坐标产生的影响。若点到雷区,则游戏结束、展示棋盘并返回0。若未踩雷,则进入展开函数。我们知道如果点开的板块对应数字不是0的话是不会展开的,所以这里笔者是将两种情况都加入了展开函数,以方便书写。


int move(char mineboard[ROWS][COLS], char playerboard[ROWS][COLS], int row, int col)
{int i = 0;int j = 0;while (1){printf("请输入坐标:>\n");scanf("%d %d", &i, &j);if ((i < 1) || (j < 1) || (i > row - 1) || (j > col - 1)){printf("输入有误,请重新输入:>\n");continue;}else if (playerboard[i][j] != '*'){printf("此处已排查,请更换坐标\n");continue;}else{if (mineboard[i][j] == '1'){printf("很遗憾,游戏结束(>_<)\n");displayboard(mineboard, ROWS, COLS);printf("所踩雷的坐标为:%d %d",i,j);return 0;}else{unfold_zero_area(mineboard, playerboard, i, j);//displayboard(playerboard, ROWS, COLS);return 1;}}}
}

展开函数实现

由于此函数在点0时要进行成片展开,那就需要借助递归来完成,因此一上来我们就对于递归的范围进行了界定,以保证能够顺利跳出递归,也就是将范围控制在向玩家展示的棋盘之内。而后算出传进来的板块周围雷数,如果不为零,标出数字后函数结束;如果等于零且不等于空格(不等于空格是为了防止死递归,因为不进行判断的话,就会在相邻的空白之间无限递归,最终导致栈溢出),我们就将其赋值为空格,并且遍历其周围的八个格子每个格子都再调用一次展开函数,即可实现空白展开功能。(这里我们对于为0的区域进行赋空格处理,就像我们玩的扫雷一样,为0的区域是空白的)

void unfold_zero_area(char mineboard[ROWS][COLS], char playerboard[ROWS][COLS], int row, int col)
{if (row > 0 && row <= ROW && col > 0 && col <= COL){int count = get_mine_count(mineboard, row, col);if (count != 0){playerboard[row][col] = '0' + count;}else if (playerboard[row][col] != ' '){playerboard[row][col] = ' ';int i = 0;for (int i = row - 1; i <= row + 1; i++){int j = 0;for (j = col - 1; j <= col + 1; j++){unfold_zero_area(mineboard, playerboard, i, j);}}}else{;//结束递归}}
}

自动标记地雷函数实现

这里我们用’!‘来表示已经必然是地雷的区域。然后对于传进来的板块的周围进行雷数判断,如果未点开的板块(’*‘)加上已经必然是雷的板块(’!‘)的数量已经与传进来的板块上标记出的数字一致的话,则将全部未点开的板块设置为’!'。其实这里的代码存在一定程度上冗余,存在一定的改定空间,欢迎大家提出自己的理解,笔者会在第一时间进行改进。

void mark_mine(char board[ROWS][COLS], int row, int col)
{int i = 0;for (i = 1; i < ROW; i++){int j = 0;for (j = 1; j < COL; j++){if (board[i][j] >= '1' && board[i][j] <= '8'){int count = 0;if (board[i - 1][j] == '*' || board[i - 1][j] == '!'){count++;}if (board[i - 1][j - 1] == '*' || board[i - 1][j - 1] == '!'){count++;}if (board[i][j - 1] == '*' || board[i][j - 1] == '!'){count++;}if (board[i + 1][j - 1] == '*' || board[i + 1][j - 1] == '!'){count++;}if (board[i + 1][j] == '*' || board[i + 1][j] == '!'){count++;}if (board[i + 1][j + 1] == '*' || board[i + 1][j + 1] == '!'){count++;}if (board[i][j + 1] == '*' || board[i][j + 1] == '!'){count++;}if (board[i - 1][j + 1] == '*' || board[i - 1][j + 1] == '!'){count++;}if (count == board[i][j] - '0'){if (board[i - 1][j] == '*'){board[i - 1][j] = '!';}if (board[i - 1][j - 1] == '*'){board[i - 1][j - 1] = '!';}if (board[i][j - 1] == '*'){board[i][j - 1] = '!';}if (board[i + 1][j - 1] == '*'){board[i + 1][j - 1] = '!';}if (board[i + 1][j] == '*'){board[i + 1][j] = '!';}if (board[i + 1][j + 1] == '*'){board[i + 1][j + 1] = '!';}if (board[i][j + 1] == '*'){board[i][j + 1] = '!';}if (board[i - 1][j + 1] == '*'){board[i - 1][j + 1] = '!';}}}}}}

手动标记地雷函数实现

由于自动标雷函数的局限性,它并不能标记出所有的已知雷区。就比如我们所知道的“121”、“1221”等规律仍然需要我们来自行标记和点开,这是我们就可以用这个函数对已知雷进行标记(用’!'进行标记),以加快排雷效率,而且这里我们也利用的do-while函数,可在点开板块的间隙进行多次标记。

void if_mark(char board[ROWS][COLS], int row, int col)
{int input = 0;do {displayboard(board, ROWS, COLS);printf("是否进行标记?\n");menu2();scanf("%d", &input);getchar();switch (input){case 1:{int x = 0;int y = 0;printf("请输入坐标:>\n");scanf("%d %d", &x, &y);if (board[x][y] != '*'){printf("输入坐标有误,请重新输入:>");break;}else{board[x][y] = '!';}break;}case 0:break;default:printf("输入有误,请重新输入\n");break;}} while (input);
}

判断胜利函数实现

这里我们利用雷数进行判断,如果没点开的板块(‘*’)和已经确定是雷的板块(‘!’)数量已经等于预设雷数,即非雷区域已经全部打开,则游戏胜利并返回0;否则返回1,游戏继续。


int is_win(char mineboard[ROWS][COLS], char playerboard[ROWS][COLS], int row, int col)
{int i = 0;int count = 0;for (i = 1; i < row - 1; i++){int j = 0;for (j = 1; j < col - 1; j++){if (playerboard[i][j] == '*' || playerboard[i][j] == '!'){count++;}}}if (count == NUMBER_OF_MINES){printf("游戏胜利\\^o^/\n");displayboard(mineboard, ROWS, COLS);return 0;}else{return 1;}
}

游戏实际体验画面

结束语

以上便是笔者所完成的扫雷游戏,尽管实现了部分拓展功能,但仍有很多不足之处,希望能看到这篇文章的读者能够留下宝贵的意见和建议,使其能够不断地完善,笔者感激不尽。

【C语言】扫雷游戏(包含递归展开、手自动标记功能)相关推荐

  1. 【C语言】童年的扫雷游戏(递归展开)你也可以做出来,将他发给你的网瘾室友玩吧 ——含详细注释及解析

    目录 一.预览 1.测试文件(text.c) 2.函数声明(game.h) 3.函数实现(game,c) 二.思路分析 1.打印菜单以及初始化棋盘 2.布置雷 * 3.排查雷(递归展开) 三.导出为 ...

  2. C语言 扫雷(含递归展开)

    目录 前言 一.设计思路 基本的构思方向 准备基本框架 二.函数功能设置 菜单界面 主函数 初始化 显示棋盘 设置雷 计算周围雷数 排雷 总体game函数 后续优化 1.标记雷 2.递归展开 3.防止 ...

  3. 【C语言扫雷游戏详解及如何实现递归展开】

    提示:全文已采用物理深色模式,请放心观看 文章目录 一.整体框架 1.设计思路 2.实现细节 二.主要函数 1.打印棋盘 2.递归展开 三.其他函数 一.整体框架 1.设计思路 基础难度的扫雷游戏含有 ...

  4. c语言扫雷源代码简单版,C语言扫雷游戏源代码

    C语言扫雷游戏源代码 /* 模拟扫雷游戏 */ #include #include #include #include #include #include #include union REGS re ...

  5. c语言扫雷游戏计时功能_C语言实现扫雷游戏(可以自动展开)

    前言 本篇博客主要介绍如何使用C语言实现扫雷游戏. 一.游戏规则 在一张ROW行COL列的地图上存在MINE_COUNT个地雷.玩家输入坐标翻开格子,若没有踩雷,则计算此格子周围8个格子的地雷总数,并 ...

  6. C语言实现扫雷游戏(可展开)

    本人初学者一枚,反复尝试写扫雷游戏,终于勉强成功,就不做过多讲解了,直接上代码,希望对同为初学者的你起到借鉴作用. 头文件内容,我的头文件名"saolei.h ". #includ ...

  7. 探秘C语言扫雷游戏实现技巧

    本篇博客会讲解,如何使用C语言实现扫雷小游戏. 0.思路及准备工作 使用2个二维数组mine和show,分别来存储雷的位置信息和排查出来的雷的信息,前者隐藏,后者展示给玩家.假设盘面大小是9×9,这2 ...

  8. C语言扫雷游戏(简易版)

    前言 经过学习数组.函数.循环语句.选择语句等C语言的一些基础知识后,我想借助编写扫雷小游戏来对所学知识进行一个巩固.游戏只会实现一些基本的功能,展开.标记雷.取消雷等不实现(还不会). 1.游戏编写 ...

  9. c语言扫雷游戏代码_C语言游戏详解---扫雷游戏

    扫雷游戏大家应该都不陌生,一个扫雷游戏要满足的基本要求是: 1. 第一次扫的位置不能是雷 2. 每展开一个位置要显示该位置周围雷的个数 3. 若该位置周围没雷,要把周围展开 该游戏的界面是10X10的 ...

  10. c语言扫雷游戏构成原理,扫雷游戏的C语言实现

    在学习C语言初期,我们可以找一些平常玩的游戏进行简单的C语言实现.今天就和大家分享一下关于windows中扫雷游戏的实现. 在正式写代码前,简单说一下对这个游戏的分析: 1.先提示的应该是一个简单的m ...

最新文章

  1. 简单的文本框输入实时计数
  2. 小白学python系列-(4)list
  3. VScode Python插件
  4. Webstorm 10.0.4 配置
  5. mysql 表与表之间的条件比对_Mysql分库分表面试题(mysql高可用方案解析)
  6. PHP的$_SERVER['HTTP_HOST']获取服务器地址功能详解
  7. gpio库_斗牛犬:出奇的快速GPIO库
  8. Cocoa教学:Windows OOP与Cocoa MVC之对比
  9. 博士论文答辩||基于深度强化学习的复杂作业车间调度问题研究
  10. 挑筋(挑治)疗法——针挑治疗痔疮
  11. Python制作个税计算器
  12. java设置单元格为文本_怎样设置单元格属性为文本格式?
  13. android项目epub格式电子书开源开发
  14. 每平每屋模型组件采集策略研究
  15. 数字图像处理(MATLAB版)学习笔记(1)——第1章 绪言
  16. 解决“由于应用程序配置不正确,应用程序未能启动。重新安装应用程序可能会纠正这个问题”
  17. 约瑟夫问题-输出最后的编号
  18. html页面插入百度谷歌地图的方法
  19. 游戏服务器列表为空,游戏服务器列表为空
  20. AVAudioplayer时error解决 创建失败

热门文章

  1. Chrome Edge与Safari书签同步
  2. html+css实现百度首页(简单版)
  3. 电脑键盘上的快捷建大全
  4. 现在的国产深度deepin操作系统不赖,推荐试用
  5. java高德地图urlapi_高德地图POI采集(URL-API)
  6. 用简单易懂的话语来快速入门windows缓冲区溢出
  7. 在matlab中讲矩阵一次性检验,层次分析法原理和matlab代码实现
  8. 创意爆破效果PS动作
  9. Ticket Lock的Relaxed Atomics优化
  10. Win32反汇编(一) 初步探索Win32反汇编 与 Ollydbg的简单使用