人机对战-黑白棋

news/2024/10/18 22:26:41/

先大致了解一下黑白棋:

规则
如果玩家在棋盘上没有地方可以下子,则该玩家对手可以连下。双方都没有棋子可以下时棋局结束,以棋子数目来计算胜负,棋子多的一方获胜。
在棋盘还没有下满时,如果一方的棋子已经被对方吃光,则棋局也结束。将对手棋子吃光的一方获胜。
翻转棋类似于棋盘游戏“奥赛罗 (Othello)”,是一种得分会戏剧性变化并且需要长时间思考的策略性游戏。
翻转棋的棋盘上有 64 个可以放置黑白棋子的方格(类似于国际象棋和跳棋)。游戏的目标是使棋盘上自己颜色的棋子数超过对手的棋子数。
该游戏非常复杂,其名称就暗示着结果的好坏可能会迅速变化。
当游戏双方都不能再按规则落子时,游戏就结束了。通常,游戏结束时棋盘上会摆满了棋子。结束时谁的棋子最多谁就是赢家。
玩法
每个“翻转棋”游戏开始时,棋盘上已经交叉放好了四颗棋子。其中两颗是黑棋,另两颗是白棋。黑棋总是先走。
当您的棋子在某一直线方向包围了对手的棋子时,就可以翻转这些棋子的颜色,使它们成为您方的颜色。例如,如果您执黑棋,并且看到在一排白棋的某一端是一颗黑棋,那么当您将一颗黑棋放在这一排的另一端时,所有的白棋都将翻转并变为黑棋!
所有的直线方向均有效:水平、垂直和斜线方向。
走棋的唯一规则是只能走包围并翻转对手的棋子。每一回合都必须至少翻转一颗对手的棋子。
按规则不能再走棋时,这一回合弃权。这一步的行棋权将被交给对方。

由以上可知,在做黑白棋人机对战时,AI要遵守游戏规则。根据

感知(Sense)→思考(Think)→行动(Act)

这个基本架构去设计AI
感知玩家下的棋子位置;
思考我方下子后,增加多少分,玩家接下来走的位置,会减少我方多少分,以相差最高为标准,确定下棋位置;
行动落子;

根据以上分析,编写程序,代码如下:

#include <stdio.h>
//显示棋盘上棋子的状态
void Output(char chessboard[][8]) 
{int row, col;printf("\n   ");//输出列标号 for (col = 0; col < 8; col++)   {printf("  %c ", 'A' + col);}printf("\n");//输出项部横线 printf("  ┌");  //输出一行 for (col = 0; col < 7; col++)   {printf("─┬");}printf("─┐\n");for (row = 0; row < 8; row++){//输出行号printf("%2d│", row + 1);    //输出棋盘各单元格中棋子的状态 for (col = 0; col < 8; col++){if (chessboard[row][col] == 1)//白棋 {printf("○│");}else if (chessboard[row][col] == -1)//黑棋 {printf("●│");}else//未下子处 {printf("  │");}}printf("\n");if (row < 8 - 1){printf("  ├");  //输出交叉线 //输出一行 for (col = 0; col < 8 - 1; col++){printf("─┼");}printf("─┤\n");}}printf("  └");//最后一行的横线for (col = 0; col < 8 - 1; col++)    {printf("─┴");}printf("─┘\n");
}
//检查某一方是否还有下子的地方
int Check(char chessboard[][8], int isDown[][8], char player)   
{int rowdelta, coldelta, row, col, x, y = 0;int iStep = 0;char opponent = (player == 1) ? -1 : 1; //对方棋子 char myplayer = -1 * opponent;  //我方棋子 //将isDown数组全部清0 for (row = 0; row < 8; row++)   {for (col = 0; col < 8; col++){isDown[row][col] = 0;}}//循环判断棋盘中哪些单元格可以下子 for (row = 0; row < 8; row++)   {for (col = 0; col < 8; col++){//若棋盘上对应位置不为空(表示已经有子)if (chessboard[row][col] != 0)   {continue;//继续处理下一个单元格 }//循环检查上下行for (rowdelta = -1; rowdelta <= 1; rowdelta++)   {//循环检查左右列for (coldelta = -1; coldelta <= 1; coldelta++)   {//检查若坐标超过棋盘 或为当前单元格if (row + rowdelta < 0 || row + rowdelta >= 8|| col + coldelta < 0 || col + coldelta >= 8|| (rowdelta == 0 && coldelta == 0))     {continue;   //继续循环 }//若(row,col)四周有对手下的子 if (chessboard[row + rowdelta][col + coldelta] == opponent) {//以对手下子位置为坐标x = row + rowdelta;  y = col + coldelta;//对对手下子为起始点,向四周查找自己方的棋子,以攻击对方棋子 while(1)    {//对手下子的四周坐标x += rowdelta;   y += coldelta;//超过棋盘if (x < 0 || x >= 8 || y < 0 || y >= 8)  {break;  //退出循环 }//若对应位置为空if (chessboard[x][y] == 0)   {break;}//若对应位置下的子是当前棋手的if (chessboard[x][y] == myplayer)    {//设置移动数组中对应位置为1 (该位置可下子,形成向对手进攻的棋形)isDown[row][col] = 1;   iStep++;    //累加可下子的位置数量 break;}}}}}}}//返回可下的位置数量(若返回值为0,表示没地方可下)return iStep; 
}
//在指定位置下子 
void PlayStep(char chessboard[][8], int row, int col, char player)  
{int rowdelta = 0;int coldelta = 0;int x = 0;int y = 0;char opponent = (player == 1) ? -1 : 1; //对方棋子char myplayer = -1 * opponent;  //我方棋子 chessboard[row][col] = myplayer;    //保存所下的棋子//检查所下子四周的棋子for (rowdelta = -1; rowdelta <= 1; rowdelta++)  {for (coldelta = -1; coldelta <= 1; coldelta++){//若坐标超过棋盘界限if (row + rowdelta < 0 || row + rowdelta >= 8 || col + coldelta < 0|| col + coldelta >= 8 || (rowdelta == 0 && coldelta == 0)) {continue;   //继续下一位置 }//若该位置是对手的棋子if (chessboard[row + rowdelta][col + coldelta] == opponent)  {//以对手棋为坐标x = row + rowdelta;  y = col + coldelta;//在对手棋子四周寻找我方棋子 while(1)    {x += rowdelta;y += coldelta;//若坐标超过棋盘if (x < 0 || x >= 8 || y < 0 || y >= 8)  {break;  //退出循环}//若对应位置为空 if (chessboard[x][y] == 0)  {break;  //退出循环 }//若对应位置是我方模子if (chessboard[x][y] == myplayer)    {//循环处理 while (chessboard[x -= rowdelta][y -= coldelta] == opponent)    {//将中间的棋子都变成我方棋子chessboard[x][y] = myplayer;     }break;  //退出循环 }}}}}
}
//获取分数
int GetMaxScore(char chessboard[][8], char player)   
{int Score, row, col;char opponent = (player == 1) ? -1 : 1; //对方棋子 char myplayer=-1*opponent;for (row = 0; row < 8; row++)   //循环 {for (col = 0; col < 8; col++){//若棋盘对应位置是对手下的棋子,从总分中减1Score -= chessboard[row][col] == opponent;  //若棋盘对应位置是我方的棋子,总分中加1分Score += chessboard[row][col] == myplayer;   }}return Score;//返回分数 
}
//获取最佳下子位置
int BestPlay(char chessboard[][8], int isDown[][8], char player)     
{int row, col, i, j;//定义一个临时数组char chessboard1[8][8] = { 0 };  int MaxScore = 0;   //保存最高分 int Score = 0;char opponent = (player == 1) ? -1 : 1; //对手下的棋子 //循环检查每个单元格for (row = 0; row < 8; row++)    {for (col = 0; col < 8; col++){//若该位置不可下子if (!isDown[row][col])  {continue;   //继续 }//复制棋盘各单元格下子的状态到临时数组for (i = 0; i < 8; i++)  {for (j = 0; j < 8; j++){chessboard1[i][j] = chessboard[i][j];}}//在临时数组中的指定行列下子PlayStep(chessboard1, row, col, player);//获取下子后可得到的分数Score = GetMaxScore(chessboard1, player);//若原方案得到的分数小于本次下子的分数 if (MaxScore < Score)   {MaxScore = Score;   //保存最高分 }}}return MaxScore;//返回得到的最高分 
}
//AI自动下子
void AutoPlayStep(char chessboard[][8], int isDown[][8], char player)    
{int row, col, row1, col1, i, j;//对方可下子提到的分数和最小分数int Score = 0, MinScore = 100;   //临时数组,保存棋盘下子位置 char chessboard1[8][8]; //临时数组,保存可下子位置 int isDown1[8][8];      char opponent = (player == 1) ? -1 : 1; //对手下的棋子    for (row = 0; row < 8; row++)   //循环检查棋盘每个单元格 {for (col = 0; col < 8; col++){   //若不可下子if (isDown[row][col] == 0)   {continue;//继续下一个位置 }//将棋盘原来的棋子复制到临时数组中for (i = 0; i < 8; i++)  {for (j = 0; j < 8; j++){chessboard1[i][j] = chessboard[i][j];}}//试着在临时棋盘中的一个位子下子PlayStep(chessboard1, row, col, player);     //检查对手是否有地方可下子Check(chessboard1, isDown1, opponent);   //获得临时棋盘中对方下子的得分情况Score = BestPlay(chessboard1, isDown1, opponent);   //保存对方得分最低的下法 if (Score < MinScore)   {MinScore = Score;row1 = row;col1 = col;}}}//AI按最优下法下子 PlayStep(chessboard, row1, col1, player);   
} 
int main()
{//保存棋盘中各单元格下子的状态char chessboard[8][8];  //保存棋盘中各位置是否可以下子,可下子的位置为1,其余位置为0 int isDown[8][8] = { 0 };       int row, col, x, y;//已下棋子数量 int iCount = 0; int player = 0; //下棋方//跳过下子的次数,若为2,表示双方都不能下子int SkipPlay = 0;    //保存AI和游戏者的得分int Score[2];    char select;printf("黑白棋\n\n");printf("游戏者执黑先下,AI执白,按回车键开始:\n");scanf("%c", &select);do{//计算下棋方(0表示游戏者,1表示AI)if (player == 0) {player = 1;}else{player = 0;}iCount = 4; //累计下子数 //棋盘各位置清空 for (row = 0; row < 8; row++)   {for (col = 0; col < 8; col++){chessboard[row][col] = 0;}}//在棋盘中间位置放置白棋 chessboard[3][3] = chessboard[4][4] = 1;    //在棋盘中间位置放置黑棋 chessboard[3][4] = chessboard[4][3] = -1;   printf("\n棋盘初始状态:\n");//显示初始棋盘下子的状况 Output(chessboard);do{//若是游戏者下棋(下白子) if (player == 1)    {player = 0;//判断是否可下黑子 if (Check(chessboard, isDown, 2))   {//死循环,直到用户输入正确的坐标为止while(1) {fflush(stdin);printf("输入下子的位置(行 列):");scanf("%d%c", &x, &y);x--;    //计算行坐标位置 if(y >= 'a'){y = y - 'a' + 1;}else{y = y - 'A' + 1;}y--;    //计算列位置 //若行列坐标输入有效if (x >= 0 && y >= 0 && x < 8 && y < 8 && isDown[x][y])  {//在指定坐标位置下黑子PlayStep(chessboard, x, y, 2);  iCount++;   //累加下子数 break;}else{printf("坐标输入错误,请重新输入。\n");}}printf("\n你下子后的状态:\n");Output(chessboard); //显示棋子状态printf("按任意键AI下子。\n");getch();}//若无效下子的次数小于2else if (++SkipPlay < 2)     {fflush(stdin);  //清除输入缓冲区 printf("你没位置可下,按回车键让对方下子。");scanf("%c", &select);} else{printf("双方都没地方下子,游戏结束!\n");}}//若是AI下棋(下黑子) else    {player = 1;//检查是否可下白子if (Check(chessboard, isDown, 1))    {SkipPlay = 0;   //清除无效下子次数 //AI下一个白子 AutoPlayStep(chessboard, isDown, 1);    iCount++;   //累加下子数printf("\nAI下子后的状态:\n");Output(chessboard); //显示棋子状态}else{//若无效下子次数小于2if (++SkipPlay < 2)  {printf("我没位置可走,请你走。\n");}else{printf("双方都没地方下子,游戏结束!");}}}}//下子数量小于64 且无效下子的次数小于2while (iCount < 64 && SkipPlay < 2);//显示各双方棋子的状况Output(chessboard);  Score[0] = Score[1] = 0;//清空计分变量 //循环统计各单元格黑白棋子的数量for (row = 0; row < 8; row++){for (col = 0; col < 8; col++){//统计黑子数 Score[0] += chessboard[row][col] == -1; //统计白子数Score[1] += chessboard[row][col] == 1;               }}printf("最终成绩:\n");printf("AI:%d\n游戏者:%d\n", Score[0], Score[1]);fflush(stdin);  //清空输入缓冲区 printf("继续下一局(y/n)?:");scanf("%c", &select);}while (select == 'y' || select == 'Y');printf("Game Over!\n");return 0;
}

运行演示:
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述

参考资源:

《零基础学算法》 第三版 戴艳等编 机械工业出版社


代码下载地址

http://download.csdn.net/download/u013553804/9500500
http://pan.baidu.com/s/1o8nbtFG


欢迎关注我的微信个人订阅号
这里写图片描述
每天多学一点0.0


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

相关文章

BSC 链最受欢迎的竞技游戏 — Thetan Arena

Apr. 2022, Grace Data Source: Footprint Analytics Thetan Arena Dashboard Thetan Arena 是基于区块链的竞技游戏&#xff0c;涵盖了对战、角色扮演、MOBA以及幻想等元素。目前用户数达到了 14 万&#xff0c;是 Footprint Anayltics BSC 游戏排行榜上最受欢迎的竞技游戏…

suparc服务器没信号,SupARC对战平台新手上手教程

11对战平台1.2.8.3 官方最新版 类型&#xff1a;修改器(游戏工具)大小&#xff1a;88.1M语言&#xff1a;中文 评分&#xff1a;9.8 标签&#xff1a; 立即下载 这里有各种版本的SupARC客户端供选择&#xff0c;另外还有很多ROM 客户端下载成功后需要玩家注册新账号 新手玩家需…

三子棋(人机对战)

目录 整体思路&#xff1a; 头文件如下&#xff1a; 为什么要使用头文件呢&#xff1f; 头文件的使用&#xff1a; 游戏主函数如下&#xff1a; 功能实现如下&#xff1a; 注意&#xff1a; 整体思路&#xff1a; 1.创建一个二维数组 2.初始化二维数组 3.打印棋盘 4…

赛事报名启动丨百度Apollo星火自动驾驶大赛开始报名啦!

作为汽车智能化、网联化的关键环节&#xff0c;自动驾驶成为全球科技界、产业界竞争的新赛道。随着人工智能、5G通信、激光雷达、高精地图等多项技术不断完善&#xff0c;自动驾驶的判断力和理解力得到了显著提升。为了推动自动驾驶技术的发展、加快人工智能技术的迭代&#xf…

2022.10.3 模拟赛

T1 frame 还是那个老 t r i c k trick trick 每个数取 log ⁡ \log log 乘法变加法&#xff0c;虽然最后还是要打一个高精。 // 高精的板子 struct Tnum{#define MAX_LEN 30030int len, isnagetive;int num[MAX_LEN];Tnum() { isnagetive 0, len 0; memset(num, 0, sizeof…

直播平台搭建,计时和倒计时功能的分别实现

直播平台搭建&#xff0c;计时和倒计时功能的分别实现 一、计时功能&#xff1a; <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas.android.com/apk/res/android"xmlns:app"http://schemas.…

AFI - all in - 逍遥棋 - 游戏规则

AFI - all in - 逍遥棋 - 游戏规则 如你所见&#xff0c;你的面前出现了两个九宫格&#xff01;一个是你的&#xff0c;还有一个属于你的opponent。 看到这个骰子了吗&#xff1f;好&#xff0c;很棒&#xff0c;转动它&#xff01; 嗯&#xff0c;你得到了一个5。这时候&…

Linux 系统编程-开发环境(二)

目录 7 压缩包管理 7.1 tar 7.2 rar 7.3 zip 8 进程管理 8.1 who 8.2 ps 8.3 jobs 8.4 fg 8.5 bg 8.6 kill 8.7 env 8.8 top 9 用户管理 9.1 创建用户 9.2 设置用户组 9.3 设置密码 9.4 切换用户 9.5 root用户 9.6 删除用户 10 网络管理 10.1 i…