五子棋人机对战完整代码

前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家:点击跳转

目录

〇,前言

一,五子棋棋盘

二,五子棋比赛规则

1,行棋顺序

2,判断胜负

三,重要棋型解释

1,五连

2,活四

3,冲四

4,活三

四,禁手规则

1,三三禁手

2,四四禁手

3,长连禁手

4,抓禁

五,代码解释

1,棋子表示

2,棋盘表示

3,flat技术

4,棋型判断和禁手判断

4.1 活四

4.2 冲四

4.3 活3

5,AI算法

6,AI的打分机制

7,搜索剪枝

8,棋谱和禁手调试

六,代码

V201912

V202001

V202401

七,棋盘显示问题

八,更多规则

1,三手交换

2,五手两打

九,相关问题

力扣 LCP 48. 无限棋局


〇,前言

本文代码修改了数次,但是只保留了有代表性的V201912和V202001,版本名是“年+月”。

本来是帮一个朋友写的作业,结果自从博客发表之后,我发现每隔一段时间,这篇博客就有挺多人看。

其中还有几个人加了我QQ或微信,我一问,全都是要交作业的大学生。

唉,当代大学生啊。

一,五子棋棋盘

棋盘正中一点为“天元”。棋盘两端的横线称端线。棋盘左右最外边的两条纵线称边线。从两条端线和两条边线向正中发展而纵横交叉在第四条线形成的四个点称为“星”。

以持黑方为准,棋盘上的纵轴线从左到右用英文字母A~O标记。横行线从近到远用阿拉伯数字1~15标记。纵横轴上的横纵线交叉点分别用横纵线标记的名称合写成。如“天元”H8,四个“星”分别为D4、D12、L12、L4等。

二,五子棋比赛规则

1,行棋顺序

黑先、白后,从天元开始相互顺序落子。

2,判断胜负

最先在棋盘横向、竖向、斜向形成连续的相同色五个棋子的一方为胜。
黑棋禁手判负,白棋无禁手。黑棋禁手包括三三禁手,四四禁手,长连禁手。
如分不出胜负,则定为平局。

三,重要棋型解释

1,五连

五颗同色棋子连在一起,

即4个方向的11111这种形式的棋型。

PS:这里1表示同色棋子,0表示空格

2,活四

有2个成五点的四颗棋子,

即4个方向的011110这种形式的棋型,注意两边一定要有空格。

3,冲四

有1个成五点的四颗棋子,棋型有点多。

4,活三

可以形成活四的三颗棋子,

要么是三连的形式,即8个方向的011100这种形式的棋型

要么是非三连的形式,即8个方向的010110这种形式的棋型

四,禁手规则

1,三三禁手

由于黑方落一子,同时形成二个或二个以上黑方活三的局面

2,四四禁手

由于黑方落一子,同时形成二个或二个以上黑方四(活四或者冲四)的局面

3,长连禁手

由于黑方落一子,形成六个或者六个以上的同色连续棋子

4,抓禁

如果白方的成五点在黑方的禁手点,轮到黑方下,黑方也是不能下这个位置的。此时黑方只要没有成五点就直接输了。

这个取胜的技巧叫抓禁。

五,代码解释

1,棋子表示

为了使自已与对手看得更清楚,刚落下的子区别表示,
白方正常子:○    白方刚落下之子:△
黑方正常字:●    黑方刚落下字:▲

2,棋盘表示

利用专门画棋盘的9个拓展字符,可以在控制台上画出非常漂亮的棋盘

out函数用来画棋盘的一个格子,要么是表示棋盘的9个拓展字符,要么是表示棋子的4个拓展字符

DrawBoard函数用来打印整个游戏界面,需要调用out函数

3,flat技术

通过dx和dy这2个常数数组,存下8个方向的向量,就可以把棋型判断、禁手判断等二维问题化作一维问题。

通过for循环即可遍历每个方向,使得代码变得非常简洁。

4,棋型判断和禁手判断

对于任何一个可以落子的位置,要独立的判断如果落子就会形成几个活四,几个冲四,几个活三。

三三禁手和四四禁手直接根据三种棋型的数量判断即可,长连禁手单独判断,很简单。

4.1 活四

判断活4的逻辑比较简单,遍历4个方向,判断是不是011110的形式即可。

PS:1011110对于左边的1是冲四,对于右边的4个1是活四。

4.2 冲四

冲4的棋型比较多,总之只要有成五点,要么是活四,要么是冲四。

进一步,我们可以得到,成五点的数量是活四的数量*2+冲四的数量

而成五点的数量可以这么求:

在8个方向上求成五点,对于每个方向,从当前位置往前延伸,第一个空格是这个方向唯一可能的成五点,跳过第一个空格继续往前延伸直到不是自身棋子的位置A,同时从当前位置往反方向延伸直到不是自身棋子的位置B,如果AB的距离大于5,即AB之间至少能放5个棋子,那么该方向就有一个成五点。

由此,我们可以根据成五点的数量和活四的数量求出冲四的数量。

4.3 活3

分开计算三连活3的数量和非3连的活3的数量,然后加起来

5,AI算法

AI 采取三层的极大极小算法,基于固定的打分机制,对每个落子位置进行打分,从而得到比较优的解。

6,AI的打分机制

为了降低计算量,采取对每个落子位置单独进行打分的方法。

打分核心机制:在不形成禁手的前提下,形成最优棋型。

落子之后计算棋型,活四计1000分,冲四和活三都计100分。

PS:虽然规则允许下禁手点,但是禁手判负,所以AI认为黑方绝对不会下禁手点(无论黑方是AI还是玩家)

7,搜索剪枝

AI采取的策略是,如果要落子,周围的8个邻居至少要有一个棋子,无论是黑是白。

对于不满足这个条件的地方,AI是不下的,直接减掉。

(PS:这个限制会导致AI的水平下降,但是计算速度大大提升。当然,如果被对方知道了这个限制,也很容易基于此完胜AI)

基于这个剪枝策略,调整打分机制:落子位置的8个邻居中,只要是有落子的位置,无论是黑是白,都计1分。

(对于边界或角落上的点,只有5个或者3个邻居,为了编程方便,棋盘本身应该用一圈空格包围)

这样的话,对于0分的点直接忽略,即可大大增加剪枝效率。

8,棋谱和禁手调试

代码有棋谱功能,棋谱存在manu数组中,下棋过程中可以随时输出棋谱,只要把棋谱复制下来,下次运行直接全部粘贴即可,这样就很方便调试,因为我的AI不带随机行为,所以每次相同情况下给出的结果也都是固定的。
比如调试三三禁手:(玩家执黑)
(1)H8  J10  J9  I8 (活三)
(2)D14  H7  C13  C11  D10 (不是活三)

调试四四禁手:

(3)H1  L9  L10  M11  N11  K11  L12 (俩活四)

(4)A1  B2  B4  A5  D2  D4(俩冲四)

(5)A1  B2  C3  C5  B6  E3 (活四加冲四)

PS:调试禁手代码时,可以把main函数中的while (!is_end)改为while (true),便于调试。让AI占先机,AI就不会妨碍我们随便做禁手。

六,代码

V201912

#include <stdio.h>
#include<string>
#include<windows.h>
#define N 15
#define samep same(row + dx[u] * i, col + dy[u] * i, p[row][col])
#define off if(!inboard(row + dx[u] * i, col + dy[u] * i) || p[row + dx[u] * i][col + dy[u] * i] != 0)continue;

int p[N + 2][N + 2]; //0空1黑2白  1●2○ -1▲-2△
int s = 0, ais = 1, s0;//s是轮到谁下,s=1,2,s=1是ai下,s=2是玩家,s=s0是黑方下,否则是白方下
bool is_end = false;
int dx[8] = { 1, 1, 0, -1, -1, -1, 0, 1 }; //flat技术
int dy[8] = { 0, 1, 1, 1, 0, -1, -1, -1 };//(dx,dy)是8个方向向量
int manu[2][300], manukey = 0;

int out(int i, int j)
{
	if (p[i][j] == 1)return printf("●");
	if (p[i][j] == 2)return printf("○");
	if (p[i][j] == -1)return printf("▲");
	if (p[i][j] == -2)return printf("△");
	if (i == N)
	{
		if (j == 1)return printf("┏");
		if (j == N)return printf("┓");
		return printf("┯");
	}
	if (i == 1)
	{
		if (j == 1)return printf("┗");
		if (j == N)return printf("┛");
		return printf("┷");
	}
	if (j == 1)return printf("┠");
	if (j == N)return printf("┨");
	return printf("┼");
}

void DrawBoard()//画棋盘
{
	system("cls");
	int row = 0, col = 0, keyr = 0, keyc = 0;
	char alpha = 'A';
	printf("\n\n\n     ");
	for (col = 1; col <= N; col++)printf("%c ", alpha++);
	for (row = N; row >= 1; row--)
	{
		printf("\n   %2d", row);
		for (col = 1; col <= N; col++)
		{
			out(row, col);
			if (p[row][col] < 0)keyr = row, keyc = col;
		}
		printf("%d", row);
	}
	alpha = 'A';
	printf("\n     ");
	for (col = 1; col <= N; col++)printf("%c ", alpha++);
	printf("\n\n");
	if (s0 == ais)printf("  AI执黑,玩家执白\n");
	else printf("  AI执白,玩家执黑\n");
	alpha = 'A';
	if (keyr)printf("  最后落子位置:%c%d\n", alpha + keyc - 1, keyr);
}

void init()
{
	system("color f0");
	printf("输入1或者2进行选择\n1,AI执黑先行\n2,玩家执黑先行\n");
	scanf_s("%d", &s);
	if (s != 1 && s != 2)return init();
	s0 = s;
	int i, j;
	for (i = 0; i <= N + 1; i++)for (j = 0; j <= N + 1; j++)p[i][j] = 0;//以空格包围棋盘	
	DrawBoard();
	for (j = 0; j < 300; j++)manu[0][j] = manu[1][j] = 0;
}

bool inboard(int row, int col)//是否在棋盘内
{
	if (row <1 || row > N)return false;
	return col >= 1 && col <= N;
}

int same(int row, int col, int key)//判断2个棋子是否同色
{
	if (!inboard(row, col))return false;
	return (p[row][col] == key || p[row][col] + key == 0);
}

int num(int row, int col, int u)//坐标(row,col),方向向量u
{
	int i = row + dx[u], j = col + dy[u], sum = 0, ref = p[row][col];
	if (ref == 0)return 0;
	while (same(i, j, ref))sum++, i += dx[u], j += dy[u];
	return sum;
}

int live4(int row, int col)//活4的数量
{
	int sum = 0, i, u;
	for (u = 0; u < 4; u++)//4个方向,每个方向最多1个
	{
		int sumk = 1;
		for (i = 1; samep; i++)sumk++;
		off;
		for (i = -1; samep; i--)sumk++;
		off;
		if (sumk == 4)sum++;
	}
	return sum;
}

int chong4(int row, int col)//冲4的数量
{
	int sum = 0, i, u;
	for (u = 0; u < 8; u++)//8个方向,每个方向最多1个
	{
		int  sumk = 0;
		bool flag = true;
		for (i = 1; samep || flag; i++)//成五点的方向
		{
			if (!samep)
			{
				if (flag&&p[row + dx[u] * i][col + dy[u] * i])sumk -= 10;
				flag = false;
			}
			sumk++;
		}
		if (!inboard(row + dx[u] * --i, col + dy[u] * i))continue;
		for (i = -1; samep; i--)sumk++;
		if (sumk == 4)sum++;
	}
	return sum - live4(row, col) * 2;
}

int live3(int row, int col)//活3的数量
{
	int key = p[row][col], sum = 0, i, u;
	for (u = 0; u < 4; u++)//三连的活三
	{
		int sumk = 1;
		for (i = 1; samep; i++)sumk++;
		off;
		i++;
		off;
		for (i = -1; samep; i--)sumk++;
		off;
		i++;
		off;
		if (sumk == 3)sum++;
	}
	for (u = 0; u < 8; u++)//8个方向,每个方向最多1个非三连的活三
	{
		int  sumk = 0;
		bool flag = true;
		for (i = 1; samep || flag; i++)//成活四点的方向
		{
			if (!samep)
			{
				if (flag&&p[row + dx[u] * i][col + dy[u] * i])sumk -= 10;
				flag = false;
			}
			sumk++;
		}
		off;
		if (p[row + dx[u] * --i][col + dy[u] * i] == 0)continue;
		for (i = 1; samep; i++)sumk++;
		off;
		if (sumk == 3)sum++;
	}
	return sum;
}

bool overline(int row, int col)//长连禁手
{
	for (int u = 0; u < 4; u++)if (num(row, col, u) + num(row, col, u + 4) > 4)return true;
	return false;
}

bool ban(int row, int col)//判断落子后是否成禁手
{
	if (same(row, col, 2))return false;//白方无禁手
	return live3(row, col) > 1 || overline(row, col) || live4(row, col) + chong4(row, col) > 1;
}

bool end_(int row, int col)//(row,col)处落子之后是否游戏结束
{
	for (int u = 0; u < 4; u++)if (num(row, col, u) + num(row, col, u + 4) >= 4)is_end = true;
	if (is_end)return true;
	is_end = ban(row, col);
	return is_end;
}

void go(int row, int col)//落下一子
{
	if (s == s0)p[row][col] = -1; //标出最新下的棋
	else p[row][col] = -2;
	for (int i = 0; i <= N; i++)for (int j = 0; j <= N; j++) //取消上一个最新棋的标识
	{
		if (i == row && j == col)continue;
		if (p[i][j] < 0)p[i][j] *= -1;
	}
	DrawBoard();
	if (ban(row, col))
	{
		if (s0 == 1)printf("玩家胜");
		else printf("AI胜");
		Sleep(10000);
	}
	if (end_(row, col))
	{
		if (s == ais)printf("AI胜");
		else printf("玩家胜");
		Sleep(10000);
	}
	manu[0][manukey] = row, manu[1][manukey++] = col;
}

bool ok(int row, int col)//能否落子
{
	return inboard(row, col) && (p[row][col] == 0);
}

int point(int row, int col)//非负分值
{
	if (ban(row, col))return 0;//禁手0分
	if (end_(row, col))
	{
		is_end = false;
		return 10000;
	}
	int ret = live4(row, col) * 1000 + (chong4(row, col) + live3(row, col)) * 100, u;
	for (u = 0; u < 8; u++)if (p[row + dx[u]][col + dy[u]])ret++;//无效点0分
	return ret;
}

int AI3(int p2)
{
	int keyp = -100000, tempp;
	for (int i = 1; i <= N; i++)for (int j = 1; j <= N; j++)
	{
		if (!ok(i, j))continue;
		p[i][j] = s0;
		tempp = point(i, j);
		if (tempp == 0)
		{
			p[i][j] = 0;
			continue;
		}
		if (tempp == 10000)
		{
			p[i][j] = 0;
			return 10000;
		}
		p[i][j] = 0;
		if (tempp - p2 * 2 > keyp)keyp = tempp - p2 * 2;//第三层取极大
	}
	return keyp;
}

int AI2()
{
	int keyp = 100000, tempp;
	for (int i = 1; i <= N; i++)for (int j = 1; j <= N; j++)
	{
		if (!ok(i, j))continue;
		p[i][j] = 3 - s0;
		tempp = point(i, j);
		if (tempp == 0)
		{
			p[i][j] = 0;
			continue;
		}
		if (tempp == 10000)
		{
			p[i][j] = 0;
			return -10000;
		}
		tempp = AI3(tempp);
		p[i][j] = 0;
		if (tempp < keyp)keyp = tempp;//第二层取极小
	}
	return keyp;
}

void AI()
{
	DrawBoard();
	printf("  轮到AI下,请稍候: ");
	if (p[8][8] == 0)return go(8, 8);
	int i, j;
	int keyp = -100000, keyi, keyj, tempp;
	for (i = 1; i <= N; i++)
	{
		for (j = 1; j <= N; j++)
		{
			if (!ok(i, j))continue;
			p[i][j] = s0;
			tempp = point(i, j);
			if (tempp == 0)
			{
				p[i][j] = 0;
				continue;
			}//高效剪枝,避开了禁手点和无效点
			if (tempp == 10000)return go(i, j);
			tempp = AI2();
			p[i][j] = 0;
			if (tempp > keyp)keyp = tempp, keyi = i, keyj = j;//第一层取极大
		}
	}
	return go(keyi, keyj);
}

void out_manual()
{
	char alpha = 'A';
	int i;
	printf("\n  黑方落子位置: ");
	for (i = 0; i < manukey; i += 2)printf("  %c%d", alpha + manu[1][i] - 1, manu[0][i]);
	printf("\n  白方落子位置: ");
	for (i = 1; i < manukey; i += 2)printf("  %c%d", alpha + manu[1][i] - 1, manu[0][i]);
	Sleep(5000);
}

void player()
{
	DrawBoard();
	printf("  轮到玩家下,请输入坐标(输入=0查看棋谱): ");
	char c = '\n';
	int row = 0, col = 0;
	while (c<'0')scanf("%c%d", &c, &row);
	if (c == '=')
	{
		out_manual();
		return player();
	}
	if (c < 'a')col = c - 'A' + 1;
	else col = c - 'a' + 1;
	if (!ok(row, col))
	{
		printf("此处不能下");
		Sleep(1000);
		return player();
	}
	go(row, col);
}

void main()
{
	init();
	while (!is_end)
	{
		if (s == ais)AI();
		else player();
		s = 3 - s;//换下棋方
	}
	return;
}

V202001

2020年1月再次更新,修改了宏,使得代码可读性更高,修改了live3函数的逻辑。

#include <stdio.h>
#include<string>
#include<windows.h>

#define N 15
#define same_u_i same(row + dx[u] * i, col + dy[u] * i, p[row][col])//u方向i距离的点是否同色
#define OutOrNotEmpty (!inboard(row + dx[u] * i, col + dy[u] * i) || p[row + dx[u] * i][col + dy[u] * i] != 0) //出了棋盘或者非空格点

int p[N + 2][N + 2]; //0空1黑2白  1●2○ -1▲-2△
int s = 0, ais = 1, s0;//s是轮到谁下,s=1,2,s=1是ai下,s=2是玩家,s=s0是黑方下,否则是白方下
bool is_end = false;
int dx[8] = { 1, 1, 0, -1, -1, -1, 0, 1 }; //flat技术
int dy[8] = { 0, 1, 1, 1, 0, -1, -1, -1 };//(dx,dy)是8个方向向量
int manu[2][300], manukey = 0;//棋谱

int out(int i, int j)//打印棋盘
{
    if (p[i][j] == 1)return printf("●");
    if (p[i][j] == 2)return printf("○");
    if (p[i][j] == -1)return printf("▲");
    if (p[i][j] == -2)return printf("△");
    if (i == N)
    {
        if (j == 1)return printf("┏");
        if (j == N)return printf("┓");
        return printf("┯");
    }
    if (i == 1)
    {
        if (j == 1)return printf("┗");
        if (j == N)return printf("┛");
        return printf("┷");
    }
    if (j == 1)return printf("┠");
    if (j == N)return printf("┨");
    return printf("┼");
}

void DrawBoard()//打印整个游戏界面
{
    system("cls");
    int row = 0, col = 0, keyr = 0, keyc = 0;
    char alpha = 'A';
    printf("\n\n\n     ");
    for (col = 1; col <= N; col++)printf("%c ", alpha++);
    for (row = N; row >= 1; row--)
    {
        printf("\n   %2d", row);
        for (col = 1; col <= N; col++)
        {
            out(row, col);
            if (p[row][col] < 0)keyr = row, keyc = col;
        }
        printf("%d", row);
    }
    alpha = 'A';
    printf("\n     ");
    for (col = 1; col <= N; col++)printf("%c ", alpha++);
    printf("\n\n");
    if (s0 == ais)printf("  AI执黑,玩家执白\n");
    else printf("  AI执白,玩家执黑\n");
    alpha = 'A';
    if (keyr)printf("  最后落子位置:%c%d\n", alpha + keyc - 1, keyr);
}

void init()//游戏开局初始化
{
    system("color f0");
    printf("输入1或者2进行选择\n1,AI执黑先行\n2,玩家执黑先行\n");
    scanf("%d", &s);
    if (s != 1 && s != 2)return init();
    s0 = s;
    int i, j;
    for (i = 0; i <= N + 1; i++)for (j = 0; j <= N + 1; j++)p[i][j] = 0;//以空格包围棋盘	
    DrawBoard();
    for (j = 0; j < 300; j++)manu[0][j] = manu[1][j] = 0;
}

bool inboard(int row, int col)//判断(row,col)是否在棋盘内
{
    if (row <1 || row > N)return false;
    return col >= 1 && col <= N;
}

int same(int row, int col, int key)//判断2个棋子是否同色
{
    if (!inboard(row, col))return false;
    return (p[row][col] == key || p[row][col] + key == 0);
}

int num(int row, int col, int u)//坐标(row,col),方向向量u,返回该方向有多少连续同色棋子
{
    int i = row + dx[u], j = col + dy[u], sum = 0, ref = p[row][col];
    if (ref == 0)return 0;
    while (same(i, j, ref))sum++, i += dx[u], j += dy[u];
    return sum;
}

int live4(int row, int col)//落子成活4的数量
{
    int sum = 0, i, u;
    for (u = 0; u < 4; u++)//4个方向,判断每个方向是否落子就成活4
    {
        int sumk = 1;
        for (i = 1; same_u_i; i++)sumk++;
        if(OutOrNotEmpty)continue;
        for (i = -1; same_u_i; i--)sumk++;
        if(OutOrNotEmpty)continue;
        if (sumk == 4)sum++;
    }
    return sum;
}

int cheng5(int row, int col)//成5点的数量
{
    int sum = 0, i, u;
    for (u = 0; u < 8; u++)//8个成五点的方向
    {
        int  sumk = 0;
        bool flag = true;
        for (i = 1; same_u_i || flag; i++)
        {
            if (!same_u_i)//该方向的第一个不同色的点,超出边界或者对方棋子或空格
            {
                if (p[row + dx[u] * i][col + dy[u] * i])sumk -= 10;//该方向的第一个不同色的点是对方棋子,没有成五点
                flag = false;
            }
            sumk++;
        }
        if (!inboard(row + dx[u] * --i, col + dy[u] * i))continue;//该方向的第一个不同色的点是超出边界,没有成五点
        for (i = -1; same_u_i; i--)sumk++;
        if (sumk == 4)sum++;
    }
    return sum;
}

int chong4(int row, int col)//冲4的数量
{
    return cheng5(row, col) - live4(row, col) * 2;
}

int live3(int row, int col)//落子成活3的数量
{
    int key = p[row][col], sum = 0, i, u,flag=2;
    for (u = 0; u < 4; u++)//三连的活三
    {
        int sumk = 1;
        for (i = 1; same_u_i; i++)sumk++;
        if(OutOrNotEmpty)continue;
        i++;
        if(OutOrNotEmpty)flag--;
        for (i = -1; same_u_i; i--)sumk++;
        if(OutOrNotEmpty)continue;
        i--;
        if(OutOrNotEmpty)flag--;
        if (sumk == 3 && flag>0)sum++;
    }
    for (u = 0; u < 8; u++)//8个方向,每个方向最多1个非三连的活三
    {
        int  sumk = 0;
        bool flag = true;
        for (i = 1; same_u_i || flag; i++)//成活四点的方向
        {
            if (!same_u_i)
            {
                if (flag&&p[row + dx[u] * i][col + dy[u] * i])sumk -= 10;
                flag = false;
            }
            sumk++;
        }
        if(OutOrNotEmpty)continue;;
        if (p[row + dx[u] * --i][col + dy[u] * i] == 0)continue;
        for (i = 1; same_u_i; i++)sumk++;
        if(OutOrNotEmpty)continue;;
        if (sumk == 3)sum++;
    }
    return sum;
}

bool overline(int row, int col)//长连禁手
{
    for (int u = 0; u < 4; u++)if (num(row, col, u) + num(row, col, u + 4) > 4)return true;
    return false;
}

bool ban(int row, int col)//判断落子后是否成禁手
{
    if (same(row, col, 2))return false;//白方无禁手
    return live3(row, col) > 1 || overline(row, col) || live4(row, col) + chong4(row, col) > 1;
}

bool end_(int row, int col)//(row,col)处落子之后是否游戏结束
{
    for (int u = 0; u < 4; u++)if (num(row, col, u) + num(row, col, u + 4) >= 4)is_end = true;
    if (is_end)return true;
    is_end = ban(row, col);
    return is_end;
}

void go(int row, int col)//落下一子
{
    if (s == s0)p[row][col] = -1; //标出最新下的棋
    else p[row][col] = -2;
    for (int i = 0; i <= N; i++)for (int j = 0; j <= N; j++) //取消上一个最新棋的标识
    {
        if (i == row && j == col)continue;
        if (p[i][j] < 0)p[i][j] *= -1;
    }
    DrawBoard();
    if (ban(row, col))
    {
        printf("禁手\n");
        if (s0 == 1)printf("玩家胜");
        else printf("AI胜");
        Sleep(10000);
    }
    if (end_(row, col))
    {
        if (s == ais)printf("AI胜");
        else printf("玩家胜");
        Sleep(10000);
    }
    manu[0][manukey] = row, manu[1][manukey++] = col;
}

bool ok(int row, int col)//能否落子
{
    return inboard(row, col) && (p[row][col] == 0);
}

int point(int row, int col)//非负分值
{
    if (ban(row, col))return 0;//禁手0分
    if (end_(row, col))
    {
        is_end = false;
        return 10000;
    }
    int ret = live4(row, col) * 1000 + (chong4(row, col) + live3(row, col)) * 100, u;
    for (u = 0; u < 8; u++)if (p[row + dx[u]][col + dy[u]])ret++;//无效点0分
    return ret;
}

int AI3(int p2)
{
    int keyp = -100000, tempp;
    for (int i = 1; i <= N; i++)for (int j = 1; j <= N; j++)
    {
        if (!ok(i, j))continue;
        p[i][j] = s0;
        tempp = point(i, j);
        if (tempp == 0)
        {
            p[i][j] = 0;
            continue;
        }
        if (tempp == 10000)
        {
            p[i][j] = 0;
            return 10000;
        }
        p[i][j] = 0;
        if (tempp - p2 * 2 > keyp)keyp = tempp - p2 * 2;//第三层取极大
    }
    return keyp;
}

int AI2()
{
    int keyp = 100000, tempp;
    for (int i = 1; i <= N; i++)for (int j = 1; j <= N; j++)
    {
        if (!ok(i, j))continue;
        p[i][j] = 3 - s0;
        tempp = point(i, j);
        if (tempp == 0)
        {
            p[i][j] = 0;
            continue;
        }
        if (tempp == 10000)
        {
            p[i][j] = 0;
            return -10000;
        }
        tempp = AI3(tempp);
        p[i][j] = 0;
        if (tempp < keyp)keyp = tempp;//第二层取极小
    }
    return keyp;
}

void AI()
{
    DrawBoard();
    printf("  轮到AI下,请稍候: ");
    if (p[8][8] == 0)return go(8, 8);
    int i, j;
    int keyp = -100000, keyi, keyj, tempp;
    for (i = 1; i <= N; i++)
    {
        for (j = 1; j <= N; j++)
        {
            if (!ok(i, j))continue;
            p[i][j] = s0;
            tempp = point(i, j);
            if (tempp == 0)
            {
                p[i][j] = 0;
                continue;
            }//高效剪枝,避开了禁手点和无效点
            if (tempp == 10000)return go(i, j);
            tempp = AI2();
            p[i][j] = 0;
            if (tempp > keyp)keyp = tempp, keyi = i, keyj = j;//第一层取极大
        }
    }
    return go(keyi, keyj);
}

void out_manual()
{
    char alpha = 'A';
    int i;
    printf("\n  黑方落子位置: ");
    for (i = 0; i < manukey; i += 2)printf("  %c%d", alpha + manu[1][i] - 1, manu[0][i]);
    printf("\n  白方落子位置: ");
    for (i = 1; i < manukey; i += 2)printf("  %c%d", alpha + manu[1][i] - 1, manu[0][i]);
    Sleep(5000);
}

void player()
{
    DrawBoard();
    printf("  轮到玩家下,请输入坐标(输入=0查看棋谱): ");
    char c = '\n';
    int row = 0, col = 0;
    while (c<'0')scanf("%c%d", &c, &row);
    if (c == '=')
    {
        out_manual();
        return player();
    }
    if (c < 'a')col = c - 'A' + 1;
    else col = c - 'a' + 1;
    if (!ok(row, col))
    {
        printf("此处不能下");
        Sleep(1000);
        return player();
    }
    go(row, col);
}

void main()
{
    init();
   while (!is_end)
    {
        if (s == ais)AI();
        else player();
        s = 3 - s;//换下棋方
    }
    return;
}

V202401

再次修改了live3函数的逻辑。

#include <stdio.h>
#include<string>
#include<windows.h>

#define N 15
#define same_u_i same(row + dx[u] * i, col + dy[u] * i, p[row][col])//u方向i距离的点是否同色
#define OutOrNotEmpty (!inboard(row + dx[u] * i, col + dy[u] * i) || p[row + dx[u] * i][col + dy[u] * i] != 0) //出了棋盘或者非空格点

int p[N + 2][N + 2]; //0空1黑2白  1●2○ -1▲-2△
int s = 0, ais = 1, s0;//s是轮到谁下,s=1,2,s=1是ai下,s=2是玩家,s=s0是黑方下,否则是白方下
bool is_end = false;
int dx[8] = { 1, 1, 0, -1, -1, -1, 0, 1 }; //flat技术
int dy[8] = { 0, 1, 1, 1, 0, -1, -1, -1 };//(dx,dy)是8个方向向量
int manu[2][300], manukey = 0;//棋谱

int out(int i, int j)//打印棋盘
{
    if (p[i][j] == 1)return printf("●");
    if (p[i][j] == 2)return printf("○");
    if (p[i][j] == -1)return printf("▲");
    if (p[i][j] == -2)return printf("△");
    if (i == N)
    {
        if (j == 1)return printf("┏");
        if (j == N)return printf("┓");
        return printf("┯");
    }
    if (i == 1)
    {
        if (j == 1)return printf("┗");
        if (j == N)return printf("┛");
        return printf("┷");
    }
    if (j == 1)return printf("┠");
    if (j == N)return printf("┨");
    return printf("┼");
}

void DrawBoard()//打印整个游戏界面
{
    system("cls");
    int row = 0, col = 0, keyr = 0, keyc = 0;
    char alpha = 'A';
    printf("\n\n\n     ");
    for (col = 1; col <= N; col++)printf("%c ", alpha++);
    for (row = N; row >= 1; row--)
    {
        printf("\n   %2d", row);
        for (col = 1; col <= N; col++)
        {
            out(row, col);
            if (p[row][col] < 0)keyr = row, keyc = col;
        }
        printf("%d", row);
    }
    alpha = 'A';
    printf("\n     ");
    for (col = 1; col <= N; col++)printf("%c ", alpha++);
    printf("\n\n");
    if (s0 == ais)printf("  AI执黑,玩家执白\n");
    else printf("  AI执白,玩家执黑\n");
    alpha = 'A';
    if (keyr)printf("  最后落子位置:%c%d\n", alpha + keyc - 1, keyr);
}

void init()//游戏开局初始化
{
    system("color f0");
    printf("输入1或者2进行选择\n1,AI执黑先行\n2,玩家执黑先行\n");
    scanf("%d", &s);
    if (s != 1 && s != 2)return init();
    s0 = s;
    int i, j;
    for (i = 0; i <= N + 1; i++)for (j = 0; j <= N + 1; j++)p[i][j] = 0;//以空格包围棋盘	
    DrawBoard();
    for (j = 0; j < 300; j++)manu[0][j] = manu[1][j] = 0;
}

bool inboard(int row, int col)//判断(row,col)是否在棋盘内
{
    if (row <1 || row > N)return false;
    return col >= 1 && col <= N;
}

int same(int row, int col, int key)//判断2个棋子是否同色
{
    if (!inboard(row, col))return false;
    return (p[row][col] == key || p[row][col] + key == 0);
}

int num(int row, int col, int u)//坐标(row,col),方向向量u,返回该方向有多少连续同色棋子
{
    int i = row + dx[u], j = col + dy[u], sum = 0, ref = p[row][col];
    if (ref == 0)return 0;
    while (same(i, j, ref))sum++, i += dx[u], j += dy[u];
    return sum;
}

int live4(int row, int col)//活4的数量
{
    int sum = 0, i, u;
    for (u = 0; u < 4; u++)//4个方向,判断每个方向是否成活4
    {
        int sumk = 1;
        for (i = 1; same_u_i; i++)sumk++;
        if (OutOrNotEmpty)continue;
        for (i = -1; same_u_i; i--)sumk++;
        if (OutOrNotEmpty)continue;
        if (sumk == 4)sum++;
    }
    return sum;
}

int cheng5(int row, int col)//成5点的数量
{
    int sum = 0, i, u;
    for (u = 0; u < 8; u++)//8个成五点的方向
    {
        int  sumk = 0;
        bool flag = true;
        for (i = 1; same_u_i || flag; i++)
        {
            if (!same_u_i)//该方向的第一个不同色的点,超出边界或者对方棋子或空格
            {
                if (p[row + dx[u] * i][col + dy[u] * i])sumk -= 10;//该方向的第一个不同色的点是对方棋子,没有成五点
                flag = false;
            }
            sumk++;
        }
        if (!inboard(row + dx[u] * --i, col + dy[u] * i))continue;//该方向的第一个不同色的点是超出边界,没有成五点
        for (i = -1; same_u_i; i--)sumk++;
        if (sumk == 4)sum++;
    }
    return sum;
}

int chong4(int row, int col)//冲4的数量
{
    return cheng5(row, col) - live4(row, col) * 2;
}

int live3(int row, int col)//活3的数量
{
    int key = p[row][col], sum = 0, i, u, flag = 2;
    for (u = 0; u < 4; u++)//三连的活三
    {
        int sumk = 1;
        for (i = 1; same_u_i; i++)sumk++;
        if (OutOrNotEmpty)continue;
        i++;
        if (OutOrNotEmpty)flag--;
        for (i = -1; same_u_i; i--)sumk++;
        if (OutOrNotEmpty)continue;
        i--;
        if (OutOrNotEmpty)flag--;
        if (sumk == 3 && flag > 0)sum++;
    }
    for (u = 0; u < 8; u++)//8个方向,每个方向最多1个非三连的活三
    {
        int  sumk = 0;
        bool flag = true;
        for (i = 1; same_u_i || flag; i++)//成活四点的方向
        {
            if (!same_u_i)
            {
                if (p[row + dx[u] * i][col + dy[u] * i])sumk -= 10;
                flag = false;
            }
            sumk++;
        }
        if (OutOrNotEmpty)continue;
        if (p[row + dx[u] * --i][col + dy[u] * i] == 0)continue;
        for (i = -1; same_u_i; i--)sumk++;
        if (OutOrNotEmpty)continue;
        if (sumk == 3)sum++;
    }
    return sum;
}

bool overline(int row, int col)//长连禁手
{
    for (int u = 0; u < 4; u++) {
        if (num(row, col, u) + num(row, col, u + 4) > 4)return true;
    }
    return false;
}

bool ban(int row, int col)//判断落子后是否成禁手
{
    if (same(row, col, 2))return false;//白方无禁手
    return live3(row, col) > 1 || overline(row, col) || live4(row, col) + chong4(row, col) > 1;
}

bool end_(int row, int col)//(row,col)处落子之后是否游戏结束
{
    for (int u = 0; u < 4; u++) {
        if (num(row, col, u) + num(row, col, u + 4) >= 4)return is_end = true;
    }
    return is_end = ban(row, col);
}

void go(int row, int col)//落下一子
{
    if (s == s0)p[row][col] = -1; //标出最新下的棋
    else p[row][col] = -2;
    for (int i = 0; i <= N; i++)for (int j = 0; j <= N; j++) //取消上一个最新棋的标识
    {
        if (i == row && j == col)continue;
        if (p[i][j] < 0)p[i][j] *= -1;
    }
    DrawBoard();
    if (ban(row, col))
    {
        printf("禁手\n");
        if (s0 == 1)printf("玩家胜");
        else printf("AI胜");
        Sleep(10000);
    }
    if (end_(row, col))
    {
        if (s == ais)printf("AI胜");
        else printf("玩家胜");
        Sleep(10000);
    }
    manu[0][manukey] = row, manu[1][manukey++] = col;
}

bool ok(int row, int col)//能否落子
{
    return inboard(row, col) && (p[row][col] == 0);
}

int point(int row, int col)//非负分值
{
    if (ban(row, col))return 0;//禁手0分
    if (end_(row, col))
    {
        is_end = false;
        return 10000;
    }
    int ret = live4(row, col) * 1000 + (chong4(row, col) + live3(row, col)) * 100, u;
    for (u = 0; u < 8; u++) {
        if (p[row + dx[u]][col + dy[u]])ret++;//无效点0分
    }
    return ret;
}

int AI3(int p2)
{
    int keyp = -100000, tempp;
    for (int i = 1; i <= N; i++)for (int j = 1; j <= N; j++)
    {
        if (!ok(i, j))continue;
        p[i][j] = s0;
        tempp = point(i, j);
        if (tempp == 0)
        {
            p[i][j] = 0;
            continue;
        }
        if (tempp == 10000)
        {
            p[i][j] = 0;
            return 10000;
        }
        p[i][j] = 0;
        if (tempp - p2 * 2 > keyp)keyp = tempp - p2 * 2;//第三层取极大
    }
    return keyp;
}

int AI2()
{
    int keyp = 100000, tempp;
    for (int i = 1; i <= N; i++)for (int j = 1; j <= N; j++)
    {
        if (!ok(i, j))continue;
        p[i][j] = 3 - s0;
        tempp = point(i, j);
        if (tempp == 0)
        {
            p[i][j] = 0;
            continue;
        }
        if (tempp == 10000)
        {
            p[i][j] = 0;
            return -10000;
        }
        tempp = AI3(tempp);
        p[i][j] = 0;
        if (tempp < keyp)keyp = tempp;//第二层取极小
    }
    return keyp;
}

void AI()
{
    DrawBoard();
    printf("  轮到AI下,请稍候: ");
    if (p[8][8] == 0)return go(8, 8);
    int i, j;
    int keyp = -100000, keyi, keyj, tempp;
    for (i = 1; i <= N; i++)
    {
        for (j = 1; j <= N; j++)
        {
            if (!ok(i, j))continue;
            p[i][j] = s0;
            tempp = point(i, j);
            if (tempp == 0)
            {
                p[i][j] = 0;
                continue;
            }//高效剪枝,避开了禁手点和无效点
            if (tempp == 10000)return go(i, j);
            tempp = AI2();
            p[i][j] = 0;
            if (tempp > keyp)keyp = tempp, keyi = i, keyj = j;//第一层取极大
        }
    }
    return go(keyi, keyj);
}

void out_manual()
{
    char alpha = 'A';
    int i;
    printf("\n  黑方落子位置: ");
    for (i = 0; i < manukey; i += 2)printf("  %c%d", alpha + manu[1][i] - 1, manu[0][i]);
    printf("\n  白方落子位置: ");
    for (i = 1; i < manukey; i += 2)printf("  %c%d", alpha + manu[1][i] - 1, manu[0][i]);
    Sleep(5000);
}

void player()
{
    DrawBoard();
    printf("  轮到玩家下,请输入坐标(输入=0查看棋谱): ");
    char c = '\n';
    int row = 0, col = 0;
    while (c < '0')scanf("%c%d", &c, &row);
    if (c == '=')
    {
        out_manual();
        return player();
    }
    if (c < 'a')col = c - 'A' + 1;
    else col = c - 'a' + 1;
    if (!ok(row, col))
    {
        printf("此处不能下");
        Sleep(1000);
        return player();
    }
    go(row, col);
}

void main()
{
    init();
    while (!is_end)
    {
        if (s == ais)AI();
        else player();
        s = 3 - s;//换下棋方
    }
    return;
}

七,棋盘显示问题

很多网友反馈棋盘显示异常的问题,为此我特意写了一篇博客来解释:

命令行显示棋盘异常_nameofcsdn的博客-CSDN博客

八,更多规则

1,三手交换

在三手交换规则里,第一手必须下天元,而第二手必须在第一手的周围。黑棋下盘面第3手棋后,白方在下第四手之前,如感觉黑方棋形不利于己方,可提出交换,即执白棋一方变为执黑棋一方,而黑方不可以不换。

2,五手两打

黑棋在下盘面上关键的第5手棋时,必须下两步棋,让白棋在这两步棋中拿掉一粒棋子,然后再继续对弈。

九,相关问题

力扣 LCP 48. 无限棋局

小力正在通过残局练习来备战「力扣挑战赛」中的「五子棋」项目,他想请你能帮他预测当前残局的输赢情况。棋盘中的棋子分布信息记录于二维数组 pieces 中,其中 pieces[i] = [x,y,color] 表示第 i 枚棋子的横坐标为 x,纵坐标为 y,棋子颜色为 color(0 表示黑棋,1 表示白棋)。假如黑棋先行,并且黑棋和白棋都按最优策略落子,请你求出当前棋局在三步(按 黑、白、黑 的落子顺序)之内的输赢情况(三步之内先构成同行、列或对角线连续同颜色的至少 5 颗即为获胜):

  • 黑棋胜, 请返回 "Black"
  • 白棋胜, 请返回 "White"
  • 仍无胜者, 请返回 "None"

注意:

  • 和传统的五子棋项目不同,「力扣挑战赛」中的「五子棋」项目 不存在边界限制,即可在 任意位置 落子;
  • 黑棋和白棋均按 3 步内的输赢情况进行最优策略的选择
  • 测试数据保证所给棋局目前无胜者;
  • 测试数据保证不会存在坐标一样的棋子。

示例 1:

输入: pieces = [[0,0,1],[1,1,1],[2,2,0]]

输出:"None"

解释:无论黑、白棋以何种方式落子,三步以内都不会产生胜者。

示例 2:

输入: pieces = [[1,2,1],[1,4,1],[1,5,1],[2,1,0],[2,3,0],[2,4,0],[3,2,1],[3,4,0],[4,2,1],[5,2,1]]

输出:"Black"

解释:三步之内黑棋必胜,以下是一种可能的落子情况:

902b87df29998b1c181146c8fdb3a4b6.gif

提示:

  • 0 <= pieces.length <= 1000
  • pieces[i].length = 3
  • -10^9 <= pieces[i][0], pieces[i][1] <=10^9
  • 0 <= pieces[i][2] <=1

直接搬运上面的代码,修修补补最后得到这样的代码:

#define same_u_i same(row + dx[u] * i, col + dy[u] * i, p[row][col])//u方向i距离的点是否同色
#define OutOrNotEmpty (!inboard(row + dx[u] * i, col + dy[u] * i) || p[row + dx[u] * i][col + dy[u] * i] != 0) //出了棋盘或者非空格点

map<int, map<int, int>>p;
int s = 0, ais = 1, s0;//s是轮到谁下,s=1,2,s=1是ai下,s=2是玩家,s=s0是黑方下,否则是白方下
bool is_end = false;
int dx[8] = { 1, 1, 0, -1, -1, -1, 0, 1 }; //flat技术
int dy[8] = { 0, 1, 1, 1, 0, -1, -1, -1 };//(dx,dy)是8个方向向量
int manu[2][300], manukey = 0;//棋谱
bool inboard(int row, int col)//判断(row,col)是否在棋盘内
{
    return true;
}

int same(int row, int col, int key)//判断2个棋子是否同色
{
    if (!inboard(row, col))return false;
    return (p[row][col] == key || p[row][col] + key == 0);
}

int num(int row, int col, int u)//坐标(row,col),方向向量u,返回该方向有多少连续同色棋子
{
    int i = row + dx[u], j = col + dy[u], sum = 0, ref = p[row][col];
    if (ref == 0)return 0;
    while (same(i, j, ref))sum++, i += dx[u], j += dy[u];
    return sum;
}


int cheng5(int row, int col)//成5点的数量
{
    int sum = 0, i, u;
    for (u = 0; u < 8; u++)//8个成五点的方向
    {
        int  sumk = 0;
        bool flag = true;
        for (i = 1; same_u_i || flag; i++)
        {
            if (!same_u_i)//该方向的第一个不同色的点,超出边界或者对方棋子或空格
            {
                if (p[row + dx[u] * i][col + dy[u] * i])sumk -= 10;//该方向的第一个不同色的点是对方棋子,没有成五点
                flag = false;
            }
            sumk++;
        }
        if (!inboard(row + dx[u] * --i, col + dy[u] * i))continue;//该方向的第一个不同色的点是超出边界,没有成五点
        for (i = -1; same_u_i; i--)sumk++;
        if (sumk >= 4)sum++;
    }
    return sum;
}
bool line(int row, int col) //是成5点
{
    for (int u = 0; u < 4; u++) {
        if (num(row, col, u) + num(row, col, u + 4) >= 4)return true;
    }
    return false;
}
class Solution {
public:
    string gobang(vector<vector<int>>& pieces) {
        p.clear();
        for (auto v : pieces) {
            p[v[0]][v[1]] = 2 - v[2];
        }
        if (canWinOneStep(pieces))return "Black";
        int x = canWin1PosNum(pieces);
        if (x>1)return "White";
        if(x==0){
            if (canWin0(pieces))return "Black";
            return "None";
        }else{
            if (canWin0(pieces,ti,tj))return "Black";
            return "None";
        }
    }
    bool canWinOneStep(vector<vector<int>>& pieces) {
        //0已经有成5点
        for (auto v : pieces) {
            if (v[2] == 0 && cheng5(v[0], v[1]))return true;
        }
        return false;
    }    
    int canWin1PosNum(vector<vector<int>>& pieces) {
        //1的成5点数量
        set<pair<int, int>>s;
        for (auto v : pieces) {
            if (v[2]==0)continue;
            for (int i = v[0] - 2; i <= v[0] + 2; i++) {
                for (int j = v[1] - 2; j <= v[1] + 2; j++) {
                    if (p[i][j] == 0)s.insert(make_pair(i, j));
                }
            }
        }
        int n=0;
        for (auto par : s) {
            int i = par.first, j = par.second;
            p[i][j]=1;
            if(line(i,j))n++,ti=i,tj=j;
            p[i][j]=0;
        }
        cout<<n<<endl;
        return n;
    }
    bool canWin0(vector<vector<int>>& pieces) {
        set<pair<int, int>>s;
        for (auto v : pieces) {
            if (v[2])continue;
            for (int i = v[0] - 2; i <= v[0] + 2; i++) {
                for (int j = v[1] - 2; j <= v[1] + 2; j++) {
                    if (p[i][j] == 0)s.insert(make_pair(i, j));
                }
            }
        }
        for (auto par : s) {
            int i = par.first, j = par.second;
            p[i][j] = 2;
            if (cheng5(i, j)>1){
                return true;
            }
            p[i][j] = 0;
        }
        return false;
    }
    bool canWin0(vector<vector<int>>& pieces,int i,int j) {
        p[i][j] = 2;
        if (cheng5(i, j)>1){
            return true;
        }
        p[i][j] = 0;
        return false;
    }    
    int ti,tj;
};

稍微化简一下:

#define same_u_i same(row + dx[u] * i, col + dy[u] * i, p[row][col])//u方向i距离的点是否同色

map<int, map<int, int>>p;
int dx[8] = { 1, 1, 0, -1, -1, -1, 0, 1 }; //flat技术
int dy[8] = { 0, 1, 1, 1, 0, -1, -1, -1 };//(dx,dy)是8个方向向量

int same(int row, int col, int key)//判断2个棋子是否同色
{
    return (p[row][col] == key || p[row][col] + key == 0);
}

int num(int row, int col, int u)//坐标(row,col),方向向量u,返回该方向有多少连续同色棋子
{
    int i = row + dx[u], j = col + dy[u], sum = 0, ref = p[row][col];
    if (ref == 0)return 0;
    while (same(i, j, ref))sum++, i += dx[u], j += dy[u];
    return sum;
}
int cheng5(int row, int col)//成5点的数量
{
    int sum = 0, i, u;
    for (u = 0; u < 8; u++)//8个成五点的方向
    {
        int  sumk = 0;
        bool flag = true;
        for (i = 1; same_u_i || flag; i++)
        {
            if (!same_u_i)//该方向的第一个不同色的点,超出边界或者对方棋子或空格
            {
                if (p[row + dx[u] * i][col + dy[u] * i])sumk -= 10;//该方向的第一个不同色的点是对方棋子,没有成五点
                flag = false;
            }
            sumk++;
        }
        for (i = -1; same_u_i; i--)sumk++;
        if (sumk >= 4)sum++;
    }
    return sum;
}
bool line(int row, int col) //是成5点
{
    for (int u = 0; u < 4; u++) {
        if (num(row, col, u) + num(row, col, u + 4) >= 4)return true;
    }
    return false;
}
class Solution {
public:
    string gobang(vector<vector<int>>& pieces) {
        p.clear();
        for (auto v : pieces) {
            p[v[0]][v[1]] = 2 - v[2];
        }
        if (numCheng5(pieces,0))return "Black";
        int x = numCheng5(pieces,1);
        if (x>1)return "White";
        if(x==0){
            if (canWin0(pieces))return "Black";
        }else{
            if (canWin0(pieces,ti,tj))return "Black";
        }
        return "None";
    }
    int numCheng5(vector<vector<int>>& pieces,int x) {
        set<pair<int, int>>s;
        for (auto v : pieces) {
            if (v[2]!=x)continue;
            for (int i = v[0] - 1; i <= v[0] + 1; i++) {
                for (int j = v[1] - 1; j <= v[1] + 1; j++) {
                    if (p[i][j] == 0)s.insert(make_pair(i, j));
                }
            }
        }
        int n=0;
        for (auto par : s) {
            int i = par.first, j = par.second;
            p[i][j]=2-x;
            if(line(i,j))n++,ti=i,tj=j;
            p[i][j]=0;
        }
        return n;
    }
    bool canWin0(vector<vector<int>>& pieces) {
        set<pair<int, int>>s;
        for (auto v : pieces) {
            if (v[2])continue;
            for (int i = v[0] - 2; i <= v[0] + 2; i++) {
                for (int j = v[1] - 2; j <= v[1] + 2; j++) {
                    if (p[i][j] == 0)s.insert(make_pair(i, j));
                }
            }
        }
        for (auto par : s) {
            if(canWin0(pieces, par.first, par.second))return true;
        }
        return false;
    }
    bool canWin0(vector<vector<int>>& pieces,int i,int j) {
        p[i][j] = 2;
        if (cheng5(i, j)>1){
            return true;
        }
        p[i][j] = 0;
        return false;
    }    
    int ti,tj;
};

用c#编写的五子棋人机对战 核心算法 核心算法就是计算计算机应该在哪里落子。    思路的伪代码如下。    PC_Stone    For i = 1 to 15     For j = 1 to 15     If ( board[i][j] != -1)     Qz[i][j] = -     Esle     FindQz(Qz[i][j])    getTheMaxQz()    而在这个过程中最主要的算法是计算每个点的权重,由此判断电脑应该将棋子落在哪个地方。    计算确定点的权重的函数是FindQz();,函数里面有对于多种不同情况下,函数所赋给那个点的权重值,这些值是累加的。函数主要通过对四个函数X1(),X2(),X3(),X4()的调用来确定每个点所处地位。    FindQz()函数可以分为两部分。    第一部分是假设人在此点落下一子后,此点给人所带来的好处是多少。通过对函数X1()计算如果在点board[i][j]落下,所在行有多少连续的相同的点数。与X1(),类似的是X2()是计算board[i][j]所在列的点数。X3()是计算左高右低的斜排,X4()是左低右高的情况。经过计算后,将这四种情况所带来的改变加到一起,就是将棋子落在这里对假设方带来的好处。    第二部分是假设电脑在此落一点之后,此点给电脑带来的好处是多少。调用过程与第一部分基本一样,没什么不同。    经过这样的调用,将两部分计算出的结果加到一起,算出来的就是电脑下在这一点会带来的所有影响。选取影响最大的一点,落子,这样就能在一定程度上达到某种智能。    对于X1()函数,他的作用实现是这样的。运用两个计数器count与flag,count用于计算有多少相连的相同的子,并且是一board[i][j]为中心,向两边发散(说他是中心并不意味着他是相连的点的中心位置)。Flag是计算相连的子的两端是否有阻挡。阻挡分为两部分,一部分是到了表格的尽头,另一种是被另一种颜色的棋子挡住。这样综合count与flag两个参数,给出board[i][j]点对于行的贡献值。    对于X2(),X3(),X4()他们的原理是与X1()一样的,只不过是坐标不同罢了。这样,计算完之后,再进行比较,就能得到最好的点了。        对于特殊落点的判断问题:   设以围棋棋盘左下角为坐标原点建立直角坐标系xOy,   若(9,10)(9,11)(10,10)(11,9)上有黑子,(8,12)(10,9)(11,8)上有白子,现在到白棋走子,   若走(10,12)(11,11)就属于斜向走子,但是通常直向的(紧贴着棋子走的)走子要比斜向的走子对防守的贡献大,   若走(8,11)(8,10)(8,9)(9,9)其中一个的均属于单侧走子,而另一侧则空虚,所以也不好,   若走(11,10)也不好,因为在(11,8)的子已经对防守有了一定的贡献了,同理走(10,11)的也不好,因为(10,9)的子也对防守有了一定的贡献.所以说此时最佳的走子方法就是走(9,12)   若(8,10)有白子,(10,10)(11,10)(12,10)有黑子,到白子走,由于(8,10)对防守贡献了一部分,所以应该走(13,10)而不走(9,10)。   若为黑走,就应该走(13,10)而不走(9,10),因为白子的(8,10)会削弱它对攻的贡献
这里是一个简单的五子棋人机对战的 C 代码,可以供您参考。 ``` #include <stdio.h> #include <stdlib.h> #include <time.h> #define ROW 15 #define COL 15 char board[ROW][COL]; // 棋盘 // 初始化棋盘 void InitBoard() { int i, j; for (i = 0; i < ROW; i++) { for (j = 0; j < COL; j++) { board[i][j] = ' '; } } } // 打印棋盘 void PrintBoard() { int i, j; printf(" "); for (i = 0; i < ROW; i++) { printf("%d ", i); } printf("\n"); for (i = 0; i < ROW; i++) { printf("%d ", i); for (j = 0; j < COL; j++) { printf("%c|", board[i][j]); } printf("\n"); } } // 判断胜负 int IsWin(char c) { int i, j, k; // 横向判断 for (i = 0; i < ROW; i++) { for (j = 0; j < COL - 4; j++) { for (k = 0; k < 5; k++) { if (board[i][j+k] != c) { break; } } if (k == 5) { return 1; } } } // 纵向判断 for (i = 0; i < ROW - 4; i++) { for (j = 0; j < COL; j++) { for (k = 0; k < 5; k++) { if (board[i+k][j] != c) { break; } } if (k == 5) { return 1; } } } // 斜向判断 for (i = 0; i < ROW - 4; i++) { for (j = 0; j < COL - 4; j++) { for (k = 0; k < 5; k++) { if (board[i+k][j+k] != c) { break; } } if (k == 5) { return 1; } } } // 反斜向判断 for (i = 4; i < ROW; i++) { for (j = 0; j < COL - 4; j++) { for (k = 0; k < 5; k++) { if (board[i-k][j+k] != c) { break; } } if (k == 5) { return 1; } } } return 0; } // 人下棋 void PlayerMove() { int x, y; while (1) { printf("请输入坐标(x y):"); scanf("%d %d", &x, &y); if (x < 0 || x >= ROW || y < 0 || y >= COL || board[x][y] != ' ') { printf("坐标输入有误,请重新输入!\n"); } else { board[x][y] = '*'; break; } } } // 电脑下棋 void ComputerMove() { int x, y; srand((unsigned int)time(NULL)); while (1) { x = rand() % ROW; y = rand() % COL; if (board[x][y] == ' ') { board[x][y] = '#'; break; } } } // 主函数 int main() { InitBoard(); PrintBoard(); while (1) { PlayerMove(); PrintBoard(); if (IsWin('*')) { printf("恭喜你,你赢了!\n"); break; } ComputerMove(); PrintBoard(); if (IsWin('#')) { printf("很遗憾,你输了!\n"); break; } } return 0; } ``` 这个代码实现了一个简单的五子棋人机对战,人执黑子,电脑执白子。电脑下棋使用了随机策略,没有使用更加智能的算法。如果您想要提高电脑的水平,可以尝试使用更加高级的算法,比如 Alpha-Beta 剪枝、蒙特卡罗树搜索等。
评论 100
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值