概要
琐碎的地方就不提了,人机对战五子棋的核心在于如何分析棋局,使程序落子最优化。当然我只是简易版本,没有棋盘界面化,没有禁手,策略也不能算是精良,只能勉强当作一个参考供大家学习。
Ps:AI有时候会犯傻,可能是分值设置的原因,请谅解??
如何分析棋局
这里只是概括,具体可以看之后挂出来的网址。
- 确定有效空位(即AI下棋的空位选项):落子处周围米字型区域(8个点),每次落子都要更新记录空位的集合。
- 空位的评分:这里需要对每种棋局进行打分(四子、三子、两子、一子都有各自的情况),最后累加得到该空位的局势得分,具体评分内容可以看这里的网址(https://www.write-bug.com/article/1372.html) [转载自write-bug.com]
- 求最高分的空位:即为程序最优落子点
下面po上代码
fivechess.c
#include <stdio.h>
#include <stdlib.h>
#define CHESS_MAX 24 //行列坐标
struct local //空位信息
{
int local_carrier; //存储四位坐标信息
int score; //空位的得分信息
}empty1[CHESS_MAX*CHESS_MAX];
int judge[CHESS_MAX][CHESS_MAX] = {{0}}; //用于判断该空位是否在空位结构体中 0:不在 1:在 排除重复性计入结构体
int board[CHESS_MAX][CHESS_MAX] = {{0}}; //棋盘数组
int number_empty = 0; //空位数
int x, y; //临时记录坐标
//根据棋型计算空位得分,count1为相连棋子数,
//leftStatus、rightStatus为1或2,1代表为空,2为墙或者对方棋子
int getScoreBySituation(int count1, int left1, int right1);//这个需要对照评分表
//横向扫描计算得分 cur表示AI或者人类
int getXScore(int x, int y, int cur);
//纵向扫描计算得分
int getYScore(int x, int y, int cur);
//正斜向扫描计算得分
int getSkewScore1(int x, int y, int cur);
//反斜向扫描计算得分
int getSkewScore2(int x, int y, int cur);
//上面五个函数在get_score.c中定义
//显示棋盘
void show_chess_board(int (*a)[CHESS_MAX][CHESS_MAX]);
//用户下棋 返回落子信息
int get_inputchess();
//落子处为中心的米字型区域记录空位
void add_empty(int x, int y);
//双方在空位处下子 要在judge数组中更新状态
void update_judge_empty(int x, int y);
//更新各个空位的总得分
void update_score_empty();
//返回得分最高
int return_high();
int win(int a);
//以上为函数
//将空位存入数组中
void add_empty(int x, int y)
{
int i, j;
for (i = x - 1; i <= x + 1; i++)
{
for (j = y - 1; j <= y + 1; j++)
{
if(i == x && j == y)
break;
if(board[i][j] == 0 && judge[i][j] == 0)//该位置没有下过并且不在空位结构体中
{
empty1[number_empty].local_carrier = i*100+j;
number_empty++;
judge[i][j] = 1;
}
}
}
}
//人类或者AI下一次棋后更新空位分数
void update_score_empty()
{
int i, grade;
for(i = 0; i < number_empty; i++)
{
grade = empty1[i].local_carrier;
int X = grade / 100, Y = grade % 100;
if(board[X][Y] != 0)//空位被下过就不再计算得分
{
empty1[i].score = 0;
continue;
}
else
{
empty1[i].score = getXScore(X,Y,1) + getXScore(X,Y,2)
+ getYScore(X,Y,1) + getYScore(X,Y,2) + getSkewScore1(X,Y,1)
+ getSkewScore1(X,Y,2) + getSkewScore2(X,Y,1) + getSkewScore2(X,Y,2);
}
}
}
int win(int a)
{ //判断胜负
int x,y;//x means line,y means column
//judge line
for(y=0;y<17;y++)//last four column no need to judge
{
for(x=0;x<20;x++)
{
if(a==board[x][y]&&a==board[x+1][y]&&a==board[x+2][y]&&a==board[x+3][y]&&a==board[x+4][y])
return a;//win
}
}
//judge vitical
for(y=0;y<20;y++)
{
for(x=0;x<17;x++)//last four line no need to judge
{
if(a==board[x][y]&&a==board[x][y+1]&&a==board[x][y+2]&&a==board[x][y+3]&&a==board[x][y+4])
return a;
}
}
// judge "\"
for(y=0;y<20;y++)
{
for(x=0;x<17;x++)//last four line no need to judge
{
if(a==board[x][y]&&a==board[x+1][y+1]&&a==board[x+2][y+2]&&a==board[x+3][y+3]&&a==board[x+4][y+4])
return a;
}
}
// judge "/"
for(y=19;y>3;y--)
{
for(x=0;x<17;x++)//last four line no need to judge
{
if(a==board[x][y]&&a==board[x+1][y-1]&&a==board[x+2][y-2]&&a==board[x+3][y-3]&&a==board[x+4][y+4])
return a;
}
}
return 0;
}
//返回得分最高
int return_high()
{
int i, grade_msg;
int max = 0, max_location;
for (i = 0; i < number_empty; i++)
{
grade_msg = empty1[i].local_carrier;
if(judge[grade_msg/100][grade_msg%100] == 1 && board[grade_msg/100][grade_msg%100] == 0)//在空位结构体中 且未下过
{
if(empty1[i].score > max)//若得分都相同,就返回第一个找到的空位
{
max = empty1[i].score;
max_location = empty1[i].local_carrier;
}
}
}
return max_location;
}
//人类落子
int get_inputchess()
{
int a, circle_x = 0, circle_y = 0;
char local_x, local_y;
do//记录x坐标
{
printf("please input the chess of line:");
scanf("%c", &local_x);
getchar();//换行符去掉
switch(local_x)
{
case '0':
x = 0;
case '1':
x = 1;
break;
case '2':
x = 2;
break;
case '3':
x = 3;
break;
case '4':
x = 4;
break;
case '5':
x = 5;
break;
case '6':
x = 6;
break;
case '7':
x = 7;
break;
case '8':
x = 8;
break;
case '9':
x = 9;
break;
case 'a':
x = 10;
break;
case 'b':
x = 11;
break;
case 'c':
x = 12;
break;
case 'd':
x = 13;
break;
case 'e':
x = 14;
break;
case 'f':
x = 15;
break;
case 'g':
x = 16;
break;
case 'h':
x = 17;
break;
case 'i':
x = 18;
break;
case 'j':
x = 19;
break;
case 'k':
x = 20;
break;
case 'l':
x = 21;
break;
case 'm':
x = 22;
break;
case 'n':
x = 23;
break;
default:
printf("wrong input%c\n",local_x);
circle_x = 1;
break;
}
}
while(circle_x != 0);
printf("please input the chess of row:"); //y的处理同上
do
{
scanf("%c", &local_y); //获取列坐标
getchar();
switch (local_y)
{
case '0':
y = 0;
case '1':
y = 1;
break;
case '2':
y = 2;
break;
case '3':
y = 3;
break;
case '4':
y = 4;
break;
case '5':
y = 5;
break;
case '6':
y = 6;
break;
case '7':
y = 7;
break;
case '8':
y = 8;
break;
case '9':
y = 9;
break;
case 'a':
y = 10;
break;
case 'b':
y = 11;
break;
case 'c':
y = 12;
break;
case 'd':
y = 13;
break;
case 'e':
y = 14;
break;
case 'f':
y = 15;
break;
case 'g':
y = 16;
break;
case 'h':
y = 17;
break;
case 'i':
y = 18;
break;
case 'j':
y = 19;
break;
case 'k':
y = 20;
break;
case 'l':
y = 21;
break;
case 'm':
y = 22;
break;
case 'n':
y = 23;
break;
default:
printf("wrong input %c\n", local_y);
circle_y = 1;
break;
}
}
while (circle_y == 1);
a = x*100+y;//坐标信息
board[x][y] = 1;
return a;
}
//显示棋盘
void show_chess_board(int (*a)[CHESS_MAX][CHESS_MAX])
{
int i, j, k = 0; //k用来控制显示空格,换行
printf("\n\n");
printf(" 0123 4567 89ab cdef ghij klmn\n"); //打印出列坐标
for (i = 0; i < CHESS_MAX; i++) //i为行
{
for (j = 0; j < CHESS_MAX; j++) //j为列
{
//实现每四个棋子就出现一个空格,方便查找位置去下子
if (k > 0 && (k % 4 == 0))
printf(" ");
//每24个棋子输出,就换行显示
if (k > 0 && (k % CHESS_MAX == 0))
printf("\n");
//显示每行的开头的数字
if (j == 0)
{
if (i <= 9)
printf("%d", i); //开始9行用数字显示
else
{
switch (i)
{
case 10:
printf("%c", 'a');
break;
case 11:
printf("%c", 'b'); //1-9不够显示行坐标,9后用a-k表示
break;
case 12:
printf("%c", 'c');
break;
case 13:
printf("%c", 'd');
break;
case 14:
printf("%c", 'e');
break;
case 15:
printf("%c", 'f');
break;
case 16:
printf("%c", 'g');
break;
case 17:
printf("%c", 'h');
break;
case 18:
printf("%c", 'i');
break;
case 19:
printf("%c", 'j');
break;
case 20:
printf("%c", 'k');
break;
case 21:
printf("%c", 'l');
break;
case 22:
printf("%c", 'm');
break;
case 23:
printf("%c", 'n');
break;
}
}
}
int l = (*a)[i][j];
if (l == 0)
printf("-"); //此时代表未落子
else if (l == 1)
printf("O"); // 人类落子
else if (l == 2)
printf("X"); // AI落子
k++; //每落一个子后k++来控制空格与换行
}
}
printf("\n"); //打印棋盘完后再输出一个换行,让用户看得更清楚
}
get_score.c(计算空位处分数)
#include <stdio.h>
#include <stdlib.h>
#define CHESS_MAX 24
//根据棋型计算空位得分,count1为相连棋子数,leftStatus、rightStatus为1或2,1代表为空,2为墙或者对方棋子
int getScoreBySituation(int count1, int left1, int right1);//这个需要对照评分表
//横向扫描计算得分 cur表示AI或者人类
int getXScore(int x, int y, int cur);
//纵向扫描计算得分
int getYScore(int x, int y, int cur);
//正斜向扫描计算得分
int getSkewScore1(int x, int y, int cur);
//反斜向扫描计算得分
int getSkewScore2(int x, int y, int cur);
extern int board[CHESS_MAX][CHESS_MAX]; //棋盘数组
int left = 0, right = 0; //空位左右两边的情况
int count_left, count_right; //左右连子数
int temp, temp_1, temp_2; //记录空位坐标的临时变量
int getScoreBySituation(int count1, int left1, int right1)//评分表参照网站中的设定(不固定)
{
if(count1 == 4)
return 200000;
if(count1 == 3)
{
if(left1 == 1 && right1 == 1)
return 50000;
else if( (left1 == 1 && right1 == 2) || (left1 == 2 && right1 == 1) )
return 3000;
else
return 1000;
}
if(count1 == 2)
{
if(left1 == 1 && right1 == 1)
return 3000;
else if( (left1 == 1 && right1 == 2) || (left1 == 2 && right1 == 1) )
return 1000;
else
return 500;
}
if(count1 == 1)
{
if(left1 == 1 && right1 == 1)
return 500;
else if( (left1 == 1 && right1 == 2) || (left1 == 2 && right1 == 1) )
return 200;
else
return 100;
}
if(count1 == 0)
{
if(left1 == 1 && right1 == 1)
return 100;
else if( (left1 == 1 && right1 == 2) || (left1 == 2 && right1 == 1) )
return 50;
else
return 30;
}
return 0;
}
int getXScore(int x, int y, int cur)//对同一个位置要调用两次函数(AI和人类)
{
temp = y;
count_left = count_right = 0;
while(1)//左边移动
{
--y;
if(y == -1 || board[x][y] != cur)//对方棋子或者墙
{
left = 2;
break;
}
else if(board[x][y] == cur)//自己棋子
{
++count_left;
continue;
}
if(board[x][y] == 0)//空格
{
left = 1;
break;
}
}
while(1)//右边移动
{
++temp;
if(y == CHESS_MAX || board[x][temp] != cur)//对方棋子或者墙
{
right = 2;
break;;
}
else if(board[x][temp] == cur)//自己棋子
{
++count_right;
continue;
}
if(board[x][temp] == 0)//空格
{
right = 1;
break;
}
}
return getScoreBySituation(count_left + count_right, left, right);
}
int getYScore(int x, int y, int cur)
{
temp = x;
count_left = count_right = 0;
while(1)//向下移动
{
++x;
if(x == CHESS_MAX || board[x][y] != cur)//对方棋子或者墙
{
left = 2;
break;
}
else if(board[x][y] == cur)//自己棋子
{
++count_left;
continue;
}
if(board[x][y] == 0)//空格
{
left = 1;
break;
}
}
while(1)//向上移动
{
--temp;
if(temp == -1 || board[temp][y] != cur)//对方棋子或者墙
{
right = 2;
break;;
}
else if(board[temp][y] == cur)//自己棋子
{
++count_right;
continue;
}
if(board[temp][y] == 0)//空格
{
right = 1;
break;
}
}
return getScoreBySituation(count_left + count_right, left, right);
}
int getSkewScore1(int x, int y, int cur)
{
temp_1 = x, temp_2 = y;
count_left = count_right = 0;
while(1)//斜向左下移动
{
++x;
--y;
if(x == CHESS_MAX || y == -1 || board[x][y] != cur)//对方棋子或者墙
{
left = 2;
break;
}
else if(board[x][y] == cur)//自己棋子
{
++count_left;
continue;
}
if(board[x][y] == 0)//空格
{
left = 1;
break;
}
}
while(1)//斜向右下移动
{
--temp_1;
++temp_2;
if(temp_1 == -1 || temp_2 == CHESS_MAX ||board[temp_1][temp_2] != cur)//对方棋子或者墙
{
right = 2;
break;
}
else if(board[temp_1][temp_2] == cur)//自己棋子
{
++count_right;
continue;
}
if(board[temp_1][temp_2] == 0)//空格
{
right = 1;
break;
}
}
return getScoreBySituation(count_left + count_right, left, right);
}
int getSkewScore2(int x, int y, int cur)
{
temp_1 = x, temp_2 = y;
count_left = count_right = 0;
while(1)//斜向左上移动
{
--x;
--y;
if(x == -1 || y == -1 || board[x][y] != cur)//对方棋子或者墙
{
left = 2;
break;
}
else if(board[x][y] == cur)//自己棋子
{
++count_left;
continue;
}
if(board[x][y] == 0)//空格
{
left = 1;
break;
}
}
while(1)//斜向右下移动
{
++temp_1;
++temp_2;
if(temp_1 == CHESS_MAX || temp_2 == CHESS_MAX ||board[temp_1][temp_2] != cur)//对方棋子或者墙
{
right = 2;
break;
}
else if(board[temp_1][temp_2] == cur)//自己棋子
{
++count_right;
continue;
}
if(board[temp_1][temp_2] == 0)//空格
{
right = 1;
break;
}
}
return getScoreBySituation(count_left + count_right, left, right);
}
main.c
int main()
{
printf("如果打算开始请输入1\n");
int msg;
scanf("%d", &msg);
getchar();
if(msg == 1)
{
board[12][12] = 2;//AI先落子
show_chess_board(&board);
add_empty(12,12);
while(1)
{
//人类后落子
int Chess_msg = get_inputchess();
show_chess_board(&board);
if(win(1))
{
printf("人类胜利\n");
break;
}
printf("请按任意键继续\n");
getchar();
//落子后机器分析
add_empty(Chess_msg / 100, Chess_msg % 100);//记录空位
update_score_empty();//更新空位得分
//AI落子
int max_location = return_high();
board[max_location/100][max_location%100] = 2;
show_chess_board(&board);
if(win(2))
{
printf("AI胜利");
break;
}
// 落子后机器分析
add_empty(max_location / 100, max_location % 100);
update_score_empty();//更新空位得分
}
}
return 0;
}
演示图片:
感谢sky老师的指导
做到有些粗糙,有不对的地方欢迎在评论区留言哟?~~~~~~~~~~~~~~~~~~~~~~~~~~~~