三子棋获胜条件优化(可扩展大棋盘,自定义获胜条件)

本文讲述了作者在学习C语言过程中遇到的问题,如何设计一个可以适应不同棋盘大小和胜利条件的三子棋游戏,重点介绍了如何通过Tiaojian定义来改变获胜条件。
摘要由CSDN通过智能技术生成

问题提出

        学C语言不久,尝试做三子棋,苦恼于获胜条件,胜利条件该怎么写才能在改变棋盘大小的情况下不影响胜利条件,例如在5*5的棋盘上仍是3子获胜。

        一开始写三子棋的我就是是简单的罗列出在3*3棋盘三子棋获胜的所有条件进行判断,但是一旦改变了棋盘的大小或者胜利的条件,代码就完全废除了。

        查阅了别人的文章也没有好的解决方法,大部分都是局限于3*3的棋盘,或者就是获胜条件不能更改。

        最终潜力爆发,想出来解决方法,但是仍有改进空间!

问题概述

程序主体

        关于三子棋的整体逻辑我就不赘述了,稍微简单介绍一下。

        主函数用来调出菜单,并判断是否进入游戏:

#define _CRT_SECURE_NO_WARNINGS 1
#include"Sanziqi.h"
int main()
{
	int input = 0;
	do 
	{
		menu();	
		printf("请选择(0或1):");
		scanf("%d", &input);
		srand((unsigned int)time(NULL));
		switch(input)
		{
			case 1:
				system("cls");//清屏
				Rule();//跳出规则,如果改变棋盘大小和胜利条件,此处也应该修改
				Game();//游戏的主体函数
				break;
			case 0:
				printf("退出游戏\n");
				break;
			default:
				printf("输入错误,请重新输入\n");
				break;
		}
	} while (input);
	return 0;
}

        将所有的函数声明都放入在"Sanziqi.h"中 。

        想要实现改变获胜条件,最最最重要的则是Tiaojian的定义,通过改变Tiaojian则可以改变获胜条件,如改成4则会变为四子棋,实现的步骤请看下文。

#define _CRT_SECURE_NO_WARNINGS 1
#define ROW 3
#define COLUMN 3
//改变ROW COLUMN就可以改变棋盘大小
#define Tiaojian 3
#include<stdio.h>
#include<stdlib.h>
#include<time.h>

void menu();
void Rule();
void Game();
void Initialize_Board(char board[ROW][COLUMN]);
void Display_Board(char board[ROW][COLUMN]);
void Player_change_Board(char board[ROW][COLUMN]);
char Judge(char board[ROW][COLUMN]);
void Computer_change_Board(char board[ROW][COLUMN]);
int Isfull(char board[ROW][COLUMN]);

        以下是此次游戏的逻辑,初始化棋盘,展示棋盘

        {玩家输入棋子坐标,展示棋盘,判断是否获胜,电脑输入棋子坐标,展示棋盘,判断是否获胜}

        直到{}内的步骤确定玩家获胜还是电脑获胜,结束此次游戏,回到菜单。

        补充一点,由于棋盘是固定的,所以传参只要根据头文件中定义的ROW和COLUMN,也就是棋盘的行和列为已知的,所以只需要输入数组名就可以改变棋盘内容,判断胜负。

void Game()
{
	char board[ROW][COLUMN] = { 0 };
	Initialize_Board(board);
	Display_Board(board);
	char tmp;
	while (1)
	{
		Player_change_Board(board);
		Display_Board(board);
		tmp = Judge(board);
		if (tmp != 'c')break;
		Computer_change_Board(board);
		Display_Board(board);
		tmp = Judge(board);
		if (tmp != 'c')break;
	}
	if (tmp == '*')
	{
		printf("你赢了\n");
	}
	else if (tmp == '#')
	{
		printf("你输了\n");
	}
	else
	{
		printf("平局\n");
	}
}

        接下来是除了Judge函数(也就是判断胜负的函数)的简析

        可以跳至Judge

函数简析

初始化棋盘(Initialize_Board)

        将二维数组board所有中的元素初始化为空格

void Initialize_Board(char board[ROW][COLUMN])
{
	int i;
	int j;
	for (i = 0; i < ROW; i++)
	{
		for (j = 0; j < COLUMN; j++)
		{
			board[i][j] = ' ';
		}
	}
}

展示棋盘(Display_Board)

        简单来说就是打印二维数组。

        棋盘的设计个人看来无关紧要,甚至不需要棋盘,只需要能将二维数组board每个元素依次打印出来即可,每个格子可大可小,怎么美观怎么来

        如果不追求程序的美观,代码就会看起来简洁美观哈哈哈

void Display_Board(char board[ROW][COLUMN])
{
	int i = 0;
	int j = 0;
	for (i = 0; i < ROW; i++)
	{
		for (j = 0; j < COLUMN; j++)
		{
			if (j < COLUMN - 1)printf(" %c |", board[i][j]);
			else printf(" %c \n", board[i][j]);
		}
		if(i<ROW-1)
		{
			for (j = 0; j < COLUMN; j++)
			{
				if (j < COLUMN - 1)printf("---|");
				else printf("---\n");
			}
		}
		
	}
}

玩家下棋(Player_change_Board)

        这块函数无非就是判断输入的坐标(x1,y1)有没有超过(ROW,COLUMN),或者有没有小于(1,1),或者这个坐标上是不是已经有棋子了

        由于人与机器识别有差别,第一个点一般来说是(1,1),但是考虑的数组的下标从零开始,所以将x1和y1都减一,复合人的习惯。

        简单的循环判断:

void Player_change_Board(char board[ROW][COLUMN])
{
	printf("请输入你要下的位置:");
	while (1)
	{
		int x1, y1;
		scanf("%d %d", &x1, &y1);
		if (x1 > ROW || y1 > COLUMN || x1 <= 0 || y1 <= 0)
		{
			printf("输入值错误,重新输入:");
		}
		else
		{
			if (board[x1 - 1][y1 - 1] != ' ')
			{
				printf("此处已有棋子重新输入:");
			}
			else
			{
				board[x1 - 1][y1 - 1] = '*';
				break;
			}
		}
	}
}

电脑下棋( Computer_change_Board)

        电脑下棋采用了随机的方法,这样子的电脑比较笨,但是赢得几率并不是0%(%d写多了,都快忘记百分之零是%0还是0%了)。

        没有找到空的元素就一直随机,知道下出'#':

void Computer_change_Board(char board[ROW][COLUMN])
{
	printf("电脑下棋:\n");
	int i, j;
	while (1)
	{		
		i = rand() % ROW;
		j = rand() % COLUMN;
		if (board[i][j] == ' ')break;
	}
	board[i][j] = '#';
}

判断棋盘是否满载( Isfull)

        这里的返回值用int是因为只有两种情况,可以方便选择语句或者循环语句判断。

        如果棋盘满了就爆炸,输出1:

int Isfull(char board[ROW][COLUMN])
{
	int i, j;
	for (i = 0; i < ROW; i++)
	{
		for (j = 0; j < COLUMN; j++)
		{
			if (board[i][j] == ' ')return 0;
		}
	}
	return 1;
}

判断胜利条件(Judge)

Judge()主体

        不可避免的我们需要判断胜利的条件,也将这个函数单独拎出来,原因也是这个函数前前后后一直再改,终于改成了一个较为通用的函数,能够应对所有棋盘以及不同的获胜条件:

char Judge(char board[ROW][COLUMN])
{
	int i, j;
	for (i = 0; i < ROW; i++)
	{
		int tmp = 0;
		for (j = 0; j <COLUMN-1; j++)
		{
			if (board[i][j] == board[i][j + 1] && board[i][j] != ' ')tmp++;
			else tmp = 0;
			if (tmp == Tiaojian - 1)return board[i][j];
		}
	}
	for (i = 0; i < COLUMN; i++)
	{
		int tmp = 0;
		for (j = 0; j < ROW - 1; j++)
		{
			if (board[j][i] == board[j+1][i] && board[j][i] != ' ')tmp++;
			else tmp = 0;
			if (tmp == Tiaojian - 1)return board[j][i];
		}
	}
	for (i = 0; i <=ROW-Tiaojian; i++)
	{
		int tmp = 0;
		for (j = 0; j <= COLUMN - Tiaojian; j++)
		{
			int k = 1;
			while ((i + k) < ROW && (j + k) < COLUMN)
			{
				if (board[i][j] == board[i + k][j + k] && board[i][j] != ' ')tmp++;
				else tmp = 0;
				if (tmp == Tiaojian - 1)return board[i][j];
				k++;
			}
		}
	}
	for (i = 0; i <=ROW-Tiaojian; i++)
	{
		int tmp = 0;
		for (j = COLUMN-1; j >=Tiaojian-1; j--)
		{
			int k = 1;
			while ((i + k) < ROW && (j - k) >=0)
			{
				if (board[i][j] == board[i + k][j - k] && board[i][j] != ' ')tmp++;
				else tmp = 0;
				if (tmp == Tiaojian - 1)return board[i][j];
				k++;
			}
		}
	}
	if(Isfull(board))return 'q';
	return 'c';
}

定义获胜条件以及棋盘大小

        代码很长,由我慢慢说明 。

        之前所有的函数都以ROW和COLUMN作为参数进行运算,也就是改变了这了两个的值就能够改变上述函数的结果。

        如打印出的棋盘大小和数组board的元素个数。

        而获胜条件则用Tiaojian代表,它的意思是需要多少个棋子才能够胜利:

#define ROW 3
#define COLUMN 3
#define Tiaojian 3

行和列的判断

        行与列的判断较为简单,i的在它的第一个for循环中代表着行数。

        三子棋由于棋盘只有3*3所以本来不用考虑是否连续,但是如果在一个更大的棋盘,就得考虑一行有三个棋子但是中间不能有其他的棋子参杂。

        board[i][j] == board[i][j + 1]此时j代表着列数,行数不变,列数加一。

        所以用tmp表示连续且相同的次数,如果是三子那么就是需要两次,如果是四子那么就是需要连续判断三次。

        i在它的第二个for循环代表着列数,但是原理相同,就不赘述了:

	for (i = 0; i < ROW; i++)
	{
		int tmp = 0;
		for (j = 0; j <COLUMN-1; j++)
		{
			if (board[i][j] == board[i][j + 1] && board[i][j] != ' ')tmp++;
			else tmp = 0;
			if (tmp == Tiaojian - 1)return board[i][j];
		}
	}
	for (i = 0; i < COLUMN; i++)
	{
		int tmp = 0;
		for (j = 0; j < ROW - 1; j++)
		{
			if (board[j][i] == board[j+1][i] && board[j][i] != ' ')tmp++;
			else tmp = 0;
			if (tmp == Tiaojian - 1)return board[j][i];
		}
	}

 斜线的判断

        在我看来对角线的判断无疑是最困难的,再扩大棋盘时尤为复杂。

        我们只需要看斜线的第一个点,能不能斜着延申出去,并且连续Tiaojian个。

        如果斜线向右,数组元素就会从(0,0)开始查找,这条斜线的第一个点的行数(列数)下标不能大于棋盘行数(列数)减去Tiaojian,否则就会超出棋盘,也就是不成立获胜条件。

        继续引入n和m变量,比较的两个数组元素,它们的行数和列数都要进行加,这样子才能保证它们时连续的

        斜线向左的情况则大同小异,行数与上面一样的递增,列数则是从最后一列递减:

		for (i = 0; i <=ROW-Tiaojian; i++)
	{
		int tmp1 = 0;
		for (j = 0; j <= COLUMN - Tiaojian; j++)
		{
			int n1 = 0;
			int m1 = 1;
			while ((i + m1) < ROW && (j + m1) < COLUMN)
			{
				if (board[i+n1][j+n1] == board[i + m1][j + m1] && board[i+n1][j+n1] != ' ')tmp1++;
				else tmp1 = 0;
				if (tmp1 == Tiaojian - 1)return board[i][j];
				m1++;
				n1++;
			}
		}
	}
	for (i = 0; i <=ROW-Tiaojian; i++)
	{
		int tmp2 = 0;
		for (j = COLUMN-1; j >=Tiaojian-1; j--)
		{
			int n2 = 0;
			int m2 = 1;
			while ((i + m2) < ROW && (j - m2) >=0)
			{
				if (board[i+n2][j-n2] == board[i + m2][j - m2] && board[i+n2][j-n2] != ' ')tmp2++;
				else tmp2 = 0;
				if (tmp2 == Tiaojian - 1)return board[i][j];
				m2++;
				n2++;
			}
		}
	}

结语(后续优化)

        真的是一场酣畅淋漓的实战应用,结合了我这段时间所有学习的知识,更是感慨想这么简单的小游戏都要如此绞尽脑汁。

        目前也不知道是否有bug存在,也希望能有大佬指导,感觉自己的水平真的差得不行。     

        而且肉眼可见的程序还可以进行优化,比如说棋盘变大了,电脑如果还是随机下,那么电脑会显得更加笨蛋。

        以及Judge的判断,每次都是将整个棋盘的数据进行判断。可以根据每次下的棋子来判断是否满足胜利条件,这样子就不用每次都将整个棋盘判断。

        如果有问题!欢迎指出!!!

  • 31
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值