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

avatar
作者
猴君
阅读量:0

目录

一、分析和设计

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'; 		else 			i--; // 这个位置已经是雷了,这轮不算 	} }  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+2  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 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'; 		else 			i--; // 这个位置已经是雷了,这轮不算 	} }  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]); 			else 				printf("%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] = ' '; 	else 		show[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; }

        效果展示:

广告一刻

为您即时展示最新活动产品广告消息,让您随时掌握产品活动新动态!