最近做了一个简单的五子棋游戏,其中涉及到界面的布局设计,以及简单的AI算法的应用。
我创建了五个类。
1、public class ChessJpanel extends JPanel implements Config:这个类中我主要写棋盘的设计以及画面的还原代码。
2、public interface Config:写了一个接口,其中主要是关于棋盘内棋子,网格等尺寸的参数的保存,一般也在此处修改参数。
3、public class DrawFrame:此类中我写界面的初始化设计,主函数也在其中。
4、public class DrawMouse implements ActionListener, MouseMotionListener, MouseListener, Config:这个类中我写了监听器的程序,以及AI算法,这个类也是包含内容最多的类了。用到了动作监听器、鼠标监听器,继承了参数接口。
5、public class Shape implements Config:此类用于设置棋子的位置颜色等参数,继承接口。
我们先贴出界面的部分代码:
public class DrawFrame {
public static void main(String[] args) {
DrawFrame frame = new DrawFrame();
frame.showUI();
}
public void showUI() {
// 创建窗体对象
javax.swing.JFrame jf = new javax.swing.JFrame();
ImageIcon img = new ImageIcon("E:\\workspace\\mayifan\\src\\com\\myf\\gobang1030\\timg.jpg");
JLabel jla = new JLabel(img);
jf.getLayeredPane().add(jla, new Integer(Integer.MIN_VALUE));
jla.setBounds(0, 0, 950, 640);
JPanel jp1 = (JPanel) jf.getContentPane();
jp1.setOpaque(false);
jf.setSize(950, 640);
//jf.getContentPane().setBackground(Color.ORANGE);//设置背景色
jf.setTitle("五子棋");
jf.setDefaultCloseOperation(3);
// 设置居中显示
jf.setLocationRelativeTo(null);
DrawMouse mouse = new DrawMouse();
JButton jb1=new JButton("新游戏");
jf.add(jb1);
jb1.setBounds(675, 185, 100,40);
jb1.addActionListener(mouse);
JButton jb2=new JButton("悔棋");
jf.add(jb2);
jb2.setBounds(675, 265, 100,40);
jb2.addActionListener(mouse);
JButton jb3=new JButton("人机对战");
jf.add(jb3);
jb3.setBounds(675, 345, 100,40);
jb3.addActionListener(mouse);
//棋盘对象
ChessJpanel cp = new ChessJpanel();
//cp.setBackground(Color.ORANGE);
jf.add(cp,BorderLayout.CENTER);
cp.setOpaque(false);
cp.addMouseListener(mouse);
cp.addMouseMotionListener(mouse);
jf.setVisible(true);
Graphics g=cp.getGraphics();
mouse.setgr(g);
mouse.setcp(cp);
cp.setMouse(mouse);
}
}
我们在界面的底层容器加上了背景图片,添加了几个功能按键,设置了其坐标和大小。创建了一些对象,调用了相应传递对象的方法。并添加了若干监听器。
public class ChessJpanel extends JPanel implements Config{
private Shape[] arrayshape;
private DrawMouse mouse;
public void setarray(Shape[] arrayshape)
{
this.arrayshape=arrayshape;
}
public void setMouse(DrawMouse mouse)
{
this.mouse=mouse;
}
public void paint (Graphics g)
{
super.paint(g);
ImageIcon img = new ImageIcon("E:\\workspace\\mayifan\\src\\com\\myf\\gobang1030\\muban.jpg");//加载图片,绘制图片
g.drawImage(img.getImage(), 175, 80, 455, 455, null);
for(int i=0;i<LINE;i++)
{
g.drawLine(X0, Y0+i*SIZE, X0+(LINE-1)*SIZE, Y0+i*SIZE);
g.drawLine(X0+i*SIZE, Y0, X0+i*SIZE, Y0+(LINE-1)*SIZE);
}
g.drawLine(X0-10,Y0-10,X0+(LINE-1)*SIZE+10,Y0-10);
g.drawLine(X0-10,Y0+(LINE-1)*SIZE+10,X0+(LINE-1)*SIZE+10,Y0+(LINE-1)*SIZE+10);
g.drawLine(X0-10,Y0-10,X0-10,Y0+(LINE-1)*SIZE+10);
g.drawLine(X0+(LINE-1)*SIZE+10,Y0-10,X0+(LINE-1)*SIZE+10,Y0+(LINE-1)*SIZE+10);
for(int i=0;i<mouse.index;i++)
{
Shape shape=arrayshape[i];
if(shape!=null)
{
shape.drawShape(g);
}
else
break;
}
}
public void paint1 (Graphics g)
{
super.paint(g);
ImageIcon img1 = new ImageIcon("E:\\workspace\\mayifan\\src\\com\\myf\\gobang1030\\muban.jpg");//加载图片,绘制图片
g.drawImage(img1.getImage(), 175, 80, 455, 455, null);
for(int i=0;i<LINE;i++)
{
g.drawLine(X0, Y0+i*SIZE, X0+(LINE-1)*SIZE, Y0+i*SIZE);
g.drawLine(X0+i*SIZE, Y0, X0+i*SIZE, Y0+(LINE-1)*SIZE);
}
g.drawLine(X0-10,Y0-10,X0+(LINE-1)*SIZE+10,Y0-10);
g.drawLine(X0-10,Y0+(LINE-1)*SIZE+10,X0+(LINE-1)*SIZE+10,Y0+(LINE-1)*SIZE+10);
g.drawLine(X0-10,Y0-10,X0-10,Y0+(LINE-1)*SIZE+10);
g.drawLine(X0+(LINE-1)*SIZE+10,Y0-10,X0+(LINE-1)*SIZE+10,Y0+(LINE-1)*SIZE+10);
}
}
这里我们写了两个paint函数,一个用于恢复初始的棋盘,一个用于恢复棋盘以及棋子(防止最小化或者窗口伸缩导致画面丢失),我们通过循环绘制棋盘线条和棋子。其中基本上所有参数都来自于接口中定义的属性。
值得一提的是我们用到了HashMap:
HashMap <String,Integer> map =new HashMap<>();
它的作用是用来做数据的存储和调用,创建权值表:
例如:HashMap<String,Integer> hm = new HashMap<String,Integer>();
<>:泛型(泛指类:类,接口,数组)
hm.put(“1”,20);
hm.put(“11”,200);
hm.put(“111”,2000);
使用方法很简单,通过 hm.get(111);即可获取参数2000。
HashMap在代码中的作用就是为各种棋局赋相应的权值,然后通过遍历的方法,做棋局分析,实现AI。我们本次的思路就是遍历棋盘所有的空格,分别对其“上,下,左,右,左上,左下,右上,右下”八个方向进行棋局判断,即从相邻子开始读取并判断存值,把八个方向的权值相加,便可以得到本空位的权值,遍历棋盘可得到权值最大的空位。自然,权值分为进攻和防守两种,假如执黑子,进攻的权值需要看连续黑子的数量,防守权值需要看连续白子的数量,最终要把进攻最大权值和防守最大权值进行比较,选择进攻或者防守,这样一来电脑就可以代替人去“思考”了。仔细一想,确实,电脑的行棋思路也很符合我们的思维习惯,通过观察棋局来判断是该进攻还是防守,以及该在何处落子。
接下来我们先贴监听器类的完整代码,再分别介绍各个方法的作用。
public class DrawMouse implements ActionListener, MouseMotionListener, MouseListener, Config {
private int x, y, z;
public int x1,y1;
private int point_x, point_y;
private Graphics g;
private int turn = 0;//0:黑子 1:白子
private ChessJpanel cp;
private int flagStart = 0;
private Shape[] arrayshape = new Shape[250];
public int index = 0;
public int winFlag=0;
public int flagAi=0;
public int xMax=0;
public int yMax=0;
public int xMax1=0;
public int yMax1=0;
public int weightMax=0;
public int weightMax1=0;
public int random;
int add=1;
private Color color = Color.BLACK;
int value[][] = new int[LINE][LINE];// 0:未存 1:黑 2:白 3:边界
HashMap <String,Integer> map =new HashMap<>();
public void setgr(Graphics g) {
this.g = g;
}
public void setcp(ChessJpanel cp) {
this.cp = cp;
this.cp.setarray(arrayshape);
}
public void actionPerformed(ActionEvent e)
{
if ("新游戏".equals(e.getActionCommand()))
{
System.out.println("新游戏");
turn=0;
cp.paint1(g);
flagStart = 1;
winFlag=0;
index=0;
for(int i=0;i<LINE;i++)
{
for(int j=0;j<LINE;j++)
{
value[i][j]=0;
}
}
if(flagAi==1)
{
random=1+(int)(Math.random()*2);
if(random==1)//AI 先手,执黑子
{
map.put("11110", AI_A_1);//活四连 初始化权重
map.put("11112", AI_A_2);//死四连
map.put("11113", AI_A_3);
map.put("1110",AI_A_4);//活三连
map.put("1112",AI_A_5);//死三连
map.put("1113",AI_A_6);
map.put("110",AI_A_7);//活二连
map.put("112",AI_A_8);//死二连
map.put("113",AI_A_9);
map.put("10",AI_A_10);//活一连
map.put("12",AI_A_11);//死一连
map.put("13",AI_A_12);
map.put("2", AI_A_13);
map.put("0", AI_1);
map.put("3", AI_2);
map.put("22220", AI_G_1);
map.put("22221", AI_G_2);
map.put("22223", AI_G_3);
map.put("2220", AI_G_4);
map.put("2221", AI_G_5);
map.put("2223", AI_G_6);
map.put("220", AI_G_7);
map.put("221", AI_G_8);
map.put("223", AI_G_9);
map.put("20", AI_G_10);
map.put("21", AI_G_11);
map.put("23", AI_G_12);
map.put("1", AI_G_13);
value[LINE/2][LINE/2]=1;
g.setColor(Color.BLACK);
g.drawOval(X0+(LINE/2)*SIZE-CHESS_SIZE/2, Y0+(LINE/2)*SIZE-