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

[复制链接]
发表于 2024-8-2 12:22:08 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
目次

一、分析和设计
1.1、扫雷游戏的功能分析
1.2、文件结构设计(多文件的练习)
1.3、数据结构的设计
二、代码
三、效果展示
三、优化


一、分析和设计

1.1、扫雷游戏的功能分析

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


  • 利用控制台的输入控制游戏,控制台的输出打印游戏。
  • 简单版:9 x 9 尺寸的棋盘,10个雷。


  • 排雷规则:
  1. (1)如果不是雷,则显示周围(8个位置)的雷个数。
  2. (2)如果是雷,游戏失败。
  3. (3)如果把雷之外的所有非雷位置找出,游戏成功。
复制代码
1.2、文件结构设计(多文件的练习)

  1. test.c:游戏的主逻辑,可用于测试。
  2. game.c:实现游戏的各种函数。
  3. game.c:实现游戏的各种函数的声明、数据的声明。
复制代码
1.3、数据结构的设计

       存储棋盘形状的数据,利用二维数组再好不过了,易理解易利用。需要存储的数据:① 哪些位置是雷(用0和1表示)。② 打印在控制台的扫雷画面(未排雷的位置显示 ' * ' ,已排雷且非雷的位置显示四周雷的个数)。
        由于有字符' * ',以是存四周雷个数的数组的元素用char类型;固然存雷位置的数组都是数字0和1,但是统一起见,元素的类型都界说为char,如许写代码的时候不消区分char照旧int,不宜弄错。
        固然说这两种数据可以放到一起,比如非雷位置放9,雷位置放10;排雷时非雷位置(9)可改成四周雷个数(≤8);打印时,即是9或10的位置打印' * ',小于即是8的位置打印四周雷个数。但是如许在打印时会多一些判定,读代码的人理解起来也没有分成两个数组容易。
        数据结构如下:
  1. char mine[ROWS][COLS] = {0};
  2. char show[ROWS][COLS] = {0};
复制代码
        排雷时,如果找到非雷,则显示四周雷的个数。当这个非雷是二维数组的边缘部门时,统计四周雷的个数会发生越界:

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

二、代码

        game.c
  1. #define _CRT_SECURE_NO_WARNINGS 1;
  2. #include "game.h"
  3. void print_menu(){
  4.         printf("******************************\n");
  5.         printf("*********    1.play   ********\n");
  6.         printf("*********    2.exit   ********\n");
  7.         printf("******************************\n");
  8.         printf("请选择:");
  9. }
  10. void init_mine(char mine[][EASY_COLS]) {
  11.         for (int i = 0; i < EASY_ROWS; i++)
  12.                 for (int j = 0; j < EASY_COLS; j++)
  13.                         mine[i][j] = '0';
  14. }
  15. void init_show(char show[][EASY_COLS]) {
  16.         for (int i = 0; i < EASY_ROWS; i++)
  17.                 for (int j = 0; j < EASY_COLS; j++)
  18.                         show[i][j] = '*';
  19. }
  20. void set_mine(char mine[][EASY_COLS]) {
  21.         int r = 0;
  22.         int c = 0;
  23.         for (int i = 0; i < EASY_COUNT; i++) {
  24.                 r = rand() % EASY_ROW + 1; // 雷要在棋盘里边,不能在增加的一圈里
  25.                 c = rand() % EASY_COL + 1;
  26.                 if ('0' == mine[r][c]) // 这个位置不是雷,放雷
  27.                         mine[r][c] = '1';
  28.                 else
  29.                         i--; // 这个位置已经是雷了,这轮不算
  30.         }
  31. }
  32. void print_mine(char mine[][EASY_COLS]) {
  33.         for (int i = 0; i <= EASY_ROW; i++)
  34.                 printf("%d ", i);
  35.         printf("\n");
  36.         for (int i = 1; i <= EASY_ROW; i++) {
  37.                 printf("%d ", i);
  38.                 for (int j = 1; j <= EASY_COL; j++)
  39.                         printf("%c ", mine[i][j]);
  40.                 printf("\n");
  41.         }
  42. }
  43. void print_show(char show[][EASY_COLS]) {
  44.         for (int i = 0; i <= EASY_ROW; i++)
  45.                 printf("%d ", i);
  46.         printf("\n");
  47.         for (int i = 1; i <= EASY_ROW; i++) {
  48.                 printf("%d ", i);
  49.                 for (int j = 1; j <= EASY_COL; j++)
  50.                         printf("%c ", show[i][j]);
  51.                 printf("\n");
  52.         }
  53.         printf("输入坐标:");
  54. }
  55. void find_mine(char mine[][EASY_COLS], char show[][EASY_COLS]) {
  56.         int x = 0; // 输入的坐标
  57.         int y = 0;
  58.         int count = 0; // 已找到的的非雷
  59.         while (count != EASY_ROW * EASY_COL - EASY_COUNT) { // 没有找到所有非雷,游戏就继续
  60.                 print_show(show); // 打印棋盘
  61.                 scanf("%d %d", &x, &y); // 输入坐标
  62.                 system("cls");
  63.                 if (mine[x][y] == '1') {
  64.                         printf("炸弹!游戏失败!\n");
  65.                         break;
  66.                 }
  67.                 else if (mine[x][y] == '0') {
  68.                         char c = count_mine(mine, x, y); // 计算周围雷的个数
  69.                         show[x][y] = c;
  70.                         count++; //找到一个非雷
  71.                 }
  72.         }
  73.         if (count == EASY_ROW * EASY_COL - EASY_COUNT)
  74.                 printf("排雷成功!\n");
  75. }
  76. char count_mine(char mine[][EASY_COLS], int x, int y) {
  77.         return mine[x - 1][y - 1] + mine[x - 1][y] + mine[x - 1][y + 1] + mine[x][y - 1] + mine[x][y + 1]
  78.                 + mine[x + 1][y - 1] + mine[x + 1][y] + mine[x + 1][y + 1] - 8 * '0' + '0';
  79. }
复制代码
        game.h
  1. #pragma once
  2. #include<stdio.h>
  3. #include<stdlib.h>
  4. #include<time.h>
  5. #include<Windows.h>
  6. #define EASY_ROW 9
  7. #define EASY_COL 9
  8. #define EASY_COUNT 10
  9. #define EASY_ROWS EASY_ROW+2
  10. #define EASY_COLS EASY_COL+2
  11. void print_menu();
  12. void init_mine(char mine[][EASY_COLS]);
  13. void init_show(char show[][EASY_COLS]);
  14. void set_mine(char mine[][EASY_COLS]);
  15. void print_mine(char mine[][EASY_COLS]);
  16. void print_show(char show[][EASY_COLS]);
  17. void find_mine(char mine[][EASY_COLS], char show[][EASY_COLS]);
  18. char count_mine(char mine[][EASY_COLS], int x, int y);
复制代码
        test.c
  1. #define _CRT_SECURE_NO_WARNINGS 1;
  2. #include "game.h"
  3. void game() {
  4.         char mine[EASY_ROWS][EASY_COLS]; // 存储生成的雷
  5.         char show[EASY_ROWS][EASY_COLS]; // 存储排雷后雷的个数
  6.         init_mine(mine); // 初始化,全放'0'
  7.         init_show(show); // 初始化棋盘,全为'*'
  8.         set_mine(mine); // 随机放雷
  9.         find_mine(mine, show);// 扫雷
  10. }
  11. int main() {
  12.         int choose = 0;
  13.         srand((unsigned int)time(NULL)); // 设置随机种子
  14.         do{
  15.                 print_menu(); // 打印主菜单
  16.                 scanf("%d", &choose); // 输入选择
  17.                 system("cls"); // 清屏
  18.                 switch (choose) {
  19.                 case 1: // 开始游戏
  20.                         game();
  21.                         break;
  22.                 case 2: // 退出
  23.                         break;
  24.                 default:
  25.                         printf("输入错误,请重新输入。\n");
  26.                 }
  27.         } while (choose != 2);
  28.         return 0;
  29. }
复制代码
三、效果展示




三、优化

        代码另有许多需要优化的地方,比如:


  • 如果排查的位置不是雷,它四周也没雷,四周可以举行展开。
  • 可以选择游戏难度:简单(9x9,10个雷)、中等(16x16,40个雷)、困难(16x30,99个雷)、自界说。
  • 可以标志雷。
  • 游戏中,可以显示时间。
(1)增加展开、标志雷功能

        代码如下:
        game.h:
  1. #pragma once
  2. #include<stdio.h>
  3. #include<stdlib.h>
  4. #include<time.h>
  5. #include<Windows.h>
  6. #define EASY_ROW 9
  7. #define EASY_COL 9
  8. #define EASY_COUNT 10
  9. #define EASY_ROWS EASY_ROW+2
  10. #define EASY_COLS EASY_COL+2
  11. // 打印字体颜色
  12. #define NONE "\033[m"
  13. #define BLUE "\033[0;32;34m"
  14. #define RED "\033[0;32;31m"
  15. void print_menu();
  16. void init_mine(char mine[][EASY_COLS]);
  17. void init_show(char show[][EASY_COLS]);
  18. void set_mine(char mine[][EASY_COLS]);
  19. void print_mine(char mine[][EASY_COLS]);
  20. void print_show(char show[][EASY_COLS]);
  21. void find_mine(char mine[][EASY_COLS], char show[][EASY_COLS], char flag[][EASY_COLS]);
  22. char count_mine(char mine[][EASY_COLS], int x, int y);
  23. void find_other_mine(char mine[][EASY_COLS], char show[][EASY_COLS], int x, int y, int* cnt);
  24. void mark_mine(char flag[][EASY_COLS]);
复制代码
        game.c:
  1. #define _CRT_SECURE_NO_WARNINGS 1;
  2. #include "game.h"
  3. void print_menu(){ // 打印主菜单
  4.         printf("******************************\n");
  5.         printf("*********    1.play   ********\n");
  6.         printf("*********    2.exit   ********\n");
  7.         printf("******************************\n");
  8.         printf("请选择:");
  9. }
  10. void init_mine(char mine[][EASY_COLS]) { // 初始化雷信息
  11.         for (int i = 0; i < EASY_ROWS; i++)
  12.                 for (int j = 0; j < EASY_COLS; j++)
  13.                         mine[i][j] = '0';
  14. }
  15. void init_show(char show[][EASY_COLS]) { //初始化棋盘
  16.         for (int i = 0; i < EASY_ROWS; i++)
  17.                 for (int j = 0; j < EASY_COLS; j++)
  18.                         show[i][j] = '*';
  19. }
  20. void set_mine(char mine[][EASY_COLS]) { // 随即放置雷
  21.         int r = 0;
  22.         int c = 0;
  23.         for (int i = 0; i < EASY_COUNT; i++) {
  24.                 r = rand() % EASY_ROW + 1; // 雷要在棋盘里边,不能在增加的一圈里
  25.                 c = rand() % EASY_COL + 1;
  26.                 if ('0' == mine[r][c]) // 这个位置不是雷,放雷
  27.                         mine[r][c] = '1';
  28.                 else
  29.                         i--; // 这个位置已经是雷了,这轮不算
  30.         }
  31. }
  32. void print_mine(char mine[][EASY_COLS]) { // 打印雷信息
  33.         for (int i = 0; i <= EASY_ROW; i++)
  34.                 printf(BLUE"%d "NONE, i);
  35.         printf("\n");
  36.         for (int i = 1; i <= EASY_ROW; i++) {
  37.                 printf(BLUE"%d "NONE, i);
  38.                 for (int j = 1; j <= EASY_COL; j++)
  39.                         printf("%c ", mine[i][j]);
  40.                 printf("\n");
  41.         }
  42. }
  43. void print_show(char show[][EASY_COLS], char flag[][EASY_COLS]) { // 打印棋盘
  44.         for (int i = 0; i <= EASY_ROW; i++)
  45.                 printf(BLUE"%d "NONE, i);
  46.         printf("\n");
  47.         for (int i = 1; i <= EASY_ROW; i++) {
  48.                 printf(BLUE"%d "NONE, i);
  49.                 for (int j = 1; j <= EASY_COL; j++) {
  50.                         if('1' == flag[i][j])
  51.                                 printf(RED"%c "NONE, show[i][j]);
  52.                         else
  53.                                 printf("%c ", show[i][j]);
  54.                 }
  55.                 printf("\n");
  56.         }
  57.         printf("输入坐标(输入0 0标记雷):");
  58. }
  59. void find_mine(char mine[][EASY_COLS], char show[][EASY_COLS], char flag[][EASY_COLS]) { // 排雷
  60.         int x; // 输入的坐标
  61.         int y;
  62.         int count = 0; // 已找到的的非雷
  63.         while (count != EASY_ROW * EASY_COL - EASY_COUNT) { // 没有找到所有非雷,游戏就继续
  64.                 x = 0;
  65.                 y = 0;
  66.                 while (x == 0 && y == 0) {
  67.                         print_show(show, flag); // 打印棋盘
  68.                         scanf("%d %d", &x, &y); // 输入坐标
  69.                         if (0 == x && 0 == y) // 标记雷
  70.                                 mark_mine(flag);
  71.                         system("cls");
  72.                 }
  73.                 if (mine[x][y] == '1') { // 碰到炸弹了
  74.                         printf("炸弹!游戏失败!\n");
  75.                         break;
  76.                 }
  77.                 else if (mine[x][y] == '0' && show[x][y] == '*') { // (x,y)不是炸弹,并且没有被排查过
  78.                         find_other_mine(mine, show, x, y, &count);
  79.                 }
  80.         }
  81.         if (count == EASY_ROW * EASY_COL - EASY_COUNT) // 所有非雷点找齐了
  82.                 printf("排雷成功!\n");
  83. }
  84. char count_mine(char mine[][EASY_COLS], int x, int y) { // 计算非雷点(x,y)周围有几个雷
  85.         return mine[x - 1][y - 1] + mine[x - 1][y] + mine[x - 1][y + 1] + mine[x][y - 1] + mine[x][y + 1]
  86.                 + mine[x + 1][y - 1] + mine[x + 1][y] + mine[x + 1][y + 1] - 8 * '0' + '0';
  87. }
  88. void find_other_mine(char mine[][EASY_COLS], char show[][EASY_COLS], int x, int y, int* cnt) { // 递归,排查的位置不是雷,周围也没有雷,可以展开
  89.         char c = count_mine(mine, x, y); // 计算周围雷的个数
  90.         if(c == '0')
  91.                 show[x][y] = ' ';
  92.         else
  93.                 show[x][y] = c;
  94.         (*cnt)++; //找到一个非雷
  95.         if (show[x][y] != ' ') { // 周围有雷,就不需要扩展了
  96.                 return;
  97.         }
  98.         for (int i = -1; i <= 1; i++) {
  99.                 for (int j = -1; j <= 1; j++) {
  100.                         int xt = x + i;
  101.                         int yt = y + j;
  102.                         if (show[xt][yt] != '*' || mine[xt][yt] == '1') // 已经排查过的,就不需要重复扩展了;本身是炸弹的,也不要扩展
  103.                                 continue;
  104.                         if(xt >= 1 && yt >= 1 && xt <= EASY_ROW && yt <= EASY_COL) // 拓展的坐标要在棋盘范围内
  105.                                 find_other_mine(mine, show, xt, yt, cnt);
  106.                 }
  107.         }
  108. }
  109. void mark_mine(char flag[][EASY_COLS]) {
  110.         int x;
  111.         int y;
  112.         printf("输入雷的坐标:");
  113.         scanf("%d %d", &x, &y); // 输入坐标
  114.         flag[x][y] = '1';
  115. }
复制代码
        test.c:
  1. #define _CRT_SECURE_NO_WARNINGS 1;
  2. #include "game.h"
  3. void game() {
  4.         char mine[EASY_ROWS][EASY_COLS]; // 存储生成的雷
  5.         char show[EASY_ROWS][EASY_COLS]; // 存储排雷后雷的个数
  6.         char flag[EASY_ROWS][EASY_COLS]; // 屏幕上,标记的雷
  7.         init_mine(mine); // 初始化,全放'0'
  8.         init_show(show); // 初始化棋盘,全为'*'
  9.         init_mine(flag); // 初始化全'0','0' 表示未标记,‘1’表示已标记
  10.         set_mine(mine); // 随机放雷
  11.         find_mine(mine, show, flag);// 扫雷
  12. }
  13. int main() {
  14.         int choose = 0;
  15.         srand((unsigned int)time(NULL)); // 设置随机种子
  16.         do{
  17.                 print_menu(); // 打印主菜单
  18.                 scanf("%d", &choose); // 输入选择
  19.                 system("cls"); // 清屏
  20.                 switch (choose) {
  21.                 case 1: // 开始游戏
  22.                         game();
  23.                         break;
  24.                 case 2: // 退出
  25.                         break;
  26.                 default:
  27.                         printf("输入错误,请重新输入。\n");
  28.                 }
  29.         } while (choose != 2);
  30.         return 0;
  31. }
复制代码
        效果展示:



回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表