一、需求分析
1、画一个15x15的棋盘版面
2、功能按钮:开始游戏,悔棋,认输
3、单选按钮:人人对战、人机对战
4、要求:在棋盘上下棋子,棋子必须要在交叉点上;同一个位置上不能有再下棋子;棋子不能消失;判断输赢。
二、设计思路
1、图形界面(棋盘版面)
通过JFrame与JPanel 窗体实现,将JFrame分为2个部分,一部分用于绘制棋盘,另一部分用于放置功能按钮等。 并且添加监听。
(1)创建窗体
public void showUI(){
JFrame frame=new JFrame(); //创建窗体 frameframe.setTitle("五子棋 "); //设置窗体的标题frame.setSize(750,650);//设置大小frame.setResizable(false);//大小不可变frame.setLocationRelativeTo(null);//窗体居中frame.setDefaultCloseOperation(3);//退出时关闭进程cl=new ChessListener(this);// 实例化事件处理类的对象,将棋盘面板作为参数传递过去centerPanel(frame); //在窗体frame上添加中间面板 ---棋盘eastPanel(frame);//窗体frame上添加东边面板 ---功能按钮
frame.setVisible(true);//设置窗体可见cl.setExist(exist);//将棋子数组传入到事件监听类中
}
(2)创建两个面板---中间面板(绘制棋盘)、东边面板(放置功能按钮)
/*** 往窗体上添加中间面板的方法* @param frame 窗体*/
public void centerPanel(JFrame frame){
this.setBackground(Color.ORANGE);
frame.add(this);
}
/*** 往窗体上添加东边面板的方法 ---用于放置功能按钮* @param frame窗体*/
public void eastPanel(JFrame frame){
JPanel epanel=new JPanel();//创建一个面板对象epanel.setBackground(Color.GRAY);//背景颜色设置为grayepanel.setPreferredSize(new Dimension(150,600)); //设置大小epanel.setLayout(new FlowLayout());//默认也是流式布局String[] buttonArray={"开始游戏","悔棋","认输"}; //数组存储 功能按钮命令for(int i=0;i
String[] radioArray={"人人对战","人机对战"};//数组存储 单选按钮命令ButtonGroup bg=new ButtonGroup();//实例化一个按钮组的对象for(int i=0;i
}
epanel.add(radioButton);//在面板上添加单选按钮radioButton.addActionListener(cl);//加监听器}
frame.add(epanel,BorderLayout.EAST);//为窗体(边框布局)添加面板---放置在东侧}
(3)新建一个接口,用于保存棋盘的各种参数(常量)
public interface Config {
public static final int X0=20;
//棋盘起点坐标x0public static final int Y0=30; //棋盘起点坐标y0public static final int ROWS=15; //行数public static final int COLUMNS =15; //列数public static final int CHESS_SIZE=40; //棋子的大小public static final int SIZE=40; //棋盘行与行 列与列之间的距离}
(4)绘制棋盘
/*** 绘制棋盘的方法* @param g 传入画笔*/
public void drawChessTable(Graphics g){
for(int r=0;r
}
for(int c=0;c
}
}
(5)棋子重绘
我们下棋的时候并不希望因为最大化或最小化窗体而导致棋子消失,因为组件具有重绘的方法,不需要我们重绘,但是对于棋子来说,我们必须将棋子重新画出。
这里我们考虑用一个exist[][] 二维数组存储棋子的颜色与位置,值为0时表示没有棋子,值为1时表示黑色棋子,值为-1 时表示该位置有白色的棋子。
/*** 棋子重绘的方法* @param g*/
public void reDrawChess(Graphics g){
Graphics2D g2d=(Graphics2D) g;//转为Graphics2D 后面要为画笔设置颜色g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
for(int r=0;r
g2d.fillOval(Config.X0+c*Config.SIZE-Config.CHESS_SIZE/2,Config.Y0+r*Config.SIZE-Config.CHESS_SIZE/2 , Config.CHESS_SIZE, Config.CHESS_SIZE);
}else if(exist[r][c]==-1){ //该位置是白子g2d.setColor(Color.WHITE);
g2d.fillOval(Config.X0+c*Config.SIZE-Config.CHESS_SIZE/2,Config.Y0+r*Config.SIZE-Config.CHESS_SIZE/2 , Config.CHESS_SIZE, Config.CHESS_SIZE);
}
}
}
}
}(5)重写面板的paint() 方法,完成重绘
因为我们的主类(fiveChess) 继承了JPanel类,在中间面板的方法里,直接向窗体添加的面板就是主类面板。即:
/*** 往窗体上添加中间面板的方法* @param frame 窗体*/
public void centerPanel(JFrame frame){
this.setBackground(Color.ORANGE);
frame.add(this);
}
所以在主类里直接从写JPanel的paint()方法即可:
public void paint(Graphics g){
super.paint(g);
drawChessTable(g);
reDrawChess(g);
}
注:主类的完整代码
package com.xs.chessAI;
import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
/*** 五子棋* @author Administrator**/
public class FiveChess extends JPanel{ //继承JPanel类 使FiveChess作为一个面板private int[][] exist=new int[Config.ROWS][Config.COLUMNS]; //创建一个棋子数组 用于保存棋盘上哪个位置有哪个颜色的棋子private ChessListener cl; //声明事件处理类型的 变量/*** 图像界面的显示方法*/
public void showUI(){
JFrame frame=new JFrame(); //创建窗体 frameframe.setTitle("五子棋 "); //设置窗体的标题frame.setSize(750,650);//设置大小frame.setResizable(false);//大小不可变frame.setLocationRelativeTo(null);//窗体居中frame.setDefaultCloseOperation(3);//退出时关闭进程cl=new ChessListener(this);// 实例化事件处理类的对象,将棋盘面板作为参数传递过去centerPanel(frame); //在窗体frame上添加中间面板 ---棋盘eastPanel(frame);//窗体frame上添加东边面板 ---功能按钮
frame.setVisible(true);//设置窗体可见cl.setExist(exist);//将棋子数组传入到事件监听类中
}
/*** 重写面板重绘的方法*/
public void paint(Graphics g){
super.paint(g);
drawChessTable(g);
reDrawChess(g);
}
/*** 往窗体上添加中间面板* @param frame 窗体*/
public void centerPanel(JFrame frame){
this.setBackground(Color.ORANGE);
frame.add(this);
}
/*** 往窗体上添加东边面板 ---用于放置功能按钮* @param frame窗体*/
public void eastPanel(JFrame frame){
JPanel epanel=new JPanel();//创建一个面板对象epanel.setBackground(Color.GRAY);//背景颜色设置为grayepanel.setPreferredSize(new Dimension(150,600)); //设置大小epanel.setLayout(new FlowLayout());//默认也是流式布局String[] buttonArray={"开始游戏","悔棋","认输"}; //数组存储 功能按钮命令for(int i=0;i
String[] radioArray={"人人对战","人机对战"};//数组存储 单选按钮命令ButtonGroup bg=new ButtonGroup();//实例化一个按钮组的对象for(int i=0;i
}
epanel.add(radioButton);//在面板上添加单选按钮radioButton.addActionListener(cl);//加监听器}
frame.add(epanel,BorderLayout.EAST);//为窗体(边框布局)添加面板---放置在东侧}
/*** 绘制棋盘的方法* @param g 传入画笔*/
public void drawChessTable(Graphics g){
for(int r=0;r
}
for(int c=0;c
}
}
/*** 棋子重绘的方法* @param g*/
public void reDrawChess(Graphics g){
Graphics2D g2d=(Graphics2D) g;//转为Graphics2D 后面要为画笔设置颜色g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
for(int r=0;r
g2d.fillOval(Config.X0+c*Config.SIZE-Config.CHESS_SIZE/2,Config.Y0+r*Config.SIZE-Config.CHESS_SIZE/2 , Config.CHESS_SIZE, Config.CHESS_SIZE);
}else if(exist[r][c]==-1){ //该位置是白子g2d.setColor(Color.WHITE);
g2d.fillOval(Config.X0+c*Config.SIZE-Config.CHESS_SIZE/2,Config.Y0+r*Config.SIZE-Config.CHESS_SIZE/2 , Config.CHESS_SIZE, Config.CHESS_SIZE);
}
}
}
}
}
public static void main(String[] args) {
FiveChess chess=new FiveChess();
chess.showUI();
}
}
二、监听类
功能:根据按钮命令---采取相应的操作
下棋(将棋子放到棋盘上) 人人对战 人机对战(AI算法) 开始游戏(清空棋盘)
悔棋 认输
(1) 下棋功能
鼠标在棋盘上点击时,棋子落在相应的位置
public void mouseClicked(MouseEvent e){//鼠标点击事件的处理方法x=e.getX();//获取点击位置的x坐标y=e.getY();//获取点击位置的y坐标if(mode.equals("人人对战")){ //调用人人对战方法pvp(x,y);
}else if(mode.equals("人机对战")){//调用人机对战的方法pvai(x,y);
}
}
这里mode 是根据按钮命令获取的对战模式,默认为人人对战,mode 的获取命令在在actionPerformed 方法中,后面会提到。
(2)人人对战的方法
/*** 人人对战的方法* @param x* @param y*/
public void pvp(int x,int y){
flag++;//步数+1g=fiveChess.getGraphics();//从棋盘面板类中获取画布g2d=(Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
for(int r=0;r
exist[r][c]=1;//记录下了黑色棋子r与c的位置g2d.fillOval(Config.X0+c*Config.SIZE-Config.CHESS_SIZE/2,Config.Y0+r*Config.SIZE-Config.CHESS_SIZE/2 , Config.CHESS_SIZE, Config.CHESS_SIZE);
count=-1;//下一次点击时 下白色的棋子list.add(new Chess(r,c));//将棋子对象存到数组队列中,保存了棋子的属性 r,cif(win.checkWin()==1){//判断黑色棋子是否赢了JOptionPane.showMessageDialog(fiveChess, "黑色棋子获得胜利");
fiveChess.removeMouseListener(this);//获胜之后,不允许再在棋盘上下棋子,所以移除鼠标监听return;
}
}else if(count==-1){//放置白子g2d.setColor(Color.WHITE);
exist[r][c]=-1;
g2d.fillOval(Config.X0+c*Config.SIZE-Config.CHESS_SIZE/2,Config.Y0+r*Config.SIZE-Config.CHESS_SIZE/2 , Config.CHESS_SIZE, Config.CHESS_SIZE);
count=1;//下一步要下黑色棋子list.add(new Chess(r,c));//将棋子对象存到数组队列中,保存了棋子的属性 r,cif(win.checkWin()==-1){//判断白色棋子是否赢了JOptionPane.showMessageDialog(fiveChess, "白色棋子获得胜利");
fiveChess.removeMouseListener(this);
return;
}
}
}
}
}
}
}
根据获取的坐标,先判断是否在棋盘交点1/3处,根据count 值判断下一步下白棋还是黑棋(count = 1 黑棋 count = -1 白棋),并把位置与颜色传入二维数组中。
(3)人机对战的方法
/*** 人机对战的方法* @param x 人所下棋子的横坐标* @param y 人所下棋子的纵坐标*/
public void pvai(int x,int y){
flag++;//步数+1g=fiveChess.getGraphics();
g2d=(Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
for(int r=0;r
for(int c=0;c
if(exist[r][c]==0){//判断该位置上是否有棋子if((Math.abs(x-Config.X0-c*Config.SIZE)
exist[r][c]=1;//记录下了黑色棋子的位置g2d.fillOval(Config.X0+c*Config.SIZE-Config.CHESS_SIZE/2,Config.Y0+r*Config.SIZE-Config.CHESS_SIZE/2 , Config.CHESS_SIZE, Config.CHESS_SIZE);
list.add(new Chess(r,c));//队列中添加棋子数组对象 存 r cif(win.checkWin()==1){//判断黑色棋子是否胜利JOptionPane.showMessageDialog(fiveChess, "黑色棋子获得胜利");
fiveChess.removeMouseListener(this);
return;
}
ai(g2d);//调用ai下棋的方法if(win.checkWin()==-1){//ai下的是白色棋子,每下完一步,都要判断一次是否获胜JOptionPane.showMessageDialog(fiveChess, "白色棋子获得胜利");
fiveChess.removeMouseListener(this);
return;
}
}
}
}
}
人下棋时,算法与人人对战时一样,ai下棋,调用ai下棋的方法。每次人下棋或者ai下棋后,都要进行判断输赢。
ai下棋的方法:
/*** ai 下棋的方法* @param g2d*/
public void ai(Graphics2D g2d){
g2d.setColor(Color.WHITE);//设置ai所下棋子的颜色为白色int[][] weightArray=new int[Config.ROWS][Config.COLUMNS];//创建一个存储权值的二维数组/*** 设置每种棋子相连情况下的权值*/
map.put("010", 1);
map.put("0110", 20);
map.put("01110", 50);
map.put("011110", 500);
map.put("0-10", 10);
map.put("0-1-10", 30);
map.put("0-1-1-10", 70);
map.put("0-1-1-1-10", 500);
map.put("-110", 1);
map.put("-1110", 10);
map.put("-11110", 30);
map.put("-111110", 500);
map.put("1-10", 1);
map.put("1-1-10", 10);
map.put("1-1-1-10", 30);
map.put("1-1-1-1-10", 500);
for(int r=0;r
if(exist[r][c]==0){//判断是否是空位String code=countHL(r,c);
Integer weight = map.get(code);//获取棋子相连情况对应的权值if(null != weight){//判断权值不为nullweightArray[r][c] += weight;//累加权值}
code=countVU(r,c);
weight = map.get(code);//获取棋子相连情况对应的权值if(null != weight){//判断权值不为nullweightArray[r][c] += weight;//累加权值}
code=countLLU(r,c);
weight = map.get(code);//获取棋子相连情况对应的权值if(null != weight){//判断权值不为nullweightArray[r][c] += weight;//累加权值}
code=countLRU(r,c);
weight = map.get(code);//获取棋子相连情况对应的权值if(null != weight){//判断权值不为nullweightArray[r][c] += weight;//累加权值}
}
}
}
int max=weightArray[0][0]; //找出最大的权值for(int r=0;r
for(int c=0;c
if(weightArray[r][c]>max){
max=weightArray[r][c];
}
}
}
for(int r=0;r
if(weightArray[r][c]==max&&exist[r][c]==0){//权值最大且是空位exist[r][c]=-1;
g2d.fillOval(Config.X0+c*Config.SIZE-Config.CHESS_SIZE/2,Config.Y0+r*Config.SIZE-Config.CHESS_SIZE/2 , Config.CHESS_SIZE, Config.CHESS_SIZE);
list.add(new Chess(r,c));
return;
}
}
}
}
AI算法有很多种,这里采用了权值算法,即根据棋子相连的情况,统计每一个空位的权值大小,选出权值最大点 在这个位置下棋。存储棋子相连情况与权值使用HashMap 。
统计权值方法:4种(水平向左统计、垂直向上统计、向上正斜统计( \ ) 、向下反斜统计( / ))
/*** 水平向左方向统计棋子相连的情况* @param r 行* @param c 列* @return*/
private String countHL(int r,int c){
String code = "0";//默认记录r,c位置的情况int chess = 0;//记录第一次出现的棋子for(int c1=c-1;c1>=0;c1--){//向左统计if(exist[r][c1]==0){//表示没有棋子的位置if(c1+1==c){//相邻的空位break;
}else{//空位不相连code = exist[r][c1] + code;//记录棋子相连的情况break;
}
}else{//表示有棋子if(chess==0){//判断是否是第一次出现棋子code = exist[r][c1] + code;//记录棋子相连的情况chess = exist[r][c1];//记录第一次的棋子的颜色}else if(chess==exist[r][c1]){//表示之后的棋子和第一次的棋子颜色一致code = exist[r][c1] + code;//记录棋子相连的情况}else{//表示之后的棋子和第一次的棋子颜色不同code = exist[r][c1] + code;//记录棋子相连的情况break;
}
}
}
return code;
}
/*** 垂直向上统计棋子相连的情况* @param r行* @param c列* @return*/
private String countVU(int r,int c){
String code = "0";//默认记录r,c位置的情况int chess = 0;//记录第一次出现的棋子for(int r1=r-1;r1>=0;r1--){//向上统计if(exist[r1][c]==0){//表示没有棋子if(r1+1==r){//相邻的空位break;
}else{//不相邻的空位code = exist[r1][c] + code;//记录棋子相连的情况break;
}
}else{//表示有棋子if(chess==0){//判断是否是第一次出现棋子code = exist[r1][c] + code;//记录棋子相连的情况chess = exist[r1][c];//记录第一次的棋子的颜色}else if(chess==exist[r1][c]){//表示之后的棋子和第一次的棋子颜色一致code = exist[r1][c] + code;//记录棋子相连的情况}else{//表示之后的棋子和第一次的棋子颜色不同code = exist[r1][c] + code;//记录棋子相连的情况break;
}
}
}
return code;
}
/*** 正斜(\) 棋子相连统计* @param r* @param c* @return*/
private String countLLU(int r,int c){
String code = "0";//默认记录r,c位置的情况int chess = 0;//记录第一次出现的棋子for(int r1=r-1,c1=c-1;r1>=0&&c1>0;r1--,c1--){//向上统计if(exist[r1][c1]==0){//表示没有棋子if(r1+1==r&&c1+1==c){//相邻的空位break;
}else{//不相邻的空位code = exist[r1][c1] + code;//记录棋子相连的情况break;
}
}else{//表示有棋子if(chess==0){//判断是否是第一次出现棋子code = exist[r1][c1] + code;//记录棋子相连的情况chess = exist[r1][c1];//记录第一次的棋子的颜色}else if(chess==exist[r1][c1]){//表示之后的棋子和第一次的棋子颜色一致code = exist[r1][c1] + code;//记录棋子相连的情况}else{//表示之后的棋子和第一次的棋子颜色不同code = exist[r1][c1] + code;//记录棋子相连的情况break;
}
}
}
return code;
}
/*** 反斜(/) 棋子相连的统计* @param r* @param c* @return*/
private String countLRU(int r,int c){
String code = "0";//默认记录r,c位置的情况int chess = 0;//记录第一次出现的棋子for(int r1=r-1,c1=c+1;r1>=0&&c1
if(exist[r1][c1]==0){//表示没有棋子if(r1+1==r&&c1-1==c){//相邻的空位break;
}else{
code = exist[r1][c1] + code;//记录棋子相连的情况break;
}
}else{//表示有棋子if(chess==0){//判断是否是第一次出现棋子code = exist[r1][c1] + code;//记录棋子相连的情况chess = exist[r1][c1];//记录第一次的棋子的颜色}else if(chess==exist[r1][c1]){//表示之后的棋子和第一次的棋子颜色一致code = exist[r1][c1] + code;//记录棋子相连的情况}else{//表示之后的棋子和第一次的棋子颜色不同code = exist[r1][c1] + code;//记录棋子相连的情况break;
}
}
}
return code;
}
(4)获取命令的方法
这里根据点击的功能按钮 采取不同的功能操作
/*** 点击事件处理方法*/
public void actionPerformed(ActionEvent e){
if(e.getSource() instanceof JRadioButton){//点击单选按钮mode=e.getActionCommand();
}else if(e.getSource() instanceof JButton){//点击其他功能按钮if(e.getActionCommand().equals("开始游戏")){
MouseListener[] mls=fiveChess.getMouseListeners();
if(mls.length>0){//如果还有其他监听 ---移除fiveChess.removeMouseListener(this);
}
reset();//调用棋盘回到初始状态的方法fiveChess.addMouseListener(this);// 为棋盘添加鼠标监听}else if(e.getActionCommand().equals("悔棋")){ //悔棋的算法if(mode.equals("人人对战")){
if(list.size()>1){//存储棋子的数组队列长度大于1Chess chess=list.get(list.size()-1); //获取最后一个被存入数组队列的棋子对象int r=chess.getR(); //分别获取棋子的x与y坐标int c=chess.getC();
exist[r][c]=0;//设置最后一步所下棋子的位置为空list.remove(list.size()-1);//移除队列中最后一个棋子对象fiveChess.repaint();//调用面板重绘的方法if(count==1){//如果悔棋的是黑色方,下一步下棋的还是黑色方count=-1;//如果悔棋的是白色方,下一步下棋的还是白色方}else{
count=1;
}
}
}else if(mode.equals("人机对战")){//人机对战,悔一次棋要退两步,1、电脑所下的棋 2、人所下的棋子if(list.size()>2){//至少下了两步Chess chess=list.get(list.size()-2);//获取倒数第二步的棋子对象int r=chess.getR();
int c=chess.getC();
exist[r][c]=0;//令其为空Chess chessAI=list.get(list.size()-1);//获取最后一步棋子对象r=chessAI.getR();
c=chessAI.getC();
exist[r][c]=0;//令其为空list.remove(list.size()-1);//删除数组队列中后两个对象list.remove(list.size()-1);
fiveChess.repaint();//调用棋子面板的重绘方法}
}
}else if(e.getActionCommand().equals("认输")){//认输的算法if(flag<10){
JOptionPane.showMessageDialog(fiveChess, "总步数小于10,不能认输");
}else{
if(mode.equals("人人对战")){
if(count==1){//本来应该下黑色棋子了 但是点击了认输,所以白色棋子获胜JOptionPane.showMessageDialog(fiveChess, "白色棋子获得胜利");
}else if(count==-1){//本来应该下白色棋子了 但是点击了认输,所以黑色棋子获胜JOptionPane.showMessageDialog(fiveChess, "黑色棋子获得胜利");
}
}else if(mode.equals("人机对战")){//电脑不会认输的JOptionPane.showMessageDialog(fiveChess, "白色棋子AI获得胜利");
}
fiveChess.removeMouseListener(this);//认输后不能在棋盘上上下棋了 所以要移除棋盘上的鼠标监听}
}
}
}
棋盘重置的方法:
/*** 设置棋盘回到初始状态的方法*/
public void reset(){
count=1;//默认黑色棋子先下棋flag=0;//下棋步数重置为0for(int r=0;r
exist[r][c]=0;
}
}
fiveChess.repaint(); //调用棋盘重绘的方法}
即让存储棋子的二维数组每一个点的值都为0;然后重绘棋子面板。
注:监听类完整代码
package com.xs.chessAI;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.ArrayList;
import java.util.HashMap;
import javax.swing.JButton;
import javax.swing.JOptionPane;
import javax.swing.JRadioButton;
/*** 事件处理类* @author Administrator**/
public class ChessListener extends MouseAdapter implements ActionListener{
private int x,y; //记录点击坐标private Graphics g; //存储画笔private int count=1; //判别人人对战时下一步下黑子还是白子private int[][] exist; //生命棋子数组private Graphics2D g2d; //画笔对象private HashMap map=new HashMap();//创建集合对象,用途是存储每一种棋子相连对应的权值private FiveChess fiveChess; //生命面板类型的变量private WhoWin win;//生命判断输赢类的对象private String mode="人人对战"; //默认的对战模式为人人对战private int flag=0;//记录下棋的步数private ArrayList list=new ArrayList(); //数组队列 存储的Chess类型的对象/*** 构造方法* @param fiveChess*/
public ChessListener(FiveChess fiveChess) {
this.fiveChess=fiveChess;
}
/*** 设置方法,接收数组* @param exist 存储棋盘上棋子的位置*/
public void setExist(int[][] exist){
this.exist=exist;
win=new WhoWin(exist);
}
/*** 点击事件处理方法*/
public void actionPerformed(ActionEvent e){
if(e.getSource() instanceof JRadioButton){//点击单选按钮mode=e.getActionCommand();
}else if(e.getSource() instanceof JButton){//点击其他功能按钮if(e.getActionCommand().equals("开始游戏")){
MouseListener[] mls=fiveChess.getMouseListeners();
if(mls.length>0){//如果还有其他监听 ---移除fiveChess.removeMouseListener(this);
}
reset();//调用棋盘回到初始状态的方法fiveChess.addMouseListener(this);// 为棋盘添加鼠标监听}else if(e.getActionCommand().equals("悔棋")){ //悔棋的算法if(mode.equals("人人对战")){
if(list.size()>1){//存储棋子的数组队列长度大于1Chess chess=list.get(list.size()-1); //获取最后一个被存入数组队列的棋子对象int r=chess.getR(); //分别获取棋子的x与y坐标int c=chess.getC();
exist[r][c]=0;//设置最后一步所下棋子的位置为空list.remove(list.size()-1);//移除队列中最后一个棋子对象fiveChess.repaint();//调用面板重绘的方法if(count==1){//如果悔棋的是黑色方,下一步下棋的还是黑色方count=-1;//如果悔棋的是白色方,下一步下棋的还是白色方}else{
count=1;
}
}
}else if(mode.equals("人机对战")){//人机对战,悔一次棋要退两步,1、电脑所下的棋 2、人所下的棋子if(list.size()>2){//至少下了两步Chess chess=list.get(list.size()-2);//获取倒数第二步的棋子对象int r=chess.getR();
int c=chess.getC();
exist[r][c]=0;//令其为空Chess chessAI=list.get(list.size()-1);//获取最后一步棋子对象r=chessAI.getR();
c=chessAI.getC();
exist[r][c]=0;//令其为空list.remove(list.size()-1);//删除数组队列中后两个对象list.remove(list.size()-1);
fiveChess.repaint();//调用棋子面板的重绘方法}
}
}else if(e.getActionCommand().equals("认输")){//认输的算法if(flag<10){
JOptionPane.showMessageDialog(fiveChess, "总步数小于10,不能认输");
}else{
if(mode.equals("人人对战")){
if(count==1){//本来应该下黑色棋子了 但是点击了认输,所以白色棋子获胜JOptionPane.showMessageDialog(fiveChess, "白色棋子获得胜利");
}else if(count==-1){//本来应该下白色棋子了 但是点击了认输,所以黑色棋子获胜JOptionPane.showMessageDialog(fiveChess, "黑色棋子获得胜利");
}
}else if(mode.equals("人机对战")){//电脑不会认输的JOptionPane.showMessageDialog(fiveChess, "白色棋子AI获得胜利");
}
fiveChess.removeMouseListener(this);//认输后不能在棋盘上上下棋了 所以要移除棋盘上的鼠标监听}
}
}
}
public void mouseClicked(MouseEvent e){//鼠标点击事件的处理方法x=e.getX();//获取点击位置的x坐标y=e.getY();//获取点击位置的y坐标if(mode.equals("人人对战")){ //调用人人对战方法pvp(x,y);
}else if(mode.equals("人机对战")){//调用人机对战的方法pvai(x,y);
}
}
/*** 设置棋盘回到初始状态的方法*/
public void reset(){
count=1;//默认黑色棋子先下棋flag=0;//下棋步数重置为0for(int r=0;r
exist[r][c]=0;
}
}
fiveChess.repaint(); //调用棋盘重绘的方法}
/*** 人人对战的方法* @param x* @param y*/
public void pvp(int x,int y){
flag++;//步数+1g=fiveChess.getGraphics();//从棋盘面板类中获取画布g2d=(Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
for(int r=0;r
exist[r][c]=1;//记录下了黑色棋子r与c的位置g2d.fillOval(Config.X0+c*Config.SIZE-Config.CHESS_SIZE/2,Config.Y0+r*Config.SIZE-Config.CHESS_SIZE/2 , Config.CHESS_SIZE, Config.CHESS_SIZE);
count=-1;//下一次点击时 下白色的棋子list.add(new Chess(r,c));//将棋子对象存到数组队列中,保存了棋子的属性 r,cif(win.checkWin()==1){//判断黑色棋子是否赢了JOptionPane.showMessageDialog(fiveChess, "黑色棋子获得胜利");
fiveChess.removeMouseListener(this);//获胜之后,不允许再在棋盘上下棋子,所以移除鼠标监听return;
}
}else if(count==-1){//放置白子g2d.setColor(Color.WHITE);
exist[r][c]=-1;
g2d.fillOval(Config.X0+c*Config.SIZE-Config.CHESS_SIZE/2,Config.Y0+r*Config.SIZE-Config.CHESS_SIZE/2 , Config.CHESS_SIZE, Config.CHESS_SIZE);
count=1;//下一步要下黑色棋子list.add(new Chess(r,c));//将棋子对象存到数组队列中,保存了棋子的属性 r,cif(win.checkWin()==-1){//判断白色棋子是否赢了JOptionPane.showMessageDialog(fiveChess, "白色棋子获得胜利");
fiveChess.removeMouseListener(this);
return;
}
}
}
}
}
}
}
/*** 人机对战的方法* @param x 人所下棋子的横坐标* @param y 人所下棋子的纵坐标*/
public void pvai(int x,int y){
flag++;//步数+1g=fiveChess.getGraphics();
g2d=(Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
for(int r=0;r
for(int c=0;c
if(exist[r][c]==0){//判断该位置上是否有棋子if((Math.abs(x-Config.X0-c*Config.SIZE)
exist[r][c]=1;//记录下了黑色棋子的位置g2d.fillOval(Config.X0+c*Config.SIZE-Config.CHESS_SIZE/2,Config.Y0+r*Config.SIZE-Config.CHESS_SIZE/2 , Config.CHESS_SIZE, Config.CHESS_SIZE);
list.add(new Chess(r,c));//队列中添加棋子数组对象 存 r cif(win.checkWin()==1){//判断黑色棋子是否胜利JOptionPane.showMessageDialog(fiveChess, "黑色棋子获得胜利");
fiveChess.removeMouseListener(this);
return;
}
ai(g2d);//调用ai下棋的方法if(win.checkWin()==-1){//ai下的是白色棋子,每下完一步,都要判断一次是否获胜JOptionPane.showMessageDialog(fiveChess, "白色棋子获得胜利");
fiveChess.removeMouseListener(this);
return;
}
}
}
}
}
}
/*** ai 下棋的方法* @param g2d*/
public void ai(Graphics2D g2d){
g2d.setColor(Color.WHITE);//设置ai所下棋子的颜色为白色int[][] weightArray=new int[Config.ROWS][Config.COLUMNS];//创建一个存储权值的二维数组/*** 设置每种棋子相连情况下的权值*/
map.put("010", 1);
map.put("0110", 20);
map.put("01110", 50);
map.put("011110", 500);
map.put("0-10", 10);
map.put("0-1-10", 30);
map.put("0-1-1-10", 70);
map.put("0-1-1-1-10", 500);
map.put("-110", 1);
map.put("-1110", 10);
map.put("-11110", 30);
map.put("-111110", 500);
map.put("1-10", 1);
map.put("1-1-10", 10);
map.put("1-1-1-10", 30);
map.put("1-1-1-1-10", 500);
for(int r=0;r
if(exist[r][c]==0){//判断是否是空位String code=countHL(r,c);
Integer weight = map.get(code);//获取棋子相连情况对应的权值if(null != weight){//判断权值不为nullweightArray[r][c] += weight;//累加权值}
code=countVU(r,c);
weight = map.get(code);//获取棋子相连情况对应的权值if(null != weight){//判断权值不为nullweightArray[r][c] += weight;//累加权值}
code=countLLU(r,c);
weight = map.get(code);//获取棋子相连情况对应的权值if(null != weight){//判断权值不为nullweightArray[r][c] += weight;//累加权值}
code=countLRU(r,c);
weight = map.get(code);//获取棋子相连情况对应的权值if(null != weight){//判断权值不为nullweightArray[r][c] += weight;//累加权值}
}
}
}
int max=weightArray[0][0]; //找出最大的权值for(int r=0;r
for(int c=0;c
if(weightArray[r][c]>max){
max=weightArray[r][c];
}
}
}
for(int r=0;r
if(weightArray[r][c]==max&&exist[r][c]==0){//权值最大且是空位exist[r][c]=-1;
g2d.fillOval(Config.X0+c*Config.SIZE-Config.CHESS_SIZE/2,Config.Y0+r*Config.SIZE-Config.CHESS_SIZE/2 , Config.CHESS_SIZE, Config.CHESS_SIZE);
list.add(new Chess(r,c));
return;
}
}
}
}
/*** 水平向左方向统计棋子相连的情况* @param r 行* @param c 列* @return*/
private String countHL(int r,int c){
String code = "0";//默认记录r,c位置的情况int chess = 0;//记录第一次出现的棋子for(int c1=c-1;c1>=0;c1--){//向左统计if(exist[r][c1]==0){//表示没有棋子的位置if(c1+1==c){//相邻的空位break;
}else{//空位不相连code = exist[r][c1] + code;//记录棋子相连的情况break;
}
}else{//表示有棋子if(chess==0){//判断是否是第一次出现棋子code = exist[r][c1] + code;//记录棋子相连的情况chess = exist[r][c1];//记录第一次的棋子的颜色}else if(chess==exist[r][c1]){//表示之后的棋子和第一次的棋子颜色一致code = exist[r][c1] + code;//记录棋子相连的情况}else{//表示之后的棋子和第一次的棋子颜色不同code = exist[r][c1] + code;//记录棋子相连的情况break;
}
}
}
return code;
}
/*** 垂直向上统计棋子相连的情况* @param r行* @param c列* @return*/
private String countVU(int r,int c){
String code = "0";//默认记录r,c位置的情况int chess = 0;//记录第一次出现的棋子for(int r1=r-1;r1>=0;r1--){//向上统计if(exist[r1][c]==0){//表示没有棋子if(r1+1==r){//相邻的空位break;
}else{//不相邻的空位code = exist[r1][c] + code;//记录棋子相连的情况break;
}
}else{//表示有棋子if(chess==0){//判断是否是第一次出现棋子code = exist[r1][c] + code;//记录棋子相连的情况chess = exist[r1][c];//记录第一次的棋子的颜色}else if(chess==exist[r1][c]){//表示之后的棋子和第一次的棋子颜色一致code = exist[r1][c] + code;//记录棋子相连的情况}else{//表示之后的棋子和第一次的棋子颜色不同code = exist[r1][c] + code;//记录棋子相连的情况break;
}
}
}
return code;
}
/*** 正斜(\) 棋子相连统计* @param r* @param c* @return*/
private String countLLU(int r,int c){
String code = "0";//默认记录r,c位置的情况int chess = 0;//记录第一次出现的棋子for(int r1=r-1,c1=c-1;r1>=0&&c1>0;r1--,c1--){//向上统计if(exist[r1][c1]==0){//表示没有棋子if(r1+1==r&&c1+1==c){//相邻的空位break;
}else{//不相邻的空位code = exist[r1][c1] + code;//记录棋子相连的情况break;
}
}else{//表示有棋子if(chess==0){//判断是否是第一次出现棋子code = exist[r1][c1] + code;//记录棋子相连的情况chess = exist[r1][c1];//记录第一次的棋子的颜色}else if(chess==exist[r1][c1]){//表示之后的棋子和第一次的棋子颜色一致code = exist[r1][c1] + code;//记录棋子相连的情况}else{//表示之后的棋子和第一次的棋子颜色不同code = exist[r1][c1] + code;//记录棋子相连的情况break;
}
}
}
return code;
}
/*** 反斜(/) 棋子相连的统计* @param r* @param c* @return*/
private String countLRU(int r,int c){
String code = "0";//默认记录r,c位置的情况int chess = 0;//记录第一次出现的棋子for(int r1=r-1,c1=c+1;r1>=0&&c1
if(exist[r1][c1]==0){//表示没有棋子if(r1+1==r&&c1-1==c){//相邻的空位break;
}else{
code = exist[r1][c1] + code;//记录棋子相连的情况break;
}
}else{//表示有棋子if(chess==0){//判断是否是第一次出现棋子code = exist[r1][c1] + code;//记录棋子相连的情况chess = exist[r1][c1];//记录第一次的棋子的颜色}else if(chess==exist[r1][c1]){//表示之后的棋子和第一次的棋子颜色一致code = exist[r1][c1] + code;//记录棋子相连的情况}else{//表示之后的棋子和第一次的棋子颜色不同code = exist[r1][c1] + code;//记录棋子相连的情况break;
}
}
}
return code;
}
}
三、判断输赢类:
我们注意到,人人对战与人机对战时,每下一步棋都要判断一次输赢,所以这里我把判断输赢的方法单独写到一个类里。
输赢判断规则: 同一颜色棋子5子相连 (共4个方向 水平 垂直 正斜(\) 反斜(/))
package com.xs.chessAI;
/**
* 判断输赢类
* @author Administrator
*
*/
public class WhoWin {
private int[][]exist;
public WhoWin(int exist[][]){
this.exist=exist;
}
/**
* 判断输赢的方法
*/
public int checkWin(){
if((rowWin()==1)||(columnWin()==1)||(rightSideWin()==1)||(leftSideWin()==1)){
return 1;
}else if((rowWin()==-1)||(columnWin()==-1)||(rightSideWin()==-1)||(leftSideWin()==-1)){
return -1;
}
return 0;
}
/**
* 以行的方式赢
*/
public int rowWin(){
for(int i=0;i
for(int j=0;j
if(exist[i][j]==-1){
if(exist[i][j+1]==-1&&exist[i][j+2]==-1&&exist[i][j+3]==-1&&exist[i][j+4]==-1){
return -1;
}
}
if(exist[i][j]==1){
if(exist[i][j+1]==1&&exist[i][j+2]==1&&exist[i][j+3]==1&&exist[i][j+4]==1){
return 1;
}
}
}
}
return 0;
}
/**
* 以列的方式赢
*/
public int columnWin(){
for(int i=0;i
for(int j=0;j
if(exist[i][j]==-1){
if(exist[i+1][j]==-1&&exist[i+2][j]==-1&&exist[i+3][j]==-1&&exist[i+4][j]==-1){
return -1;
}
}
if(exist[i][j]==1){
if(exist[i+1][j]==1&&exist[i+2][j]==1&&exist[i+3][j]==1&&exist[i+4][j]==1){
return 1;
}
}
}
}
return 0;
}
/**
* 斜的方式赢
*/
public int rightSideWin(){ //正斜
for(int i=0;i
for(int j=0;j
if(exist[i][j]==-1){
if(exist[i+1][j+1]==-1&&exist[i+2][j+2]==-1&&exist[i+3][j+3]==-1&&exist[i+4][j+4]==-1){
return -1;
}
}
if(exist[i][j]==1){
if(exist[i+1][j+1]==1&&exist[i+2][j+2]==1&&exist[i+3][j+3]==1&&exist[i+4][j+4]==1){
return 1;
}
}
}
}
return 0;
}
public int leftSideWin(){ //反斜
for(int i=4;i
for(int j=0;j
if(exist[i][j]==-1){
if(exist[i-1][j+1]==-1&&exist[i-2][j+2]==-1&&exist[i-3][j+3]==-1&&exist[i-4][j+4]==-1){
return -1;
}
}
if(exist[i][j]==1){
if(exist[i-1][j+1]==1&&exist[i-2][j+2]==1&&exist[i-3][j+3]==1&&exist[i-4][j+4]==1){
return 1;
}
}
}
}
return 0;
}
}
四、棋子类
在悔棋的过程,我们使用队列来实现的,即每下一步棋,我们都要记录这一棋子的位置,添加到队列中,悔棋时,只需删掉这一位置的棋子即可。
故在每次下棋后,应当为队列添加一个棋子对象(记录了行和列),记录这一次下棋的位置,所以这里创建一个棋子类。
package com.xs.chessAI;
/**
* 棋子类---具有棋子行与列的属性
* @author Administrator
*
*/
public class Chess {
private int r;//行
private int c;//列
public Chess(int r,int c){
this.r=r;
this.setC(c);
}
public int getR() {
return r;
}
public void setR(int r) {
this.r = r;
}
public int getC() {
return c;
}
public void setC(int c) {
this.c = c;
}
}
这样,一个简单的五子棋小游戏制作就完成啦。