暑假实训一:基于Alpha-Beta剪枝极大极小博弈算法的五子棋AI实现

基于Alpha-Beta剪枝极大极小博弈算法的五子棋AI实现

1、实训目标

通过设计和开发移动应用产品,学习和掌握以下方法和技术

  • 项目分析及解决思路
  • 产品原型设计
  • 产品界面设计
  • Java高级编程
  • 极小极大值算法学习及应用

Alpha-Beta剪枝算法学习及应用

2、实训模块

1.棋盘绘制

绘制五子棋棋盘

2.五子棋的人人对弈实现

实现双方手动下五子棋,定义下棋规则,判断下棋输赢

3.五子棋的人机对弈实现

初步实现人机对弈,采用估值函数和搜索树

4.基于极大值极小值算法的优化

初步进行对弈优化

5.基于Alpha-Beta剪枝算法优化

对算法进一步优化

运行界面:

 

 

核心算法记录:

一.极大极小值算法

 

 假如有如上图的博弈树,设先手为 A ,后手为 B ;则 A 为 max 局面,B 为 min 局面。上图中 A 一开始有 2 种走法( w2 和 w3 ,w表示结点记号),它走 w2 还是 w3 取决于 w2 和 w3 的估价函数值f(),因为 A 是 max 局面,所以它会取 f(w2)  和 f(w3) 中大的那个,f(x)通常是以递归的方式对博弈树进行搜索,可以设定叶子结点局面的估价值。 如上图的搜索过程为 w1 --> w2 --> w4 ,然后回溯到 w1 --> w2 得到 f(w2) = 3 ,接着 w1 --> w2 --> w5 得到 f'(w2) = 1,因为 w2 在第二层,是 min 局面,所以它会选择得到的结果中小的那个,即用 f'(w2) 替代 f(w2) ,即 f(w2) = 1,接着 w1 --> w2 --> w6 得到 f'(w2) = 6 > f(w2) ,直接忽略。因此如果 A 往 w2 走的话将会得到一个估价值为 f(w2) = 1 的局面;类似地,如果往 w3 走的话将会得到一个估价值为 f(w3) = -3 的局面。而 A 是 max 局面,所以它会选择估价值大的走法,f(w2) = 1 > f(w3) = -3,因此它下一步走 w2。

二.alpha-beta剪枝

 

    alpha 初始值为 -oo,beta 初始值为 +oo;w1 在得到 f(w1) = 6 的整个过程中, alpha 和 beta 是这样变化的:w1 --> w2 -- > w5 得到 f'(w2) = 6 < f(w2) = +oo,即修改 f(w2) = f'(w2) = 6;同时修改 beta = 6 (因为 w2 的子局面是否需要剪枝依赖于 w2 的估价值 f(),而与 alpha 无关,故不需修改 alpha)。在正常搜索完 w1 --> w2 --> w6 后, w2 层把 beta = 6 返回给上一层 w1 的 alpha,即 w1 层的 alpha = 6,beta = +oo。

 

  接下来在 w3 里搜索就可以体现 alpha-beta 剪枝的效果;A 在选择 w3 走的时候同时把所得的 alpha 和 beta 传递下去,在经过 w1 --> w3 --> w7 得到 f(w3) = 4 (同时使 beta = 4)后,首先进行判断:如果 alpha > beta,则直接返回 beta = 4,没有必要再搜索 w8 和 w9。这是因为这个 alpha 是 w1 在走 w2 这条路时得到的一个估价值 f(w1),而 w3 是 min 局面,它会选择子局面 w7, w8, w9 中 f() 的值小的作为 f(w3),所以 w3 在得到 f(w3) = 4 后如果继续搜索 w8, w9,只会得到更小的值;w1 是 max 局面,它的 f() 要修改的条件是找到估价值比它更大的子局面,而 w1 目前已知的估价值 f(w1) = 6 比 f(w3) 要大,所以无论 w3 再怎么继续搜索下去,w1 都不会选 w3 作为下一步,所以没有必要搜索下去。这样就剪掉了 w8, w9 这两个分支,直接跳出 w3 进入 w4 继续搜索。另外一种情形也是类似的道理,这样就实现了有效的剪枝优化。

源码如下:

1.GoBangTest.java

package wuziqi;

//运行入口
public class GoBangTest {
public static void main(String[] args) {
GoBangFrame frame=new GoBangFrame();

frame.start();

}
}

2.ChessBean.java

package wuziqi;
//棋子
public class ChessBean implements Comparable<ChessBean>{
	
	
	//棋子的坐标(0,0)-(14,14)
	private int X;
	private int Y;
	
	//棋子属于哪个玩家  0:空(没有下棋子) 1:黑子  2:白子
	private int player;
	//落子顺序
	private int orderNumber;
	
	private int offense;
	private int defense;
	private int sum;
	
	private StringBuffer buffer=new StringBuffer();
	
	public ChessBean(){
		
	}
	//定义非空参数构造器,需要先定义一个无参的构造器
	public ChessBean(int x,int y,int player,int orderNumber){
		this.X=x;
		this.Y=y;
		this.player=player;
		this.orderNumber=orderNumber;
	}
	public int getX() {
		return X;
	}
	public void setX(int x) {
		X = x;
	}
	public int getY() {
		return Y;
	}
	public void setY(int y) {
		Y = y;
	}
	public int getPlayer() {
		return player;
	}
	public void setPlayer(int player) {
		this.player = player;
	}
	public int getOrderNumber() {
		return orderNumber;
	}
	public void setOrderNumber(int orderNumber) {
		this.orderNumber = orderNumber;
	}
	public int getOffense() {
		return offense;
	}
	public void setOffense(int offense) {
		this.offense = offense;
	}
	public int getDefense() {
		return defense;
	}
	public void setDefense(int defense) {
		this.defense = defense;
	}
	public int getSum() {
		return sum;
	}
	public void setSum(int sum) {
		this.sum = sum;
	}
	public StringBuffer getBuffer() {
		return buffer;
	}
	public void setBuffer(StringBuffer buffer) {
		this.buffer = buffer;
	}
	@Override
	public int compareTo(ChessBean o) {
		if(this.getSum()>o.getSum()){
			return -1;
		}else {
			if(this.getSum()<o.getSum())
				return 1;
		}
		return 0;
	}
	
}

3.GoBangContantes.java

package wuziqi;

//存放常量
public class GoBangContantes {
//游戏界面宽和高
public static final int GAME_WIDTH=900;
public static final int GAME_HEIGHT=700;

//棋盘宽和高
public static final int PANEL_WIDTH=650;
public static final int PANEL_HEIGHT=700;
//线的大小和数量
public static final int LINE_SIZE=40;
public static final int LINE_NUMBER=15;
//棋盘的偏移量和star
public static final int OFFSET=40;
public static final int STAR=10;

//定义棋子
public static final int EMPTY=0;
public static final int BLACK=1;
public static final int WHITE=2;
}

4.GoBangFrame.java

package wuziqi;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;

import javax.swing.BoxLayout;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.border.TitledBorder;

//游戏窗口类
public class GoBangFrame extends JFrame{

private GoBangPanel panel;
private JCheckBox showorder;
private JButton nudo;
private JButton newGame;
private JLabel label1,label2;
private JPanel panel1,panel2,panel3,panel4,panel5,panel6;
private JTextArea textArea;
private JRadioButton model,robot,intel,radioButton4,first,radioButton6;
private ButtonGroup buttonGroup1,buttonGroup2,buttonGroup3;
private JComboBox<Integer> depth,nodeCount;
//启动游戏窗口
public void start(){

panel=new GoBangPanel();
add(panel,BorderLayout.WEST);

JPanel rightPanel=new JPanel();
//设置垂直布局
rightPanel.setLayout(new BoxLayout(rightPanel, BoxLayout.Y_AXIS));

//多行文本框
panel1=new JPanel();
panel1.setBorder(new TitledBorder("在键盘上单击鼠标右键,查看各个估值"));
panel1.setLayout(new BorderLayout());
textArea=new JTextArea();
textArea.setEditable(false);
panel1.add(new JScrollPane(textArea));
rightPanel.add(panel1);

//模式
panel2=new JPanel();
panel2.setBorder(new TitledBorder("模式"));
//单选框
model=new JRadioButton("人人对战");
model.setSelected(true);
robot=new JRadioButton("人机对战");
//单选框互斥
buttonGroup1=new ButtonGroup();
buttonGroup1.add(model);
buttonGroup1.add(robot);
panel2.add(model);
panel2.add(robot);
rightPanel.add(panel2);

//智能
panel3=new JPanel();
panel3.setBorder(new TitledBorder("智能"));
//单选框
intel=new JRadioButton("估值函数");
intel.setSelected(true);
radioButton4=new JRadioButton("估值函数+搜索树");
//单选框互斥
buttonGroup2=new ButtonGroup();
buttonGroup2.add(intel);
buttonGroup2.add(radioButton4);
panel3.add(intel);
panel3.add(radioButton4);
rightPanel.add(panel3);

//搜索树
panel4=new JPanel();
panel4.setBorder(new TitledBorder("搜索树"));
label1=new JLabel("搜索深度");
depth=new JComboBox<>(new Integer[] {1,2,3});
label2=new JLabel("每层节点");
nodeCount=new JComboBox<>(new Integer[] {1,2,3});

panel4.add(label1);
panel4.add(depth);
panel4.add(label2);
panel4.add(nodeCount);
rightPanel.add(panel4);

//其他
panel5=new JPanel();
panel5.setBorder(new TitledBorder("其他"));
showorder=new JCheckBox("显示顺序");
nudo=new JButton("悔棋");
newGame=new JButton("新游戏");
showorder.addMouseListener(mouseListener);
nudo.addMouseListener(mouseListener);
newGame.addMouseListener(mouseListener);
panel5.add(showorder);
panel5.add(nudo);
panel5.add(newGame);
rightPanel.add(panel5);

//人机模式
panel6=new JPanel();
panel6.setBorder(new TitledBorder("人机模式"));
//单选框
first=new JRadioButton("人类先手");
first.setSelected(true);
radioButton6=new JRadioButton("机器先手");
//单选框互斥
buttonGroup3=new ButtonGroup();
buttonGroup3.add(first);
buttonGroup3.add(radioButton6);
panel6.add(first);
panel6.add(radioButton6);
rightPanel.add(panel6);

add(rightPanel);

//游戏窗口设置
setSize(GoBangContantes.GAME_WIDTH,GoBangContantes.GAME_HEIGHT);
setLocation(200, 200);
setTitle("五子棋");
setResizable(false);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setVisible(true);
}
//监听器
private MouseListener mouseListener=new MouseListener() {

@Override
public void mouseReleased(MouseEvent e) {
// TODO Auto-generated method stub

}

@Override
public void mousePressed(MouseEvent e) {
// TODO Auto-generated method stub

}

@Override
public void mouseExited(MouseEvent e) {
// TODO Auto-generated method stub

}

@Override
public void mouseEntered(MouseEvent e) {
// TODO Auto-generated method stub

}

@Override
public void mouseClicked(MouseEvent e) {
Object object=e.getSource();
if(object==nudo){
if(model.isSelected())
panel.huiqi();
else if(robot.isSelected())
panel.huiqi2();
}else if(object==showorder){
//显示状态传到panel进行判断
panel.showOrder(showorder.isSelected());
}else if(object==newGame){
panel.newGame(model.isSelected()?true:false,intel.isSelected()?true:false,
(int)depth.getSelectedItem(),(int)nodeCount.getSelectedItem(),
first.isSelected()?true:false,showorder.isSelected(),textArea);

}

}
};

}

5.GoBangPanel.java

package wuziqi;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.util.List;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.security.GuardedObject;
import java.util.ArrayList;
import java.util.Collections;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextArea;

//左侧棋盘
public class GoBangPanel extends JPanel{
private int currentPlayer=GoBangContantes.BLACK;
//true为游戏结束
private boolean isGameOver=false;
//记录棋子的步数
private int count=0;
//模式true为人人对战 false为人机对战
private boolean model;
//true:估值函数 false:估值函数+搜索树
private boolean intel;
//搜索深度和节点数量
private int depth;
private int nodeCount;
//true为人类先手,false为机器先手
private boolean first;
//
private JTextArea textArea;
//显示状态
private boolean showOrder;

private ChessBean chessBeanBytree;

private int x,y;
ChessBean[][] chessBeans=new ChessBean[GoBangContantes.LINE_NUMBER][GoBangContantes.LINE_NUMBER];
//创建棋盘
public GoBangPanel(){
//setSize(650,700);
setPreferredSize(new Dimension(GoBangContantes.PANEL_WIDTH, GoBangContantes.PANEL_HEIGHT));
setBackground(Color.ORANGE);
//棋盘绑定鼠标移动事件
addMouseMotionListener(mouseMotionListener);
//棋盘绑定鼠标点击事件
addMouseListener(mouseListener);
//对棋盘上的棋子初始化
for(int i=0;i<chessBeans.length;i++){
for(int j=0;j<chessBeans[i].length;j++)
{

chessBeans[i][j]=new ChessBean(i,j,0,0);
//测试 b-w-b-w
//chessBeans[i][j]=new ChessBean(i,j,currentPlayer,i*chessBeans.length+j);
//currentPlayer=3-currentPlayer;
}
}

}
//绑定事件
MouseMotionListener mouseMotionListener=new MouseMotionListener() {

@Override
public void mouseMoved(MouseEvent arg0) {
//鼠标移动,获取坐标
int x_mv=arg0.getX();
int y_mv=arg0.getY();

//判断越界
if(x_mv>=GoBangContantes.OFFSET&&x_mv<=GoBangContantes.OFFSET+(GoBangContantes.LINE_NUMBER-1)*GoBangContantes.LINE_SIZE
&&y_mv>=GoBangContantes.OFFSET&&y_mv<=GoBangContantes.OFFSET+(GoBangContantes.LINE_NUMBER-1)*GoBangContantes.LINE_SIZE)
{
x=(x_mv-GoBangContantes.OFFSET/2)/GoBangContantes.LINE_SIZE;
y=(y_mv-GoBangContantes.OFFSET/2)/GoBangContantes.LINE_SIZE;
repaint();
}
}

@Override
public void mouseDragged(MouseEvent arg0) {
// TODO Auto-generated method stub

}
};

//绘制棋盘
public void paintComponent(Graphics g){
super.paintComponent(g);
Graphics2D g2d=(Graphics2D) g;
g2d.setStroke(new BasicStroke(2));//加粗线
drawLine(g2d);//画棋盘
drawStar(g2d);//画天元和星
drawTrips(g2d);//画提醒框
drawNumber(g2d);//画坐标
drawChess(g2d);//绘制棋子
drawOrderNumber(g2d);//绘制下棋顺序
}

//绘制棋盘
private void drawLine(Graphics2D g2d){
//横线
for(int i=0;i<GoBangContantes.LINE_NUMBER;i++){
g2d.drawLine(GoBangContantes.OFFSET, GoBangContantes.OFFSET+i*GoBangContantes.LINE_SIZE,
GoBangContantes.OFFSET+(15-1)*GoBangContantes.LINE_SIZE, GoBangContantes.OFFSET+i*GoBangContantes.LINE_SIZE);
}
//竖线
for(int i=0;i<GoBangContantes.LINE_NUMBER;i++){
g2d.drawLine(GoBangContantes.OFFSET+i*GoBangContantes.LINE_SIZE,GoBangContantes.OFFSET,
GoBangContantes.OFFSET+i*GoBangContantes.LINE_SIZE,GoBangContantes.OFFSET+(15-1)*GoBangContantes.LINE_SIZE);
}
}
//画天元和星
private void drawStar(Graphics2D g2d){
int center=GoBangContantes.LINE_NUMBER/2;
int quarter=GoBangContantes.LINE_NUMBER/4;
//天元
g2d.fillOval(center*GoBangContantes.LINE_SIZE+GoBangContantes.OFFSET-GoBangContantes.STAR/2,center*GoBangContantes.LINE_SIZE+GoBangContantes.OFFSET-GoBangContantes.STAR/2,
GoBangContantes.STAR,GoBangContantes.STAR);
//左上角星星
g2d.fillOval(quarter*GoBangContantes.LINE_SIZE+GoBangContantes.OFFSET-GoBangContantes.STAR/2,quarter*GoBangContantes.LINE_SIZE+GoBangContantes.OFFSET-GoBangContantes.STAR/2,
GoBangContantes.STAR,GoBangContantes.STAR);
//左下角星星
g2d.fillOval(quarter*GoBangContantes.LINE_SIZE+GoBangContantes.OFFSET-GoBangContantes.STAR/2,(GoBangContantes.LINE_NUMBER-quarter-1)*GoBangContantes.LINE_SIZE+GoBangContantes.OFFSET-GoBangContantes.STAR/2,
GoBangContantes.STAR,GoBangContantes.STAR);
//右上角星星
g2d.fillOval((GoBangContantes.LINE_NUMBER-quarter-1)*GoBangContantes.LINE_SIZE+GoBangContantes.OFFSET-GoBangContantes.STAR/2,quarter*GoBangContantes.LINE_SIZE+GoBangContantes.OFFSET-GoBangContantes.STAR/2,
GoBangContantes.STAR,GoBangContantes.STAR);
//右下角星星
g2d.fillOval((GoBangContantes.LINE_NUMBER-quarter-1)*GoBangContantes.LINE_SIZE+GoBangContantes.OFFSET-GoBangContantes.STAR/2,(GoBangContantes.LINE_NUMBER-quarter-1)*GoBangContantes.LINE_SIZE+GoBangContantes.OFFSET-GoBangContantes.STAR/2,
GoBangContantes.STAR,GoBangContantes.STAR);
}
//绘制提醒框
private void drawTrips(Graphics2D g2d) {
g2d.setColor(Color.red);
//交叉点的坐标
int x_tmp=GoBangContantes.OFFSET+x*GoBangContantes.LINE_SIZE;
int y_tmp=GoBangContantes.OFFSET+y*GoBangContantes.LINE_SIZE;

int half=GoBangContantes.LINE_SIZE/2;
int quarter=GoBangContantes.LINE_SIZE/4;
//左上角
g2d.drawLine(x_tmp-half,y_tmp-half, x_tmp-half+quarter,y_tmp-half);
g2d.drawLine(x_tmp-half,y_tmp-half, x_tmp-half,y_tmp-half+quarter);

//左下角
g2d.drawLine(x_tmp-half,y_tmp+half, x_tmp-half+quarter,y_tmp+half);
g2d.drawLine(x_tmp-half,y_tmp+half, x_tmp-half,y_tmp+half-quarter);

//右上角
g2d.drawLine(x_tmp+half,y_tmp-half, x_tmp+half-quarter,y_tmp-half);
g2d.drawLine(x_tmp+half,y_tmp-half, x_tmp+half,y_tmp-half+quarter);

//右下角
g2d.drawLine(x_tmp+half,y_tmp+half, x_tmp+half-quarter,y_tmp+half);
g2d.drawLine(x_tmp+half,y_tmp+half, x_tmp+half,y_tmp+half-quarter);

}
//画坐标
private void drawNumber(Graphics2D g2d) {

g2d.setColor(Color.BLACK);
for(int i=GoBangContantes.LINE_NUMBER;i>0;i--)
{
FontMetrics fn=g2d.getFontMetrics();
int height=fn.getAscent();
g2d.drawString(16-i+"",10,i*GoBangContantes.LINE_SIZE+height/2);

int width=fn.stringWidth(((char)(64+i))+"");
g2d.drawString(((char)(64+i))+"",GoBangContantes.LINE_SIZE*i-width/2,
GoBangContantes.OFFSET+GoBangContantes.LINE_NUMBER*GoBangContantes.LINE_SIZE);

}

}

//绘制棋子
private void drawChess(Graphics2D g2d) {
for(int i=0;i<chessBeans.length;i++){
for(int j=0;j<chessBeans[i].length;j++)
{
ChessBean bean=chessBeans[i][j];
//判空
if(bean.getPlayer()!=GoBangContantes.EMPTY)
{
//黑子
if(bean.getPlayer()==GoBangContantes.BLACK){
g2d.setColor(Color.black);

}//白子
else if(bean.getPlayer()==GoBangContantes.WHITE){
g2d.setColor(Color.white);
}

//计算坐标
int x_tmp=GoBangContantes.OFFSET+bean.getX()*GoBangContantes.LINE_SIZE;
int y_tmp=GoBangContantes.OFFSET+bean.getY()*GoBangContantes.LINE_SIZE;

int width=GoBangContantes.LINE_SIZE/5*3;
g2d.fillOval(x_tmp-width/2, y_tmp-width/2, width, width);
}

}
}
if(!showOrder){
//获取最后一个棋子
ChessBean bean=getLastBean();
if(bean!=null){
//棋子显示,要么是数字,要么最后一个棋子是红框
g2d.setColor(Color.red);
int width=GoBangContantes.LINE_SIZE/5;

//计算坐标
int x_tmp=GoBangContantes.OFFSET+bean.getX()*GoBangContantes.LINE_SIZE;
int y_tmp=GoBangContantes.OFFSET+bean.getY()*GoBangContantes.LINE_SIZE;
g2d.fillRect(x_tmp-width/2,y_tmp-width/2, width, width);
}

}

}

//查询最后一个棋子
private ChessBean getLastBean() {
ChessBean bean=null;
for(int i=0;i<chessBeans.length;i++){
for(int j=0;j<chessBeans[i].length;j++)
{
ChessBean tmp=chessBeans[i][j];
if(tmp.getPlayer()!=GoBangContantes.EMPTY)
{
if(bean==null)
bean=tmp;
else
{
if(tmp.getOrderNumber()>bean.getOrderNumber())
bean=tmp;
}
}

}
}
return bean;
}

//绘制下棋数字顺序
private void drawOrderNumber(Graphics2D g2d) {
if(showOrder){
g2d.setColor(Color.red);
for(int i=0;i<chessBeans.length;i++){
for(int j=0;j<chessBeans[i].length;j++)
{
ChessBean bean=chessBeans[i][j];
//判空
if(bean.getPlayer()!=GoBangContantes.EMPTY)
{
int number=bean.getOrderNumber();
FontMetrics fontMetrics=g2d.getFontMetrics();
int width=fontMetrics.stringWidth(number+"");
int height=fontMetrics.getAscent();

//计算坐标
int x_tmp=GoBangContantes.OFFSET+bean.getX()*GoBangContantes.LINE_SIZE;
int y_tmp=GoBangContantes.OFFSET+bean.getY()*GoBangContantes.LINE_SIZE;

g2d.drawString(number+"", x_tmp-width/2, y_tmp+height/2);
}
}
}
}

}

//显示状态判断
public void showOrder(boolean selected) {
this.showOrder=selected;
//重绘棋盘
repaint();

}

//新游戏
public void newGame(boolean model, boolean intel, int depth,int nodeCount, boolean first, boolean showorder, JTextArea textArea)
{
this.model=model;
this.intel=intel;
this.depth=depth;
this.nodeCount=nodeCount;
this.first=first;
this.showOrder=showorder;
this.textArea=textArea;
this.textArea.setText("");
//防止多次新游戏,第一个是黑子
currentPlayer=GoBangContantes.BLACK;
//初始化
for(int i=0;i<chessBeans.length;i++){
for(int j=0;j<chessBeans[i].length;j++)
{

chessBeans[i][j]=new ChessBean(i,j,0,0);
}
}
isGameOver=false;
count=0;
JOptionPane.showMessageDialog(GoBangPanel.this, "新游戏开始!");

if(!model&&!first)
{
//人机对战,机器先手
int center=GoBangContantes.LINE_NUMBER/2;
chessBeans[center][center].setPlayer(currentPlayer);
chessBeans[center][center].setOrderNumber(count);
count++;
currentPlayer=3-currentPlayer;
}

repaint();
}

MouseListener mouseListener =new MouseListener() {

@Override
public void mouseClicked(MouseEvent e) {
if(isGameOver){
JOptionPane.showMessageDialog(GoBangPanel.this, "游戏已经结束!");
return;
}
//获取坐标
//鼠标移动,获取坐标
int x_mv=e.getX();
int y_mv=e.getY();

//判断越界
if(x_mv>=GoBangContantes.OFFSET&&x_mv<=GoBangContantes.OFFSET+(GoBangContantes.LINE_NUMBER-1)*GoBangContantes.LINE_SIZE
&&y_mv>=GoBangContantes.OFFSET&&y_mv<=GoBangContantes.OFFSET+(GoBangContantes.LINE_NUMBER-1)*GoBangContantes.LINE_SIZE)
{
x=(x_mv-GoBangContantes.OFFSET/2)/GoBangContantes.LINE_SIZE;
y=(y_mv-GoBangContantes.OFFSET/2)/GoBangContantes.LINE_SIZE;
if(e.getButton()==MouseEvent.BUTTON1){
//点击鼠标左键
if(model){
//人人对战
//落子
if(chessBeans[x][y].getPlayer()==GoBangContantes.EMPTY)
{
//为空落子
chessBeans[x][y]=new ChessBean(x,y,currentPlayer,count);
count++;
currentPlayer=3-currentPlayer;
checkWin(chessBeans[x][y]);
repaint();
}
}else{
//人机对战
if(intel){
//估值函数
//落子
if(chessBeans[x][y].getPlayer()==GoBangContantes.EMPTY)
{
//为空落子
chessBeans[x][y]=new ChessBean(x,y,currentPlayer,count);
count++;
currentPlayer=3-currentPlayer;
boolean win=checkWin(chessBeans[x][y]);
repaint();

//机器下棋
if(!win){

List<ChessBean> list=getSortedBean(currentPlayer);
if(list.size()>0){
//获取最大的分值
ChessBean bean=list.get(0);
bean.setPlayer(currentPlayer);
bean.setOrderNumber(count);
count++;
currentPlayer=3-currentPlayer;
chessBeans[bean.getX()][bean.getY()]=bean;
checkWin(chessBeans[bean.getX()][bean.getY()]);
repaint();
}
}

}

}else{
//估值函数+搜索树
//落子
if(chessBeans[x][y].getPlayer()==GoBangContantes.EMPTY)
{
//为空落子
chessBeans[x][y]=new ChessBean(x,y,currentPlayer,count);
count++;
currentPlayer=3-currentPlayer;
boolean win=checkWin(chessBeans[x][y]);
repaint();

//机器下棋
if(!win){

getByTree2(0,currentPlayer,chessBeans,-Integer.MAX_VALUE,Integer.MAX_VALUE);
ChessBean bean=chessBeanBytree;
bean.setPlayer(currentPlayer);
bean.setOrderNumber(count);
count++;
currentPlayer=3-currentPlayer;
chessBeans[bean.getX()][bean.getY()]=bean;
checkWin(chessBeans[bean.getX()][bean.getY()]);
repaint();

/*ChessBean bean=getByTree(0,currentPlayer,chessBeans);
if(bean!=null)
{
bean.setPlayer(currentPlayer);
bean.setOrderNumber(count);
count++;
currentPlayer=3-currentPlayer;
chessBeans[bean.getX()][bean.getY()]=bean;
checkWin(chessBeans[bean.getX()][bean.getY()]);
repaint();
}*/

}

}
}
}

}else if(e.getButton()==MouseEvent.BUTTON3){
//点击鼠标右键
ChessBean bean=chessBeans[x][y];
int offense=getValue(bean,currentPlayer);
int defense=getValue(bean,3-currentPlayer);
int sum=offense+defense;
chessBeans[x][y].getBuffer().append("点(" + x + "," + y + ")的" + "攻击:" + offense + " "
+ "防御:" + defense + " " + "总和:" + (sum) + "\n\n");
textArea.append(chessBeans[x][y].getBuffer().toString());

}

}
}

@Override
public void mouseReleased(MouseEvent arg0) {
// TODO Auto-generated method stub

}

@Override
public void mousePressed(MouseEvent arg0) {
// TODO Auto-generated method stub

}

@Override
public void mouseExited(MouseEvent arg0) {
// TODO Auto-generated method stub

}

@Override
public void mouseEntered(MouseEvent arg0) {
// TODO Auto-generated method stub

}

};
//根据搜索树进行查询
protected ChessBean getByTree(int depth, int currentPlayer,ChessBean[][] chessBeans) {
//模拟下棋,克隆棋盘
ChessBean[][] chessBeans2=clone(chessBeans);
//计算空位置的得分
List<ChessBean> list=getSortedBean(currentPlayer,chessBeans2);
if(this.depth==depth){
//搜索到指定深度
return list.get(0);
}
for(int i=0;i<nodeCount;i++){
ChessBean chessBean=list.get(i);
if(chessBean.getSum()>Level.ALIVE_4.score){
return chessBean;
}else{
//模拟下棋,继续递归
chessBeans2[chessBean.getX()][chessBean.getY()].setPlayer(currentPlayer);
return getByTree(depth+1,3-currentPlayer , chessBeans2);
}
}
return null;
}

//极大极小值搜索
protected int getByTree2(int depth, int currentPlayer,ChessBean[][] chessBeans, int alpha, int beta) {
//模拟下棋,克隆棋盘
ChessBean[][] chessBeans2=clone(chessBeans);
//计算空位置的得分
List<ChessBean> list=getSortedBean(currentPlayer,chessBeans2);
if(this.depth==depth){
//搜索到指定深度
return list.get(0).getSum();
}
for(int i=0;i<nodeCount;i++){
ChessBean chessBean=list.get(i);
int score;
if(chessBean.getSum()>Level.ALIVE_4.score){
score=chessBean.getSum();
}else{
//模拟下棋,继续递归
chessBeans2[chessBean.getX()][chessBean.getY()].setPlayer(currentPlayer);
score=getByTree2(depth+1,3-currentPlayer , chessBeans2,alpha,beta);

}
if (depth % 2 == 0) {
// 自己,找最大值
if (score > alpha) {
alpha = score;
if (depth == 0) {
// 结果
chessBeanBytree=chessBean;
// System.out.println(chessBeansForTree);
}
}
if (alpha >= beta) {
// 剪枝
score = alpha;
return score;
}
} else {
if (score < beta) {
beta = score;
}
if (alpha >= beta) {
// 剪枝
score = beta;
return score;
}
}
}
return depth % 2 == 0 ? alpha : beta;
}

private List<ChessBean> getSortedBean(int currentPlayer, ChessBean[][] chessBeans) {

List<ChessBean> list=new ArrayList<ChessBean>();
//初始化
for(int i=0;i<chessBeans.length;i++){
for(int j=0;j<chessBeans[i].length;j++)
{
//棋子初始化
ChessBean bean=chessBeans[i][j];
if(bean.getPlayer()==GoBangContantes.EMPTY)
{
//该位置为空,计算得分
int offense=getValue(bean,currentPlayer);
int defense=getValue(bean,3-currentPlayer);

bean.setOffense(offense);
bean.setDefense(defense);
bean.setSum(offense+defense);
list.add(bean);
}
}
}
//根据总得分进行大小排序
Collections.sort(list);
return list;
}

//数组克隆
private ChessBean[][] clone(ChessBean[][] chessBeans) {
ChessBean[][] result=new ChessBean[GoBangContantes.LINE_NUMBER][GoBangContantes.LINE_NUMBER];
for(int i=0;i<result.length;i++)
for(int j=0;j<result[i].length;j++)
{
result[i][j]=new ChessBean(chessBeans[i][j].getX(),chessBeans[i][j].getY(),
chessBeans[i][j].getPlayer(),chessBeans[i][j].getOrderNumber());
}
return result;
}
//判断是否输赢
private boolean checkWin(ChessBean chessBean) {
boolean result=false;
//判断四个方面棋子的状态
//横
if(checkChessCount(chessBean,-1,0)+checkChessCount(chessBean,1,0)>=4)
{
result=true;
}
if(checkChessCount(chessBean,0,-1)+checkChessCount(chessBean,0,1)>=4)
{
result=true;
}
if(checkChessCount(chessBean,1,1)+checkChessCount(chessBean,-1,-1)>=4)
{
result=true;
}
if(checkChessCount(chessBean,1,-1)+checkChessCount(chessBean,-1,1)>=4)
{
result=true;
}
if(result){
//游戏结束
JOptionPane.showMessageDialog(GoBangPanel.this, "游戏结束");
isGameOver=true;
}
return result;
}

protected List<ChessBean> getSortedBean(int currentPlayer) {
List<ChessBean> list=new ArrayList<ChessBean>();
//初始化
for(int i=0;i<chessBeans.length;i++){
for(int j=0;j<chessBeans[i].length;j++)
{
//棋子初始化
ChessBean bean=chessBeans[i][j];
if(bean.getPlayer()==GoBangContantes.EMPTY)
{
//该位置为空,计算得分
int offense=getValue(bean,currentPlayer);
int defense=getValue(bean,3-currentPlayer);

bean.setOffense(offense);
bean.setDefense(defense);
bean.setSum(offense+defense);
list.add(bean);
}
}
}
//根据总得分进行大小排序
Collections.sort(list);
return list;
}

//对四个方向进行计分
private int getValue(ChessBean bean, int currentPlayer) {
//计算4个方向的棋形level
Level level1=getLevel(bean,currentPlayer,Direction.HENG);
Level level2=getLevel(bean,currentPlayer,Direction.SHU);
Level level3=getLevel(bean,currentPlayer,Direction.PIE);
Level level4=getLevel(bean,currentPlayer,Direction.NA);
return getValueByLevel(level1,level2,level3,level4)+position[bean.getX()][bean.getY()];
}

//检测4个方向的棋形是否重复
private int getValueByLevel(Level level1, Level level2, Level level3,Level level4) {
int[] levelCount=new int[Level.values().length];
for(int i=0;i<levelCount.length;i++){
levelCount[i]=0;
}
levelCount[level1.index]++;
levelCount[level2.index]++;
levelCount[level3.index]++;
levelCount[level4.index]++;

int score = 0;
if (levelCount[Level.GO_4.index] >= 2
|| levelCount[Level.GO_4.index] >= 1 && levelCount[Level.ALIVE_3.index] >= 1)// 双活4,冲4活三
score = 10000;
else if (levelCount[Level.ALIVE_3.index] >= 2)// 双活3
score = 5000;
else if (levelCount[Level.SLEEP_3.index] >= 1 && levelCount[Level.ALIVE_3.index] >= 1)// 活3眠3
score = 1000;
else if (levelCount[Level.ALIVE_2.index] >= 2)// 双活2
score = 100;
else if (levelCount[Level.SLEEP_2.index] >= 1 && levelCount[Level.ALIVE_2.index] >= 1)// 活2眠2
score = 10;
score = Math.max(score, Math.max(Math.max(level1.score, level2.score), Math.max(level3.score, level4.score)));
return score;
}

//计算某个方向的棋形
private Level getLevel(ChessBean bean, int currentPlayer, Direction dire) {
String left="";
String right="";
if(dire==Direction.HENG){
left=getStringByDire(bean,currentPlayer,-1,0);
right=getStringByDire(bean,currentPlayer,1,0);
}else if(dire==Direction.SHU){
left=getStringByDire(bean,currentPlayer,0,1);
right=getStringByDire(bean,currentPlayer,0,-1);
}else if(dire==Direction.PIE){
left=getStringByDire(bean,currentPlayer,1,1);
right=getStringByDire(bean,currentPlayer,-1,-1);
}else if(dire==Direction.NA){
left=getStringByDire(bean,currentPlayer,-1,1);
right=getStringByDire(bean,currentPlayer,1,-1);
}

//正方向
String str=left+currentPlayer+right;

//chessBeans[bean.getX()][bean.getY()].getBuffer().append("(" + (bean.getX() + 1) + "," + (bean.getY()- 1) + ")" + dire + "\t" + str + "\t");
//反向
String strres=new StringBuffer(str).reverse().toString();

for(Level level:Level.values()){
//根据正则表达式进行比较
Pattern pattern=Pattern.compile(level.regex[currentPlayer-1]);

Matcher matcher=pattern.matcher(str);
//为true为1
boolean b1=matcher.find();

Matcher matcher2=pattern.matcher(strres);
//为true为1
boolean b2=matcher2.find();

if(b1||b2){
//匹配成功
return level;
}
}

return Level.NULL;
}

private String getStringByDire(ChessBean bean, int currentPlayer2, int x,int y) {
boolean res=false;
if(y>0||(y==0&&x<0))
{
//反向拼接
res=true;
}
int x_tmp=bean.getX();
int y_tmp=bean.getY();
String str="";
for(int i=0;i<5;i++){
x_tmp+=x;
y_tmp+=y;
if(x_tmp>=0&&x_tmp<GoBangContantes.LINE_NUMBER&&y_tmp>=0&&y_tmp<GoBangContantes.LINE_NUMBER)
{
if(res){
//反向拼接
str=chessBeans[x_tmp][y_tmp].getPlayer()+str;
}else{
str+=chessBeans[x_tmp][y_tmp].getPlayer();
}
}
}

return str;
}

private int checkChessCount(ChessBean chessBean, int x, int y) {
//判断某个方向4个棋子
int sum=0;
int x_tmp=chessBean.getX();
int y_tmp=chessBean.getY();
for(int i=0;i<4;i++)
{
x_tmp+=x;
y_tmp+=y;
if(x_tmp>=0&&x_tmp<GoBangContantes.LINE_NUMBER&&y_tmp>=0&&y_tmp<GoBangContantes.LINE_NUMBER)
{
if(chessBeans[x_tmp][y_tmp].getPlayer()==chessBean.getPlayer())
{
sum++;
}
else
{
break;
}
}
}

return sum;
}

public void huiqi() {
if(isGameOver){
JOptionPane.showMessageDialog(GoBangPanel.this, "请先开始游戏");
return;
}else{
if(count>0){
ChessBean chessBean=getLastBean();
chessBeans[chessBean.getX()][chessBean.getY()].setOrderNumber(0);
chessBeans[chessBean.getX()][chessBean.getY()].setPlayer(GoBangContantes.EMPTY);
count--;
repaint();

}
else{
JOptionPane.showMessageDialog(GoBangPanel.this, "请先下棋");
}
}

}

public void huiqi2() {
if (isGameOver) {
JOptionPane.showMessageDialog(GoBangPanel.this, "请先开始新游戏!");
} else {
if (count > 2) {
for (int i = 0; i < 2; i++) {
ChessBean tempBean =getLastBean();
currentPlayer = tempBean.getPlayer();
chessBeans[tempBean.getX()][tempBean.getY()].setPlayer(GoBangContantes.EMPTY); //
chessBeans[tempBean.getX()][tempBean.getY()].setOrderNumber(0);
count--;
repaint();
}
} else {
JOptionPane.showMessageDialog(GoBangPanel.this, "你还没下棋呢!");
}
}
}

// 棋型信息
public static enum Level {
CON_5("长连", 0, new String[] { "11111", "22222" }, 100000),
ALIVE_4("活四", 1, new String[] { "011110", "022220" }, 10000),
GO_4("冲四", 2, new String[] { "011112|0101110|0110110", "022221|0202220|0220220" }, 500),
DEAD_4("死四", 3, new String[] { "211112", "122221" }, -5),
ALIVE_3("活三", 4, new String[] { "01110|010110", "02220|020220" }, 200),
SLEEP_3("眠三", 5,
new String[] { "001112|010112|011012|10011|10101|2011102", "002221|020221|022021|20022|20202|1022201" },
50),
DEAD_3("死三", 6, new String[] { "21112", "12221" }, -5),
ALIVE_2("活二", 7, new String[] { "00110|01010|010010", "00220|02020|020020" }, 5),
SLEEP_2("眠二", 8,
new String[] { "000112|001012|010012|10001|2010102|2011002",
"000221|002021|020021|20002|1020201|1022001" },
3),
DEAD_2("死二", 9, new String[] { "2112", "1221" }, -5), NULL("null", 10, new String[] { "", "" }, 0);
private String name;
private int index;
private String[] regex;// 正则表达式
int score;// 分值

// 构造方法
private Level(String name, int index, String[] regex, int score) {
this.name = name;
this.index = index;
this.regex = regex;
this.score = score;
}

// 覆盖方法
@Override
public String toString() {
return this.name;
}
};

// 方向
private static enum Direction {
HENG, SHU, PIE, NA
};

// 位置分
private static int[][] position = {
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0 },
{ 0, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 0 },
{ 0, 1, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 1, 0 },
{ 0, 1, 2, 3, 4, 4, 4, 4, 4, 4, 4, 3, 2, 1, 0 },
{ 0, 1, 2, 3, 4, 5, 5, 5, 5, 5, 4, 3, 2, 1, 0 },
{ 0, 1, 2, 3, 4, 5, 6, 6, 6, 5, 4, 3, 2, 1, 0 },
{ 0, 1, 2, 3, 4, 5, 6, 7, 6, 5, 4, 3, 2, 1, 0 },
{ 0, 1, 2, 3, 4, 5, 6, 6, 6, 5, 4, 3, 2, 1, 0 },
{ 0, 1, 2, 3, 4, 5, 5, 5, 5, 5, 4, 3, 2, 1, 0 },
{ 0, 1, 2, 3, 4, 4, 4, 4, 4, 4, 4, 3, 2, 1, 0 },
{ 0, 1, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 1, 0 },
{ 0, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 0 },
{ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } };

}

未经原作者允许,请勿转载!

======================================================================== MICROSOFT FOUNDATION CLASS LIBRARY : fir ======================================================================== AppWizard has created this fir application for you. This application not only demonstrates the basics of using the Microsoft Foundation classes but is also a starting point for writing your application. This file contains a summary of what you will find in each of the files that make up your fir application. fir.dsp This file (the project file) contains information at the project level and is used to build a single project or subproject. Other users can share the project (.dsp) file, but they should export the makefiles locally. fir.h This is the main header file for the application. It includes other project specific headers (including Resource.h) and declares the CFirApp application class. fir.cpp This is the main application source file that contains the application class CFirApp. fir.rc This is a listing of all of the Microsoft Windows resources that the program uses. It includes the icons, bitmaps, and cursors that are stored in the RES subdirectory. This file can be directly edited in Microsoft Visual C++. fir.clw This file contains information used by ClassWizard to edit existing classes or add new classes. ClassWizard also uses this file to store information needed to create and edit message maps and dialog data maps and to create prototype member functions. res\fir.ico This is an icon file, which is used as the application's icon. This icon is included by the main resource file fir.rc. res\fir.rc2 This file contains resources that are not edited by Microsoft Visual C++. You should place all resources not editable by the resource editor in this file. ///////////////////////////////////////////////////////////////////////////// For the main frame window: MainFrm.h, MainFrm.cpp These files contain the frame class CMainFrame, which is derived from CFrameWnd and controls all SDI frame features. ///////////////////////////////////////////////////////////////////////////// AppWizard creates one document type and one view: firDoc.h, firDoc.cpp - the document These files contain your CFirDoc class. Edit these files to add your special document data and to implement file saving and loading (via CFirDoc::Serialize). firView.h, firView.cpp - the view of the document These files contain your CFirView class. CFirView objects are used to view CFirDoc objects. ///////////////////////////////////////////////////////////////////////////// Other standard files: StdAfx.h, StdAfx.cpp These files are used to build a precompiled header (PCH) file named fir.pch and a precompiled types file named StdAfx.obj. Resource.h This is the standard header file, which defines new resource IDs. Microsoft Visual C++ reads and updates this file. ///////////////////////////////////////////////////////////////////////////// Other notes: AppWizard uses "TODO:" to indicate parts of the source code you should add to or customize. If your application uses MFC in a shared DLL, and your application is in a language other than the operating system's current language, you will need to copy the corresponding localized resources MFC42XXX.DLL from the Microsoft Visual C++ CD-ROM onto the system or system32 directory, and rename it to be MFCLOC.DLL. ("XXX" stands for the language abbreviation. For example, MFC42DEU.DLL contains resources translated to German.) If you don't do this, some of the UI elements of your application will remain in the language of the operating system. /////////////////////////////////////////////////////////////////////////////
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值