五子棋人机对战完整代码

news/2024/12/4 3:50:55/

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

目录

〇,前言

一,五子棋棋盘

二,五子棋比赛规则

1,行棋顺序

2,判断胜负

三,重要棋型解释

1,五连

2,活四

3,冲四

4,活三

四,禁手规则

1,三三禁手

2,四四禁手

3,长连禁手

五,代码解释

1,棋子表示

2,棋盘表示

3,flat技术

4,棋型判断和禁手判断

4.1 活四

4.2 冲四

4.3 活3

5,AI算法

6,AI的打分机制

7,搜索剪枝

8,棋谱和禁手调试

六,代码


〇,前言

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

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

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

唉,当代大学生啊。

一,五子棋棋盘

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

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

二,五子棋比赛规则

1,行棋顺序

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

2,判断胜负

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

三,重要棋型解释

1,五连

五颗同色棋子连在一起,

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

2,活四

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

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

3,冲四

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

4,活三

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

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

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

PS:这个三连描述的不准确,在01110的两端,必须至少有一个空格

四,禁手规则

1,三三禁手

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

2,四四禁手

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

3,长连禁手

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

五,代码解释

1,棋子表示

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

2,棋盘表示

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

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

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

3,flat技术

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

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

4,棋型判断和禁手判断

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

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

4.1 活四

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

4.2 冲四

冲4的棋型比较多:

(1)8个方向的Y01111X,X是超出边界或者对方棋子,Y是超出边界或者对方棋子或空格,下同

(2)8个方向的Y11101Y,当前位置是右边的那个1

(3)8个方向的Y10111Y,当前位置是右边的3个1中的一个

(4)8个方向的Y11011Y,当前位置是右边的2个1中的一个

可以说按照这种思路来求就非常复杂。

分析这几种情况,我们可以发现,活四和冲四加起来,可以用一个统一的描述:

YabcdeY,其中abcde由4个1和1个0组成。

进一步,我们可以得到,成五点的数量是活四的数量*2+冲四的数量,而成五点的数量可以根据YabcdeY的形式来求,即:

在8个方向上求成五点,对于每个方向,从当前位置往前延伸,第一个不同色的点是空格,跳过空格继续往前延伸直到第二个不同色的点,同时从当前位置往反方向延伸直到第一个不同色的点, 算上当前位置本身,如果同色点的数量一共是4,那么该方向就有成五点。

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

4.3 活3

在V201912代码中,活3是分开计算三连活3的数量和非3连的活3的数量,然后加起来

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++;  //据网友提示这里应该是i--,写代码过了很久了,懒得确认真相了off;if (sumk == 3)sum++;
}

网友提示把其中一个i++改成i--,这样确实好一些,不过仍然是错的。

改成i--之后,代码实际求的是4个方向的0011100这种三连,但是实际上011100或者001110的形式都可以。

新的代码在上述i++改成i--的基础之上,再加一个flag变量,用来判断01110的两端是否至少还有一个空格。

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++;  //据网友提示这里应该是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;
}

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

V202001:

#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;
}

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

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


http://www.ppmy.cn/news/117020.html

相关文章

轨迹数据

如何通过轨迹相似性度量方法&#xff0c;发现新冠易感人群 轨迹作为一种时空数据&#xff0c;指的是某物体在空间中的移动路径&#xff0c;通常表示为GPS点的序列&#xff0c;例如tr<p1→p2→…pn>&#xff0c;其中点pi(lat,lng,t)&#xff0c;表示该物体在t时刻位于地理…

百度离线地图示例之十三:动态运行轨迹实现(附源码)

前言介绍&#xff1a; 主要是基于v3.0的API版本进行的离线&#xff0c;纯内网可操作&#xff0c;基本上实现了现有90%以上的功能点&#xff0c;能兼容jpg和png格式的瓦片图层&#xff0c;实现了原生和基于Vue两个版本&#xff08;包含常用的55个示例&#xff09;&#xff0c;文…

EmuELEC初体验(源码下载)

接触这个EmuELEC复古系统有几个月&#xff0c;从了解到深度定制开发&#xff0c;也花了不少时间。记得当时开始接触emuelec的时候&#xff0c;也是各种查阅资料&#xff0c;发现这块开发的资料在国内的确非常少&#xff0c;所以走了很多弯路&#xff0c;目前对此系统源码也还算…

光之轨迹探险记:迷失之城 NFT 素材包

光之轨迹探险记带来了他们的标志性资产&#xff0c;伟大的《失落之城》中又一个丰富多彩、独创的未来主义体验。 不要犹豫&#xff0c;这些酷炫的资产值得拥有&#xff01; 別忘了&#xff0c;购买该系列中的任意一个资产将授予你「收藏家徽章」&#xff0c;可以解锁 30 SAND 的…

web前端 -- javascript(02) -- 数据类型转换、常见运算符及顺序、选择和循环结构

数据类型转换 &#xff08;1&#xff09;自动类型转换&#xff1a; 特点&#xff1a;低类型自动向高类型进行转换 boolean < int < float < string PS&#xff1a; boolean类型如果与数值型进行运算&#xff1a;true 1&#xff1b;false 0其他基本数据类型与字符串…

人效九宫格|三个提升路径,三种管理模式,让人效实时可量化

文&#xff5c;盖雅学苑‍‍ 本文共5202字 在经济高速发展的过去&#xff0c;企业更关注机遇&#xff0c;当经济发展速度进入新常态时&#xff0c;企业更关注效率。在盖雅工场发布的《企业人效管理白皮书》中的数据显示&#xff0c;69.9%的企业依旧将人效提升作为紧急事项&am…

WinForm——获取窗体或控件可见属性来达到隐藏时不动作的目的

WinForm——获取窗体或控件可见属性来达到隐藏时不动作的目的 前言一、获取窗体的可见状态&#xff08;Visible&#xff09;二、当控件内部判断时&#xff0c;想获取对应父窗体的可见状态时&#xff08; FindForm()&#xff09;三、当控件内部判断时&#xff0c;想获取对应父容…

【明解STM32】中断系统理论基础知识篇之中断寄存器功能原理

目录 一、前言 二、寄存器概述 三、NVIC寄存器组 四、SCB寄存器组 五、中断屏蔽寄存器组 六、总结 一、前言 在之前的STM32的中断系统理论基础知识之基本原理及NVIC中&#xff0c;分别中断的基本原理&#xff0c;中断的管理机制和中断的处理流程进行了较为详细的论述&…