【c语言刷题】三子棋(万字详解)

代码汇总:

先上总代码

game.h文件:

#pragma once
#include <stdio.h>
#define ROW 3
#define COL 3
#define _CRT_SECURE_NO_WARNINGS
#pragma warning(disable:4996)

void game();
void initborad(char arr[ROW][COL], int row, int col);
void Displayborad(char arr[ROW][COL], int row, int col);
void playermove(char arr[ROW][COL], int row, int col);
void computermove(char arr[ROW][COL], int row, int col);
char isWin(char arr[ROW][COL], int row, int col);
int isFull(char arr[ROW][COL], int row, int col);

 tset.c文件: 

#include "game.h"
void menu() {

	printf("欢迎游玩三子棋\n");
	printf("***********************\n");
	printf("****** 1.开始游戏 ******\n");
	printf("****** 0.退出游戏 ******\n");
	printf("***********************\n");
}
int main() {
	int falg = 1;//标记使程序能够退出。
	do {
		menu();
		printf("请输入数字\n");
		int scanner;
		scanf("%d", &scanner);
		switch (scanner) {
		case 1:
			printf("祝您游戏愉快!!!\n");
			game();
			falg = 1;//继续游玩
			break;
		case 0:
			printf("感谢游玩\n");
			falg = 0;//结束游玩
			break;
		default:
			printf("输入非法,请重新输入\n");
			break;
		}
	}while (falg);//利用do whlie循环以完成菜单栏的开启与退出。
	return 0;
}
void game() {
	char arr[ROW][COL] = { 0 };//定义数组用来存放位置坐标。
	//初始化
	initborad(arr, ROW, COL);
	char win = 0;//标记
	int flag = 1;//标记
	while (flag) {
		Displayborad(arr, ROW, COL);//打印棋盘,初始化棋盘,让玩家看到电脑落子
		playermove(arr, ROW, COL);
		Displayborad(arr, ROW, COL);//打印棋盘,让玩家知晓是否落子成功
		//判断输赢
		win = isWin(arr, ROW, COL);//利用先前的标记存储
		if (win != 'c') {
			Displayborad(arr, ROW, COL);//打印棋盘,显示哪里三个相连。
			flag = 0;
			break;
		}
		computermove(arr, ROW, COL);
		win = isWin(arr, ROW, COL);
		if (win != 'c') {
			Displayborad(arr, ROW, COL);//打印棋盘,显示哪里三个相连。
			flag = 0;
			break;
		}
	}
	if (win == '*') {
		printf("玩家获胜!!!!\n");
	}
	if (win == '#') {
		printf("电脑获胜!!!!\n");
	}
	if (win == 'Q') {
		printf("平局!!!!\n");
	}
	return 0;
}

game.c文件:

 

#include "game.h"

void initborad(char arr[ROW][COL], int row, int col) {
	//初始化棋盘
	for (int i = 0; i < row; i++) {
		for (int j = 0; j < col; j++) {
			//利用数组遍历完成对每一个元素的填充,以保证每一个元素初始值都为‘ ’;
			arr[i][j] = ' ';
		}
	}//初始化使每一个节点都存储‘ ’避免棋盘内部因为没有存储字符而导致棋盘不规整。
}
void Displayborad(char arr[ROW][COL], int row, int col) {
	for (int i = 0; i < row; i++) {
		//打印数据
		for (int j = 0; j < col; j++) {
			//利用数组遍历来打印完整个棋盘
			printf(" %c ", arr[i][j]);
			if (j < col - 1) {
				//通过实践发现在棋盘的最后我们并不需要‘|’故在此处对其进行限制
				//col表示的是列,col - 1即代表少输出一列。
				printf("|");
			}
		}
		printf("\n");

		if (i < row - 1) {
			//此处同理,row表示行,但最后一行并不需要输出‘---’所以此处限制 i < row-1,少输出一行
			for (int j = 0; j < col; j++) {
				printf("---");
				if (j < col - 1) {
					printf("|");
				}
			}
			printf("\n");
		}
	}
}
void playermove(char arr[ROW][COL], int row, int col) {
	int x, y;
	int	flag = 1;//标记
	while (flag) {
		printf("玩家落子\n");
		printf("请输入所落子位置坐标\n");
		scanf("%d%d", &x, &y);
		//因为玩家落子的不确定性所以需要对玩家输入数据进行筛选,以避免越界行为产生。
		if (x > 0 && x <= row && y > 0 && y <= col) {
			/*判断完后需要判断该落点是否为空,但玩家没有程序员思维故输入坐标是从1开始的与数组索引从0开始不同
			在此处需要我们手动的将玩家输入的坐标转化为其在二维数组中所对应的索引*/
			if (arr[x - 1][y - 1] == ' ') {
				arr[x - 1][y - 1] = '*';
				flag = 0;
				break;
			}
			else
				printf("非法输入,请重新输入坐标\n");
		}
		else
			printf("坐标非法,请重新输入.\n");//提示玩家,提供的坐标非法
	}
}
void computermove(char arr[ROW][COL], int row, int col) {
	printf("电脑落子\n");//给出提示电脑落子
	int x, y;
	int	flag = 1;//标记
	while(flag){
		x = rand() % row;//生成随机数,由于x对row进行取模运算,故实际上x取值范围被缩小到了(0~row)
		y = rand() % col;//生成随机数,由于y对col进行取模运算,故实际上y取值范围被缩小到了(0~col)
		//因为上述运算故在电脑落子时不存在越界行为所以可以直接判断落点是否为空
		if (arr[x][y] == ' ') {		
			arr[x][y] = '#';
			flag = 0;
			break;
		}
	}


}
char isWin(char arr[ROW][COL], int row, int col) {
	for (int i = 0; i < row; i++) { 
		if (arr[0][i] == arr[1][i] && arr[1][i] == arr[2][i] && arr[1][i] != ' ') {
			return arr[1][i];//不需要知到arr[1][i]存储的是谁,返回此值后在game函数中通过if语句筛选即可
		}
	}
	for (int i = 0; i < col; i++) {
		if (arr[i][0] == arr[i][1] && arr[i][1] == arr[i][2] && arr[i][1] != ' ') {
			return arr[i][1];//不需要知到arr[i][1]存储的是谁,返回此值后在game函数中通过if语句筛选即可
		}
	}
	/*for (int i = 0, j = 0; i < row, j < col; i++, j++) {
		if (arr[i][j] == arr[i + 1][j + 1] && arr[i + 1][j + 1] == arr[i + 2][j + 2]) {
			return arr[i][j];
			//循环算法不仅导致了时间复杂度的增加,在判断上依然依靠人为遍历,该算法除非优化成为自行判断,
			//否则实用性不强。(优化后也许可以适用于五子棋等类似棋类游戏)
		}
	}
	*/
	if (arr[0][0] == arr[1][1] && arr[1][1] == arr[2][2] && arr[1][1] != ' ') {
		return arr[1][1];//不需要知到arr[1][1]存储的是谁,返回此值后在game函数中通过if语句筛选即可
	}
	if (arr[2][0] == arr[1][1] && arr[1][1] == arr[0][2] && arr[1][1] != ' ') {
		return arr[1][1];//不需要知到arr[1][1]存储的是谁,返回此值后在game函数中通过if语句筛选即可
	}
	if (isFull(arr, ROW, COL)) {
		return 'Q';
	}
	return 'c';//在判断输赢时如果上述条件都不满足那么需要继续进行游戏

}
int isFull(char arr[ROW][COL], int row, int col) {
	for (int i = 0; i < row; i++) {
		for (int j = 0; j < col; j++) {
			if (arr[i][j] == ' ') {
				return 0;//利用数组遍历来实现对整个棋盘的检查
			}
		}
	}
	return 1;//0为假,1为真,返回1时即棋盘已被填满
}

 emmm,接下来来讲解上述代码我们将代码分为了三部分 game.h , game.c , test.c三部分。(在实际工程当中代码的书写也是这样的)

game.h头文件:

#pragma once
#include <stdio.h>
#define ROW 3
#define COL 3
#define _CRT_SECURE_NO_WARNINGS
#pragma warning(disable:4996)

void game();
void initborad(char arr[ROW][COL], int row, int col);
void Displayborad(char arr[ROW][COL], int row, int col);
void playermove(char arr[ROW][COL], int row, int col);
void computermove(char arr[ROW][COL], int row, int col);
char isWin(char arr[ROW][COL], int row, int col);
int isFull(char arr[ROW][COL], int row, int col);

在这里我们将所有的需要使用的函数全部定义一遍(当然是边写边定义一开始谁知道有多少捏),在实际的操作当中我们会发现#include <stdio.h>这个头文件在每一个文件中都需要使用而且我们的game.h文件也都被使用所以我们将<stdio.h>包含到game.h文件中

#define ROW和#define COL的宏定义可更改其值以改变棋盘大小因为此处我们是三子棋所以定义为三。

test.c文件:

这里是我们的主函数所在文件即游戏的入口;

在此处我们需要考虑游戏的框架(即菜单,游戏主体)。

对于menu函数我们需要设置菜单栏给玩家提示

void menu() {

	printf("欢迎游玩三子棋\n");
	printf("***********************\n");
	printf("****** 1.开始游戏 ******\n");
	printf("****** 0.退出游戏 ******\n");
	printf("***********************\n");
}

这个函数并不需要多考虑什么毕竟只是一个菜单栏我们在此处不多花时间

main函数: 

main函数是函数入口此处我们将会调用我们的menu函数和game函数(这样分开写可以有效的减少代码冗余,以提高可读性)

int main() {
	int falg = 1;//标记使程序能够退出。
	do {
		menu();
		printf("请输入数字\n");
		int scanner;
		scanf("%d", &scanner);
		switch (scanner) {
		case 1:
			printf("祝您游戏愉快!!!\n");
			game();
			falg = 1;//继续游玩
			break;
		case 0:
			printf("感谢游玩\n");
			falg = 0;//结束游玩
			break;
		default:
			printf("输入非法,请重新输入\n");
			break;
		}
	}while (falg);//利用do whlie循环以完成菜单栏的开启与退出。
	return 0;
}

在这里我们首先调用menu函数打印出菜单栏,因为需要玩家选择所以我们需要创建一个scanf函数来接收玩家读入的数据。并且为了程序能够退出我们在外部定义一个flag标记。拿到数据后利用switch分支结构来判断玩家输入的内容 ,玩家选择游玩我们将会调用game函数。考虑到玩家可能多次游玩所以定义flag初值为1,并在玩家选择游玩后不更改flag值。玩家退出游戏时将flag更改为0以退出程序。

game函数:

来到这里我们需要对游戏框架进行搭建首先考虑的是棋盘怎么输出。我们想到棋盘是一个3x3的方阵所以二维数组!!!!!这不就是二维数组吗所以定义一个二维数组

char arr[][] = {};

emmm但是行列上面填什么捏???啊回到前面发现咱有个现成的常量ROW和COL,所以完美解决喽那么我们的数组就变成了

char arr[ROW][COL] ={ 0 };

江江数组定义好啦那么我们的棋盘也就有个初始的模型了。

但是这个时候去打印数组得到的将会是9个0(别着急)

所以我们的数组还要优化为了避免代码冗杂所以写个函数吧!

initborad函数:

在这个函数里我们要考虑的是将棋盘初始化(即把数组转化为棋盘的样子)。

怎么做捏??其实非常简单只需要遍历这个二维数组将每个值都改为‘ ’就可以啦

void initborad(char arr[ROW][COL], int row, int col) {
	//初始化棋盘
	for (int i = 0; i < row; i++) {
		for (int j = 0; j < col; j++) {
			//利用数组遍历完成对每一个元素的填充,以保证每一个元素初始值都为‘ ’;
			arr[i][j] = ' ';
		}
	}//初始化使每一个节点都存储‘ ’避免棋盘内部因为没有存储字符而导致棋盘不规整。
}

那么在初始化完了之后我们该怎么做呢??

笨蛋该打印出来让玩家看到啊,不然人家怎么玩,虚空下棋吗??

ps:作者在此处吃了亏一定一定要记住是单引号不是双引号,否则的话你看到的就会是一堆字符在你的棋盘里。(悲)

Displayborad函数:

在这个函数里我们需要打印输出棋盘,但是也存在一些问题,比如在初始化中我们只将数组存储了‘ ’字符但并没有表现出棋盘边界所以在这里我们还需要将棋盘边界表示出来

void Displayborad(char arr[ROW][COL], int row, int col) {
	for (int i = 0; i < row; i++) {
		//打印数据
		for (int j = 0; j < col; j++) {
			//利用数组遍历来打印完整个棋盘
			printf(" %c ", arr[i][j]);
			printf("|");
			}
		}
		printf("\n");
		for (int j = 0; j < col; j++) {
			printf("---");	
			printf("|");
			}
			printf("\n");
		}
	}
}

目标输出:

 

 

实际输出: 

 

这样我们如果输出就会发现棋盘变成了全包围其边界也被画出来了,但是我们不需要啊。这里就需要if函数来帮忙过滤了。我们发现实际输出与目标输出区别在于最后一行和最后一列所以对它稍加限制变为

void Displayborad(char arr[ROW][COL], int row, int col) {
	for (int i = 0; i < row; i++) {
		//打印数据
		for (int j = 0; j < col; j++) {
			//利用数组遍历来打印完整个棋盘
			printf(" %c ", arr[i][j]);
			if (j < col - 1) {
				//通过实践发现在棋盘的最后我们并不需要‘|’故在此处对其进行限制
				//col表示的是列,col - 1即代表少输出一列。
				printf("|");
			}
		}
		printf("\n");

		if (i < row - 1) {
			//此处同理,row表示行,但最后一行并不需要输出‘---’所以此处限制 i < row-1,少输出一行
			for (int j = 0; j < col; j++) {
				printf("---");
				if (j < col - 1) {
					printf("|");
				}
			}
			printf("\n");
		}
	}
}

呼呼呼好啦棋盘准备好了该下棋了!!!

playermove:

到选手入场的时候了,呼呼呼。玩家的行为是不受控的所以在这里让玩家输入一个坐标时我们需要判断该坐标是否合法,即是否在数组索引内。而且要判断这个点是否已经被占用了。如果玩家一直输入错误的坐标的话我们的程序不能结束啊所以还有个循环

void playermove(char arr[ROW][COL], int row, int col) {
	int x, y;
	int	flag = 1;//标记
	while (flag) {
		printf("玩家落子\n");
		printf("请输入所落子位置坐标\n");
		scanf("%d%d", &x, &y);
		//因为玩家落子的不确定性所以需要对玩家输入数据进行筛选,以避免越界行为产生。
		if (x > 0 && x <= row && y > 0 && y <= col) {
			/*判断完后需要判断该落点是否为空,但玩家没有程序员思维故输入坐标是从1开始的与数组索引从0开始不同
			在此处需要我们手动的将玩家输入的坐标转化为其在二维数组中所对应的索引*/
			if (arr[x - 1][y - 1] == ' ') {
				arr[x - 1][y - 1] = '*';
				flag = 0;
				break;
			}
			else
				printf("非法输入,请重新输入坐标\n");
		}
		else
			printf("坐标非法,请重新输入.\n");//提示玩家,提供的坐标非法
	}
}

scanf拿到坐标后由于玩家不是程序员所以对玩家而言坐标从1开始,作为一名程序员(人机翻译官)我们要将这个值-1以转换为数组的索引。

ps:记得打上标记啊不然程序出不来了。倒霉蛋的亲身经历

好了玩家下完棋了该电脑下棋了!!!

computermove:

电脑下棋就简单些了因为电脑不会越界,但如何生成一个坐标捏???(可爱脸)

emmmm为了简单省事我们可以直接让它生成两个随机数就像这样:

x =  rand();
y =  rand();

但是如何控制这两个数保证其在我们数组的索引范围内呢??

嘿嘿此时就要祭出我们的%(有叫取模的有叫取余的作者最开始学的Java所以这里就叫取余啦)取余运算符怎么用呢,我们知道取余运算最后能保留的数字是从0 ~右算子;所以这里右算子只需要放上我们的棋盘行列最大值即可

x = rand() % ROW;
y = rand() % COL;

这样一来x,y就都在棋盘内啦,最后只需要判断落点是否为空就好啦!!!!!

最终代码:

void computermove(char arr[ROW][COL], int row, int col) {
	printf("电脑落子\n");//给出提示电脑落子
	int x, y;
	int	flag = 1;//标记
	while(flag){
		x = rand() % row;//生成随机数,由于x对row进行取模运算,故实际上x取值范围被缩小到了(0~row)
		y = rand() % col;//生成随机数,由于y对col进行取模运算,故实际上y取值范围被缩小到了(0~col)
		//因为上述运算故在电脑落子时不存在越界行为所以可以直接判断落点是否为空
		if (arr[x][y] == ' ') {		
			arr[x][y] = '#';
			flag = 0;
			break;
		}
	}


}

呼好累但是还没完啊!!接下来既然大家棋都下好了那怎么判断输赢捏??

isWin函数:

接着写函数!!

首先判断输赢即判断是否有三个相连的相同字符(横竖斜三个方向)

横、竖方向:

这两个方向最好弄了直接祭出for循环外加if辅助

直接上代码喽:

for (int i = 0; i < row; i++) { 
		if (arr[0][i] == arr[1][i] && arr[1][i] == arr[2][i] && arr[1][i] != ' ') {
			return arr[1][i];//不需要知到arr[1][i]存储的是谁,返回此值后在game函数中通过if语句筛选即可
		}
	}
	for (int i = 0; i < col; i++) {
		if (arr[i][0] == arr[i][1] && arr[i][1] == arr[i][2] && arr[i][1] != ' ') {
			return arr[i][1];//不需要知到arr[i][1]存储的是谁,返回此值后在game函数中通过if语句筛选即可
		}
	}

我们返回任意值即可这里不需要纠结到底谁赢了,只判断是否有一方获胜即可。

斜方向:

斜方向的不太好弄因为它横纵坐标都在变化所以只能用笨办法了

if (arr[0][0] == arr[1][1] && arr[1][1] == arr[2][2] && arr[1][1] != ' ') {
		return arr[1][1];//不需要知到arr[1][1]存储的是谁,返回此值后在game函数中通过if语句筛选即可
	}
	if (arr[2][0] == arr[1][1] && arr[1][1] == arr[0][2] && arr[1][1] != ' ') {
		return arr[1][1];//不需要知到arr[1][1]存储的是谁,返回此值后在game函数中通过if语句筛选即可
	}

但是在这里作者捏有个不成熟的想法也给各位大佬们看看能不能优化一下嘻嘻

/*for (int i = 0, j = 0; i < row, j < col; i++, j++) {
		if (arr[i][j] == arr[i + 1][j + 1] && arr[i + 1][j + 1] == arr[i + 2][j + 2]) {
			return arr[i][j];
			//循环算法不仅导致了时间复杂度的增加,在判断上依然依靠人为遍历,该算法除非优化成为自行判断,
			//否则实用性不强。(优化后也许可以适用于五子棋等类似棋类游戏)
		}
	}
	*/

欸这是直接抽取的我自己打的代码我是认为这个方法没用还是和上述方法一样需要人去控制

好啦到这里我们还少考虑了一种情况平局!!!

所以再写个判断平局的函数

isFull函数:

函数名已经暴露了我们的想法对于平局嘛全都满了还没人获胜不就是平局吗

所以我们只需要遍历数组看看还有没有空位而且上面的获胜条件都不满足(但获胜条件都是用if书写的所以事实上我们已经完成了后面的步骤)

int isFull(char arr[ROW][COL], int row, int col) {
	for (int i = 0; i < row; i++) {
		for (int j = 0; j < col; j++) {
			if (arr[i][j] == ' ') {
				return 0;//利用数组遍历来实现对整个棋盘的检查
			}
		}
	}
	return 1;//0为假,1为真,返回1时即棋盘已被填满
}

欧克isFull搞定了我们返回到前面补全条件即可啦!!

汇总代码:

char isWin(char arr[ROW][COL], int row, int col) {
	for (int i = 0; i < row; i++) { 
		if (arr[0][i] == arr[1][i] && arr[1][i] == arr[2][i] && arr[1][i] != ' ') {
			return arr[1][i];//不需要知到arr[1][i]存储的是谁,返回此值后在game函数中通过if语句筛选即可
		}
	}
	for (int i = 0; i < col; i++) {
		if (arr[i][0] == arr[i][1] && arr[i][1] == arr[i][2] && arr[i][1] != ' ') {
			return arr[i][1];//不需要知到arr[i][1]存储的是谁,返回此值后在game函数中通过if语句筛选即可
		}
	}
	/*for (int i = 0, j = 0; i < row, j < col; i++, j++) {
		if (arr[i][j] == arr[i + 1][j + 1] && arr[i + 1][j + 1] == arr[i + 2][j + 2]) {
			return arr[i][j];
			//循环算法不仅导致了时间复杂度的增加,在判断上依然依靠人为遍历,该算法除非优化成为自行判断,
			//否则实用性不强。(优化后也许可以适用于五子棋等类似棋类游戏)
		}
	}
	*/
	if (arr[0][0] == arr[1][1] && arr[1][1] == arr[2][2] && arr[1][1] != ' ') {
		return arr[1][1];//不需要知到arr[1][1]存储的是谁,返回此值后在game函数中通过if语句筛选即可
	}
	if (arr[2][0] == arr[1][1] && arr[1][1] == arr[0][2] && arr[1][1] != ' ') {
		return arr[1][1];//不需要知到arr[1][1]存储的是谁,返回此值后在game函数中通过if语句筛选即可
	}
	if (isFull(arr, ROW, COL)) {
		return 'Q';
	}
	return 'c';//在判断输赢时如果上述条件都不满足那么需要继续进行游戏

}
int isFull(char arr[ROW][COL], int row, int col) {
	for (int i = 0; i < row; i++) {
		for (int j = 0; j < col; j++) {
			if (arr[i][j] == ' ') {
				return 0;//利用数组遍历来实现对整个棋盘的检查
			}
		}
	}
	return 1;//0为假,1为真,返回1时即棋盘已被填满
}

到这里我们的细节就填充完毕了!!返回到test.c里补全即可

void game() {
	char arr[ROW][COL] = { 0 };//定义数组用来存放位置坐标。
	//初始化
	initborad(arr, ROW, COL);
	//打印
	char win = 0;//标记
	int flag = 1;//标记
	while (flag) {
		Displayborad(arr, ROW, COL);//打印棋盘,初始化棋盘,让玩家看到电脑落子
		playermove(arr, ROW, COL);
		Displayborad(arr, ROW, COL);//打印棋盘,让玩家知晓是否落子成功
		//判断输赢
		win = isWin(arr, ROW, COL);//利用先前的标记存储
		if (win != 'c') {
			Displayborad(arr, ROW, COL);//打印棋盘,显示哪里三个相连。
			flag = 0;
			break;
		}
		computermove(arr, ROW, COL);
		win = isWin(arr, ROW, COL);
		if (win != 'c') {
			Displayborad(arr, ROW, COL);//打印棋盘,显示哪里三个相连。
			flag = 0;
			break;
		}
	}
	if (win == '*') {
		printf("玩家获胜!!!!\n");
	}
	if (win == '#') {
		printf("电脑获胜!!!!\n");
	}
	if (win == 'Q') {
		printf("平局!!!!\n");
	}
	return 0;
}

我们在后面补全了到底是谁获胜的代码也是简单的if就可以解决了捏

好啦到这就都写完了呼呼呼累死偶力。

总的来说:

三子棋作为一个小游戏初学者独立完成还是挺不容易的(作者写三子棋写了整整三个小时(悲)各种报错输出格式不对折磨了一晚上)三子棋我认为是一个对初学者的c语言能力不错的检验几乎涵盖了所有基础知识所以是非常值得初学c的一起来尝试的

萌新作者,求赞求关注!!!!

大佬大佬快看我!!!

大家一起加油呀!!!!!冲冲冲!!!!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值