yh0721

五子棋人机对战实践项目

总的任务和目标

完成一个人机对战的五子棋项目,基本效果如下:

 

 

第一部分 Java绘图原理

1.   基本概念

像素,坐标

2.   组件自定义绘图原理

参见:https://www.cnblogs.com/leier/archive/2012/03/31/2426520.html

3.   Graphics对象的常用方法

setFont(), setColor(), drawLine(), drawString(), drawOval(), drawRect(), fillOval(), fillRect(), drawImage()

 

第二部分 绘制棋盘

1.   基本思路

在一个JPanel上绘制一个背景,然后绘制水平和垂直的若干条线,使其构成等距离的格子,通常是15*15(条线)。

2.   代码实现

(1)主类代码:

(2) ChessPanel代码:

 

 

第三部分 绘制棋子

1.   基本思路

使用drawOval()可以绘制空心的圆,使用fillOval()可以填充实心的圆。

2.   坐标计算

由于格子是水平和垂直的有下标的,而绘制时需要使用实际的像素坐标,所以,需要进行行列下标到像素坐标的转换:

int x = col * GRID_WIDTH;

int y = row * GRID_WIDTH;

3.   代码实现

第四部分 鼠标下棋

1.   基本思路

需要处理鼠标单点事件,获取鼠标所在的位置,然后计算出应该绘制棋子的行列下标,并使用一个二维数组来全局存储棋子的位置。

2.   鼠标位置与行列下标计算

        int x = e.getX();

        int y = e.getY();

        int row = y / GRID_WIDTH;

     int col = x / GRID_WIDTH;

3.   代码实现

(1)      ChessPanel属性和构造方法代码:

 

 

 

(2)监听器类(内部类)代码:

 

 

(3)绘图代码:

 

第五部分 判断胜负

1.   基本思路

判断胜负是因为在当前位置(row, col)落子导致了胜负,所以,判断胜负其实是在当前落子位置为中心,横向搜索左边第4个位置开始到右边第4个位置(其余位置不需要考虑),或者从上到下,或者正向45度,或者反向45度位置。

2.   处理方法

处理方法有很多,可以采用计数的方式,也可以采用字符串连接的方式,此处采用了将从左边第4颗开始,到右边第4颗结束,将每颗的颜色表示成字符1(黑色)或者2(白色),只需要判断其中是否有连续的5个1或5个2,即“11111”或“22222”即可知道胜负。

3.   代码实现

(1)     监听器类(内部类)代码:

 

 

(2)checkWin判断胜负的代码:

/** 判断胜负

        * @param row      落子的行下标

        * @param col       落子的列下标

        * @return 是否获胜,true-是,false-否

        */

public boolean checkWin(int row, int col) {}

 

 

 

(3)重置游戏状态

 

第六部分 人机对战

1.   基本思路

当人点了鼠标落子以后,轮到电脑下棋,电脑的基本思想就是,在棋盘的空白处的每个位置,进行判断,当前位置的进攻指数和防守指数分别为多少,在进攻指数和防守指数中取一个较大值作为当前位置的评估值,在整个棋盘的所有空白处找到一个最大值,最大值的那个位置即为应该落子的位置。

2.   某个位置的进攻指数和防守指数的评估方法

可以参见:https://www.cnblogs.com/songdechiu/p/5768999.html

本例中简化此问题,依据第五部分中胜负判断的方式,对连续9个位置形成的字符串进行搜索、评分,得出一个评估方案。如“11111”代表连续5个黑色,评分100,“011110”代表连续4个黑色,两端为空位置,评分90……。

 

3.   代码实现

(1)监听器类(内部类)代码:

(2)     电脑下棋的代码:

(3)     评估关键参数代码:

(4) 评估方法代码:

 

 

以上为本次人机对战的实践项目要求及部分实现代码,下面展示自己的完整代码(有所改变):

 

【主类DrawFrame】

 

 1 package qipan;
 2 
 3 import java.awt.BorderLayout;
 4 import java.awt.Container;
 5 import java.awt.*;
 6 import javax.swing.JFrame;
 7 
 8 public class DrawFrame extends JFrame{
 9     //属性
10     Mypanel panel=null;
11 
12     //方法
13     public DrawFrame(){
14         //设置主面板
15         setTitle("五子棋");
16         setSize(620,640);
17         setDefaultCloseOperation(DISPOSE_ON_CLOSE);
18         setLocationRelativeTo(null);
19         setResizable(false);
20         
21         //设置绘图面板
22         Container cp =getContentPane();
23         panel=new Mypanel();
24         panel.setBackground(Color.ORANGE);
25         cp.add(panel,BorderLayout.CENTER);
26     }
27 
28     public static void main(String[] args) {
29         JFrame frame=new DrawFrame();
30         frame.setVisible(true);
31         
32     }
33 
34 }

 

【Mypanel】

  1 package qipan;
  2 
  3 import java.awt.Color;
  4 import java.awt.Graphics;
  5 import java.awt.Image;
  6 import java.awt.Point;
  7 import java.awt.event.MouseAdapter;
  8 import java.awt.event.MouseEvent;
  9 import java.awt.event.MouseListener;
 10 import java.io.File;
 11 import java.io.IOException;
 12 
 13 import javax.imageio.ImageIO;
 14 import javax.swing.JOptionPane;
 15 import javax.swing.JPanel;
 16 
 17 public class Mypanel extends JPanel {
 18     // 属性
 19     /** 棋盘宽度 */
 20     public static final int GRID_WIDTH = 40;
 21     /** 棋盘行数 */
 22     public static final int LINE_COUNT = 15;
 23     /** 黑子与白子 */
 24     public static final int ITEM_BLACK = 1;
 25     public static final int ITEM_WHITE = 2;
 26     
 27     /**黑棋和白棋的图片*/
 28     Image imgblack = null;
 29     Image imgwhite = null;
 30     
 31     /** 设置分数机制 */
 32     String[] blackKey = { 
 33             "11111", "011110", "11110", "01111", "10111",
 34             "11011", "11101", "01110", "11100", "00111", 
 35             "0111", "1011","1101", "1110", "111", 
 36             "01100", "00110", "011", "110", "11" };
 37 
 38     String[] whiteKey = { 
 39             "22222", "022220", "22220", "02222", "20222",
 40             "22022", "22202", "02220", "22200", "00222", 
 41             "0222", "2022","2202", "2220", "222",
 42             "02200", "00220", "022", "220", "22" };
 43 
 44     int[] Keyvalue = { 
 45             100, 90, 80, 80, 80,
 46             80, 80, 70, 60, 60,
 47             50, 50, 50, 50, 40,
 48             30, 30, 20, 20, 10 };
 49     /** 设置计算机为黑色(true),人为白色(false)*/
 50     private boolean isblack = true;
 51     /**绘制棋盘数组*/
 52     int[][] chessItems = new int[LINE_COUNT][LINE_COUNT];
 53     /**创建监听器对象*/
 54     private static MouseListener listener = null;
 55     
 56     private Point lastItemPoint = new Point();
 57     // 方法
 58     public Mypanel() {
 59         
 60         //加载图片
 61         try {
 62             imgblack = ImageIO.read(new File("imgs/black.png " ));
 63             imgwhite = ImageIO.read(new File("imgs/white.png " ));
 64         } catch (IOException e) {
 65             e.printStackTrace();
 66         }
 67         //添加监听器
 68         listener = new MyListener();
 69         this.addMouseListener(listener);
 70     }
 71 
 72     // 添加鼠标监听器
 73     public class MyListener extends MouseAdapter {
 74         @Override
 75         public void mouseClicked(MouseEvent e) {
 76             int x = e.getX();
 77             int y = e.getY();
 78             int row = y / GRID_WIDTH;
 79             int col = x / GRID_WIDTH;
 80             // 绘制落点棋子,并置换一次棋子颜色
 81             if (chessItems[row][col] == 0) {
 82                 if (isblack) {
 83                     chessItems[row][col] = ITEM_WHITE;
 84                     isblack = !isblack;
 85                 } else {
 86                     isblack = !isblack;
 87                     chessItems[row][col] = ITEM_BLACK;
 88                 }
 89                 Mypanel.this.repaint();
 90             }
 91             // 判断胜负
 92             boolean Result = checkWin(row, col);
 93 
 94             // 重置棋盘
 95             if (Result) {
 96                 JOptionPane.showMessageDialog(null, "you won");
 97                 reset();
 98             }
 99 
100             // 电脑下棋
101             computerPlay();
102         }
103         /** 电脑 */
104         private void computerPlay() {
105             int temRow = -1, temCol = -1, maxValue = 0;
106             int current = isblack ? 2 : 1;
107             for (int row = 0; row < LINE_COUNT; row++) {
108                 for (int col = 0; col < LINE_COUNT; col++) {
109                     if (chessItems[row][col] > 0)
110                         continue;
111                     int attack = checkMax(row, col, current);// 计算进攻值
112                     /**3-current是为了置换颜色,判断防守哪个棋,减5是为了在进攻值和防守值相等时优先选择进攻*/
113                     int defend = checkMax(row, col, 3-current) - 5;// 计算防守值
114                     int max = Math.max(attack, defend);
115                     if (max > maxValue) {
116                         temRow = row;
117                         temCol = col;
118                         maxValue = max;
119                     }
120                 }
121             }
122             //在计算出来的地方落子
123             if(temCol<0||temRow<0)
124             {
125                 /**用sum来计算棋盘当前有没有下棋,如果没有下棋,则电脑不执行任何操作,直到我下棋为止*/
126                 int sum=0;
127                 for(int i=0;i<LINE_COUNT;i++){
128                     for(int j=0;j<LINE_COUNT;j++){
129                         sum=sum+chessItems[i][j];
130                     }
131                 }
132                 if(sum==0){}
133                 else{
134                     JOptionPane.showMessageDialog(null, "平局");
135                     reset();
136                 }
137                 
138             }else {
139                 chessItems[temRow][temCol] = current;
140                 lastItemPoint.x=temRow;
141                 lastItemPoint.y=temCol;        
142                 isblack=!isblack;//换一次手
143                 repaint();//重画
144                 boolean result = checkWin(temRow, temCol);
145                 //判断胜负
146                 if (result) {
147                     JOptionPane.showMessageDialog(null, "you lost");
148                     reset();
149                 }
150             }
151         }
152         
153         /**
154          * 评估落子位置的分值
155          * @param row
156          * @param col
157          * @param current
158          * @return
159          */
160         private int checkMax(int row, int col, int current) {
161             int max=0,temMax=0;
162             String [] strings = blackKey;
163             if(current==2){
164                 strings = whiteKey;
165             }
166             
167             //水平方向
168             StringBuilder builder = new StringBuilder();
169             for(int i=-4;i<=4;i++)
170             {
171                 int newCol=col+i;
172                 if(newCol<0||newCol>=LINE_COUNT) continue;
173                 if(i==0){
174                     builder.append(current);
175                 }else {
176                     builder.append(chessItems[row][newCol]);
177                 }
178             }
179             
180             for(int i=0;i<strings.length;i++)
181             { 
182                 String key = strings[i];
183                 if(builder.indexOf(key)>=0){
184                     max=Keyvalue[i];
185                     break;
186                 }
187             }
188             if(max==100)
189                 return max;
190             
191             //竖直方向
192             builder.delete(0, builder.length());
193             for(int i=-4;i<=4;i++)
194             {
195                 int newRow=row+i;
196                 if(newRow<0||newRow>=LINE_COUNT) continue;
197                 if(i==0){
198                     builder.append(current);
199                 }else {
200                     builder.append(chessItems[newRow][col]);
201                 }
202             }
203             
204             for(int i=0;i<strings.length;i++)
205             {
206                 String key = strings[i];
207                 if(builder.indexOf(key)>=0){
208                     temMax=Keyvalue[i];
209                     break;
210                 }
211             }
212             if(temMax>max)
213                 max=temMax;
214             if(max==100)
215                 return max;
216             
217             //正45度方向
218             builder.delete(0, builder.length());
219             for(int i=-4;i<=4;i++)
220             {
221                 int newRow=row-i;
222                 int newCol=col+i;
223                 if(newCol<0||newCol>=LINE_COUNT||newRow<0||newRow>=LINE_COUNT) continue;
224                 if(i==0){
225                     builder.append(current);
226                 }else {
227                     builder.append(chessItems[newRow][newCol]);
228                 }
229             }
230             
231             for(int i=0;i<strings.length;i++)
232             {
233                 String key = strings[i];
234                 if(builder.indexOf(key)>=0){
235                     temMax=Keyvalue[i];
236                     break;
237                 }
238             }
239             if(temMax>max)
240                 max=temMax;
241             if(max==100)
242                 return max;
243             
244             //负45度方向
245             builder.delete(0, builder.length());
246             for(int i=-4;i<=4;i++)
247             {
248                 int newRow=row+i;
249                 int newCol=col+i;
250                 if(newCol<0||newCol>=LINE_COUNT||newRow<0||newRow>=LINE_COUNT) continue;
251                 if(i==0){
252                     builder.append(current);
253                 }else {
254                     builder.append(chessItems[newRow][newCol]);
255                 }
256             }
257             
258             for(int i=0;i<strings.length;i++)
259             {
260                 String key = strings[i];
261                 if(builder.indexOf(key)>=0){
262                     temMax=Keyvalue[i];
263                     break;
264                 }
265             }
266             if(temMax>max)
267                 max=temMax;
268             if(max==100)
269                 return max;
270             return max;
271         }
272         // 将数组的值置为0,也就将棋盘重画为空
273         private void reset() {
274             for (int row = 0; row < LINE_COUNT; row++) {
275                 for (int col = 0; col < LINE_COUNT; col++) {
276                     chessItems[row][col] = 0;
277                 }
278             }
279             isblack = true;
280             repaint();
281         }
282 
283         private boolean checkWin(int row, int col) {
284             StringBuilder builder = new StringBuilder();
285             // 判断水平方向
286             for (int i = -4; i <= 4; i++) {
287                 int newcol = col + i;
288                 if (newcol >= 0 && newcol < LINE_COUNT) {
289                     builder.append(chessItems[row][newcol]);
290                 }
291             }
292             if (builder.indexOf("11111") >= 0 || builder.indexOf("22222") >= 0) {
293                 return true;
294             }
295             // 判断竖直方向
296             builder.delete(0, builder.length());
297             for (int i = -4; i <= 4; i++) {
298                 int newrow = row + i;
299                 if (newrow >= 0 && newrow < LINE_COUNT) {
300                     builder.append(chessItems[newrow][col]);
301                 }
302             }
303             if (builder.indexOf("11111") >= 0 || builder.indexOf("22222") >= 0) {
304                 return true;
305             }
306             // 判断正45度方向
307             builder.delete(0, builder.length());
308             for (int i = 4; i >= -4; i--) {
309                 int newrow = row + i;
310                 int newcol = col - i;
311                 if (newrow >= 0 && newrow < LINE_COUNT && newcol >= 0
312                         && newcol < LINE_COUNT) {
313                     builder.append(chessItems[newrow][newcol]);
314                 }
315             }
316             if (builder.indexOf("11111") >= 0 || builder.indexOf("22222") >= 0) {
317                 return true;
318             }
319             // 判断负45度方向
320             builder.delete(0, builder.length());
321             for (int i = -4; i <= 4; i++) {
322                 int newrow = row + i;
323                 int newcol = col + i;
324                 if (newrow >= 0 && newrow < LINE_COUNT && newcol >= 0
325                         && newcol < LINE_COUNT) {
326                     builder.append(chessItems[newrow][newcol]);
327                 }
328             }
329             if (builder.indexOf("11111") >= 0 || builder.indexOf("22222") >= 0) {
330                 return true;
331             }
332             return false;
333         }
334     }
335 
336     //绘制棋盘
337     @Override
338     protected void paintComponent(Graphics g) {
339         super.paintComponent(g);
340         g.setColor(Color.black);
341 
342         // 绘制水平线
343         for (int i = 0; i < LINE_COUNT; i++) {
344             int x1 = GRID_WIDTH / 2;
345             int y1 = GRID_WIDTH / 2 + i * GRID_WIDTH;
346             int x2 = 14 * GRID_WIDTH + GRID_WIDTH / 2;
347             int y2 = y1;
348             g.drawLine(x1, y1, x2, y2);
349         }
350         // 绘制垂直线
351         for (int i = 0; i < LINE_COUNT; i++) {
352             int x1 = GRID_WIDTH / 2 + i * GRID_WIDTH;
353             int y1 = GRID_WIDTH / 2;
354             int x2 = x1;
355             int y2 = GRID_WIDTH / 2 + 14 * GRID_WIDTH;
356             g.drawLine(x1, y1, x2, y2);
357         }
358          //ChessItem(0, 2, Color.BLUE, g);
359         // 绘制一个棋子
360         for (int row = 0; row < LINE_COUNT; row++) {
361             for (int col = 0; col < LINE_COUNT; col++) {
362                 switch (chessItems[row][col]) {
363                 case ITEM_BLACK:
364                     ChessItem(col, row, Color.BLACK, g);
365                     drawlimit(lastItemPoint,Color.RED,g);//去跟踪电脑所下的棋
366                     break;
367                 case ITEM_WHITE:
368                     ChessItem(col, row, Color.WHITE, g);
369                     break;
370                 }
371 
372             }
373         }
374     }
375     /**
376      * 追踪画框
377      * @param lasPoint    上一个棋子,也就是电脑所下的棋
378      * @param color        框体颜色
379      * @param g            画笔
380      */
381     private void drawlimit(Point lasPoint, Color color, Graphics g) {
382         int x = lasPoint.y * GRID_WIDTH;
383         int y = lasPoint.x* GRID_WIDTH;
384         g.setColor(color);
385         g.drawRect(x, y, GRID_WIDTH, GRID_WIDTH);
386     }
387     /**
388      * 构建绘制棋子的方法
389      * @param col 棋子所在列坐标
390      * @param row 棋子所在行坐标
391      * @param color   棋子颜色
392      * @param g
393      */
394     public void ChessItem(int col, int row, Color color, Graphics g) {
395         int x = col * GRID_WIDTH;
396         int y = row * GRID_WIDTH;
397 //        g.setColor(color);
398 //        g.fillOval(x, y, GRID_WIDTH, GRID_WIDTH);
399         Image image = Color.BLACK.equals(color) ? imgblack:imgwhite;
400         g.drawImage(image, x, y, GRID_WIDTH, GRID_WIDTH, this);
401     }
402 }

 

分类:

技术点:

相关文章: