人工智慧五子棋 java實現
一、演算法思想
1、搜尋樹 甲乙兩人下棋,甲有很多種落子方式,乙也有多種應對走法,如果把所有的走法列出來,自然就構成了一棵樹,即為搜尋樹,也稱博弈樹。樹的根結點為先手的第一步走法,下面的走法構成了樹的子結點,直至棋局結束。顯然,如果棋盤足夠大,子結點數會以幾何級數上升,而我們的任務是從這些子結點中尋找一個對己方最有利的結點,從而得到棋局的最佳走法。 估值函式 估值函式通常是為了評價棋型的狀態,根據實現定義的一個棋局估值表,對雙方的棋局形態進行計算,根據得到的估值來判斷應該採用的走法。棋局估值表是根據當前的棋局形勢,定義一個分值來反映其優勢程度,來對整個棋局形勢進行評價。 3、極大極小搜尋 極大極小搜尋演算法就是在博弈樹在尋找最優解的一個過程,這主要是一個對各個子結點進行比較取捨的過程,定義一個估值函式F(n)來分別計算各個終結點的分值,通過雙方的分值來對棋局形勢進行分析判斷。還是以甲乙兩人下棋為例,甲為max,乙為min。當甲走棋時,自然在博弈樹中尋找最大點的走法,輪到乙時,則尋找最小點的走法,如此反覆,這就是一個極大極小搜尋過程,以此來尋找對機器的最佳走法。 4、剪枝演算法 αβ剪枝演算法簡單來說,就是在搜尋過程中減少一定的冗餘現象,如已經找到極大值,執行該走法就可以獲勝,則無須再往下進行搜尋比較,此過程即為剪枝。對於極大的MAX結點,稱為α剪枝;反之為β剪枝。具體規則可以簡單描述如下: α剪枝:對於極大值層結點的α值如果不小於它的任一祖先極小值層結點的β值,即α(後續層)≥β(祖先層),則可中止該極大值層中這個MAX節點以下的搜尋過程,這個MAX節點最終的倒推值就確定為這個α值。 β剪枝:對於極小值結點層的β值如果不大於它任一祖先極大值層結點的α值,即α(祖先層)≥β(後續層),則可中止對該極小值層中這個MIN節點以下結點的搜尋,這個MIN節點最終的倒推值就確定為這個β值。
二、詳細設計
2.1總體設計 在演算法設計中,比較重要的是評價函式的求法,因為評價函式是在中級或者高階難度的遊戲模式中,計算機選擇落棋位置的重要依據。 在五子棋遊戲中,棋型很重要,其中有連五(表示已經有五子連線),活四(四子連線並且兩邊都沒有被堵住),眠四(四子連線有一邊被堵住),活三(三子連線兩邊都沒有被堵上),眠三(三子連線,有一邊被堵上),活二,眠二依次類推,其中還有一些其他的情況如:EUUOUU,OUUOUUO等,其中E表示有衝突(邊界或者對方的棋子),U表示已經連線的棋子,O表示空白的位置,我們對這些很有可能連線的棋型,按照連成五子的概率大小不同定義不同的分值。因為我們求評價函式的作用是選擇一個合適的地方落子,所以求評價值是針對空位置。假如我要求某位置黑子的評價值,就是試驗性的將這個位置放上黑子,在橫向,縱向,兩個斜線方向分別以這個位置(x,y)為中心,向兩邊分別擷取4位(因為第五位和此位置無關)一共擷取9位,再看棋型,得到分數,再將這四個方向的分數相加。就為此位置的評價值。具體的擷取方法,匹配方法與計算的方法我在詳細設計部分會很清楚的表達。這部分用方法public int calculateScore(final int x,final int y,int color)實現,x,y為橫座標,縱座標,color為棋子的顏色。返回求得的評價值。 中級難度的下法為,遍歷棋盤,找到己方評價函式最高的位置A,再找到對方評價值最高的位置B,假如自己方的分數高於或者等於對方的分數,就將棋子落在A,反之落在位置B。 高階的下法為 整個程式設計的類,屬性,方法如下: 1、類fivechess: public class fivechess extends JFrame implements ActionListener 成員: JButton back=new JButton(“悔棋”); JButton start=new JButton(“開始”); JButton game=new JButton(“中級難度”); JButton game2=new JButton(“高階難度”); GameBoard gb=new GameBoard(); JPanel pb=new JPanel(); JPanel pg=new JPanel(); 方法: public fivechess()//建構函式 public void actionPerformed(ActionEvent e)//實現按鈕事件 2、類GameBoard: public class GameBoard extends JPanel implements MouseListener 成員: public int row=14;//棋盤行數 public int col=14;//棋盤列數 public int length=30;//每個格子的大小 public int edgedis=30;//邊距 public int[][] ChessBoard=new int[row+1][col+1];//棋子代表的二維陣列, int chessNum=0;//已經下了的棋子數目 boolean IsBlack=true;//判斷哪方下子(黑子先行) int lastx=-1,lasty=-1; //最後下子的位置 int llastx=-1,llasty=-1; //倒數第二次下的位置,方便悔棋 boolean Win=false;//是否勝利的標誌 static final int radius=15; //棋盤的位置的分值(固定為15x15的棋盤) 方法: public GameBoard()//建構函式 public void paintComponent(Graphics g)//畫棋局 public void mousePressed(MouseEvent e)//滑鼠事件 public void computerplayer()//中級下法 public void computerplayerhigher()//高階下法 public int maxMinWithAlphaBetaCut(int chessBoard[][], int whiteOrBlack, int depth, int x,int y, int alpha, int beta)//剪枝演算法 public int calculateScore(final int x,final int y,int color)//算分值 public boolean isExit(int x,int y)//此位置有沒有棋子 public boolean IsWin()//判斷是否已經有五子連線的情況 public int ChessCount(int xChange,int yChange,int color)//計算棋子在xChange和yChange方向上連線的數量(1,0)橫向,(0,1)縱向,(1,1)主對角線,(-1,-1)副對角線 2.2 演算法設計
//////////////////////在左下,右上方向上//////////////////////////////
if(x-4<0||14-(y+4)<0){
int b=0;
if(x-4<0&&14-(y+4)<0){
b=Math.min(4-Math.abs(x-4),14-y);
}
else if(x-4<0&&y<=10){
b=4-Math.abs(x-4);
}
else if(14-(y+4)<0&&x-4>=0){
b=14-y;
}
temp[2]=temp[2]+"C";
while(b!=0){
if(ChessBoard[x-b][y+b]==0){
temp[2]=temp[2]+"O";
}
else if(ChessBoard[x-b][y+b]==color){
temp[2]=temp[2]+"U";
}
else {
temp[2]=temp[2]+"C";
}
b--;
}
}
else{
for(int i=4;i>0;i--){
if(ChessBoard[x-i][y+i]==0){
temp[2]=temp[2]+"O";
}
else if(ChessBoard[x-i][y+i]==color){
temp[2]=temp[2]+"U";
}
else {
temp[2]=temp[2]+"C";
}
}
}
if(y-4<0||14-(x+4)<0){
int b=0;
if(y-4<0&&14-(x+4)<0){
b=Math.min(4-Math.abs(y-4),14-x);
}
else if(y-4<0&&x<=10){
b=4-Math.abs(y-4);
}
else if(14-(x+4)<0&&y-4>=0){
b=14-x;
}
while(b!=0){
if(ChessBoard[x+b][y-b]==0){
temp[2]=temp[2]+"O";
}
else if(ChessBoard[x+b][y-b]==color){
temp[2]=temp[2]+"U";
}
else {
temp[2]=temp[2]+"C";
}
b--;
}
temp[2]=temp[2]+"C";
}
else{
for(int i=0;i<=4;i++){
if(ChessBoard[x+i][y-i]==0){
temp[2]=temp[2]+"O";
}
else if(ChessBoard[x+i][y-i]==color){
temp[2]=temp[2]+"U";
}
else {
temp[2]=temp[2]+"C";
}
}
}
//////////////////////在左上,右下方向上////////////////////////////////
if(x-4<0||y-4<0){
int bite=0;
if(x-4<0&&y-4<0){
bite=Math.min(4-Math.abs(x-4),4-Math.abs(y-4));
}
else if(x-4<0&&y-4>=0){
bite=4-Math.abs(x-4);
}
if(y-4<0&&x-4>=0){
bite=4-Math.abs(y-4);
}
temp[3]=temp[3]+"C";
while(bite!=0){
if(ChessBoard[x-bite][y-bite]==0){
temp[3]=temp[3]+"O";
}
else if(ChessBoard[x-bite][y-bite]==color){
temp[3]=temp[3]+"U";
}
else {
temp[3]=temp[3]+"C";
}
bite--;
}
}
else {
for(int i=4;i>0;i--){
if(ChessBoard[x-i][y-i]==0){
temp[3]=temp[3]+"O";
}
else if(ChessBoard[x-i][y-i]==color){
temp[3]=temp[3]+"U";
}
else {
temp[3]=temp[3]+"C";
}
}
}
for(int i=0;i<=4;i++){
if(y+i>14||x+i>14) {
temp[3]=temp[3]+"C";
break;
}
if(ChessBoard[x+i][y+i]==0){
temp[3]=temp[3]+"O";
}
else if(ChessBoard[x+i][y+i]==color){
temp[3]=temp[3]+"U";
}
else {
temp[3]=temp[3]+"C";
}
}
擷取完之後我們需要計算得分,因為擷取到的長度是長於定義的棋型的,所以只要在擷取到棋型構成的字串裡面,有已經定義的棋型的子串,那麼就算匹配上,該位置的得分加上該棋型對應的分數。匹配引入包: import java.util.regex.Matcher; import java.util.regex.Pattern; 呼叫方法來求得 Pattern p=Pattern.compile(type[j]); Matcher m=p.matcher(temp[i]); if(m.find()) 因為棋型在type數組裡面儲存順序是按分數遞減儲存的,所以最開始找到的棋型為最好的,在這四個方向上找到最好的就停止尋找,繼續找下一個方向的。直到得到四個方向的分數並且相加,就為最終的結果。 程式碼實現為: int result=0; for(int i=0;i<4;i++){ for(int j=0;j<38;j++){ Pattern p=Pattern.compile(type[j]); Matcher m=p.matcher(temp[i]); if(m.find()){ result+=score[j]; break; } } } 3.3.2中級下法 中級難度的下法為,遍歷棋盤,找到己方評價函式最高的位置A,再找到對方評價值最高的位置B,假如自己方的分數高於或者等於對方的分數,就將棋子落在A,反之落在位置B。 程式碼實現如下: public void computerplayer(){ /////////////預設黑棋先行/////////////////////////// int MaxScoreBlack=0; int MaxScorewhite=0; int TempScore=0; int maxXB=0,maxYB=0,maxXW=0,maxYW=0; int color=2; llastx=lastx; llasty=lasty; ///////////////////////算白棋總分,當對方的棋局威脅不大時,自己方就進攻選擇得分最高的棋局//////////////// TempScore=0; for(int i=0;i<15;i++){ for(int j=0;j<15;j++){ if(ChessBoard[i][j]==0){ TempScore=this.calculateScore(i, j,color); if(TempScore>MaxScorewhite){ MaxScorewhite=TempScore; maxXW=i; maxYW=j; } } } } /////////////////////////算黑棋總分/////////////////////////////// color=1; TempScore=0; for(int i=0;i<15;i++){ for(int j=0;j<15;j++){ if(ChessBoard[i][j]==0){ TempScore=this.calculateScore(i, j,color); if(TempScore>MaxScoreBlack){ MaxScoreBlack=TempScore; maxXB=i; maxYB=j; } } } } if(MaxScorewhite>=MaxScoreBlack){ lastx=maxXW; lasty=maxYW; } else { lastx=maxXB; lasty=maxYB; } System.out.println(MaxScorewhite); System.out.println(MaxScoreBlack); ChessBoard[lastx][lasty]=2; IsBlack=!IsBlack; //換白棋 chessNum++; return; }
3、程式碼原始碼
import java.awt.Color; import java.awt.Container; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.RadialGradientPaint; import java.awt.RenderingHints; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.geom.Ellipse2D; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException;
import javax.imageio.ImageIO; import javax.swing.JFrame; import javax.swing.JOptionPane; import javax.swing.JPanel;
public class fivechess extends JFrame{
public fivechess(){
GameBoard gb=new GameBoard();
Container contentPane=getContentPane();
contentPane.add(gb);
gb.setOpaque(true);
add(gb);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(526,549);
setLocation(400,100);
}
public static void main(String[] args) {
// TODO Auto-generated method stub
fivechess f=new fivechess();
f.setTitle("五子棋單機版-人機對戰");
f.setVisible(true);
}
public class GameBoard extends JPanel implements MouseListener{
BufferedImage bgImage=null;//背景圖片
int row=14;//棋盤行數
int col=14;//棋盤列數
int span=30;//每個單元格的寬度
int dis=30;//邊距
int[][] ChessBoard=new int[row+1][col+1];//棋子代表的二維陣列,其中0代表沒有落子1代表黑子2代表白子
//儲存每一步的操作,便於悔棋
int[] allx=new int[(row+1)*(col+1)];
int[] ally=new int[(row+1)*(col+1)];
int chessNum=0;//已經下了的棋子數目
boolean IsBlack=true;//判斷哪方下子(黑子先行)
int lastx=-1,lasty=-1; //最後下子的位置
boolean Win=false;//是否勝利的標誌
int[][][] Player=new int[row+1][col+1][4];
int[][][] Computer=new int[row+1][col+1][4];
//設定棋子半徑
static final int radius=15;
//棋盤的位置的分值(固定為15x15的棋盤)
//建構函式
public GameBoard() {
try {
bgImage=ImageIO.read(new File("src/wuziqi.jpg"));
} catch (IOException e) {
e.printStackTrace();
}
this.addMouseListener(this);
//初始化棋盤
for(int i=0;i<row+1;i++){
for(int j=0;j<col+1;j++){
ChessBoard[i][j]=0;
}
}
}
//繪製棋盤
public void paintComponent(Graphics g){
super.paintComponent(g);
g.drawImage(bgImage, 0, 0, row*span+2*dis, col*span+2*dis, null);
//繪製線條
for(int i=0;i<=row;i++){//橫線
g.drawLine(dis, dis+i*span, col*span+dis, dis+i*span);
}
for(int j=0;j<=col;j++){//豎線
g.drawLine(dis+j*span, dis, dis+j*span, row*span+dis);
}
//繪製棋子
for(int i=0;i<row+1;i++){
for(int j=0;j<col+1;j++){
if(ChessBoard[i][j]!=0){//存在落子
//獲取網格交叉點的座標(在螢幕上的)
int x=i*span+dis;
int y=j*span+dis;
if(ChessBoard[i][j]==1){//如果落黑子
g.setColor(Color.BLACK);
RadialGradientPaint paint=new RadialGradientPaint(x-radius+25,y-radius+10,20,new float[]{0f,1f},new Color[]{Color.WHITE,Color.BLACK});
((Graphics2D) g).setPaint(paint);
((Graphics2D) g).setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
((Graphics2D) g).setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_DEFAULT);
}
else if(ChessBoard[i][j]==2){//如果落白子
g.setColor(Color.WHITE);
RadialGradientPaint paint=new RadialGradientPaint(x-radius+25,y-radius+10,70,new float[]{0f,1f},new Color[]{Color.WHITE,Color.BLACK});
((Graphics2D) g).setPaint(paint);
((Graphics2D) g).setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
((Graphics2D) g).setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_DEFAULT);
}
Ellipse2D e = new Ellipse2D.Float(x-radius, y-radius, 30, 30);
((Graphics2D) g).fill(e);
if(i==lastx&&j==lasty){
g.setColor(Color.RED);
g.drawRect(x-radius, y-radius, 30, 30);
}
}
}
}
}
@Override
public void mousePressed(MouseEvent e) {
// TODO Auto-generated method stub
String mes="";
String ChessColor=IsBlack?"黑子":"白子";
if(Win){
mes=String.format("遊戲結束!%s已經取得遊戲勝利,請重新開始遊戲!", ChessColor);
JOptionPane.showMessageDialog(this, mes);
return;
}
if(chessNum==(row+1)*(col+1)){
mes="棋盤已滿!";
JOptionPane.showMessageDialog(this, mes);
return;//不執行任何操作
}
//獲得滑鼠落點在二維陣列的索引
lastx=(e.getX()-dis+span/2)/span;
lasty=(e.getY()-dis+span/2)/span;
//落點不在棋盤內不能落子
if(lastx<0||lastx>row||lasty<0||lasty>col){
mes="落子處超出棋盤範圍!";
JOptionPane.showMessageDialog(this, mes);
return;
}
//落點已經存在棋子不能落子
if(isExit(lastx,lasty)){
mes="該處已有落子!";
JOptionPane.showMessageDialog(this, mes);
return;
}
//繪製落子
if(IsBlack)
ChessBoard[lastx][lasty]=1;
else ChessBoard[lastx][lasty]=2;
chessNum++;
repaint();
//判斷勝負
if(IsWin()){
Win=true;
mes=String.format("恭喜,%s贏了!", ChessColor);
JOptionPane.showMessageDialog(this, mes);
return;
}
IsBlack=!IsBlack;
}
@Override
public void mouseClicked(MouseEvent e) {
// TODO Auto-generated method stub
}
@Override
public void mouseEntered(MouseEvent e) {
// TODO Auto-generated method stub
}
@Override
public void mouseExited(MouseEvent e) {
// TODO Auto-generated method stub
}
@Override
public void mouseReleased(MouseEvent e) {
// TODO Auto-generated method stub
}
public boolean isExit(int x,int y){
if(ChessBoard[x][y]!=0)
return true;
return false;
}
public boolean IsWin(){
int count=1;
int color=ChessBoard[lastx][lasty];
//判斷橫向上的相同顏色的棋子數量
count=this.ChessCount(1, 0, color);
if(count>=5)
return true;
//判斷縱向上的相同顏色棋子的數量
count=this.ChessCount(0, 1, color);
if(count>=5)
return true;
//判斷主對角線上/的相同顏色棋子的數量
count=this.ChessCount(1, 1, color);
if(count>=5)
return true;
//判斷副對角線上\的相同顏色棋子的數量
count=this.ChessCount(1, -1, color);
if(count>=5)
return true;
return false;
}
//計算棋子在xChange和yChange方向上連線的數量
public int ChessCount(int xChange,int yChange,int color){
int count=1;//棋子計數器
int tempx=xChange;
int tempy=yChange;
//xChange取值範圍(1,0,1,1)和yChange取值範圍(0,1,1,-1)分別代表(橫向,縱向,/,\)
//先計算正方向上的數量
while (lastx + xChange >= 0 && lastx + xChange <= row && lasty + yChange >= 0 && lasty + yChange <= col && color == ChessBoard[lastx + xChange][lasty + yChange]) {
count++;
if (xChange != 0) {
xChange++;
}
if (yChange != 0) {
if (yChange > 0)
yChange++;
else
yChange--;
}
}
//回到最開始的方向
xChange = tempx;
yChange = tempy;
//接著計算反方向上的數量
while (lastx - xChange >= 0 && lastx - xChange <= row && lasty - yChange >= 0 && lasty - yChange <= col && color == ChessBoard[lastx - xChange][lasty - yChange]) {
count++;
if (xChange != 0) {
xChange++;
}
if (yChange != 0) {
if (yChange > 0)
yChange++;
else
yChange--;
}
}
return count;
}
}
}