手把手教你写三子棋(C语言超详细版本)

前言

三子棋设计是对C语言循环、判断语句、数组以及函数等知识点的综合应用,通过独立完成三子棋项目的编写,相信你对C语言的基础知识掌握会有一个大的提升。


一、游戏规则

三子棋游戏的玩法类似于我们小时候玩的井字棋游戏,棋盘大小为3×3,对战双方轮流下棋,当一方3枚棋子落于同行、同列或两个对角线上时,该方取胜;当棋盘下满仍没分出胜负时,判为平局;系统默认为玩家先下。

二、设计三子棋

1.创建文件

创建一个头文件,命名为game.h
创建两个源文件,分别命名为game.c和test.c

在这里插入图片描述
(1)game.h中存放所有头文件、define定义的宏常量以及声明所有用到的函数
在这里插入图片描述
(2)game.c中集中存放函数
在这里插入图片描述
(3)test.c源文件则存放程序的主体代码
在这里插入图片描述

2.思路与实现

(1)游戏菜单

要创建一个游戏菜单界面,至少有两个选项——开始游戏和退出游戏
在这里插入图片描述

1.使用menu函数封装菜单

封装内容即为游戏菜单的界面,界面较为简单,可直接用printf语句打印

void menu() {
	printf("**********************\n");
	printf("**** 1.play 2.exit****\n");
	printf("**********************\n");
}
2.使用do-while循环和switch语句选择

在main主函数内使用do-while循环和switch语句完成对游戏界面选项的选择:即在请选择:> 后面输入 1 即跳转至进行游戏环节,输入 2 即退出游戏,其它数字会显示输入错误,并重新打印出游戏界面,重新输入数字

int main() {

	int input = 0;
	do
	{
		menu();//打印菜单
		printf("请选择:>");
		scanf("%d", &input);
		switch (input) 
		{
		case 1:
			game();
			break;
		case 2:
			printf("退出游戏\n");
			system("pause");
			return 0;
		default:
			printf("选择错误\n");
			break;
		}
	} while(input);
	return 0;
}

注:在case 2 : 退出游戏阶段,我使用了System(“pause”) 和 return 0 语句来实现真正的退出游戏即退出程序,建议大家设计时,也使用这两行代码
在这里插入图片描述
到这里,第一步游戏菜单的代码就书写完毕,接下来是进入游戏环节

(2)棋盘的设计

三子棋进行游戏必须建立在棋盘上进行,接下来我们就开始设计棋盘

1.封装game函数

首先我们要先封装一个game函数,该函数中存放进行游戏的一系列操作,在用户在菜单界面输入1后,即进入game函数内执行操作
在这里插入图片描述

2.实现棋盘

在这里插入图片描述
根据棋盘模样,可将棋盘当做一个3×3的二维数组,行列均为3,所以我们可以在game函数内初始化一个char类型二维数组(因为棋子的模型为*和#,为char类型),以及存放下过的棋

char board[ROW][COL] = { 0 };//ROW COL在game.h中已经宏定义

为后期书写方便,我们可以把该二维数组的行与列在game.h头文件中宏定义一下

#define ROW 3
#define COL 3

接下来要对此二维数组初始化,保证该数组模样为"九宫格"

//棋盘初始化代码
void InitBoard(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++)
		{
			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]);
		if(i<row-1)
		{
			printf("---|---|---\n");
		}
	}
}

建议使用这种方法,能保证棋盘按照游戏的要求而呈现,例如:想实现五子棋,只需要宏定义 row 和 col 均为5,则数组就会变为5×5的格式,从而避免大规模改动代码

//版本二 打印棋盘的样子
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");优化做法
			int j = 0;
			for (j = 0; j < COL; j++)
			{
				printf("---");
				if(j<COL-1)
				printf("|");//保证行的最后一格最后没有|
			}
			printf("\n");
		}
	}
}

(3)玩家下棋

实现棋盘的设计后,接下来就是进行下棋环节

1.将玩家下棋操作步骤封装进PlayerMove函数中
void PlayerMove(char board[ROW][COL], int row, int col)
{
	int x = 0;
	int y = 0;
	printf("玩家下棋:>\n");
	while(1)//while循环确保玩家下了棋
	{
		printf("请输入坐标:>");
		scanf("%d %d", &x, &y);
		if (x >= 1 && x <= ROW && y >= 1 && y <= COL)//坐标范围合法进入判断
		{
			if (board[x - 1][y - 1] == ' ')//x-1,y-1后使其都在数组的下标范围内
			{
				board[x - 1][y - 1] = '*';//若此处数组存发的为空格,即将*放入其中,代表玩家下的棋
				break;//下完棋之后,交给对手下棋
			}
			else
			{
				printf("坐标被占用,重新输入\n");
			}
		}
		else
		{
			printf("坐标非法,请重新输入\n");
		}
	}
}
2.再次调用DisplayBoard函数,将玩家下好的棋盘展示出来
PlayerMove(board, ROW, COL);//玩家下棋
DisplayBoard(board, ROW, COL);//展示玩家下好后的棋盘

(4)电脑下棋

玩家下好棋后,轮到电脑下棋,注意:电脑下棋是随机性的,所以首先要引入随机函数。

1.需要在game.h头文件中添加如下三条预处理指令:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

其中#include <stdio.h>在头文件提前添加好,在源文件中直接引用就可以省去#include <stdio.h>在源文件中的再次书写

#include "game.h"

见下图,直接引用即可
在这里插入图片描述

2.在main主函数添加随机数种子

三条预处理指令添加完后,需要在main主函数添加随机数种子

//随机数函数引入
	srand((unsigned int)time(NULL));
3.用ComputerBoard函数封装电脑下棋操作

注意:调用rand函数来实现电脑随机下棋,rand() % a 代表电脑随机生成一个0~a-1的整数数值

//实现电脑下棋
//找没有下棋的位置随机下棋
void ComputerMove(char board[ROW][COL], int row, int col)
{
	printf("电脑下棋:>\n");
	
	int x = 0;
	int y = 0;

	while(1)//确保电脑下棋到正确位置上
	{
		x = rand() % ROW;//0~2的随机值
		y = rand() % COL;//0~2的随机值
		if (board[x][y] == ' ')
		{
			board[x][y] = '#';
			break;
		}
	}
}
4.再次调用DisPlayerBoard函数,展示电脑下完棋后的棋盘
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");优化做法
			int j = 0;
			for (j = 0; j < COL; j++)
			{
				printf("---");
				if(j<COL-1)
				printf("|");
			}
			printf("\n");
		}
	}
}

(5)判定游戏结束条件

用Iswin函数封装游戏结束条件,结束条件分为以下几种:

1.一方胜利

胜利条件又分为某一行相等、某一列相等、两个对角线相等三种情况;返回*为玩家赢 返回#为电脑赢

某一行相等:

其中 board[i][1] != ’ ’ 的意义在于排除均为空格的情况

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][0];//返回符号即可 返回*为玩家赢 返回#为电脑赢
		}		
	}
某一列相等:

其中 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[0][j];//返回符号即可 返回*为玩家赢 返回#为电脑赢
		}
	}
两个对角线相等:

其中 board[1][1] != ’ ’ 的意义在于排除均为空格的情况

//从左上到右下对角线
	if (board[0][0] == board[1][1] && board[1][1] == board[2][2] && board[1][1] != ' ')
	{
		return board[1][1];
	}

	//另一条对角线
	if (board[2][0] == board[1][1] && board[1][1] == board[0][2] && board[1][1] != ' ')
	{
		return board[1][1];
	}

2.平局

如果棋盘下满未分出胜负,则出现平局情况,返回Q代表游戏平局

//没有人赢,判断平局
	if (IsFull(board,ROW,COL))//判断棋盘是否满的函数IsFull()
	{
		return 'Q';//因为该函数返回值类型为char,所以不能写打印值
	}

封装一个IsFull函数判断九宫格是否下满棋,如果未下满则返回0,如果棋盘满则返回1,打印Q代表游戏平局

//判断棋盘是否满的函数,满了返回1,不满返回0
int IsFull(char board[ROW][COL], int row, int col)//相当于对二维数组的一个遍历
{
	for (int i = 0; i < ROW; i++)
	{
		for (int j = 0; j < COL; j++)
		{
			if (board[i][j] == ' ')
			{
				return 0;
				break;
			}
		}
	}
	return 1;
}
3.继续游戏

上述情况都遍历完毕后,返回C代表游戏继续

//游戏继续
	return 'C';

(6)while循环实现轮流下棋,并判断游戏是否结束

每次当玩家或者电脑下完棋之后,就判断是否满足结束条件,再打印此时的棋盘情况(ps:这里打印棋盘情况也可以放在判断条件之前,只不过差别在于最后会多打印一次棋盘)
注意:进入while循环之前要提前定义好char类型的ret,并将其初始化

char ret = 0;//ret接受结果

ret == C 代表棋未下完、未分出胜负,继续执行while循环
ret != C 代表达到游戏结束条件,但不知道胜负还是平局,需要继续进行if判断
进入if判断:
如果 ret == * 玩家胜利
如果 ret == # 电脑胜利
否则就是平局

while (1) {
		PlayerMove(board, ROW, COL);//玩家下棋
		//判断输赢
		ret = Iswin(board, ROW, COL);
		if (ret != 'C')
		{
			printf("游戏结束\n");
			break;
		}
		DisplayBoard(board, ROW, COL);
		ComputerMove(board, ROW, COL);//电脑下棋
		//判断输赢
		ret = Iswin(board, ROW, COL);
		if (ret != 'C')
		{
			printf("游戏结束\n");
			break;
		}
		DisplayBoard(board, ROW, COL);
	}
	if (ret == '*')
	{
		printf("玩家胜利\n");
		printf("棋盘最后的样子为:\n");
	}
	else if (ret == '#')
	{
		printf("电脑胜利\n");
		printf("棋盘最后的样子为:\n");
	}
	else
	{
		printf("平局\n");
		printf("棋盘最后的样子为:\n");
	}
	DisplayBoard(board, ROW, COL);//打印游戏结束时的情况
	system("pause");//确保最后游戏结束不会出现菜单
}

(7)结束游戏

最后打印一次结束时的棋盘模样,游戏结束

在这里插入图片描述
从这两张图中可看出 有无system(“pause”)的区别
所以我个人建议带上system(“pause”),这样才是真正实现游戏的结束!!

四、代码完整实现

1.头文件game.h

#define _CRT_SECURE_NO_WARNINGS 1
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <time.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 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);//四种状态 玩家赢-'*' 电脑赢-'#' 平局-'Q' 继续-'C'
//判断棋盘是否满的函数,满了返回1,不满返回0
int IsFull(char board[ROW][COL], int row, int col);

2.源文件game.h

#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"

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

void game() {
	char ret = 0;//ret接受结果
	char board[ROW][COL] = { 0 };//ROW COL在game.h中已经宏定义
	//游戏之前要初始化棋盘,要写一个棋盘的函数
	InitBoard(board, ROW, COL);//棋盘初始化
	DisplayBoard(board, ROW, COL);//构建完整九宫格棋盘
	//进行下棋步骤
	while (1) {
		PlayerMove(board, ROW, COL);//玩家下棋
		//判断输赢
		ret = Iswin(board, ROW, COL);
		if (ret != 'C')
		{
			printf("游戏结束\n");
			break;
		}
		DisplayBoard(board, ROW, COL);
		ComputerMove(board, ROW, COL);//电脑下棋
		//判断输赢
		ret = Iswin(board, ROW, COL);
		if (ret != 'C')
		{
			printf("游戏结束\n");
			break;
		}
		DisplayBoard(board, ROW, COL);
	}
	if (ret == '*')
	{
		printf("玩家胜利\n");
		printf("棋盘最后的样子为:\n");
	}
	else if (ret == '#')
	{
		printf("电脑胜利\n");
		printf("棋盘最后的样子为:\n");
	}
	else
	{
		printf("平局\n");
		printf("棋盘最后的样子为:\n");
	}
	DisplayBoard(board, ROW, COL);//打印游戏结束时的情况
	system("pause");//确保最后游戏结束不会出现菜单
}

//棋盘初始化代码
void InitBoard(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++)
		{
			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");优化做法
			int j = 0;
			for (j = 0; j < COL; j++)
			{
				printf("---");
				if(j<COL-1)
				printf("|");
			}
			printf("\n");
		}
	}
}
//实现玩家下棋
void PlayerMove(char board[ROW][COL], int row, int col)
{
	int x = 0;
	int y = 0;
	printf("玩家下棋:>\n");
	while(1)
	{
		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");
	
	int x = 0;
	int y = 0;

	while(1)//确保电脑下棋到正确位置上
	{
		x = rand() % ROW;//0~2的随机值
		y = rand() % COL;//0~2的随机值
		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][0];//返回符号即可 返回*为玩家赢 返回#为电脑赢
		}		
	}

	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[0][j];//返回符号即可 返回*为玩家赢 返回#为电脑赢
		}
	}

	//从左上到右下对角线
	if (board[0][0] == board[1][1] && board[1][1] == board[2][2] && board[1][1] != ' ')
	{
		return board[1][1];
	}

	//另一条对角线
	if (board[2][0] == board[1][1] && board[1][1] == board[0][2] && board[1][1] != ' ')
	{
		return board[1][1];
	}

	//没有人赢,判断平局
	if (IsFull(board,ROW,COL))//判断棋盘是否满的函数IsFull()
	{
		return 'Q';//因为该函数返回值类型为char,所以不能写打印值
	}

	//游戏继续
	return 'C';
}

//判断棋盘是否满的函数,满了返回1,不满返回0
int IsFull(char board[ROW][COL], int row, int col)//相当于对二维数组的一个遍历
{
	for (int i = 0; i < ROW; i++)
	{
		for (int j = 0; j < COL; j++)
		{
			if (board[i][j] == ' ')
			{
				return 0;
				break;
			}
		}
	}
	return 1;
}

3.源文件test.h

#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"

int main() {
	//随机数函数引入
	srand((unsigned int)time(NULL));

	int input = 0;
	do
	{
		menu();//打印菜单
		printf("请选择:>");
		scanf("%d", &input);
		switch (input) 
		{
		case 1:
			game();
			break;
		case 2:
			printf("退出游戏\n");
			system("pause");
			return 0;
		default:
			printf("选择错误,请重新输入\n");
			break;
		}
	} while(input);
	return 0;
}

五、总结

三子棋游戏的设计是比较考验大家对数组、循环语句、函数以及判断语句等知识点的综合应用,我在刚开始的学习过程中也遇到很多想不到的地方,通过课下反复的思考、反复的敲代码,现在对三子棋程序已具有了一个较为完整的思路框架,我认为其中的棋盘打印的优化、胜负条件的判断、如何判断棋盘已满这几个方面是思考的难点,希望我的文章对大家的学习有所帮助哦。。。

本篇文章,博主大概花了三个小时为大家精心整理,同时也是博主的第一篇博客哦,希望喜欢的uu们给博主点点关注,一键三连一下,未来我会为大家带来更多精彩的博客哦,同时也希望大家在学习编程的道路上都能够学有所成,早日实现自己进大厂的梦想!!!

  • 26
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值