三子棋游戏的具体实现


前言

大家好,我是耶鲁,今天我先出一篇关于三子棋游戏的具体实现,希望能激发大家学习C的兴趣,使咱们不仅会玩游戏,还能知道玩的游戏是怎么实现的。知其然知其所以然!!废话不多说,下面我们就直接进入主题。


一、三子棋游戏的玩法及规则

三子棋是黑白棋的一种。三子棋是一种民间传统游戏,又叫九宫棋、圈圈叉叉、一条龙、井字棋等。将正方形对角线连起来,相对两边依次摆上三个双方棋子,只要将自己的三个棋子走成一条线,对方就算输了。我相信大家小时候应该都玩过吧!

二、设计图

我们将实现三子棋游戏的程序分成下图所示的三个文件:
在这里插入图片描述
我们再将其细分,大概可设计为下图六大模块:
在这里插入图片描述

接下来我们将这几个模块一一实现,再将模块与模块之间相连接,即可实现三子棋游戏的实现。

小建议:我们写程序的时候,最好是写一部分测试一部分,特别是代码比较多的情况下,这样程序出现bug的话我们可以及时的发现,确保这一部分是正确的。如果等到最后再测试的话,出现问题,解决起来就比较麻烦。

1.游戏菜单

游戏菜单我们可以这样来设置:当玩家输入1时:开始游戏,当玩家输入0时:退出游戏,除此之外,如果玩家输入1 / 0之外的数字应该提醒玩家:输入错误,请重新输入。并且玩家可以多次进行游戏,如果玩一局不过瘾还可以再来一局。
经过上面的分析,我们大概可以知道程序要用到那些语句,我们可以使用switch语句来针对玩家所输入的数字进行相应的操作,使用循环语句使玩家可以按照自己的意愿选择是否多次进行游戏。

具体实现代码如下:在这里插入图片描述

2.设计棋盘

下棋肯定是需要一个棋盘的(一般都是3×3形式的棋盘),这里的棋盘既然是三行三列的,这不跟我们学过的二维数组很像吗?所以我们可以尝试用字符型二维数组存储棋盘上的数据。
我们上面说到棋盘形式一般都是3×3,但是如果有人想改变棋盘的形式,我们还得在程序中一处处的修改数据,太过于复杂,所以我们在头文件中利用 #define 来定义 ROW3,COL 3 这两个符号,就可以将程序中使用到3的地方用ROW和COL进行替换,也可以对这两处进行修改,进而改变我们棋盘的样式。

如下图所示:
在这里插入图片描述
我们先将棋盘初始化为空格,再利用 ’ ‘,’|’,’’ 这几个符号来刻画出棋盘,大概的效果图如下:
在这里插入图片描述
代码实现如下:
在这里插入图片描述图代码运行结果图如下:
在这里插入图片描述

如果我们想将棋盘改为10 × 10的样式,但是我们打印出来的棋盘为什么是10 × 3的样式呢?这是因为我们程序中输出数据的那一行已经写“死”了,它的列数已经固定下来了。所以就会出现如下结果:

在这里插入图片描述

通过观察我们可以把 %c | 看成一组,最后一列我们只打印 %c 不打印 | ,既然我们最后一列不打印 | ,所以我们要将 %c 和 | 分开打印。

代码实现如下:
在这里插入图片描述
代码运行结果如下,经过我们的修改,版本2的代码的实现就非常的灵活了。
在这里插入图片描述

3.规定棋子

棋子可以根据大家个人的喜好来设置,我这里规定玩家的棋子是’*’, 电脑的棋子是’#’。

4.实现玩家下棋

玩家和电脑每走一步棋之后,棋盘都要进行记录。所以再设置一个打印棋盘的函数,不论玩家还是电脑下棋之后我们都会调用打印棋盘的函数,观察对局情况。

在这里插入图片描述

5.实现电脑下棋

在实现电脑下棋之前我们先简单介绍一下rand函数:它是C语言中用来产生一个随机数的函数,rand函数界限:<stdlib.h>头文件中有宏 #define。RAND_MAX 0x7fff,rand产生一个0-0x7fff之间的的随机数,也就是最大是32767的一个数字。rand函数确实会帮我们生成一个随机数,但是生成随机数的方法有问题,看下图:这是我从rand介绍文档里面截取的一段话。这句话的大概意思是:调用rand函数之前我得先调用srand函数,我们要用srand来设置随机数的生成器。

在这里插入图片描述srand函数原型:
在这里插入图片描述

srand的参数应该是一个无符号整型并且是随时变化的,什么数字是随时在发生变化的?当然是时间啦!时间每分每秒都在发生变化。所以我们可以把时间传给srand这个函数。这里又得引入一个概念叫时间戳,大家如果感兴趣可以自行了解一下哈,我简单说明一下,时间戳就是目前的时间和计算机的起始时间的差值(秒)。我们想要获得时间戳就得利用C语言库函数time,time函数调用要给它传一个NULL,time_t是由typedef定义的整型值且函数头文件为<time.h>。

在这里插入图片描述
上述实现如下:
在这里插入图片描述
经过上面的一系列铺垫,电脑就可以得到一个随机数,再将随机数的数值通过取模运算控制在合法范围内即可。
在这里插入图片描述

6.对局状态

游戏的对局可分为四种状态
玩家赢 —’*'
电脑赢 — '#'
平均 — 'Q’
继续 ----'C’
根据返回相应的符号可知对局当前对应的状态。
分析:先设置一个判断输赢的函数,那一方三个棋子先连成一线的一方为胜方,横向、竖向、斜向均可,然后在判断是否平局,如果棋盘没有空格时就说明棋盘已经满了,此时还没有分出胜负的话,就说明此局是平局,否则就继续游戏。

代码实现如下:
在这里插入图片描述

三、游戏全部代码

test.c

#define _CRT_SECURE_NO_WARNINGS 1

#include "game.h"


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


void game()
{
	//数据存储到一个字符的二维数组中,玩家下棋是'*',电脑下棋是'#'

	char board[ROW][COL] = { 0 };      //最开始的时候数组的内容应该是全部空格

    //初始化棋牌
	InitBoard(board, ROW, COL); 

	//打印棋盘
	DisplayBoard(board, ROW, COL);

	//下棋
	char ret = 0;
	while (1)
	{
		player_move(board, ROW, COL);  //玩家先下
		DisplayBoard(board, ROW, COL); 
		ret = is_win(board, ROW, COL);
		if (ret != 'C')
		{
			break;
		}
		computer_move(board, ROW, COL);//电脑后下
		DisplayBoard(board, ROW, COL);
		ret = is_win(board, ROW, COL);
		if (ret != 'C')
		{
			break;
		}
	}
	if (ret == '*')
	{
		printf("玩家赢\n");
	}
	else if (ret == '#')
	{
		printf("电脑赢\n");
	}
	else
	{
		printf("平局\n");
	}
}




void test()
{
	int input = 0;
	srand((unsigned int)time(NULL));
	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);           //玩家可能输入1 / 0 / 其它数

		switch (input)                //运用switch语句可以根据玩家输入的数字,执行相应的语句
		{
		case 1:                      //输入1,进入game函数
			game();
			break; 
		case 0:                      //输入1,进入退出游戏
			printf("退出游戏\n");
			break;
		default:                     //输入其它数字
			printf("输入错误,请重新输入\n");
			break;
		}


	} while (input);           //输入的input为0,条件为假,跳出循环。输入的input不为0,条件为真,继续进入do...while循环
}


int main()
{
	test();


	return 0;
}

game.h

#pragma once


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


#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 player_move(char board[ROW][COL], int row, int col);


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

//
//判断输赢的代码
// 四种状态
//玩家赢 - '*'
//电脑赢 - '#'
//平均 --- 'Q'
//继续 ----'C'


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

game.c

#define _CRT_SECURE_NO_WARNINGS 1


#include "game.h"  //包含头文件,避免重复定义

//初始化棋盘的具体实现
void InitBoard(char board[ROW][COL], int row, int col)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < row; i++)  //利用for循环设置出三行三列的形式
	{
		for (j = 0; j < col; j++) 
		{
			board[i][j] = ' ';   //棋盘先初始化为空格
		}
	}
}


//版本1
//void DisplayBoard(char board[ROW][COL], int row, int col)
//{
//	int i = 0;
//	int j = 0;
//	for (i = 0; i < row; i++)
//	{
//		//数据
//		printf(" %c | %c | %c \n", board[i][0], board[i][1], board[i][2]); //列数已经固定
// 
//		//分割行
//		if(i<row-1)
//			printf("---|---|---\n");
//	}
//}




//版本2
void DisplayBoard(char board[ROW][COL], int row, int col)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < row; i++)
	{
		//数据
		for (j = 0; j < col; j++)
		{
			printf(" %c ", board[i][j]);   //一个数据的打印
			if (j < col - 1)     //使最后一列|不打印
				printf("|");
		}
		printf("\n");    

		//分割行
		if (i < row - 1)   //最后一行不打印分割行
		{
			for (j = 0; j < col; j++)
			{
				printf("---");
				if (j < col - 1)
					printf("|");
			}
		}
		printf("\n");
	}
}





//玩家下棋
void player_move(char board[ROW][COL], int row, int col)
{
	printf("玩家下棋:>");
	int x = 0;
	int y = 0;


	while (1)
	{
		scanf("%d %d", &x, &y);
		if (x >= 1 && x <= row && y >= 1 && y <= col)  //判断玩家输入坐标范围的合法性
		{
			if (board[x - 1][y - 1] == ' ')      //判断输入坐标处是否有棋子,从玩家的角度去考虑数组的行和列和我们习惯上的行和列有所差别,数组是从0开始,而我们习惯从1开始
			{
				board[x - 1][y - 1] = '*';
				break;         //玩家下完之后,跳出循环,等待电脑下棋
			}
			else
			{
				printf("该坐标被占用,请重新输入!\n");
			}
		}
		else
		{
			printf("坐标非法,请重输入!\n");
		}
	}
}




//电脑下棋
void computer_move(char board[ROW][COL], int row, int col)
{
	int x = 0;
	int y = 0;
	printf("电脑下棋>\n");
	while (1)
	{
		x = rand() % ROW;//0~2   //rand函数可以产生随机数且使随机数在合法坐标范围内
		y = rand() % COL;//0~2
		if (board[x][y] == ' ')  //判断输入坐标处是否有棋子
		{
			board[x][y] = '#';
			break;
		}
	}
}

//判断棋盘是否填满
int is_full(char board[ROW][COL], int row, int col)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < row; i++)
	{
		for (j = 0; j < col; j++)
		{
			if (board[i][j] == ' ') //有空格表示棋盘还没满
			{
				return 0;
			}
		}
	}
	return 1;   
}


char is_win(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];     //返回三个相等棋子的其中一个即可
		}
	}
	//三列
	for (i = 0; i < col; i++)
	{
		if (board[0][i] == board[1][i] && board[1][i] == board[2][i] && board[1][i] != ' ')  //每一列的棋子相等且不为空格
		{
			return board[1][i];  //返回三个相等棋子的其中一个即可
		}
	}
	//对角线的判断
	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 (1 == is_full(board, row, col))
	{
		return 'Q';
	}
	//棋盘还有空格,继续
	return 'C';
}


总结

好了,三子棋游戏具体的实现过程就介绍至此了,我坚信慢工出细活,所以文章更新得到较为缓慢,希望大家多多支持!我也会尽我最大的努力为大家讲解清楚每一处知识,但精力有限,文章不免有瑕疵之处,如果大家有什么问题或者建议的话,可以在评论区讨论指出,谢谢! ψ(*`ー´)ψ

Alt

  • 32
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 30
    评论
评论 30
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值