C语言实现三子棋游戏

本文介绍了一个使用C语言编写的三子棋游戏项目。该项目分为两个源文件,通过循环实现了游戏流程,包括玩家和电脑交替下棋直至决出胜负或平局。文章详细展示了每个函数的功能及其实现细节。

前言

这是我作为C语言入门者也是一名大一新生的第一篇博客,在此之前也经过了一小段时间的学习,成功写出了一个简易的三子棋小游戏,如有需改正之处,望大家批评指正。


思路

整个游戏的代码我计划分为两个源文件实现,第一个源文件test.c用于实现整个游戏的大体流程,第二个源文件game.c用于实现游戏具体步骤的逻辑,也因此需要一个头文件game.h来简化game.c中的函数在test.c中的声明。

在打开游戏时需要打印一份菜单供玩家选择开始游戏还是结束游戏,当玩家完成一盘游戏后,还需要询问玩家是否需要重新开始,因此可以通过循环来实现。

在游戏过程中,流程为打印空棋盘-->玩家下棋-->打印棋盘-->电脑下棋-->打印棋盘-->玩家下棋-->打印棋盘-->电脑下棋-->打印棋盘……循环至一方连成三颗棋或是棋盘被完全占用时,游戏结束。


游戏实现

    头文件game.h

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

//定义棋盘大小,可修改
#define ROW 5
#define COL 5

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

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

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

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

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

头文件game.h的作用在于声明函数、包含系统头文件、以及定义常量。

声明函数:简化在tect.c中声明game.c中函数的过程

包含系统头文件:两个源文件中只需包含game.h即可

定义常量:只需在game.h中改变常量即可改变棋盘大小


    源文件test.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 board[ROW][COL];
	char ret;
	InitBoard(board, ROW, COL);//初始化二维数组
	DisplayBoard(board, ROW, COL);//打印棋盘
	while (1)
	{
		PlayerMove(board, ROW, COL);//玩家下棋
		DisplayBoard(board, ROW, COL);
		ret = IsWin(board, ROW, COL);//判断是否结束
		if (ret != 'C')
			break;
		ComputerMove(board, ROW, COL);//电脑下棋
		DisplayBoard(board, ROW, COL);
		ret = IsWin(board, ROW, COL);
		if (ret != 'C')
			break;
	}
	if (ret == '*')
		printf("玩家获胜\n");
	else if (ret == '#')
		printf("电脑获胜\n");
	else
		printf("平局\n");
}

int main()
{
	int input = 0;
	srand((unsigned)time(NULL));
	do//初始菜单
	{
		menu();//打印菜单
		printf("请输入:>");
		scanf("%d", &input);
		switch (input)
		{
			case 1:
				game();
				break;
			case 0:
				printf("结束游戏");
				break;
			default:
				printf("输入错误,请重新输入");
				break;
		}
	}while (input);
	return 0;
}

        1.main函数

int main()
{
	int input = 0;
	srand((unsigned)time(NULL));
	do//初始菜单
	{
		menu();//打印菜单
		printf("请输入:>");
		scanf("%d", &input);
		switch (input)
		{
			case 1:
				game();
				break;
			case 0:
				printf("结束游戏");
				break;
			default:
				printf("输入错误,请重新输入");
				break;
		}
	}while (input);
	return 0;
}

在main函数中我们需要实现游戏的大体流程。

因为需要允许多次游玩,并且至少游玩一次(开始游戏或结束游戏都算),所以使用do while循环。

此处使用变量input接受玩家菜单页面选择结果,可直接使用input作为do while循环的判断条件(0为假,非0为真)。

switch语句的使用是为了更加直观,也可使用if语句。


        2.menu函数

void menu()
{
	printf("******************************\n");
	printf("**********1.开始游戏**********\n");
	printf("**********0.结束游戏**********\n");
	printf("******************************\n");
}

此函数负责打印菜单,目的在于使main函数中的思路更加直观。

 menu函数效果


         3.game函数

void game()
{
	char board[ROW][COL];
	char ret;
	InitBoard(board, ROW, COL);//初始化二维数组
	DisplayBoard(board, ROW, COL);//打印棋盘
	while (1)
	{
		PlayerMove(board, ROW, COL);//玩家下棋
		DisplayBoard(board, ROW, COL);
		ret = IsWin(board, ROW, COL);//判断是否结束
		if (ret != 'C')
			break;
		ComputerMove(board, ROW, COL);//电脑下棋
		DisplayBoard(board, ROW, COL);
		ret = IsWin(board, ROW, COL);
		if (ret != 'C')
			break;
	}
	if (ret == '*')
		printf("玩家获胜\n");
	else if (ret == '#')
		printf("电脑获胜\n");
	else
		printf("平局\n");
}

此函数同样是为了使main函数中的思路更加直观,将整个三子棋游戏逻辑单独显示。

由于棋盘是二维的,因此需要创建一个二维数组并完全初始化。初始化后则需要将棋盘打印出来展示给玩家,此后需要通过循环时间玩家和电脑反复下棋并判断游戏是否结束。


    源文件game.c

#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"

void InitBoard(char board[ROW][COL], int row, int col)
{
	int i, j;
	for (i = 0; i < row; i++)
	{
		for (j = 0; j < col; j++)
		{
			board[i][j] = ' ';
		}
	}
}

void DisplayBoard(char board[ROW][COL], int row, int col)
{
	int i, j;
	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 PlayerMove(char board[ROW][COL], int row, int col)
{
	while (1)
	{
		int x,y;
		printf("玩家走,请输入坐标:>");
		scanf("%d %d", &x, &y);
		if (x >= 1 && x <= row && y>= 1 && y <= col)
		{
			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() % row;
		int y = rand() % col;
		if (board[x][y] == ' ')
		{
			board[x][y] = '#';
			break;
		}
	}
}

char IsWin(char board[ROW][COL], int row, int col)
{
	int x, y;
	for (x = 0; x < row - 2; x++)
	{
		for (y = 0; y < col; y++)//判断一列
		{
			if (board[x][y] == board[x + 1][y] && board[x + 1][y] == board[x + 2][y] && board[x][y] != ' ')
				return board[x][y];
		}
		for (y = 0;y < col - 2; y++)//判断右斜
		{
			if (board[x][y] == board[x + 1][y + 1] && board[x + 1][y + 1] == board[x + 2][y + 2] && board[x][y] != ' ')
				return board[x][y];
		}
		for (y = 3; y < col; y++)//判断左斜
		{
			if (board[x][y] == board[x + 1][y - 1] && board[x + 1][y - 1] == board[x + 2][y - 2] && board[x][y] != ' ')
				return board[x][y];
		}
	}
	for (x = 0; x < row; x++)
	{
		for (y = 0; y < col - 2; y++)//判断一列
		{
			if (board[x][y] == board[x][y + 1] && board[x][y + 1] == board[x][y + 2] && board[x][y] != ' ')
				return board[x][y];
		}
	}
	for (x = 0; x < row; x++)
	{
		for (y = 0; y < col; y++)//判断满格
		{
			if (board[x][y] == ' ')
				return 'C';
		}
	}
	return 'P';
}

        1.InitBoard函数

void InitBoard(char board[ROW][COL], int row, int col)
{
	int i, j;
	for (i = 0; i < row; i++)
	{
		for (j = 0; j < col; j++)
		{
			board[i][j] = ' ';
		}
	}
}

 InitBoard函数通过两个for循环将二维数组全部初始化为' '。


        2.DisplayBoard函数

void DisplayBoard(char board[ROW][COL], int row, int col)
{
	int i, j;
	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");
	}

 DisplayBoard函数同样是通过双重for循环打印出二维数组以及棋盘的分界线。

DisplayBoard函数效果


        3.PlayerMove函数

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

当玩家下棋时,需要玩家成功完成下棋步骤才能够执行下一步,因此需要一个while(1)死循环使得当玩家输入坐标非法或被占用时能够重新下棋。

PlayerMove函数中需要注意二维数组的行和列都是从0开始,因此需要将玩家输入的坐标各减去1才能对应上。

 PlayerMove函数效果


        4.ComputerMove函数

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

为防止电脑随机下棋位置非法,使用rand()% row和 rand()% col限定随机数的只能取0到(row-1)或(col-1)。

同时为了防止电脑随机下棋位置已被占用后可以重新下棋,同样需要一个while(1)死循环和if语句判断并break。

 ComputerMove函数效果


         5.IsWin函数

char IsWin(char board[ROW][COL], int row, int col)
{
	int x, y;
	for (x = 0; x < row - 2; x++)
	{
		for (y = 0; y < col; y++)//判断一列
		{
			if (board[x][y] == board[x + 1][y] && board[x + 1][y] == board[x + 2][y] && board[x][y] != ' ')
				return board[x][y];
		}
		for (y = 0;y < col - 2; y++)//判断右斜
		{
			if (board[x][y] == board[x + 1][y + 1] && board[x + 1][y + 1] == board[x + 2][y + 2] && board[x][y] != ' ')
				return board[x][y];
		}
		for (y = 3; y < col; y++)//判断左斜
		{
			if (board[x][y] == board[x + 1][y - 1] && board[x + 1][y - 1] == board[x + 2][y - 2] && board[x][y] != ' ')
				return board[x][y];
		}
	}
	for (x = 0; x < row; x++)
	{
		for (y = 0; y < col - 2; y++)//判断一列
		{
			if (board[x][y] == board[x][y + 1] && board[x][y + 1] == board[x][y + 2] && board[x][y] != ' ')
				return board[x][y];
		}
	}
	for (x = 0; x < row; x++)
	{
		for (y = 0; y < col; y++)//判断满格
		{
			if (board[x][y] == ' ')
				return 'C';
		}
	}
	return 'P';
}

因为涉及到二维数组各个量,因此每次判断都需要双重for循环。

需要注意的是无论是判断一行、一列、左斜还是右斜,都需要限制x和y的范围,防止超出二维数组的范围。

另外还得确保对于是否满格平局的判断放在最后,即使判断满格和判断一列外层的for函数一致也不能放到一起,否则前四种情况还没完成判断时就返回了'C',导致即使玩家或电脑赢了,游戏依旧继续。

最后通过返回值判断电脑赢了、玩家赢了、平局还是继续。


结语

目前该函数还有优化的空间,尤其是对电脑下棋的算法进行设计,但目前我还不具备足够的能力去设计,日后也许会填坑。

第一次写博客的感觉很奇妙,本来是已经想好了大致的思路,但一上手后还是有点混乱。经过反复的打磨,我也对自己写的代码有了更深刻的认识,也许几天后就忘记的代码一下子就印在了脑海里,这让我更加确信写博客并不是浪费时间,而是对知识点的巩固,与此同时还能收获大家得指正,何乐而不为呢?

在这之后我也会利用闲暇时间将自己写过的代码以及学习经历写成博客分享出来,预计下一篇是这几天刚写完的扫雷游戏。

基于可靠性评估序贯蒙特卡洛模拟法的配电网可靠性评估研究(Matlab代码实现)内容概要:本文围绕“基于可靠性评估序贯蒙特卡洛模拟法的配电网可靠性评估研究”,介绍了利用Matlab代码实现配电网可靠性的仿真分析方法。重点采用序贯蒙特卡洛模拟法对配电网进行长时间段的状态抽样与统计,通过模拟系统元件的故障与修复过程,评估配电网的关键可靠性指标,如系统停电频率、停电持续时间、负荷点可靠性等。该方法能够有效处理复杂网络结构与设备时序特性,提升评估精度,适用于含分布式电源、电动汽车等新型负荷接入的现代配电网。文中提供了完整的Matlab实现代码与案例分析,便于复现和扩展应用。; 适合人群:具备电力系统基础知识和Matlab编程能力的高校研究生、科研人员及电力行业技术人员,尤其适合从事配电网规划、运行与可靠性分析相关工作的人员; 使用场景及目标:①掌握序贯蒙特卡洛模拟法在电力系统可靠性评估中的基本原理与实现流程;②学习如何通过Matlab构建配电网仿真模型并进行状态转移模拟;③应用于含新能源接入的复杂配电网可靠性定量评估与优化设计; 阅读建议:建议结合文中提供的Matlab代码逐段调试运行,理解状态抽样、故障判断、修复逻辑及指标统计的具体实现方式,同时可扩展至不同网络结构或加入更多不确定性因素进行深化研究。
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ZDJeffrey

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值