C语言实现扫雷游戏
代码下载
Github源码下载地址:
https://github.com/Kyrie-leon/C_Project/tree/master/MineSweeper
一、多文件思维
我们使用多文件实现扫雷游戏,培养多文件思维,分别为game.h、game.c和main.c
二、游戏总体思路与函数设计实现
扫雷游戏的主要思路为:
这里需要注意几个重要的点:
1.我们用字符0表示没有雷,字符1表示有雷,显示的面板中对于未排除的地方用字符表示;
2.需要两个二维数组用来存放显示的面板和布雷的面板,这个数组的大小为1010,为了防止越界的出现我们对边界进行全零填充,方便边界的检测;
上述函数封装在game.c文件中
2.1 游戏主控制函数Game
这里了解一下库函数memset
memset是计算机中C/C++语言初始化函数。作用是将某一块内存中的内容全部设置为指定的值, 这个函数通常为新申请的内存做初始化工作。
ptr:为指针或是数组,
value:是赋给ptr的值,
num:是ptr的长度.
void Game()
{
//定义一个随机种子用于电脑落子
srand((unsigned long)time(NULL));
//定义棋盘
char board[ROW][COL];
//棋盘初始化为空
memset(board, ' ', sizeof(board));
//定义变量result用于判断输赢情况
char result='x';
do
{
ShowBoard(board, ROW, COL);
PlayerMove(board, ROW, COL);
result = Judge(board, ROW, COL);
//出现三子成珠,跳出循环,给出输赢结果
if (result != NEXT)
{
break;
}
ComputerMove(board, ROW, COL);
result = Judge(board, ROW, COL);
//出现三子成珠情况,跳出循环,给出输赢结果
if (result != NEXT)
{
break;
}
} while (1);
//打印输赢结果
if (P_COLOR == result)
{
printf("你赢了!\n");
}
else if (C_COLOR == result)
{
printf("你输了!\n");
}
else
{
printf("和棋!\n");
}
//此处再次调用显示棋盘函数,用于显示最终的落子情况,如果不调用该函数,会出现看不到最后一次落子的棋盘
ShowBoard(board, ROW, COL);
}
2.2 游戏菜单Menu
void Menu()
{
printf("################################\n");
printf("#### 1.Play 2.Exit ####\n");
printf("################################\n");
printf("Please input your selection#: ");
}
2.3 埋雷SetMines
//埋雷
void SetMines(char mines_board[][COL], int row, int col,int num)
{
//定义一个计数器统计埋雷的个数
int count = num;
//循环实现埋雷,当计数器为0表明雷已经被埋完
while (count)
{
int x = rand() % 10 + 1;
int y = rand() % 10 + 1;
//只有没有被埋雷的地方才能埋雷同时计数器减一
if (mines_board[x][y] == '0')
{
mines_board[x][y] = '1';
count--;
}
}
}
2.3 显示面板ShowBoard
用一个二维数组来存储并显示,对于没有排除的位置统一用*存储
//显示每一行后的横线
void ShowLine(int nums)
{
printf("---");
for (int i = 0; i < nums; i++) {
printf("-");
}
printf("\n");
}
//显示10*10面板,此处的显示需要不断调试计算
void ShowBoard(char show_board[][COL], int row, int col)
{
printf(" ");
//打印横坐标标签
for (int i = 1; i <row-1; i++)
{
printf(" %d ", i);
}
printf("\n");
//双层循环,外层打印列标签,内层打印每一列
for (int i = 1; i <row-1; i++)
{
printf("%2d ", i);
for (int j = 1; j < col - 1; j++)
{
printf(" %c |", show_board[i][j]);
}
printf("\n");
ShowLine(2 * col + col + 4);
}
}
2.4 坐标周围布雷情况GetMines
//统计该坐标八个方向的雷
int GetMines(char mines_board[][COL], int row, int col, int x, int y)
{
return mines_board[x - 1][y - 1] + mines_board[x - 1][y] + mines_board[x + 1][y + 1] + mines_board[x][y + 1] \
+ mines_board[x + 1][y + 1] + mines_board[x + 1][y] + mines_board[x+1][y - 1]+mines_board[x][y-1]\
- 8 * '0';
}
三、多文件代码清单
3.1 game.h
用于存放实现游戏的所有库函数和函数声明,以及一些宏定义方便后续更改
使用ifndef/define/endif防止头文件的重复包含和编译。
#ifndef _GAME_H
#define _GAME_H
#include <stdio.h>
#include <Windows.h>
#include <time.h>
#pragma warning(disable:4996)
//定义扫雷面板大小
#define ROW 12
#define COL 12
//定义埋雷的个数,定义面板大小的五分之一,即10*10*1/5=20
#define NUM 20
void Menu();
void Game();
#endif
3.2 game.c
用于存放实现游戏的所有函数定义
#include "game.h"
void Menu()
{
printf("##########################\n");
printf("## 1. Play 2. Exit ##\n");
printf("##########################\n");
printf("请输入# ");
}
//埋雷
void SetMines(char mines_board[][COL], int row, int col,int num)
{
//定义一个计数器统计埋雷的个数
int count = num;
//循环实现埋雷,当计数器为0表明雷已经被埋完
while (count)
{
int x = rand() % 10 + 1;
int y = rand() % 10 + 1;
//只有没有被埋雷的地方才能埋雷同时计数器减一
if (mines_board[x][y] == '0')
{
mines_board[x][y] = '1';
count--;
}
}
}
//显示每一行后的横线
void ShowLine(int nums)
{
printf("---");
for (int i = 0; i < nums; i++) {
printf("-");
}
printf("\n");
}
//显示10*10面板,此处的显示需要不断调试计算
void ShowBoard(char show_board[][COL], int row, int col)
{
printf(" ");
//打印横坐标标签
for (int i = 1; i <row-1; i++)
{
printf(" %d ", i);
}
printf("\n");
//双层循环,外层打印列标签,内层打印每一列
for (int i = 1; i <row-1; i++)
{
printf("%2d ", i);
for (int j = 1; j < col - 1; j++)
{
printf(" %c |", show_board[i][j]);
}
printf("\n");
ShowLine(2 * col + col + 4);
}
}
void Game()
{
//1.定义显示面板数组与布雷数组
char show_board[ROW][COL];
char mines_board[ROW][COL];
//2.初始化显示数组为*,布雷数组为0
memset(show_board, '*', sizeof(show_board));
memset(mines_board, '0', sizeof(mines_board));
//3.随机埋NUM个雷,有雷的位置设为字符1,
srand((unsigned long)time(NULL));
SetMines(mines_board,ROW,COL,NUM);
//定义一个计数器用于计数面板中剩余的空位,即去除边界后的数组大小减去埋雷的个数 行数-2*列数-2-埋雷的个数
int count = (ROW - 2)*(COL - 2) - NUM;
//定义坐标
int x = 0;
int y = 0;
//循环检测雷是否排空
do
{
//显示面板
ShowBoard(show_board,ROW,COL);
//输入坐标排雷
printf("请输入位置坐标:");
scanf("%d %d", &x, &y);
//判断输入坐标是否越界,填充边界后的数组正好从1开始,10结束
if (x < 1 || x>10 || y < 1 || y>10)
{
printf("您输入的坐标越界,请重新输入!\n");
continue;
}
//判断输入坐标是否已排除过
if (show_board[x][y] != '*')
{
printf("您输入的位置已经排除,请重新输入!\n");
continue;
}
//判断输入位置是否有雷
if (mines_board[x][y] == '1')
{
break;
}
//不满足上述条件下,判断该位置周边埋有多少个雷
int num = GetMines(mines_board, ROW, COL, x, y);
//统计该坐标八个方向有多少雷
show_board[x][y] = num + '0';
//空位数-1
count--;
system("cls");
} while (count>0);
if (count > 0)
{
printf("你被炸死了!\n");
}
else
{
printf("你赢了!\n");
}
printf("下面是雷区的排布!\n");
ShowBoard(mines_board, ROW, COL);
}
3.3 main.c
实现三子棋游戏的主菜单和实现步骤
#include "game.h"
int main()
{
int quit = 0;
int select = 0;
while (!quit)
{
Menu();
scanf("%d", &select);
switch (select)
{
case 1:
Game();
printf("要不要在来一把?\n");
break;
case 2:
quit = 1;
break;
default:
printf("你输入的选择有误,请重新输入!\n");
break;
}
}
printf("ByeBye\n");
system("pause");
return 0;
}