创建窗体
我们首先需要创建一个窗体来承载五子棋盘。
具体代码如下:
public void initUI(){
JFrame jf = new JFrame ();
jf.setTitle ("AI 五子棋");
jf.setSize (875, 950);
jf.setResizable (false);
jf.setDefaultCloseOperation (WindowConstants.EXIT_ON_CLOSE);
ChessPanel chessPanel = new ChessPanel ();
JPanel btnPanel = new JPanel ();
chessPanel.setBackground (Color.GRAY);
btnPanel.setBackground (Color.LIGHT_GRAY);
btnPanel.setPreferredSize (new Dimension (105, 80));
JButton[] btns = this.initBtnPanel (btnPanel);
jf.add (chessPanel, BorderLayout.CENTER);
jf.add (btnPanel, BorderLayout.SOUTH);
jf.setVisible (true);
// 实现鼠标监听器
chessPanel.addMouseListener (goListen);
// 获取画笔Graphics
goListen.setG (chessPanel.getGraphics ());
goListen.setChessPanel (chessPanel);
goListen.setBtns (btns);
}
绘制棋盘
第一步,在创建好的窗体上绘制出五子棋的棋盘。需要注意的是,因为如果只是在窗体中绘制出棋盘,那么每次窗体被拖动,刷新后,窗体中的绘制好的棋盘就会被刷新掉,所以需要重绘,确保棋盘永久存在,在Chess Panel中,我们在drawChessPanel的方法中实现了棋盘重绘。
public class ChessPanel extends JPanel implements GoData{
// 重写面板的绘制方法
// 窗体面板在显示以及刷新时会调用这个方法
@Override
public void paint(Graphics g){
drawChessPanel (g);
}
public void drawChessPanel(Graphics g){
// 缓存绘制
BufferedImage buffimg = new BufferedImage (this.getWidth (), this.getHeight (), BufferedImage.TYPE_INT_ARGB);
Graphics2D bg = (Graphics2D) buffimg.getGraphics ();
// 抗锯齿
bg.setRenderingHint (RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
bg.setColor (new Color (153, 100, 0));
bg.fillRect (0, 0, this.getWidth (), this.getHeight ());
// 绘制棋盘
bg.setColor (Color.BLACK);
for(int i = 0; i <= ROW; i++){
bg.drawLine (X, Y + i * SIZE, X + COL * SIZE, Y + i * SIZE);
bg.drawLine (X + i * SIZE, Y, X + i * SIZE, Y + ROW * SIZE);
}
// 棋子都是临时的 需要保存起来
// 刷新时再绘制所有存储的棋子
for(int i = 0; i < chessArray.length; i++){
for(int j = 0; j < chessArray[i].length; j++){
int cnum = chessArray[i][j];
if(cnum != 0){
bg.setColor (cnum == 1 ? Color.BLACK : Color.WHITE);
// 还原坐标
int x = X + (j * SIZE) - SIZE / 2;
int y = Y + (i * SIZE) - SIZE / 2;
bg.fillOval (x, y, SIZE, SIZE);
}
}
}
Graphics2D g2d = (Graphics2D) g;
// 通过传入的组件Graphics 一次性绘制整张像素图片
g2d.drawImage (buffimg, 0, 0, this);
}
}
第二步,是实现在棋盘上添加一系列按钮。
public JButton[] initBtnPanel(JPanel btnPanel){
String[] btnStrs = {"开始游戏", "悔棋", "退出", "对局记录", "回放", "人人对战", "人机对战"};
JButton[] btns = new JButton[btnStrs.length];
for(int i = 0; i < btnStrs.length; i++){
JButton btn = new JButton (btnStrs[i]);
btn.setBackground (Color.WHITE);
btnPanel.add (btn);
btn.setPreferredSize (new Dimension (95, 35));
btn.addActionListener (goListen);
btns[i] = btn;
}
return btns;
}
绘制棋子
在绘制棋子时需要注意,首先,鼠标点击的位置,这个位置不一定正好是在棋盘的网格交汇处的节点上,我们就要将这个鼠标点击的坐标优化至附近的交汇节点上。原理也很简单,将鼠标点击获取的坐标,计算转化成其在哪两个焦点之间,然后将两个节点之间一分为二,如果坐标在前部分,则优化到前面的节点上,如果在后半部分,则优化到后面的节点上。
public class Chess implements GoData{
int r, c, chessFlag, index;
public Chess(int r, int c, int chessFlag, int index){
this.r = r;
this.c = c;
this.chessFlag = chessFlag;
this.index = index;
}
// 绘制棋子
public void drawChess(Graphics g){
Graphics2D g2d=(Graphics2D)g;
// 抗锯齿
g2d.setRenderingHint (RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.setColor (chessFlag == 1 ? Color.BLACK : Color.WHITE);
// 还原坐标
int x = X + (c * SIZE) - SIZE / 2;
int y = Y + (r * SIZE) - SIZE / 2;
g.fillOval (x, y, SIZE, SIZE);
}
}
监听器类
在GoListener类中,我们实现了对ActionListener, MouseListener中的一些方法的重写。
首先,在这里我们重写了MouseListener中的mousePressed方法,用于监听鼠标在棋盘上的落子位置,并根据当前的游戏模式,来调用playerChess或AIChess的方法。
其次,我们定义了一个chessFlag变量,将黑棋、白棋分别赋值1和2,且为0时代表游戏未开始。每进行一次下棋的操作chessFlag就会变化一次。并利用一个chessArray[ ][ ]的数组记录棋盘上黑白子的位置。
悔棋方法: 我们通过获取到当前棋子,并将其对应位置的数组数值重新定义为0,并将记录的棋子数减一,最后重绘棋盘,来实现悔棋的功能。
public class GoListener implements ActionListener, MouseListener, GoData{
private Graphics g;
public void setG(Graphics g){
this.g = g;
}
private ChessPanel chessPanel;
public void setChessPanel(ChessPanel chessPanel){
this.chessPanel = chessPanel;
}
private JButton[] btns;
public void setBtns(JButton[] btns){
this.btns = btns;
}
private int chessFlag = 0;//0 表示不能下棋 1 表示黑棋 2 表示白棋
private int index = 0;//记录棋子的个数
private String gameModel = "人人对战";//游戏模式
@Override
public void actionPerformed(ActionEvent e){
// 监听按钮的操作
JButton btn = (JButton) e.getSource ();
String action = btn.getText ();
if(action.equals ("开始游戏")){
chessFlag = 1;
btn.setText ("结束游戏");
for(int i = 0; i < btns.length; i++){
if(btns[i].getText ().equals ("人人对战") || btns[i].getText ().equals ("人机对战")){
btns[i].setEnabled (false);
}
if(btns[i].getText ().equals (gameModel)){
btns[i].setBackground (Color.RED);
}
}
} else if(action.equals ("结束游戏")){
chessFlag = 0;
// 清空棋盘 清除数据
btn.setText ("开始游戏");
for(int i = 0; i < btns.length; i++){
if(btns[i].getText ().equals ("人人对战") || btns[i].getText ().equals ("人机对战")){
btns[i].setEnabled (true);
}
if(btns[i].getText ().equals (gameModel)){
btns[i].setBackground (Color.WHITE);
}
}
} else if(action.equals ("悔棋")){
rbChess ();
} else if(action.equals ("人人对战")){
chessFlag =1;
gameModel = "人人对战";
btn.setBackground (Color.RED);
for(int i = 0; i < btns.length; i++){
if(btns[i].getText ().equals ("人机对战")){
btns[i].setBackground (Color.WHITE);
}
}
} else if(action.equals ("人机对战")){
chessFlag =1;
gameModel = "人机对战";
btn.setBackground (Color.RED);
for(int i = 0; i < btns.length; i++){
if(btns[i].getText ().equals ("人人对战")){
btns[i].setBackground (Color.WHITE);
}
}
}
}
@Override
public void mousePressed(MouseEvent e){
// 下棋
// 获取坐标
int x = e.getX ();
int y = e.getY ();
// 通过坐标 获取当前点击的行列值
int r = (y - Y + SIZE / 2) / SIZE;
int c = (x - X + SIZE / 2) / SIZE;
System.out.println ("当前落子位置是:" + r + " " + c);
// 检查下棋的条件
checkChessFlag (r, c);
if(gameModel.equals ("人人对战")){
// 玩家人机对战
playerChess (r, c);
} else if(gameModel.equals ("人机对战")){
// 电脑下棋
AIChess (r, c);
}
}
@Override
public void mouseClicked(MouseEvent e){
}
@Override
public void mouseReleased(MouseEvent e){
}
@Override
public void mouseEntered(MouseEvent e){
}
@Override
public void mouseExited(MouseEvent e){
}
public void rbChess(){
// 先判断游戏是否在进行中
if(chessFlag == 0){
JOptionPane.showMessageDialog (null, "游戏未开始~", "提醒", JOptionPane.WARNING_MESSAGE);
return;
}
if(index == 0){
JOptionPane.showMessageDialog (null, "没有棋子可悔~", "提醒", JOptionPane.WARNING_MESSAGE);
return;
}
// 悔棋
// 查找棋子
Chess chess = chessList[index - 1];
chessArray[chess.r][chess.c] = 0;// 矩阵中对应位置 数据清除
chessFlag = chess.chessFlag;// 棋子颜色还原
index--;// 棋子个数减少
// 重绘棋盘
chessPanel.drawChessPanel (g);
}
public boolean checkChessFlag(int r, int c){
if(chessFlag == 0){
JOptionPane.showMessageDialog (null, "请先点击开始游戏~", "提醒", JOptionPane.WARNING_MESSAGE);
return false;
}
if(r < 0 || r > COL || c < 0 || c > ROW){
JOptionPane.showMessageDialog (null, "此处不是下棋的范围~", "提醒", JOptionPane.WARNING_MESSAGE);
return false;
}
if(chessArray[r][c] != 0){
JOptionPane.showMessageDialog (null, "此处已经有棋子了~", "提醒", JOptionPane.WARNING_MESSAGE);
return false;
}
return true;
}
public void playerChess(int r, int c){
// 下棋
// 存储棋子
chessArray[r][c] = chessFlag;
Chess chess = new Chess (r, c, chessFlag, index);
chessList[index++] = chess;
chess.drawChess (g);
// 判断输赢
if(ChessJudgeWin.isWin (r, c)){
JOptionPane.showMessageDialog (null, chessFlag == 1 ? "黑棋胜利~" : (chessFlag == 2 ? "白棋胜利~":"请先点击开始游戏~"), "恭喜",
JOptionPane.INFORMATION_MESSAGE);
chessFlag = 0;
return;
}
// 交换手
if(chessFlag == 1){
chessFlag = 2;
} else if(chessFlag == 2){
chessFlag = 1;
}
}
private void AIChess(int r, int c){
// 下棋
// 存储棋子
chessFlag = 1;
chessArray[r][c] = chessFlag;
Chess chess = new Chess (r, c, chessFlag, index);
chessList[index++] = chess;
chess.drawChess (g);
// 判断输赢
if(ChessJudgeWin.isWin (r, c)){
JOptionPane.showMessageDialog (null, "玩家胜利~~", "恭喜",
JOptionPane.INFORMATION_MESSAGE);
chessFlag = 0;
return;
}
// AI下棋
chessFlag=2;
// 获取AI下棋的位置
// int[] aiPoint = AI.getAIChess ();
// int aiR = aiPoint[0];
// int aiC = aiPoint[1];
AI ai = new AI(chessArray);
ai.ai();
//ai的xy
int aiR = (ai.gety()- Y + SIZE / 2) / SIZE;
int aiC = (ai.getx()- X + SIZE / 2) / SIZE;
// checkChessFlag(aiR,aiC);
// 存储棋子
chessArray[aiR][aiC] = chessFlag;
chess = new Chess (aiR, aiC, chessFlag, index);
chessList[index++] = chess;
chess.drawChess (g);
// 判断输赢
if(ChessJudgeWin.isWin (aiR, aiC)){
JOptionPane.showMessageDialog (null, "AI胜利~~", "恭喜",
JOptionPane.INFORMATION_MESSAGE);
chessFlag = 0;
return;
}
}
}
判断输赢
当棋子都下好了之后,我们就需要在每下一颗棋子后进行输赢的判定。五子棋中,有五颗棋子连在一起即为获胜。因此我们就需要对横竖斜四个大方向上的棋子,进行查找判定。
由于在数组中,黑棋的值都为1,白棋都为2,那么就在每一次所下棋子的地方为起点,向各个方向查找,每有一个相连的棋子是同色,设置一个变量count,count就加1,当count变为5时候,就证明有五个同色棋子相连,游戏就结束,如果所下棋子是黑棋,黑棋就获胜,如果所下棋子是白棋,白棋就获胜。代码如下:
public class ChessJudgeWin implements GoData{
public static boolean isWin(int r, int c){
if(row (r, c) >= 5 | col (r, c) >= 5 || leftUp_RightDown (r, c) >= 5 || rightUp_leftDown (r, c) >= 5){
System.out.println ("win");
return true;
}
return false;
}
//纵向判断
private static int col(int r, int c){
int count = 0;
int chess = chessArray[r][c];
// 向下
for(int i = r; i < chessArray.length; i++){
if(chess == chessArray[i][c]){
count++;
} else{
break;
}
}
// 向上
for(int i = r - 1; i >= 0; i--){
if(chess == chessArray[i][c]){
count++;
} else{
break;
}
}
return count;
}
// 横向判断
private static int row(int r, int c){
int count = 0;
int chess = chessArray[r][c];
// 向右
for(int i = c; i < chessArray[0].length; i++){
if(chess == chessArray[r][i]){
count++;
} else{
break;
}
}
// 向左
for(int i = c - 1; i >= 0; i--){
if(chess == chessArray[r][i]){
count++;
} else{
break;
}
}
return count;
}
private static int rightUp_leftDown(int r, int c){
int count = 0;
int chess = chessArray[r][c];
// 向右上
for(int i = r, j = c; i >= 0 && j < chessArray[0].length; i--, j++){
if(chess == chessArray[i][j]){
count++;
} else{
break;
}
}
// 向左下
for(int i = r + 1, j = c - 1; i < chessArray.length && j >= 0; i++, j--){
if(chess == chessArray[i][j]){
count++;
} else{
break;
}
}
return count;
}
private static int leftUp_RightDown(int r, int c){
int count = 0;
int chess = chessArray[r][c];
// 向左上
for(int i = r, j = c; i >= 0 && j >= 0; i--, j--){
if(chess == chessArray[i][j]){
count++;
} else{
break;
}
}
// 向右下
for(int i = r + 1, j = c + 1; i < chessArray.length && j < chessArray[0].length; i++, j++){
if(chess == chessArray[i][j]){
count++;
} else{
break;
}
}
return count;
}
}
AI方法及人机对战
这里的人机方法我们利用HashMap建立一个权值表,赋予不同棋盘情况不同的权值,然后让电脑选择在权值最大的地方下棋。
在之前数组中,我们将黑棋、白棋分别赋值1和2,可以根据此情况来设置权值表。
- 活连: 两端都是空位
- 010 10
- 0110 50
- 01110 1000
- 011110 5000
- 020
- 0220
- 02220
- 022220
- 半活: 一端是空位,一端是边界或者对方棋子
- 01 8
- 011 40
- 0111 800
- 01111 5000
- 02 8
- 022 40
- 0222 800
- 02222 5000
这之后,AI需要遍历棋盘,在每一个未下棋子的位置上,以这个位置为起点,去看每个方向上棋盘的情况,然后通过定义一个chessValue[ ][ ] 数组来存储每一个空位置的最终各个方向上棋盘情况的权值总和,那么权值最大的地方,就是AI需要下棋的地方。
在AI下棋之后,同样要调用我们之前写过的判定输赢的方法来进行判定,确保电脑和玩家每下一次棋子就可以进行一次判定。
具体的AI下棋的方法代码如下:
public class AI implements GoData{
private HashMap<String, Integer> map = new HashMap<>();
int[][] codeArray;
int[][] chessValue = new int[16][16];
private int max_i, max_j;
public AI(int[][] codeArray) {
this.codeArray = codeArray;
}
public void ai(){
// 活连
map.put ("010", 10);
map.put ("0110", 50);
map.put ("01110", 1000);
map.put ("011110", 5000);
map.put ("020", 10);
map.put ("0220", 50);
map.put ("02220", 1000);
map.put ("022220", 5000);
//半活
map.put ("01", 8);
map.put ("011", 40);
map.put ("0111", 800);
map.put ("01111", 5000);
map.put ("02", 8);
map.put ("022", 40);
map.put ("0222", 800);
map.put ("02222", 5000);
for (int i = 0; i < codeArray.length; i++) {
for (int j= 0; j < codeArray[i].length; j++) {
System.out.print(" "+codeArray[i][j]);
}
System.out.println();
}
for (int i = 0; i < codeArray.length; i++) {
for (int j = 0; j < codeArray[i].length; j++) {
if (codeArray[i][j] == 0) {
// 向右
String code = "0";
int color = 0;
for (int k = j + 1; k < codeArray.length; k++) {
if (codeArray[i][k] == 0) {
break;
}else {
// 有棋子
if (color == 0) {
color = codeArray[i][k];// 保存第一颗棋子的颜色
code += codeArray[i][k];// 保存棋子相连的情况
} else if (codeArray[i][k] == color){
code += codeArray[i][k];}
else {// 不同棋子跳出循环
code += codeArray[i][k];
break;
}
}
}
Integer value = map.get(code);
if (value != null){
chessValue[i][j] += value; // 权值累加
}
// 向左
code = "0";
color = 0;
for (int k = j - 1; k >= 0; k--) {
if(codeArray[i][k] == 0) {
break;
}else {
//有棋子
if(color == 0) {
color = codeArray[i][k];
code += codeArray[i][k];
}else if(codeArray[i][k] == color) {
code +=codeArray[i][k];
}else {
code += codeArray[i][k];
break;
}
}
}
value = map.get(code);
if (value != null) {
chessValue[i][j] += value; // 权值累加
}
//向下
code = "0";
color = 0;
for (int k = i + 1; k < codeArray.length; k++) {
if(codeArray[k][j] == 0) {
break;
}else {
//有棋子
if(color == 0) {
color = codeArray[k][j];
code += codeArray[k][j];
}else if(codeArray[k][j] == color) {
code +=codeArray[k][j];
}else {
code += codeArray[k][j];
break;
}
}
}
value = map.get(code);
if (value != null) {
chessValue[i][j] += value; // 权值累加
}
// 向上
code = "0";
color = 0;
for (int k = i - 1; k >= 0; k--) {
if(codeArray[k][j] == 0) {
break;
}else {
//有棋子
if(color == 0) {
color = codeArray[k][j];
code += codeArray[k][j];
}else if(codeArray[k][j] == color) {
code +=codeArray[k][j];
}else {
code += codeArray[k][j];
break;
}
}
}
value = map.get(code);
if (value != null) {
chessValue[i][j] += value; // 权值累加
}
// 左上
code = "0";
color = 0;
for (int m = i - 1, n = j - 1; m >= 0 && n >= 0; m--, n--) {
if(codeArray[m][n] == 0) {
break;
}else {
if(color == 0) {
color = codeArray[m][n];
code += codeArray[m][n];
}else if(codeArray[m][n] == color){
code += codeArray[m][n];
}else {
code += codeArray[m][n];
break;
}
}
}
value = map.get(code);
if (value != null) {
chessValue[i][j] += value; // 权值累加
}
// 左下
code = "0";
color = 0;
for (int m = i + 1, n = j - 1;n >= 0 && m < codeArray.length; m++, n--) {
if(codeArray[m][n] == 0) {
break;
}else {
if(color == 0) {
color = codeArray[m][n];
code += codeArray[m][n];
}else if(codeArray[m][n] == color){
code += codeArray[m][n];
}else {
code += codeArray[m][n];
break;
}
}
}
value = map.get(code);
if (value != null) {
chessValue[i][j] += value; // 权值累加
}
// 右下
code = "0";
color = 0;
for (int m = i + 1, n = j + 1;n < codeArray.length && m < codeArray.length; m++, n++) {
if(codeArray[m][n] == 0) {
break;
}else {
if(color == 0) {
color = codeArray[m][n];
code += codeArray[m][n];
}else if(codeArray[m][n] == color){
code += codeArray[m][n];
}else {
code += codeArray[m][n];
break;
}
}
}
value = map.get(code);
if (value != null) {
chessValue[i][j] += value; // 权值累加
}
// 右上
code = "0";
color = 0;
for (int m = i - 1, n = j + 1;m >= 0 && n < codeArray.length; m--, n++) {
if(codeArray[m][n] == 0) {
break;
}else {
if(color == 0) {
color = codeArray[m][n];
code += codeArray[m][n];
}else if(codeArray[m][n] == color){
code += codeArray[m][n];
}else {
code += codeArray[m][n];
break;
}
}
}
value = map.get(code);
if (value != null){
chessValue[i][j] += value; // 权值累加
}
}
}
}
//找权值最大的空处
int max_v = 0;
for (int i = 0; i < ROW +1; i++) {
for(int j = 0;j <COL+1;j++) {
if (max_v < chessValue[i][j]) {
max_v = chessValue[i][j];
max_i = i;
max_j = j;
}
}
}
for (int j = 0; j < ROW+1; j++)
for (int i = 0; i < COL+1; i++)
chessValue[i][j] = 0;
}
public int getx() {
return X+SIZE*max_j;
}
public int gety() {
return Y + max_i * SIZE;
}