C语言--12.扫雷游戏练习

在上一章中,我们对三子棋进行了细致的分析与实现,这一章中,我们将完成另一个小游戏---就是我们耳熟能详的扫雷,那么就让我们开始设计这个小时候的游戏吧

扫雷小游戏

基本逻辑

扫雷小游戏我们同样利用键盘来进行操作,这里先把扫雷游戏的基本逻辑进行阐述,大家在之前玩的时候,首先会有一个大面板,面板上会有许多的小格子,小格子不确定有没有地雷,需要玩家去点击进行探测,如果探测踩到地雷了,那么就游戏结束,如果没有踩到地雷,便会在点击的那么格子中显示除它之外周围8个格子中地雷的个数,那么这时候便会有一个问题,就是当我们探测到边边角角的时候,并没有8个格子让我们来计算,所以在这里我们对面板进行扩张处理,比如需要10*10的区域来玩,我们就创建12*12的面板,将外围格子全部置0,此时便只使用内核来进行操作,在扫雷中我们不断扫,当剩余未被探测的个数与事先埋好的地雷数相同的时候,便取得了游戏的胜利,这便是游戏的基本逻辑,下面我们对面板进行大致模拟

 

1.设计游戏思路

准备工作

在我们开始进行设计之前,可以想像得到我们需要对其进行设置宏定义,显而易见需要设置长ROW,宽COL,雷的个数NUM,以及未被探测时的显示,接下来我们进行编写mine.h函数来引入宏定义以及其他基础代码

#pragma once//防止头文件被重复包含

#define _CRT_SECURE_NO_WARNINGS 1

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

#define ROW 8
#define COL 8

#define STYLE '?'
#define NUM 20
extern void Game();

与三子棋一样,菜单的设置基本一样,在扫雷游戏中,我们计划设置2种调用情况,1.开始扫雷游戏  0.退出,所以我们需要对之前的菜单进行稍微的修改

#include "clear_mine.h"

static void Menu()
{
	printf("####################\n");
	printf("# 1. Play    0.Exit \n");
	printf("####################\n");
}

int main()
{
	int quit = 0;
	int select = 0;
	while (!quit){
		Menu();
		printf("Please Enter# ");
		scanf("%d", &select);
		switch (select){
		case 1:
			Game();
			break;
		case 0:
			quit = 1;
			break;
		default:
			printf("Postion Error, Try Again!\n");
			break;
		}
	}

	printf("byebye!\n");

	system("pause");
	return 0;
}



在此不再赘述main函数中的过多细节,同三子棋基本一致

2.设计游戏逻辑

接下来进入我们真正的扫雷游戏的编写

可以料想得到,当我们进行扫雷游戏踩到雷被炸死结束时,我们需要将所有雷的具体位置显示出来,这时候如果在原先的面板中进行显示将是非常麻烦的,所以我们在定义面板时候,定义两个相同的,一模一样的面板,一个是给用户进行探测,可以改变的面板show_board,另一个是在我们游戏逻辑中,不可改变的,显示所有雷具体位置的面板mine_board,此时我们便需要对两个面板进行初始化,我们将1代表有雷,0代表没有雷,这里我们调用memset函数来对面板在内存层面进行初始化(与之前数组方法效果一致)

    char show_board[ROW][COL];
	char mine_board[ROW][COL];

	memset(show_board, STYLE, sizeof(show_board));
	memset(mine_board, '0', sizeof(mine_board));

当我们对面板进行初始化了之后,首先需要的就是调用SetMines来进行埋雷,埋好之后就需要ShowBoard函数显示我们的面板了,接下来准备工作完成,便需要用户在键盘中输入坐标来进行探测,此时需要限定用户输入的合法性,注意,我们在定义面板的时候,真正进行游戏的是中间的那部分,而不是整个长宽都可以探测,所以我们需要在左右及上下都需要对长宽进行减一操作,一共是减去2,来限定我们输入的合法性,当范围被确定时,我们还需要判断位置是否已经被探测,当这个位置未被探测且在范围之内时,便可以进行探测,在可以探测的条件下我们还需要判断这个位置是否有雷,如果有雷则game over并将雷的位置通过ShowBoard函数将我们的mine_board函数整体显示给用户,如果没有雷则需要调用一个Count_Mines函数将Mines图中的雷的个数显示到当前格子中,这时候便完成了我们一次的探测,最后需要在外围套上一层while循环来重复判断即可,当我们重复探测直到减去雷数后未探测的面板数减为0时,则用户胜利,我们将上述想法用代码进行实现

void Game()
{
	srand((unsigned long)time(NULL));//种一颗随机数种子,在埋雷的时候会用到

	char show_board[ROW][COL];//用户面板
	char mine_board[ROW][COL];//雷面板

	memset(show_board, STYLE, sizeof(show_board));//初始化用户面板
	memset(mine_board, '0', sizeof(mine_board));//初始化雷面板

	SetMines(mine_board, ROW, COL);//埋雷

	int count = (ROW - 2)*(COL - 2) - NUM;//减去雷之后未探测的格子数

	while (count){
		system("cls");//刷新屏幕
		ShowBoard(show_board, ROW, COL);//显示用户面板
		printf("Please Enter Your Postion<x,y># ");
		int x = 0;
		int y = 0;
		scanf("%d %d", &x, &y);//用户输入
		if (x < 1 || x > ROW-2 || y < 1 || y > COL-2){//判断范围
			printf("Postion Error!\n");
			continue;
		}
		if (show_board[x][y] != STYLE){//判断是否已被探测
			printf("Postion Is not *\n");
			continue;
		}
		if (mine_board[x][y] == '1'){//从雷图中判断踩到雷
			printf("game over!\n");
			ShowBoard(mine_board, ROW, COL);//显示雷图
			break;
		}
		//['0', '8']
		show_board[x][y] = CountMines(mine_board, x, y);//显示周围雷的个数
		count--;//减去雷之后未探测的格子数减一
	}

}

这边是我们对于扫雷游戏的整体框架设置,接下来需要完成的是各个函数的具体实现

3.设计函数

在我们主框架中出现了很多我们需要设计的函数,自顶而下的设计方式,接下来我们对他们进行封装处理并实现

SetMines(mine_board, ROW, COL);
ShowBoard(show_board, ROW, COL);
CountMines(mine_board, x, y);

对于扫雷游戏的编写只需要建立这3个接口便可以完成

SetMines函数

我们的埋雷函数,需要利用随机数来对特定长宽范围进行赋值,注意的是我们用的是ROW与COL的内核,所以需要对他们分别减2,初始设置雷数,利用while循环对面板进行随机赋值,注意,如果该位置已经被埋下了雷,就不能继续埋雷,所以需要先对位置进行是否没有雷的判断,而后才可以进行埋雷,当埋一个雷时就将总个数减一

static void SetMines(char board[][COL], int row, int col)
{
	int count = NUM;
	while (count){
		int x = rand() % (row - 2) + 1;
		int y = rand() % (col - 2) + 1;
		if (board[x][y] == '0'){
			board[x][y] = '1';
			count--;
		}
	}
}

CountMines函数

对于计数函数,因为我们之前对没有雷的格子赋值为字符‘0’,有雷的个数赋值为‘1’,所以在我们返回总个数的时候只需要将探测周边8个位置的字符加起来再都减去字符‘0’就可以了(ASIC码中会将其转换为加和的那个整型数字)而后我们再对其进行加上一个‘0’便可将那个得到的值重新转成字符,所以我们需要减去7个‘0’,而且我们不需要考虑在边角中的特殊情况,因为我们将边角的值都赋成了‘0’,加和不影响总值

static char CountMines(char board[][COL], int x, int y)
{
	return board[x - 1][y - 1] + board[x - 1][y] + board[x - 1][y + 1] + \
		board[x][y + 1] + board[x + 1][y + 1] + board[x + 1][y] + \
		board[x + 1][y - 1] + board[x][y - 1] - 7 * '0';
}

ShowBoard函数

接下来便是我们复杂度最高的ShowBoard函数,虽然其代码逻辑简单,但是需要不断地调试移动位置大小,所以复杂度会很高,我们尽量对其进行可维护性调整,再创建了一个ShowLine函数用于控制横线,以方便我们创建面板

static void ShowLine(int col)//显示横线的函数,方便随ROW与COL变化
{
	for (int i = 0; i <= (col - 2); i++){
		printf("----");
	}
	printf("\n");
}
static void ShowBoard(char board[][COL], int row, int col)
{
	printf("     ");
	for (int i = 1; i <= (col - 2); i++){
		printf("%d   ", i);
	}
	printf("\n");
	ShowLine(col);
	for (int i = 1; i <= (row - 2); i++){
		printf("%-3d|", i);
		for (int j = 1; j <= (col - 2); j++){
			printf(" %c |", board[i][j]);
		}
		printf("\n");
		ShowLine(col);
	}
}

自此我们的扫雷游戏就得到了完成,下面演示整体代码

//mine.h
#pragma once

#define _CRT_SECURE_NO_WARNINGS 1

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

#define ROW 8
#define COL 8

#define STYLE '?'
#define NUM 20
extern void Game();

//mine.c
#include "mine.h"

static void SetMines(char board[][COL], int row, int col)
{
	int count = NUM;
	while (count){
		int x = rand() % (row - 2) + 1;
		int y = rand() % (col - 2) + 1;
		if (board[x][y] == '0'){
			board[x][y] = '1';
			count--;
		}
	}
}

static void ShowLine(int col)
{
	for (int i = 0; i <= (col - 2); i++){
		printf("----");
	}
	printf("\n");
}
static void ShowBoard(char board[][COL], int row, int col)
{
	printf("     ");
	for (int i = 1; i <= (col - 2); i++){
		printf("%d   ", i);
	}
	printf("\n");
	ShowLine(col);
	for (int i = 1; i <= (row - 2); i++){
		printf("%-3d|", i);
		for (int j = 1; j <= (col - 2); j++){
			printf(" %c |", board[i][j]);
		}
		printf("\n");
		ShowLine(col);
	}
}

static char CountMines(char board[][COL], int x, int y)
{
	return board[x - 1][y - 1] + board[x - 1][y] + board[x - 1][y + 1] + \
		board[x][y + 1] + board[x + 1][y + 1] + board[x + 1][y] + \
		board[x + 1][y - 1] + board[x][y - 1] - 7 * '0';
}

void Game()
{
	srand((unsigned long)time(NULL));

	char show_board[ROW][COL];//用户面板
	char mine_board[ROW][COL];//雷面板

	memset(show_board, STYLE, sizeof(show_board));//初始化用户面板
	memset(mine_board, '0', sizeof(mine_board));//初始化雷面板

	SetMines(mine_board, ROW, COL);//埋雷

	int count = (ROW - 2)*(COL - 2) - NUM;//减去雷之后未探测的格子数

	while (count){
		system("cls");//刷新屏幕
		ShowBoard(show_board, ROW, COL);//显示用户面板
		printf("Please Enter Your Postion<x,y># ");
		int x = 0;
		int y = 0;
		scanf("%d %d", &x, &y);//用户输入
		if (x < 1 || x > ROW-2 || y < 1 || y > COL-2){//判断范围
			printf("Postion Error!\n");
			continue;
		}
		if (show_board[x][y] != STYLE){//判断是否已被探测
			printf("Postion Is not *\n");
			continue;
		}
		if (mine_board[x][y] == '1'){//从雷图中判断踩到雷
			printf("game over!\n");
			ShowBoard(mine_board, ROW, COL);//显示雷图
			break;
		}
		//['0', '8']
		show_board[x][y] = CountMines(mine_board, x, y);//显示周围雷的个数
		count--;//减去雷之后未探测的格子数减一
	}

}

//main.c
#include "mine.h"

static void Menu()
{
	printf("###################\n");
	printf("# 1. Play   0.Exit \n");
	printf("###################\n");
}

int main()
{
	int quit = 0;
	int select = 0;
	while (!quit){
		Menu();
		printf("Please Enter# ");
		scanf("%d", &select);
		switch (select){
		case 1:
			Game();
			break;
		case 0:
			quit = 1;
			break;
		default:
			printf("Postion Error, Try Again!\n");
			break;
		}
	}

	printf("byebye!\n");

	system("pause");
	return 0;
}

这便是扫雷游戏的所有代码,接下来我们对其进行思维导图分析

以上便是我们的扫雷游戏小项目的实现与总结,我们共同学习,共同进步

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值