【C】三子棋 VS 扫雷

【C】三子棋 VS 扫雷_第1张图片
博客主页:心辛向荣
系列专栏:【从0到1,C语言学习】
一句短话:你若盛开,蝴蝶自来!
博客说明:尽己所能,把每一篇博客写好,帮助自己熟悉所学知识,也希望自己的这些内容可以帮助到一些在学习路上的伙伴,文章中如果发现错误及不足之处,还望在评论区留言,我们一起交流进步!
【C】三子棋 VS 扫雷_第2张图片


前言

在这里总结用C语言实现三子棋和扫雷俩个小游戏,实现这俩个游戏的思路其实是差不多的,都是对二维数组进行操作 ,把功能通过分支和循环等实现;只要能想到,就可以通过顺序,选择,循环这三种结构结构实现!


一.三子棋
——1.实现思路
——2.game.h
——3.main.c
——4.game.c
——5.实现效果,待改进,不足之处
二.扫雷
——1.实现思路
——2.game.h
——3.main.c
——4.game.c
——5.实现效果,待改进,不足之处


三子棋

实现思路

(main.c)是测主函数测试部分
(game.c)是游戏功能具体实现部分
(game.h)是函数声明部分

首先将棋盘初始化,也就是将二维数组中的元素都放空格。
【C】三子棋 VS 扫雷_第3张图片

然后我们这里实现打印上面这样一个三子棋盘,有人想为啥不在 上下左右加上 —— 呢,其实是可以实现的,不过实现打印图中的棋盘可以锻炼编程的逻辑,下面分析一下:
【C】三子棋 VS 扫雷_第4张图片
这里可以通过for循环打印棋盘的行和列,棋盘的每一行可以拆分为数据部分和分割线部分 ,通过if语句限制条件使分割部分比数据部分少打印一次;而每一行数据部分的拆成每一列循环打印,每一列的格式是空格 %c 空格 | ,同样的做法把每一行最后一个 | 过滤掉,打印分割线(- - -| )也是一样的。

棋盘打印好后,实现下棋部分,每次玩家/电脑下过棋后,都要进行判断输赢的过程。

game.h

#pragma once
#include 
#include
#include

//可自由更改棋盘大小
#define ROW 3
#define COL 3

//初始化棋盘
void initboard(char board[ROW][COL],int row,int col);

//显示棋盘
void displayboard(char board[ROW][COL], int row, int col);
//玩家下棋

void palyermove(char board[ROW][COL], int row, int col);

//电脑下棋
void computermove(char board[ROW][COL], int row, int col);

//判断输赢
//玩家赢返回'*'
//电脑赢返回'#'
//平局返回'q'
//继续返回'c'
char iswin(char board[ROW][COL], int row, int col);

main.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"

void menu()
{
	printf("*********************\n");
	printf("***** 1.开始游戏*****\n");
	printf("***** 0.退出游戏*****\n");
	printf("*********************\n");

}

void game()
{
	char ret = '0';
	char board[ROW][COL] = { 0 };
	//初始化棋盘
	initboard(board,ROW,COL);
	//显示棋盘
	displayboard(board, ROW, COL);
	while (1)
	{
		//玩家下棋
		palyermove(board, ROW, COL);
		//判断输赢
		ret = iswin(board, ROW, COL);
		if (ret != 'c')//当返回'*'/'#'时跳出循环判断谁赢了
		{
			break;
		}
		displayboard(board, ROW, COL);

		//电脑下棋
		computermove(board, ROW, COL);
		ret = iswin(board, ROW, COL);
		if (ret != 'c')
		{
			break;
		}
		displayboard(board, ROW, COL);
	}
	if (ret == '*')
	{
		printf("玩家胜出\n");
	}
	if (ret == '#')
	{
		printf("电脑胜出\n");
	}
	if (ret == 'q')
	{
		printf("你和电脑旗鼓相当\n");
	}
}

int main()
{
	srand((unsigned)time(NULL));
	int input = 0;
	do
	{
		//打印一个游戏菜单
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			game();
			break;
		case 0:
			printf("退出游戏\n");
			break;
		default:
			printf("选择错误,请重新输入\n");
			break;
		}


	} while (input);

	return 0;
}

game.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"
//满了返回1
//不满返回0
int isfull(char board[ROW][COL], int row, int col)
{
	int i = 0, j = 0;
	for (i = 0; i < row; i++)
	{
		for (j = 0; j < col; j++)
		{
			if (board[i][j] == ' ')
				return 0;
		}
	}
	return 1;
}

void initboard(char board[ROW][COL], int row, int col)
{//未下棋,数组中放的是空格
	int i = 0;
	for (i = 0; i < row; i++)
	{
		int j = 0;
		for (j = 0; j < col; j++)
		{
			board[i][j] = ' ';
		}
	}
}

void displayboard(char board[ROW][COL], int row, int col)
{//将棋盘分行打印,每一行再分列打印
	int i = 0;
	for (i = 0; i < row; i++)
	{
		//打印数据
		//printf(" %c | %c | %c \n",board[i][0],board[i][1],board[i][2]);不推荐,把棋盘规格写死了
		int j = 0;
		for (j = 0; j < col; j++)
		{
			printf(" %c ",board[i][j]);
			if(j < col - 1)
			     printf("|");//过滤掉最后一个竖杠
		}
		printf("\n");
		if(i < row - 1)//过滤掉最后一行分割线
		{   //打印分割线 
			//printf("---|---|---\n");
			for (j = 0; j < col; j++)
			{
				printf("---");
				if (j < col - 1)//过滤掉最后一个竖杠
					printf("|");

			}
			printf("\n");
		}
	}	
}

void palyermove(char board[ROW][COL], int row, int col)
{
	while(1)
	{ 
		int x = 0;
		int y = 0;
		printf("玩家下棋\n");
		printf("请输入坐标:>");
		scanf("%d%d", &x, &y);
		if (x > 0 && x <= 3 && y > 0 && y <= 3)
		{
			if (board[x - 1][y - 1] == ' ')
			{
				board[x - 1][y - 1] = '*';
				break;
			}
			else
			{
				printf("坐标被占用,请重新选择\n");
				
			}
		}

		else
		{
			printf("坐标非法,请重新输入\n");
			
		}
	}
}

void computermove(char board[ROW][COL], int row, int col)
{
	printf("电脑下棋\n");
	while (1)
	{
		int x = rand() % 3;
		int y = rand() % 3;
		if (board[x][y] == ' ')
		{
			board[x][y] = '#';
			break;
		}
	}
}

char iswin(char board[ROW][COL], int row, int col)
{
	//判断行相同
	int i = 0;
	for (i = 0; i < row; i++)
	{
		if (board[i][0] == board[i][1] && board[i][1] == board[i][2] && board[i][1] != ' ')
		{
			return board[i][1];
		}
	}

	//列相同
	int j = 0;
	for (j = 0; j < col; j++)
	{
		if (board[0][j] == board[1][j]&&board[1][j] == board[2][j] && board[1][j] != ' ')
		{
			return board[1][j];
		}
	}
	//对角线相同
	if (board[0][0] == board[1][1] && board[1][1] == board[2][2] && board[1][1] != ' ')
	{
		return board[1][1];
	}
	if (board[0][2] == board[1][1] && board[1][1] == board[2][0] && board[1][1] != ' ')
	{
		return board[1][1];
	}
	//棋盘满了,在上面几个判断完后
	//到这里说明平局了
	if (isfull(board, row, col))
	{
		return 'q';
	}
	//上面都不满足游戏继续
	return 'c';	
}

实现效果,待改进,不足之处

【C】三子棋 VS 扫雷_第5张图片

1.电脑下棋是生成随机数随机落子,也就是它不会根据玩家的棋子位置进行堵棋,电脑赢不了玩家。。。

2.判断输赢部分写死了,只适合三子棋盘上进行三子棋的判断输赢,后面有时间我再研究改进更新!


扫雷

实现思路

(main.c)是测主函数测试部分
(game.c)是游戏功能具体实现部分
(game.h)是函数声明部分

【C】三子棋 VS 扫雷_第6张图片
这是一个扫雷的棋盘,同样的这样的棋盘实质上是一个数组,比如我们扫雷的棋盘是99的的大小,如果我们要排雷的位置位置不是雷的话,这个位置就显示周围一圈雷的个数,而如果将数组大小设置为9行9列的话,而排雷位置在边界,在计算周围雷的个数时就会出现数组越界的情况,所以为了方便这里我们将数组扩大一圈数组大小设置为11行11列,而布雷和排雷只在中间99的范围内进行!
【C】三子棋 VS 扫雷_第7张图片
我们定义俩个不同的数组,一个是布雷的数组,一个是玩家排雷的数组,排雷过程中会计算布雷数组中的信息反馈到排雷数组中显示,屏幕上只显示排雷数组中的内容。
【C】三子棋 VS 扫雷_第8张图片
定义好俩个数组后,对俩个数组中的内容进行初始化,未布雷的棋盘中初始化为字符’0’,而未排雷的棋盘中初始为字符’#'。

然后用rand函数随机布置雷(字符’1’),雷的个数不必写死,利用宏定义的标识符常量可以做到灵活设置雷的个数。

最后一部分实现排雷过程,排雷位置是雷则被炸死,游戏失败,不是雷则通过计算布置雷数组中位置周围雷的个数显示到排雷中;

这里介绍如何计算周维雷的个数:布雷的数组中’0’不是雷,'1’是雷,这里的0和1是字符,1 + ‘0’ = ‘1’,0 + ‘0’= ‘0’;所以我们将排雷坐标周围的八个字符加起来然后减去8 * ‘0’得到的就是这个坐标周围雷的个数,这个结果再加上字符’0’,得到字符数字放到排雷的数组中。

还可以实现俩个功能,对位置进行标记 ‘-’ ,把没有雷的地方展开一片,这里展开一片功能的实现要用到递归,要满足3个条件:

  1. 该坐标不是雷
  2. 该坐标周围没有雷
  3. 该坐标没有被排查过

否则就会死递归!

判断输赢我这里这样实现:棋盘大小-雷的个数-不是 ‘*’ 和 ‘-’ 等于0,就表明扫雷成功了!

game.h

#define _CRT_SECURE_NO_WARNINGS 1
#include
#include
#include

//雷盘范围,可灵活更改
#define ROW 9
#define COL 9

//防止计算周围雷的个数时数组越界
#define ROWS ROW+2
#define COLS COL+2

//可更改雷的个数
#define EASY_COUNT 10

void initboard(char board[ROWS][COLS], int rows, int cols,char set);

void displayboard(char board[ROWS][COLS], int row, int col);

void setMine(char board[ROWS][COLS], int row, int col);

void findMine(char mine[ROWS][COLS],char show[ROWS][COLS], int row, int col);

main.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"

void menu()
{
	printf("|------1.play------|\n");
	printf("|------0.exit------|\n");
}

void game()
{
	//布置雷的棋盘
	char mine[ROWS][COLS] = { 0 };
	//玩家进行排雷的棋盘
	char show[ROWS][COLS] = { 0 };
	//初始化雷盘'0'
	initboard(mine,ROWS,COLS,'0');
	//初始化排雷棋盘'*'
	initboard(show, ROWS, COLS, '*');
	//布置雷
	setMine(mine, ROW, COL);
	//显示雷盘,只显示排雷的棋盘
	/*displayboard(mine,ROW,COL);*/
	displayboard(show, ROW, COL);
	//实现排雷过程
	findMine(mine,show, ROW, COL);
}

int main()
{
	srand((unsigned)time(NULL));
	int input = 0;
	//首先显示一个游戏菜单
	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);

		switch (input)
		{
		case 1:
			//实现扫雷功能
			game();
			break;
		case 0:
			printf("退出游戏\n");
			break;
		default:
			printf("选择错误,请重新输入\n");
			break;
		}

	} while (input);

	return 0;
}

game.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"

void initboard(char board[ROWS][COLS], int rows, int cols, char set)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < rows; i++)
	{
		for (j = 0; j < cols; j++)
		{
			board[i][j] = set;
		}
	}
}

void displayboard(char board[ROWS][COLS], int row, int col)
{
	printf("------扫雷游戏------\n");
	int i = 0;
	int j = 0;
	for (j = 0; j <= col; j++)
	{
		printf("%d ", j);
	}
	printf("\n");
	for (i = 1;i <= row;i++)
	{
		printf("%d ", i);
		for (j = 1; j <= col; j++)
		{
			printf("%c ", board[i][j]);
		}
		printf("\n");

	}
	printf("------扫雷游戏------\n");
}

void setMine(char board[ROWS][COLS], int row, int col)
{
	int count = EASY_COUNT;
	while (count)
	{
		int x = rand() % row + 1;
		int y = rand() % col + 1;

		if (board[x][y] == '0')
		{
			board[x][y] = '1';
			count--;
		}
	}
}

int get_mine_count(char mine[ROWS][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';

}

void sign(char show[ROWS][COLS])
{
	int i = 0;
	int x = 0, y = 0;
	do
	{
		printf("标记坐标?(1:是/0:否/2:对已标记的坐标取消)>");
		scanf("%d", &i);
		switch (i)
		{
		case 0:
			break;
		case 1:
			printf("输入标记位置坐标:>");
			scanf("%d%d", &x, &y);
			if (show[x][y] != '*'||show[x][y] == '-')
			{
				printf("这个位置再不能进行标记了\n");
			}
			else
			{
				if (x >= 1 && x <= 9 && y >= 1 && y <= 9)
				{
					show[x][y] = '-';
					displayboard(show, ROW, COL);
				}
				else
				{
					printf("坐标非法,请重新输入!\n");
				}
			}
			break;
		    case 2:
				printf("输入要取消标记的坐标:>");
				scanf("%d%d", &x, &y);
				if (show[x][y] != '-')
				{
					printf("这个坐标未进行标记,重新输入\n");
				}
				else
				{
					show[x][y] = '*';
					displayboard(show, ROW, COL);
				}
				break;
		default:
			printf("选择错误,请重新输入\n");
			break;

		}
	} while (i);
}

void unfold1(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y)
{
	if (x >= 1 && x <= ROW && y >= 1 && y <= COL)
	{
		int i = 0;
		int j = 0;
		int flag = 1;
		if (show[x][y] == '*' || show[x][y] == '-' )
		{
			flag = 0;//没有被排查过的
		}
		int count = get_mine_count(mine, x, y);
        if (count == 0)
        {
			show[x][y] = ' ';	
        }
        else
        {
	       show[x][y] = count + '0';
        }
		if (flag == 0 && count == 0)
		{
			for (i = -1; i <= 1; i++)
			{
				for (j = -1; j <= 1; j++)//对其周围的8个坐标递归
				{
					unfold1(mine, show, x + i, y + j);
				}
			}
		}
	}
}

int is_win(char show[ROWS][COLS], int row, int col)
{
	int i = 0;
	int j = 0;
	int ret = 0;
	for (i = 1; i <= ROW; i++)
	{
		for (j = 1; j <= COL; j++)
		{
			if (show[i][j] != '*' && show[i][j] != '-')
				ret++;
		}
	}
	return ret;
}

void findMine(char mine[ROWS][COLS],char show[ROWS][COLS], int row, int col)
{
	int x = 0;
	int y = 0;
	
	while (row * col - EASY_COUNT - is_win(show, row, col))
	{

		printf("输入要排雷的坐标:>");
		scanf("%d%d", &x, &y);
		if (show[x][y] != '*'&& show[x][y] != '-')
		{
			printf("这个位置已经排过了,不能重复进行\n");
		}
		else
		{
			if (x >= 1 && x <= 9 && y >= 1 && y <= 9)
			{
				//是雷
				if (mine[x][y] == '1')
				{
					printf("游戏失败,你被炸死了!\n");
					displayboard(mine, ROW, COL);
					break;
				}
				
				else
				{
					int count = get_mine_count(mine, x, y);
					if (count == 0)
					{
						unfold1(mine, show, x, y);//展开一片的功能
						displayboard(show, ROW, COL);
						sign(show);//标记功能
					}
					else
					{
				       	show[x][y] = count + '0';
						displayboard(show, ROW, COL);
						sign(show);//标记功能
					}
				}
			}
			else
			{
				printf("坐标非法,请重新输入!\n");
			}
		}
		if (row * col - EASY_COUNT - is_win(show, row, col) == 0)
		{
			printf("恭喜你,扫雷成功!\n");
			displayboard(mine, ROW, COL);
		}
		
}

实现效果,待改进,不足之处

【C】三子棋 VS 扫雷_第9张图片
标记功能每一次排雷后都要进行选择,感觉步骤太过繁琐,有待改进,以后有时间研究更新!


结语

各位小伙伴,看到这里就是缘分嘛,希望我的这些内容可以给你带来那么一丝丝帮助,可以的话三连支持一下呗(关注✌️点赞✌️评论✌️)!!!
感谢每一位走到这里的小伙伴,我们可以一起学习交流,一起进步!!!加油!!!

【C】三子棋 VS 扫雷_第10张图片

你可能感兴趣的:(【从0到1,C语言学习】,c语言)