三子棋(N子棋)游戏的保姆级超详细教程(C语言)

本文详细介绍了使用C语言实现三子棋游戏的完整过程,包括游戏菜单、初始化棋盘、打印棋盘、玩家与计算机轮流下棋、判断输赢等功能。代码逻辑清晰,通过函数模块化设计提高了可读性和可维护性。此外,文章还探讨了游戏的运行逻辑和部分代码细节,为读者提供了深入理解的基础。
摘要由CSDN通过智能技术生成

⭐博客主页:️CS semi主页
⭐欢迎关注:点赞收藏+留言
⭐系列专栏:C语言初阶
⭐代码仓库:C Advanced
家人们更新不易,你们的点赞和关注对我而言十分重要,友友们麻烦多多点赞+关注,你们的支持是我创作最大的动力,欢迎友友们私信提问,家人们不要忘记点赞收藏+关注哦!!!

写在前面(前言)

1. 何为三子棋:

三子棋是一种民间传统游戏,又叫九宫棋、圈圈叉叉棋、一条龙、井字棋等。游戏分为双方对战,双方依次在9宫格棋盘上摆放棋子,率先将自己的三个棋子走成一条线就视为胜利,而对方就算输了,但是三子棋在很多时候会出现和棋的局面。

2. 为什么要和人工智障玩三子棋:

这个问题也困扰了我很久,如果我和朋友一起玩的话,肯定是和棋,但人工智障我有概率能赢呀,计算机的运行是由人预先编制的,很简单,因为我还没学到Alphago那种强人工智能,只能欺负欺负简单的人工智障了。(ps.关于计算机的运行为什么是人预先编制的后期我会出一篇博客,关注我,不迷路~~)

~~~~~~~~~~~~~~~~~~~~~~~~~~正文开始~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

一、制作三子棋游戏的思路

  1. 制作游戏单(menu),让玩家选择玩游戏或者退出游戏。
  2. 进入游戏后初始化棋盘并进行打印。
  3. 三子棋函数的创立
  4. 玩家先落子。
  5. 计算机随机落子。
  6. 判断哪一方胜利。
  7. 玩家选择是否还玩游戏。(自动就会跳出来了)

二、详细步骤(附每一步代码)

要实现此游戏,需要创建几个模块,当然是为了方便啦,一个头文件和两个源文件。

  1. test.c(测试逻辑)
  2. game.h.c(函数头文件)
  3. game.c(函数内容)
    (ps.括号里的命名比较具象,为了让读者能够更好理解该模块表达的意思)
    在这里插入图片描述

(一)游戏单的创建

void menu() {
	printf("\n");
	printf("这是个三子棋的游戏,冒险者你准备好了吗?\n");
	printf("如果你准备好了请扣个1,没准备好请扣个0\n");
	printf("1.直接进入\n");
	printf("0.那退出吧\n");
}
void test() {
	int input = 0;
	do   //这里是为了实现游戏的重复进行 
	{
		int input = 0;
		menu();
		printf("请选择:>");
		scanf("%d", &input);  //玩家输入1或者0显示玩不玩游戏
		switch (input) {   //switch是分支选择是否开始玩游戏
		case 1:
			printf("开始游戏\n");
			game();   //game()是这个游戏的程序开始玩的函数
			break;
		case 0:
			printf("退出游戏\n");
			break;
		default:
			printf("输入错误\n");
			break;
		}
	} while (input);  //i是输入的是1或者0,此时,非0的意思是继续游戏,0是退出游戏
}
int main() {
	menu();
	test();
	return 0;
}

大家到这里就有疑惑了,那个game();是个什么东东,这找了半天也没发现这个game();的表达式呀!客官且听我分析,在该文件中还有一个未定义的game();在抬头,后面会有模块写出game()函数的。

(二) 初始化棋盘并打印棋盘

首先,在运用一个函数的时候,不仅仅要定义一个函数,而且还要声明它,就好比你辛辛苦苦写完了一本实验报告,但是忘记给老师了,老师就在实验报告这一栏的评分打零分。显而易见,三子棋的棋盘是一个3*3的二维数组。(ps.函数为何要声明,是因为计算机运行这个代码是从上往下逐级取指令,在后期我也会出一篇博客讲关于函数栈帧和函数声明和定义的博客)
代码如下:

1. 初始化棋盘:

  1. 头文件game.h.c中:
#define ROW 3   //采用宏定义行(ROW),这是因为可以更改宏定义后面的数字使得玩N字棋
#define COL 3   //采用宏定义列(COL),理由如上
  1. 源文件game.c中
void chu_shi_hua_board(char board[ROW][COL], int row, int col) {
	for (int i = 0; i < row; i++) {
		for (int j = 0; j < col; j++) {
			board[i][j] = ' ';  //此时是定义棋盘里面的所有格子为空
		}
	}
}

此时大家可能有疑惑了,为什么这个函数就凭空出现了呢?大家不要着急,此函数的声明在头文件game.h.c中显示,详情看下面的代码。
那定义完棋盘了以后,发现,怎么打印了一堆空格?所以,接下来来进行在每个空格中打印分割线吧!

2. 打印棋盘:

void da_yin_board(char board[ROW][COL], int row, int col)
{
	int i = 0;
	for (i = 0; i < row; i++) {  //i遍历行数
		int j = 0;
		for (j = 0; j < col; j++) {  //j遍历列数
			printf(" %c ", board[i][j]);
			if (j < col - 1) {  //最后一行多了一个|,用判断语句j<col-1
				printf("|");
			}
		}
		printf("\n");
		if (i < row - 1) {    //最下面多了个---
			for (j = 0; j < col; j++) {
				printf("---");
				if (j < col - 1) {   //最右边多了个|
					printf("|");
				}
			}
				printf("\n");
		}
	}
}在这里插入代码片

看到这里,大家可能又有疑惑了,这串代码什么意思?那就让我在下面放上两串代码:

printf(" %c | %c | %c \n", board[i][0], board[i][1], board[i][2]);
printf("---|---|---\n");

很轻松的看的出来,board[i][j]对应的是初始化中的空格,刚好对应“—”中的最中间的那个“-”,其中的if(j<col-1)和if (i < row - 1)是因为如果没有这个语句,右边或者底下会多出来一个符号,不美观!强迫症必须把它对齐!!!

在这里解释一下棋盘是以什么组合形式打印的:
错误例子:在这里插入图片描述
这个组合形式有点诡异,如果你仔细一想,这怎么把顶上三个组合在一起了?如果宏定义定的行列不是3呢?是5呢?是10呢?结果发现只有三列!是不对滴!

正确的组合:
在这里插入图片描述
以一个大格子和一个竖线所组合的为一个组合,是正确滴!

(三)玩家下棋

闲话不多说,直接上代码:

void wan_jia_turn_board(char board[ROW][COL], int row, int col) {
	printf("玩家下棋\n");
	while (1) {      //while(1)就是个死循环,永远满足条件,永远循环,因为1(非0)为真
		printf("请输入要下棋的坐标点位:>");
		int x = 0;
		int y = 0;
		scanf("%d %d", &x, &y); //点的坐标
		if ((x >= 1 && x <= row) && (y >= 1 && y <= col)) {  //程序小白不知道数组是从0开始的,故从1开始,下面要减去1
			if (board[x - 1][y - 1] == ' ') {
				board[x - 1][y - 1] = '*';  //空格就代表只有个空格,加上玩家下的棋子
				break;
			}
			else {
				printf("坐标点位被占用,请重新输入\n");
			}
		}
		else {
			printf("输入非法\n");
		}
	}
}

(四)计算机下棋

void ji_suan_ji_turn_board(char board[ROW][COL], int row, int col) {
	printf("计算机下棋\n");
	while (1) {
		int x = rand() % row;  //x为rand模上个row范围为(0到2)
		int y = rand() % col;  //范围也是(0到2)
		if (board[x][y] == ' ') {
			board[x][y] = 'o';
			break;
		}
	}
}

此处随机数解释过于多,简单介绍为模3是模出来余数,范围是0到2,后续我会出一篇博客来向大家介绍一下随机数!

(五)判断输赢

如何判断输赢是此游戏最难的一关,大家要仔细看!四种情况:玩家赢,计算机赢,平局,游戏没结束(继续)。

//判断棋盘是不是满了(欲知static为何物,之后博客来讲解)
static 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++) {
			return 0;
		}
	}
	return 1;
}

//判断谁赢
char shei_ying(char board[ROW][COL], int row, int col) {
	//情况过于多,横相等,竖相等,两个斜对角线中任一一条对角线相等
	int i = 0;
	//三列相等
	for (i = 0; i < row; i++) {
		if (board[0][i] == board[1][i] && board[1][i] == board[2][i] && board[0][i] != ' ') {  //三列当中任一一列是相等的且任一一个元素为空格都不行
			return board[0][i]; //细节是返回的元素正好是判断电脑还是玩家赢的符号
		}
	}
	//三行相等
	for (i = 0; i < col; i++) {
		if (board[i][0] == board[i][1] && board[i][1] == board[i][2] && board[i][0] != ' ') {
			return board[i][0];
		}
	}
	// \样的对角线相等
	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 (is_full(board, row, col) == 1) {
		return 'P';
	}
	//继续,谁都没赢,也没平局
	return'C';
}

(六)game();

哈哈哈,虽然那么多且听下篇分析,但这个game();里面的内容没有忘记!上代码!

void game() {
	char ret = 0;
	//先声明一个二维数组
	char board[ROW][COL];
	//初始化棋盘使其为全空格
	chu_shi_hua_board(board, ROW, COL);
	//打印棋盘的横线与竖线
	da_yin_board(board, ROW, COL);
	while (1) {   //死循环,玩家和计算机交替下棋
		//玩家下棋
		wan_jia_turn_board(board, ROW, COL);
		//打印棋盘的横线与竖线
		da_yin_board(board, ROW, COL);
		
		//判断谁赢了的函数
		ret = shei_ying(board, ROW, COL);
		if (ret != 'C') {
			break;    //跳出此次循环
		}
		//计算机下棋的函数
		ji_suan_ji_turn_board(board,ROW,COL);
		//打印棋盘的横线与竖线
		da_yin_board(board, ROW, COL);
		//判断输赢
		ret = shei_ying(board, ROW, COL);
		if (ret != 'C') {
			break;  //跳出此次循环
		}
	}  
	if (ret == 'o') {
		printf("电脑赢\n");
	}
	else if (ret == '*'){
		printf("玩家赢\n");
	}
	else if (ret == 'P') {
		printf("平局\n");
	}
}
//电脑赢:o
//玩家赢:*
//平局:P
//游戏继续:C

代码汇总

game.h.c

#pragma once

#define ROW 3   //采用宏定义行(ROW),这是因为可以更改宏定义后面的数字使得玩N字棋
#define COL 3   //采用宏定义列(COL),理由如上

//所需要用的库函数(库函数也是由其他函数创立的,涉及到了函数栈帧的创建与销毁)
#include<stdio.h>
#include<string.h>
#include<time.h>
#include<stdlib.h>


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

//打印棋盘的函数
void da_yin_board(char board[ROW][COL], int row, int col);

//玩家先下棋的函数
void wan_jia_turn_board(char board[ROW][COL], int row, int col);

//计算机下棋的函数
void ji_suan_ji_turn_board(char board[ROW][COL], int row, int col);

//判断谁赢了的函数
char shei_ying(char board[ROW][COL], int row, int col);

test.c

#define _CRT_SECURE_NO_WARNINGS 1


#include"game.h.c"


void menu() {
	printf("\n");
	printf("这是个三子棋的游戏,冒险者你准备好了吗?\n");
	printf("如果你准备好了请扣个1,没准备好请扣个0\n");
	printf("1.直接进入\n");
	printf("0.那退出吧\n");
}
void game() {
	char ret = 0;
	//先声明一个二维数组
	char board[ROW][COL];
	//初始化棋盘使其为全空格
	chu_shi_hua_board(board, ROW, COL);
	//打印棋盘的横线与竖线
	da_yin_board(board, ROW, COL);
	while (1) {   //死循环,玩家和计算机交替下棋
		//玩家下棋
		wan_jia_turn_board(board, ROW, COL);
		//打印棋盘的横线与竖线
		da_yin_board(board, ROW, COL);
		
		//判断谁赢了的函数
		ret = shei_ying(board, ROW, COL);
		if (ret != 'C') {
			break;    //跳出此次循环
		}
		//计算机下棋的函数
		ji_suan_ji_turn_board(board,ROW,COL);
		//打印棋盘的横线与竖线
		da_yin_board(board, ROW, COL);
		//判断输赢
		ret = shei_ying(board, ROW, COL);
		if (ret != 'C') {
			break;  //跳出此次循环
		}
	}  
	if (ret == 'o') {
		printf("电脑赢\n");
	}
	else if (ret == '*'){
		printf("玩家赢\n");
	}
	else if (ret == 'P') {
		printf("平局\n");
	}
}
//电脑赢:o
//玩家赢:*
//平局:P
//游戏继续:C

void test() {
	srand(time(NULL));
	int input = 0;
	do   //这里是为了实现游戏的重复进行 
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);  //玩家输入1或者0显示玩不玩游戏
		switch (input) {   //switch是分支选择是否开始玩游戏
		case 1:
			/*printf("开始游戏\n");*/
			game();   //game()是这个游戏的程序开始玩的函数
			break;
		case 0:
			printf("退出游戏\n");
			break;
		default:
			printf("输入错误\n");
			break;
		}
	} while (input);  //i是输入的是1或者0,此时,非0的意思是继续游戏,0是退出游戏
}
int main() {
	test();
	return 0;
}

game.c

#define _CRT_SECURE_NO_WARNINGS 1


#include"game.h.c"
//初始化空的棋盘
void chu_shi_hua_board(char board[ROW][COL], int row, int col) {
	for (int i = 0; i < row; i++) {
		for (int j = 0; j < col; j++) {
			board[i][j] = ' ';  //此时是定义棋盘里面的所有格子为空
		}
	}
}
//打印棋盘当中的横线和竖线,使得棋盘更加简洁明了
void da_yin_board(char board[ROW][COL], int row, int col)
{
	int i = 0;
	for (i = 0; i < row; i++) {  //i遍历行数
		int j = 0;
		for (j = 0; j < col; j++) {  //j遍历列数
			printf(" %c ", board[i][j]);
			if (j < col - 1) {  //最后一行多了一个|,用判断语句j<col-1
				printf("|");
			}
		}
		printf("\n");
		if (i < row - 1) {    //最下面多了个---
			for (j = 0; j < col; j++) {
				printf("---");
				if (j < col - 1) {   //最右边多了个|
					printf("|");
				}
			}
				printf("\n");
		}
	}
}
//printf(" %c | %c | %c \n", board[i][0], board[i][1], board[i][2]);
//printf("---|---|---\n");

//玩家下棋
void wan_jia_turn_board(char board[ROW][COL], int row, int col) {
	printf("玩家下棋\n");
	while (1) {      //while(1)就是个死循环,永远满足条件,永远循环,因为1(非0)为真
		printf("请输入要下棋的坐标点位:>");
		int x = 0;
		int y = 0;
		scanf("%d %d", &x, &y); //点的坐标
		if ((x >= 1 && x <= row) && (y >= 1 && y <= col)) {  //程序小白不知道数组是从0开始的,故从1开始,下面要减去1
			if (board[x - 1][y - 1] == ' ') {
				board[x - 1][y - 1] = '*';  //空格就代表只有个空格,加上玩家下的棋子
				break;
			}
			else {
				printf("坐标点位被占用,请重新输入\n");
			}
		}
		else {
			printf("输入非法\n");
		}
	}
}
//计算机下棋
//随机生成坐标,只要坐标没有被占用,就下棋
void ji_suan_ji_turn_board(char board[ROW][COL], int row, int col) {
	printf("计算机下棋\n");
	while (1) {
		int x = rand() % row;  //x为rand模上个row范围为(0到2)
		int y = rand() % col;  //范围也是(0到2)
		if (board[x][y] == ' ') {
			board[x][y] = 'o';
			break;
		}
	}
}

//判断棋盘是不是满了(欲知static为何物,之后博客来讲解)
static 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++) {
			return 0;
		}
	}
	return 1;
}

//判断谁赢
char shei_ying(char board[ROW][COL], int row, int col) {
	//情况过于多,横相等,竖相等,两个斜对角线中任一一条对角线相等
	int i = 0;
	//三列相等
	for (i = 0; i < row; i++) {
		if (board[0][i] == board[1][i] && board[1][i] == board[2][i] && board[0][i] != ' ') {  //三列当中任一一列是相等的且任一一个元素为空格都不行
			return board[0][i]; //细节是返回的元素正好是判断电脑还是玩家赢的符号
		}
	}
	//三行相等
	for (i = 0; i < col; i++) {
		if (board[i][0] == board[i][1] && board[i][1] == board[i][2] && board[i][0] != ' ') {
			return board[i][0];
		}
	}
	// \样的对角线相等
	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 (is_full(board, row, col) == 1) {
		return 'P';
	}
	//继续,谁都没赢,也没平局
	return'C';
}

总结

人工智障怎么那么笨啊!玩了那么多把它就是赢不了!
话回正题,此串代码虽然很长很复杂,但是思路很清晰,按照思路来,终有一刻会敲好的,在设计这个代码的时候,要进行分区设计,这样子会让程序更加清晰,还有很多复杂的需要细节的小程序块需要大家多多注意,只要肯吃苦,代码就能敲好!

亟须解决的问题

这个代码看似很完善,可是,有一个小bug,没有处理出来当电脑先下棋还是玩家先下棋,欲知后事如何,且看下篇博客~~~~~~~~

客官,都读到这边了,给个三连吧 ~谢谢~

评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

2022horse

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

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

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

打赏作者

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

抵扣说明:

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

余额充值