当前位置: 首页 > news >正文

【C语言】简易版扫雷游戏(数组、函数的练习)

目录

一、分析和设计

1.1、扫雷游戏的功能分析

1.2、文件结构设计(多文件的练习)

1.3、数据结构的设计

二、代码

三、效果展示

三、优化


一、分析和设计

1.1、扫雷游戏的功能分析

        以在线版的扫雷游戏为参考,分析它的功能:扫雷游戏网页版 - Minesweeper

  • 使用控制台的输入控制游戏,控制台的输出打印游戏。
  • 简单版:9 x 9 尺寸的棋盘,10个雷。
  • 排雷规则:
(1)如果不是雷,则显示周围(8个位置)的雷个数。(2)如果是雷,游戏失败。(3)如果把雷之外的所有非雷位置找出,游戏成功。

1.2、文件结构设计(多文件的练习)

test.c:游戏的主逻辑,可用于测试。game.c:实现游戏的各种函数。game.c:实现游戏的各种函数的声明、数据的声明。

1.3、数据结构的设计

       存储棋盘形状的数据,使用二维数组再好不过了,易理解易操作。需要存储的数据:① 哪些位置是雷(用0和1表示)。② 打印在控制台的扫雷画面(未排雷的位置显示 ' * ' ,已排雷且非雷的位置显示周围雷的个数)。

        因为有字符' * ',所以存周围雷个数的数组的元素用char类型;虽然存雷位置的数组都是数字0和1,但是统一起见,元素的类型都定义为char,这样写代码的时候不用区分char还是int,不宜弄错。

        虽然说这两种数据可以放到一起,比如非雷位置放9,雷位置放10;排雷时非雷位置(9)可改成周围雷个数(≤8);打印时,等于9或10的位置打印' * ',小于等于8的位置打印周围雷个数。但是这样在打印时会多一些判断,读代码的人理解起来也没有分成两个数组容易。

        数据结构如下:

char mine[ROWS][COLS] = {0};
char show[ROWS][COLS] = {0};

        排雷时,如果找到非雷,则显示周围雷的个数。当这个非雷是二维数组的边缘部分时,统计周围雷的个数会发生越界:

        为了解决这个问题,需要把数组的大小增大一圈(两个数组统一增大,免得写代码判断的时候范围不统一,记混了):

二、代码

        game.c

#define _CRT_SECURE_NO_WARNINGS 1;
#include "game.h"void print_menu(){printf("******************************\n");printf("*********    1.play   ********\n");printf("*********    2.exit   ********\n");printf("******************************\n");printf("请选择:");
}void init_mine(char mine[][EASY_COLS]) {for (int i = 0; i < EASY_ROWS; i++)for (int j = 0; j < EASY_COLS; j++)mine[i][j] = '0';
}void init_show(char show[][EASY_COLS]) {for (int i = 0; i < EASY_ROWS; i++)for (int j = 0; j < EASY_COLS; j++)show[i][j] = '*';
}void set_mine(char mine[][EASY_COLS]) {int r = 0;int c = 0;for (int i = 0; i < EASY_COUNT; i++) {r = rand() % EASY_ROW + 1; // 雷要在棋盘里边,不能在增加的一圈里c = rand() % EASY_COL + 1;if ('0' == mine[r][c]) // 这个位置不是雷,放雷mine[r][c] = '1';elsei--; // 这个位置已经是雷了,这轮不算}
}void print_mine(char mine[][EASY_COLS]) {for (int i = 0; i <= EASY_ROW; i++)printf("%d ", i);printf("\n");for (int i = 1; i <= EASY_ROW; i++) {printf("%d ", i);for (int j = 1; j <= EASY_COL; j++)printf("%c ", mine[i][j]);printf("\n");}
}void print_show(char show[][EASY_COLS]) {for (int i = 0; i <= EASY_ROW; i++)printf("%d ", i);printf("\n");for (int i = 1; i <= EASY_ROW; i++) {printf("%d ", i);for (int j = 1; j <= EASY_COL; j++)printf("%c ", show[i][j]);printf("\n");}printf("输入坐标:");
}void find_mine(char mine[][EASY_COLS], char show[][EASY_COLS]) {int x = 0; // 输入的坐标int y = 0;int count = 0; // 已找到的的非雷while (count != EASY_ROW * EASY_COL - EASY_COUNT) { // 没有找到所有非雷,游戏就继续print_show(show); // 打印棋盘scanf("%d %d", &x, &y); // 输入坐标system("cls");if (mine[x][y] == '1') {printf("炸弹!游戏失败!\n");break;}else if (mine[x][y] == '0') {char c = count_mine(mine, x, y); // 计算周围雷的个数show[x][y] = c;count++; //找到一个非雷}}if (count == EASY_ROW * EASY_COL - EASY_COUNT)printf("排雷成功!\n");
}char count_mine(char mine[][EASY_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' + '0';
}

        game.h

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include<Windows.h>#define EASY_ROW 9
#define EASY_COL 9#define EASY_COUNT 10#define EASY_ROWS EASY_ROW+2
#define EASY_COLS EASY_COL+2void print_menu();
void init_mine(char mine[][EASY_COLS]);
void init_show(char show[][EASY_COLS]);
void set_mine(char mine[][EASY_COLS]);
void print_mine(char mine[][EASY_COLS]);
void print_show(char show[][EASY_COLS]);
void find_mine(char mine[][EASY_COLS], char show[][EASY_COLS]);
char count_mine(char mine[][EASY_COLS], int x, int y);

        test.c

#define _CRT_SECURE_NO_WARNINGS 1;
#include "game.h"void game() {char mine[EASY_ROWS][EASY_COLS]; // 存储生成的雷char show[EASY_ROWS][EASY_COLS]; // 存储排雷后雷的个数init_mine(mine); // 初始化,全放'0'init_show(show); // 初始化棋盘,全为'*'set_mine(mine); // 随机放雷find_mine(mine, show);// 扫雷
}int main() {int choose = 0;srand((unsigned int)time(NULL)); // 设置随机种子do{print_menu(); // 打印主菜单scanf("%d", &choose); // 输入选择system("cls"); // 清屏switch (choose) {case 1: // 开始游戏game();break;case 2: // 退出break;default:printf("输入错误,请重新输入。\n");}} while (choose != 2);return 0;
}

三、效果展示

三、优化

        代码还有很多需要优化的地方,比如:

  • 如果排查的位置不是雷,它周围也没雷,周围可以进行展开。
  • 可以选择游戏难度:简单(9x9,10个雷)、中等(16x16,40个雷)、困难(16x30,99个雷)、自定义。
  • 可以标记雷。
  • 游戏中,可以显示时间。

(1)增加展开、标记雷功能

        代码如下:

        game.h:

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include<Windows.h>#define EASY_ROW 9
#define EASY_COL 9#define EASY_COUNT 10#define EASY_ROWS EASY_ROW+2
#define EASY_COLS EASY_COL+2// 打印字体颜色
#define NONE "\033[m"
#define BLUE "\033[0;32;34m"
#define RED "\033[0;32;31m"void print_menu();
void init_mine(char mine[][EASY_COLS]);
void init_show(char show[][EASY_COLS]);
void set_mine(char mine[][EASY_COLS]);
void print_mine(char mine[][EASY_COLS]);
void print_show(char show[][EASY_COLS]);
void find_mine(char mine[][EASY_COLS], char show[][EASY_COLS], char flag[][EASY_COLS]);
char count_mine(char mine[][EASY_COLS], int x, int y);
void find_other_mine(char mine[][EASY_COLS], char show[][EASY_COLS], int x, int y, int* cnt);
void mark_mine(char flag[][EASY_COLS]);

        game.c:

#define _CRT_SECURE_NO_WARNINGS 1;
#include "game.h"void print_menu(){ // 打印主菜单printf("******************************\n");printf("*********    1.play   ********\n");printf("*********    2.exit   ********\n");printf("******************************\n");printf("请选择:");
}void init_mine(char mine[][EASY_COLS]) { // 初始化雷信息for (int i = 0; i < EASY_ROWS; i++)for (int j = 0; j < EASY_COLS; j++)mine[i][j] = '0';
}void init_show(char show[][EASY_COLS]) { //初始化棋盘for (int i = 0; i < EASY_ROWS; i++)for (int j = 0; j < EASY_COLS; j++)show[i][j] = '*';
}void set_mine(char mine[][EASY_COLS]) { // 随即放置雷int r = 0;int c = 0;for (int i = 0; i < EASY_COUNT; i++) {r = rand() % EASY_ROW + 1; // 雷要在棋盘里边,不能在增加的一圈里c = rand() % EASY_COL + 1;if ('0' == mine[r][c]) // 这个位置不是雷,放雷mine[r][c] = '1';elsei--; // 这个位置已经是雷了,这轮不算}
}void print_mine(char mine[][EASY_COLS]) { // 打印雷信息for (int i = 0; i <= EASY_ROW; i++)printf(BLUE"%d "NONE, i);printf("\n");for (int i = 1; i <= EASY_ROW; i++) {printf(BLUE"%d "NONE, i);for (int j = 1; j <= EASY_COL; j++)printf("%c ", mine[i][j]);printf("\n");}
}void print_show(char show[][EASY_COLS], char flag[][EASY_COLS]) { // 打印棋盘for (int i = 0; i <= EASY_ROW; i++)printf(BLUE"%d "NONE, i);printf("\n");for (int i = 1; i <= EASY_ROW; i++) {printf(BLUE"%d "NONE, i);for (int j = 1; j <= EASY_COL; j++) {if('1' == flag[i][j])printf(RED"%c "NONE, show[i][j]);elseprintf("%c ", show[i][j]);}printf("\n");}printf("输入坐标(输入0 0标记雷):");
}void find_mine(char mine[][EASY_COLS], char show[][EASY_COLS], char flag[][EASY_COLS]) { // 排雷int x; // 输入的坐标int y;int count = 0; // 已找到的的非雷while (count != EASY_ROW * EASY_COL - EASY_COUNT) { // 没有找到所有非雷,游戏就继续x = 0;y = 0;while (x == 0 && y == 0) {print_show(show, flag); // 打印棋盘scanf("%d %d", &x, &y); // 输入坐标if (0 == x && 0 == y) // 标记雷mark_mine(flag);system("cls");}if (mine[x][y] == '1') { // 碰到炸弹了printf("炸弹!游戏失败!\n");break;}else if (mine[x][y] == '0' && show[x][y] == '*') { // (x,y)不是炸弹,并且没有被排查过find_other_mine(mine, show, x, y, &count);}}if (count == EASY_ROW * EASY_COL - EASY_COUNT) // 所有非雷点找齐了printf("排雷成功!\n");
}char count_mine(char mine[][EASY_COLS], int x, int y) { // 计算非雷点(x,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' + '0';
}void find_other_mine(char mine[][EASY_COLS], char show[][EASY_COLS], int x, int y, int* cnt) { // 递归,排查的位置不是雷,周围也没有雷,可以展开char c = count_mine(mine, x, y); // 计算周围雷的个数if(c == '0')show[x][y] = ' ';elseshow[x][y] = c;(*cnt)++; //找到一个非雷if (show[x][y] != ' ') { // 周围有雷,就不需要扩展了return;}for (int i = -1; i <= 1; i++) {for (int j = -1; j <= 1; j++) {int xt = x + i;int yt = y + j;if (show[xt][yt] != '*' || mine[xt][yt] == '1') // 已经排查过的,就不需要重复扩展了;本身是炸弹的,也不要扩展continue;if(xt >= 1 && yt >= 1 && xt <= EASY_ROW && yt <= EASY_COL) // 拓展的坐标要在棋盘范围内find_other_mine(mine, show, xt, yt, cnt);}}
}void mark_mine(char flag[][EASY_COLS]) {int x;int y;printf("输入雷的坐标:");scanf("%d %d", &x, &y); // 输入坐标flag[x][y] = '1';
}

        test.c:

#define _CRT_SECURE_NO_WARNINGS 1;
#include "game.h"void game() {char mine[EASY_ROWS][EASY_COLS]; // 存储生成的雷char show[EASY_ROWS][EASY_COLS]; // 存储排雷后雷的个数char flag[EASY_ROWS][EASY_COLS]; // 屏幕上,标记的雷init_mine(mine); // 初始化,全放'0'init_show(show); // 初始化棋盘,全为'*'init_mine(flag); // 初始化全'0','0' 表示未标记,‘1’表示已标记set_mine(mine); // 随机放雷find_mine(mine, show, flag);// 扫雷
}int main() {int choose = 0;srand((unsigned int)time(NULL)); // 设置随机种子do{print_menu(); // 打印主菜单scanf("%d", &choose); // 输入选择system("cls"); // 清屏switch (choose) {case 1: // 开始游戏game();break;case 2: // 退出break;default:printf("输入错误,请重新输入。\n");}} while (choose != 2);return 0;
}

        效果展示:

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 05-ArcGIS For JavaScript-RenderNode后处理效果
  • [012-1].第12节:Mysql的配置文件的使用
  • ubuntu安装workon
  • MyBatis缓存
  • DataKit之OpenGauss数据迁移工具
  • 大数据技术基础编程、实验和案例----大数据课程综合实验案例
  • SpringBoot如何实现简单的跨域配置
  • (七)Appdesigner-初步入门及常用组件的使用方法说明
  • 程序员修炼之路
  • vue3 动态加载组件
  • web文件上传与下载
  • 【附安装包】CentOS7(Linux)详细安装教程(手把手图文详解版)
  • Selenium 无法定位元素的几种解决方案
  • masscan 端口扫描——(Golang 简单使用总结)
  • playbooks 分布式部署 LNMP
  • JS中 map, filter, some, every, forEach, for in, for of 用法总结
  • 002-读书笔记-JavaScript高级程序设计 在HTML中使用JavaScript
  • 5、React组件事件详解
  • Angular Elements 及其运作原理
  • Flex布局到底解决了什么问题
  • Java 内存分配及垃圾回收机制初探
  • mongodb--安装和初步使用教程
  • 工作手记之html2canvas使用概述
  • 力扣(LeetCode)357
  • 区块链将重新定义世界
  • media数据库操作,可以进行增删改查,实现回收站,隐私照片功能 SharedPreferences存储地址:
  • PostgreSQL之连接数修改
  • ​学习一下,什么是预包装食品?​
  • # 消息中间件 RocketMQ 高级功能和源码分析(七)
  • $().each和$.each的区别
  • (1)bark-ml
  • (2024)docker-compose实战 (8)部署LAMP项目(最终版)
  • (ctrl.obj) : error LNK2038: 检测到“RuntimeLibrary”的不匹配项: 值“MDd_DynamicDebug”不匹配值“
  • (javascript)再说document.body.scrollTop的使用问题
  • (Matlab)使用竞争神经网络实现数据聚类
  • (佳作)两轮平衡小车(原理图、PCB、程序源码、BOM等)
  • (一)C语言之入门:使用Visual Studio Community 2022运行hello world
  • (一)搭建springboot+vue前后端分离项目--前端vue搭建
  • .bat批处理(八):各种形式的变量%0、%i、%%i、var、%var%、!var!的含义和区别
  • .NET C# 使用 SetWindowsHookEx 监听鼠标或键盘消息以及此方法的坑
  • .NET Core中的去虚
  • .NET Core中如何集成RabbitMQ
  • .net web项目 调用webService
  • .NET 表达式计算:Expression Evaluator
  • .NET 常见的偏门问题
  • .NET(C#、VB)APP开发——Smobiler平台控件介绍:Bluetooth组件
  • .net与java建立WebService再互相调用
  • @EnableWebSecurity 注解的用途及适用场景
  • @EventListener注解使用说明
  • @ModelAttribute使用详解
  • @vueup/vue-quill使用quill-better-table报moduleClass is not a constructor
  • [ 第一章] JavaScript 简史
  • [1159]adb判断手机屏幕状态并点亮屏幕
  • [ASP]青辰网络考试管理系统NES X3.5
  • [BJDCTF2020]EzPHP1