您的位置:首页 > 大数据 > 人工智能

博弈算法实现简单五子棋

2015-12-21 16:46 525 查看
一、  问题介绍

实现交互式五子棋

采用博弈算法

二、  程序设计与算法分析

l   博弈问题简介:

– 双人对弈,轮流走步。

– 信息完备,双方所得到的信息是一样的。

– 零和,即对一方有利的棋,对另一方肯定是不利的,不存在对双方均有利或无利的棋。

l   博弈的特性:

– 两个棋手交替地走棋 ;

– 比赛的最终结果,是赢、输和平局中的一种;

– 可用图搜索技术进行,但效率很低;


– 博弈的过程,是寻找置对手于必败态的过程;

– 双方都无法干预对方的选择。

l   结论:五子棋问题属于博弈问题。

l   博弈问题的两种常用算法思路:

– 与或图直接搜索法:搜索枚举双方棋手的下棋方法,构建与或图直接搜索出答案 ;

u  与或图简介:

与或图是一个超图,节点间通过连接符连接。

u  与或图节点分类:

n  能解节点:

Ø  终节点是能解节点 


Ø  若非终节点有“或”子节点时,当且仅当其子节点至少有一能解时,该非终结点才能解。

Ø  若非终节点有“与”子节点时,当且仅当其子节点均能解时, 该非终节点才能解。

n  不能解节点

Ø  没有后裔的非终节点是不能解节点。


Ø  若非终节点有“或”子节点,当且仅当所有子节点均不能解时, 
该非终节点才不能解。 


Ø  若非终节点有“与”子节点时,当至少有一个子节点不能解时, 该非终节点才不能解。

u  与或图搜索算法:

n  1.建立搜索图G:=s,计算q(s)=h(s), If Goal(s)Then M(s, Solved) 


n  2.Until S 被标记为Solved, Do: 


n  3.Begin ;扩展 


n  4.G’:= Find(g) ; G’为根据连接符找到的待扩展局部解图 


n  5.n := G’中任一非终结点 


n  6.{nj}:=Expand(n), 计算q(nj)=h(nj),If Goal(nj) Then M(nj,solved) 


n  7.S:={n} ;回溯,修改指针和耗散值 


n  8.Until S为空, Do: 


n  9.Begin 


n  10.Remove(m, S) If m的子结点不在s中 ;子结点耗散值已确定 3/21/2014 第二章 与或图搜索

n  11.修改m的耗散值:
对m的每个连接符i{n1i,n2i,...,nki},计算qi(m)=Ci+q(n1i)+...+q(nki)q(m):=min qi (m)
修改m的指针到min qi (m)对应的连接符上
If(nji,Solved) THEN M(m,Solved) ;某一个连接符已解决

n  12.If M(m,Solved) or q(m)被修改 Then Add(ma,S), ma为m的所有先辈节点

n  13.End

n  14.End

u  两个过程:

n  图生成过程,即扩展节点,从最优的局部途中选择一个节点扩展

n  计算耗散值的过程
– 对当前的局部图从新计算耗散值

u  算法实现:

n  终结点为某一方胜利的状态:可用true或者false分别标记胜负

n  利用与或图搜索更新到根节点

n  若根节点为false表示当前棋局状态为必败情况。

n  若根节点为true表示当前棋局为必胜状态。

n  根节点总是尽量选择状态为true的子节点进行状态转移。

u  算法局限性:

n  搜索的没一层都要穷举所有可能的情况,很容易引起组合爆炸的问题。

 

– 极大极小搜索算法:利用优先深度的搜索,通过对局势进行评估来选择状态转移的方向。

u  极大极小搜索简介:

n  下棋的双方是对立的;

n  一方为“正方”,这类节点称为“MAX”节点; 


n  另一方为“反方”,这类节点称为“MIN”节点; 


n  正方从所有子节点中,选取具有最大评估值的节点进行状态转移; 


n  反方从其所有子节点中,选取具有最小评估值的节点惊醒状态转移; 


n  反复进行这种选取,就可以得到双方各个节点的评估值。这种确定棋步的方法,称为极大极小搜索法。 


u  对各个局面进行评估:

n  评估的目的:对后面的状态提前进行考虑,并且以各种状态的评估值为基础作出最好的走棋选择。       


n  评估的方法:用评价函数对棋局进行评估。赢的评估值设为+∞,输的评估值设为-∞,平局的评估值设为0。 


n  评估的标准:由于下棋的双方是对立的,只能选择其中一 方为评估的标准方。 
   所有评估站在正方的立场!

u  极大极小搜索算法:

n  1.T:=(s,max), Open:=(s),Close:=();


n  2.Lopp1:                ;扩展深度至d

n  3.If Open=() Then Goto Loop2


n  4.N:=First(Open),Remove(n,Open), Add(n,Close)


n  5.If F(n)=-∞, +∞, 0, Then GotoLoop1 ;                               可以被判定

Else {ni}:=Expand(n), Add({ni},T)


If d(ni)<kThen Add(ni,Open), Goto Loop1

Else 计算f(ni), Goto Loop1

n  6.Loop2:                                  ;赋值

n  7.If Close=() Then Goto Loop3

Else np:=First(Close)

n  8.If (np Is Max) And (F(npi)有值)                      ; npi是np的子结点,且都有值

Then f(np):=maxf(npi), Remove(np,Close)

If (np Is MIN)And (f(npi)有值)                           ; npi是np的子结点,且都有值

THEN f(np):=minf(npi), Remove(np,Close)


n  9.Goto Loop2

n  10.Loop3:


n  10.If f(s)有值 Then Exit(End or M(Move, T))                   ;s被赋值,结束该步

u  alpha-beta剪枝:在极小极大法中,必须求出所有终端节点的评估值,当预先考虑的棋步比较多时,计算量会大大增加。为了提高搜索的效率,引入了通过对评估值的上下限进行估计、从而减少需进行评估的节点范围。

n  MAX节点的评估下限值:

作为正方出现的MAX节点,假设它的MIN子节点有N个,那么当它的第一

个MIN子节点的评估值为alpha时,则对于其它的子节点,如果有高过alpha的, 就取那最高的值作为该MAX节点的评估值;如果没有,则该MAX节点的评估值为alpha。总之,该MAX节点的评估值不会低于alpha,这个alpha就称为该MAX节 点的评估下限值。

n  MIN节点的评估上限值:

作为反方出现的MIN节点,假设它的MAX子节点有N个,那么当它的第一 个MAX子节点的评估值为beta时,则对于其它子节点,如果有低于beta的,就取那个低于beta的值作为该MIN节点的评估值;如果没有,则该MIN节点的评估值取beta总之,该MIN节点的评估值不会高过beta这个beta就称为该MIN节点的评估上限值。

n  剪枝条件:

Ø  后辈节点的beta值≤祖先节点的alpha值时,alpha剪枝。

Ø  – 后辈节点的alpha值≥祖先节点的beta值时,beta剪枝。

 

l   五子棋算法分析:

u  因为每一步可走棋的位置很多,所以不适合直接使用与或图搜索,在这里使用了极大极小搜索,并用alpha-beta剪枝进行了优化。

u  为方便实现剪枝,而不是全部拓展所有节点,使用了dfs的搜索方式。

u  维护一个当前棋局的数组和一个当前节点下一层可能走的点的集合。因为涉及到回溯,需要一种高效的插入和删除的数据结构,我这里采用了HashSet来作为集合的数据结构。

u  将当前所有下过棋子的点的相邻点放入到下一层待搜索的集合中,而不是搜索整个棋盘,这样缩小了搜索的范围,减少了扩展的节点数。

u  评估函数使用当前局面连在一线上棋子的个数进行打分,连在一起的个数越多得分越高。

u  利用java的drawingPanel库实现了图形界面。附drawingPanel下载地址:http://www.buildingjavaprograms.com/DrawingPanel.java

 

import java.util.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class Ai3{
private static DrawingPanel panel=new DrawingPanel(700,700);
private static Graphics g=panel.getGraphics();
public static boolean isBlack=false;//标志棋子的颜色
public static int[][] chessBoard=new int[17][17]; //棋盘棋子的摆放情况:0无子,1黑子,-1白子
private static HashSet<Point> toJudge=new HashSet<Point>(); // ai可能会下棋的点
private static int dr[]=new int[]{-1,1,-1,1,0,0,-1,1}; // 方向向量
private static int dc[]=new int[]{1,-1,-1,1,-1,1,0,0}; //方向向量
public static final int MAXN=1<<28;
public static final int MINN=-MAXN;
private static int searchDeep=4; //搜索深度
private static final int size=15; //棋盘大小
public static boolean isFinished=false;

public static void main(String[] args){
MyMouseEvent myMouseEvent=new MyMouseEvent();
panel.addMouseListener(myMouseEvent);
initChessBoard();
}

// 初始化函数,汇图
public static void initChessBoard(){

isBlack=false;
toJudge.clear();
panel.clear();
panel.setBackground(Color.GRAY);
g.setColor(Color.BLACK);
for(int i=45;i<=675;i+=45){
g.drawLine(45,i,675,i);
g.drawLine(i,45,i,675);
}
// 棋盘上的五个定位基本点,图中的小圆圈
g.setColor(Color.BLACK);
g.fillOval(353,353,14,14);
g.fillOval(218,218,14,14);
g.fillOval(488,218,14,14);
g.fillOval(488,488,14,14);
g.fillOval(218,488,14,14);
// 初始化棋盘
for(int i=1;i<=15;++i)
for(int j=1;j<=15;++j)
chessBoard[i][j]=0;
// ai先手
g.fillOval(337,337,45,45);
chessBoard[8][8]=1;
for(int i=0;i<8;++i)
if(1<=8+dc[i] && 8+dc[i]<=size && 1<=8+dr[i] && 8+dr[i]<=size){
Point now=new Point(8+dc[i],8+dr[i]);
if(!toJudge.contains(now))
toJudge.add(now);
}
isBlack=false;
}

// 通过点击事件,得到棋子位置进行下棋
public static void putChess(int x,int y){
if(isBlack)
g.setColor(Color.BLACK);
else
g.setColor(Color.WHITE);
g.fillOval(x-22,y-22,45,45);
chessBoard[y/45][x/45]=isBlack?1:-1;
if(isEnd(x/45,y/45)){
String s=Ai3.isBlack?"黑子胜":"白子胜";
JOptionPane.showMessageDialog(null,s);
isBlack=true;
initChessBoard();
}
else{
Point p=new Point(x/45,y/45);
if(toJudge.contains(p))
toJudge.remove(p);
for(int i=0;i<8;++i){
Point now=new Point(p.x+dc[i],p.y+dr[i]);
if(1<=now.x && now.x<=size && 1<=now.y && now.y<=size && chessBoard[now.y][now.x]==0)
toJudge.add(now);
}

// Iterator it=toJudge.iterator();
// while(it.hasNext()){
// Point now=(Point)it.next();
// System.out.printf("%d\t%d\n",now.x,now.y);
// }
// System.out.printf("*******************************************************\n");
}
}

// ai博弈入口函数
public static void myAI(){
Node node=new Node();
dfs(0,node,MINN,MAXN,null);
Point now=node.bestChild.p;
// toJudge.remove(now);
putChess(now.x*45,now.y*45);
isBlack=false;
}

// alpha beta dfs
private static void dfs(int deep,Node root,int alpha,int beta,Point p){
if(deep==searchDeep){
root.mark=getMark();
// System.out.printf("%d\t%d\t%d\n",p.x,p.y,root.mark);
return;
}
ArrayList<Point> judgeSet=new ArrayList<Point>();
Iterator it=toJudge.iterator();
while(it.hasNext()){
Point now=new Point((Point)it.next());
judgeSet.add(now);
}
it=judgeSet.iterator();
while(it.hasNext()){
Point now=new Point((Point)it.next());
Node node=new Node();
node.setPoint(now);
root.addChild(node);
boolean flag=toJudge.contains(now);
chessBoard[now.y][now.x]=((deep&1)==1)?-1:1;
if(isEnd(now.x,now.y)){
root.bestChild=node;
root.mark=MAXN*chessBoard[now.y][now.x];
chessBoard[now.y][now.x]=0;
return;
}

boolean flags[]=new boolean[8]; //标记回溯时要不要删掉
Arrays.fill(flags,true);
for(int i=0;i<8;++i){
Point next=new Point(now.x+dc[i],now.y+dr[i]);
if(1<=now.x+dc[i] && now.x+dc[i]<=size && 1<=now.y+dr[i] && now.y+dr[i]<=size && chessBoard[next.y][next.x]==0){
if(!toJudge.contains(next)){
toJudge.add(next);
}
else flags[i]=false;
}
}

if(flag)
toJudge.remove(now);
dfs(deep+1,root.getLastChild(),alpha,beta,now);
chessBoard[now.y][now.x]=0;
if(flag)
toJudge.add(now);
for(int i=0;i<8;++i)
if(flags[i])
toJudge.remove(new Point(now.x+dc[i],now.y+dr[i]));
// alpha beta剪枝
// min层
if((deep&1)==1){
if(root.bestChild==null || root.getLastChild().mark<root.bestChild.mark){
root.bestChild=root.getLastChild();
root.mark=root.bestChild.mark;
if(root.mark<=MINN)
root.mark+=deep;
beta=Math.min(root.mark,beta);
}
if(root.mark<=alpha)
return;
}
// max层
else{
if(root.bestChild==null || root.getLastChild().mark>root.bestChild.mark){
root.bestChild=root.getLastChild();
root.mark=root.bestChild.mark;
if(root.mark==MAXN)
root.mark-=deep;
alpha=Math.max(root.mark,alpha);
}
if(root.mark>=beta)
return;
}
}
// if(deep==0) System.out.printf("******************************************\n");
}

public static int getMark(){
int res=0;
for(int i=1;i<=size;++i){
for(int j=1;j<=size;++j){
if(chessBoard[i][j]!=0){
// 行
boolean flag1=false,flag2=false;
int x=j,y=i;
int cnt=1;
int col=x,row=y;
while(--col>0 && chessBoard[row][col]==chessBoard[y][x]) ++cnt;
if(col>0 && chessBoard[row][col]==0) flag1=true;
col=x;row=y;
while(++col<=size && chessBoard[row][col]==chessBoard[y][x]) ++cnt;
if(col<=size && chessBoard[row][col]==0) flag2=true;
if(flag1 && flag2)
res+=chessBoard[i][j]*cnt*cnt;
else if(flag1 || flag2) res+=chessBoard[i][j]*cnt*cnt/4;
if(cnt>=5) res=MAXN*chessBoard[i][j];
// 列
col=x;row=y;
cnt=1;flag1=false;flag2=false;
while(--row>0 && chessBoard[row][col]==chessBoard[y][x]) ++cnt;
if(row>0 && chessBoard[row][col]==0) flag1=true;
col=x;row=y;
while(++row<=size && chessBoard[row][col]==chessBoard[y][x]) ++cnt;
if(row<=size && chessBoard[row][col]==0) flag2=true;
if(flag1 && flag2)
res+=chessBoard[i][j]*cnt*cnt;
else if(flag1 || flag2)
res+=chessBoard[i][j]*cnt*cnt/4;
if(cnt>=5) res=MAXN*chessBoard[i][j];
// 左对角线
col=x;row=y;
cnt=1;flag1=false;flag2=false;
while(--col>0 && --row>0 && chessBoard[row][col]==chessBoard[y][x]) ++cnt;
if(col>0 && row>0 && chessBoard[row][col]==0) flag1=true;
col=x;row=y;
while(++col<=size && ++row<=size && chessBoard[row][col]==chessBoard[y][x]) ++cnt;
if(col<=size && row<=size && chessBoard[row][col]==0) flag2=true;
if(flag1 && flag2)
res+=chessBoard[i][j]*cnt*cnt;
else if(flag1 || flag2) res+=chessBoard[i][j]*cnt*cnt/4;
if(cnt>=5) res=MAXN*chessBoard[i][j];
// 右对角线
col=x;row=y;
cnt=1;flag1=false;flag2=false;
while(++row<=size && --col>0 && chessBoard[row][col]==chessBoard[y][x]) ++cnt;
if(row<=size && col>0 && chessBoard[row][col]==0) flag1=true;
col=x;row=y;
while(--row>0 && ++col<=size && chessBoard[row][col]==chessBoard[y][x]) ++cnt;
if(row>0 && col<=size && chessBoard[i][j]==0) flag2=true;
if(flag1 && flag2)
res+=chessBoard[i][j]*cnt*cnt;
else if(flag1 || flag2) res+=chessBoard[i][j]*cnt*cnt/4;
if(cnt>=5) res=MAXN*chessBoard[i][j];

}
}
}
return res;
}

// for debug
public static void debug(){
for(int i=1;i<=size;++i){
for(int j=1;j<=size;++j){
System.out.printf("%d\t",chessBoard[i][j]);
}
System.out.println("");
}
}

// 判断是否一方取胜
public static boolean isEnd(int x,int y){
// 判断一行是否五子连珠
int cnt=1;
int col=x,row=y;
while(--col>0 && chessBoard[row][col]==chessBoard[y][x]) ++cnt;
col=x;row=y;
while(++col<=size && chessBoard[row][col]==chessBoard[y][x]) ++cnt;
if(cnt>=5){
isFinished=true;
return true;
}
// 判断一列是否五子连珠
col=x;row=y;
cnt=1;
while(--row>0 && chessBoard[row][col]==chessBoard[y][x]) ++cnt;
col=x;row=y;
while(++row<=size && chessBoard[row][col]==chessBoard[y][x]) ++cnt;
if(cnt>=5){
isFinished=true;
return true;
}
// 判断左对角线是否五子连珠
col=x;row=y;
cnt=1;
while(--col>0 && --row>0 && chessBoard[row][col]==chessBoard[y][x]) ++cnt;
col=x;row=y;
while(++col<=size && ++row<=size && chessBoard[row][col]==chessBoard[y][x]) ++cnt;
if(cnt>=5){
isFinished=true;
return true;
}
// 判断右对角线是否五子连珠
col=x;row=y;
cnt=1;
while(++row<=size && --col>0 && chessBoard[row][col]==chessBoard[y][x]) ++cnt;
col=x;row=y;
while(--row>0 && ++col<=size && chessBoard[row][col]==chessBoard[y][x]) ++cnt;
if(cnt>=5){
isFinished=true;
return true;
}
return false;
}
}

// 树节点
class Node{
public Node bestChild=null;
public ArrayList<Node> child=new ArrayList<Node>();
public Point p=new Point();
public int mark;
Node(){
this.child.clear();
bestChild=null;
mark=0;
}
public void setPoint(Point r){
p.x=r.x;
p.y=r.y;
}
public void addChild(Node r){
this.child.add(r);
}
public Node getLastChild(){
return child.get(child.size()-1);
}
}

// 实现鼠标事件接口
class MyMouseEvent implements MouseListener{
public void mouseClicked(MouseEvent e){
int x=round(e.getX()),y=round(e.getY());
if(x>=45 && x<=675 && y>=45 && y<=675 && Ai3.chessBoard[y/45][x/45]==0 && Ai3.isBlack==false){
Ai3.putChess(x,y);
if(!Ai3.isFinished){
Ai3.isBlack=true;
Ai3.myAI();
}
Ai3.isFinished=false;
}
}
// 得到鼠标点击点附近的棋盘精准点
public static int round(int x){
return (x%45<22)?x/45*45:x/45*45+45;
}
public void mouseExited(MouseEvent e){}
public void mouseEntered(MouseEvent e){}
public void mouseReleased(MouseEvent e){}
public void mousePressed(MouseEvent e){}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息