一.基本功能实现
1.创建棋盘:
package gobang;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.JPanel;
/**
* Board类 控制棋盘
* 继承自JPanel
* 负责棋盘的绘制和棋局的记录
*/
public class Board extends JPanel {
private static final long serialVersionUID = 1L;
private Chess[][] state; // 记录棋局
/**
* 构造器 初始化state 设置尺寸大小
*/
public Board () {
state = new Chess[Tool.ROWNUM][Tool.COLNUM];
setSize(Tool.BOARDSIZE, Tool.BOARDSIZE);
}
/**
* 绘制棋盘背景
*/
@Override
protected void paintComponent(Graphics g) {
g.drawImage(Tool.board, 0, 0, getSize().width, getSize().height, this);
}
/**
* 绘制棋盘
*/
@Override
public void paint(Graphics g) {
super.paint(g);
// 绘制棋盘
Graphics2D g2 = (Graphics2D) g;
g2.setStroke(new BasicStroke(2.0f));
// 绘制中心和四角五个点
int size = Tool.BOARDPOINTSIZE;
g2.fillOval(Tool.BOARDSIZE / 2 - 5, Tool.BOARDSIZE / 2 - 5, 10, 10);
g2.fillOval(Tool.MARGIN + Tool.CELL * 3 - size / 2, Tool.MARGIN + Tool.CELL * 3 - size / 2, size, size);
g2.fillOval(Tool.MARGIN + Tool.CELL * 3 - size / 2, Tool.BOARDSIZE - Tool.MARGIN - Tool.CELL * 3 - size / 2, size, size);
g2.fillOval(Tool.BOARDSIZE - Tool.MARGIN - Tool.CELL * 3 - size / 2, Tool.MARGIN + Tool.CELL * 3 - size / 2, size, size);
g2.fillOval(Tool.BOARDSIZE - Tool.MARGIN - Tool.CELL * 3 - size / 2, Tool.BOARDSIZE - Tool.MARGIN - Tool.CELL * 3 - size / 2, size, size);
// 绘制横竖15条线
for (int i = 0; i < Tool.ROWNUM; i++)
g2.drawLine(Tool.MARGIN, Tool.MARGIN + i * Tool.CELL, Tool.BOARDSIZE - Tool.MARGIN, Tool.MARGIN + i * Tool.CELL);
for (int i = 0; i < Tool.COLNUM; i++)
g2.drawLine(Tool.MARGIN + i * Tool.CELL, Tool.MARGIN, Tool.MARGIN + i * Tool.CELL, Tool.BOARDSIZE - Tool.MARGIN);
// 绘制棋子
for (int i = 0; i < Tool.ROWNUM; i++) {
for (int j = 0; j < Tool.COLNUM; j++) {
if (state[i][j] != null) {
state[i][j].draw(g, i, j);
}
}
}
// 显示标记
Tool.show(g);
}
/**
* 初始化状态数组 重新绘制棋盘
*/
public void restart () {
state = new Chess[Tool.ROWNUM][Tool.COLNUM];
repaint();
}
/**
* 落子后更新状态数组 重新绘制棋盘
* @param i 棋子横坐标
* @param j 棋子纵坐标
* @param color 棋子颜色
* @return 返回是否成功
*/
public Boolean setChess (int i, int j, Color color) {
// 如果当前位置不为空 落子失败
if (state[i][j] != null)
return false;
state[i][j] = new Chess(color);
repaint();
return true;
}
/**
* 获取状态数组
*/
public Chess[][] getState() {
return state;
}
}
2.棋子设置:
package gobang;
import java.awt.Color;
import java.awt.Graphics;
/**
* Chess类 代表棋子
* 负责记录颜色和绘制自己
*/
public class Chess {
private Color color; // 棋子颜色
/**
* 构造器
* @param color 棋子颜色
*/
public Chess (Color color) {
this.color = color;
}
/**
* 获取该棋子颜色
*/
public Color getColor () {
return color;
}
/**
* 设置该棋子颜色
* @param color 颜色
*/
public void setColor(Color color) {
this.color = color;
}
/**
* 绘制自己
* @param g 画笔
* @param i 横坐标
* @param j 纵坐标
*/
public void draw(Graphics g, int i, int j) {
if (color.equals(Color.BLACK))
g.drawImage(Tool.blackChess, Tool.CELL * j - Tool.CHESSSIZE / 2 + Tool.MARGIN, Tool.CELL * i - Tool.CELL / 2 + Tool.MARGIN, Tool.CHESSSIZE, Tool.CHESSSIZE, null);
else
g.drawImage(Tool.whiteChess, Tool.CELL * j - Tool.CHESSSIZE / 2 + Tool.MARGIN, Tool.CELL * i - Tool.CELL / 2 + Tool.MARGIN, Tool.CHESSSIZE, Tool.CHESSSIZE, null);
}
}
3.游戏页面设置以及图像管理:
package gobang;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.GridBagLayout;
import java.awt.Image;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
/**
* GamePanel类 代表游戏面板
* 负责控制游戏及显示状态
*/
public class GamePanel extends JPanel {
private static final long serialVersionUID = 1L;
private JLabel title; // 游戏标题
JButton start; // 开始游戏按钮
JButton setting; // 游戏设置按钮
JButton exit; // 退出游戏按钮
JButton withdraw; // 悔棋按钮
JButton defeat; // 认输按钮
private JLabel step; // 步数标签
private JLabel image; // 图标
private JLabel statusText; // 状态标签
private JPanel controlPanel; // 控制面板 负责5个按钮
private JPanel statusPanel; // 状态面板 负责3个标签
private ImageIcon blackIcon; // 黑棋图标
private ImageIcon whiteIcon; // 白棋图标
/**
* 构造器
* 设置显示位置 背景透明 初始化组件等
*/
public GamePanel () {
setPreferredSize(Tool.PANELDIMENSION); // 显示位置
setOpaque(false); // 背景透明
// 初始化各个组件 设置内容 字体 鼠标监听器 图标等
title = new JLabel();
title.setIcon(new ImageIcon(Tool.gobang.getScaledInstance(Tool.TITLEWIDTH, Tool.TTITLEHEIGHT, Image.SCALE_AREA_AVERAGING)));
start = new JButton("开始游戏");
Tool.initComponent(start, Tool.getFont(Tool.fontkai, Font.PLAIN, 32));
Tool.addMListener(start, Tool.LARGEBUTTONWIDTH, Tool.LARGEBUTTONHEIGHT);
setting = new JButton("游戏设置");
Tool.initComponent(setting, Tool.getFont(Tool.fontkai, Font.PLAIN, 32));
Tool.addMListener(setting, Tool.LARGEBUTTONWIDTH,Tool.LARGEBUTTONHEIGHT);
exit = new JButton("退出游戏");
Tool.initComponent(exit, Tool.getFont(Tool.fontkai, Font.PLAIN, 32));
Tool.addMListener(exit, Tool.LARGEBUTTONWIDTH, Tool.LARGEBUTTONHEIGHT);
withdraw = new JButton("悔棋");
Tool.initComponent(withdraw, Tool.getFont(Tool.fontkai, Font.PLAIN, 24));
withdraw.addMouseListener(new MouseAdapter() {
@Override
public void mouseEntered(MouseEvent arg0) {
if (withdraw.isEnabled())
Tool.setEntered(withdraw, Tool.SMALLBUTTONWIDTH, Tool.SMALLBUTTONHEIGHT);
super.mouseEntered(arg0);
}
@Override
public void mouseExited(MouseEvent arg0) {
if (withdraw.isEnabled()) {
Tool.setExited(withdraw);
}
super.mouseExited(arg0);
}
});
defeat = new JButton("认输");
Tool.initComponent(defeat, Tool.getFont(Tool.fontkai, Font.PLAIN, 24));
Tool.addMListener(defeat, Tool.SMALLBUTTONWIDTH, Tool.SMALLBUTTONHEIGHT);
step = new JLabel();
step.setFont(Tool.getFont(Tool.fontwei, Font.PLAIN, 16));
image = new JLabel();
statusText = new JLabel();
statusText.setFont(Tool.getFont(Tool.fontwei, Font.PLAIN, 16));
controlPanel = new JPanel();
statusPanel = new JPanel();
blackIcon = new ImageIcon(Tool.blackChess.getScaledInstance(Tool.ICONSIZE, Tool.ICONSIZE, Image.SCALE_DEFAULT));
whiteIcon = new ImageIcon(Tool.whiteChess.getScaledInstance(Tool.ICONSIZE, Tool.ICONSIZE, Image.SCALE_DEFAULT));
// 设置边界布局
setLayout(new BorderLayout());
add(controlPanel, BorderLayout.CENTER);
add(statusPanel, BorderLayout.SOUTH);
// controlPanel设置网格袋布局
controlPanel.setLayout(new GridBagLayout());
JPanel empty = new JPanel();
empty.setOpaque(false);
controlPanel.add(empty, new GBC(0, 0).setInsetsUpandDown(15));
controlPanel.add(title, new GBC(0, 1, 1, 6).setInsetsUpandDown(30));
controlPanel.add(start, new GBC(0, 8).setInsetsUpandDown(30));
controlPanel.add(setting, new GBC(0, 9).setInsetsUpandDown(30));
controlPanel.add(exit, new GBC(0, 10).setInsetsUpandDown(30));
controlPanel.add(withdraw, new GBC(0, 11).setInsetsUpandDown(5));
controlPanel.add(defeat, new GBC(0, 12).setInsetsUpandDown(15));
controlPanel.add(empty, new GBC(0, 13).setInsetsUpandDown(5));
controlPanel.setOpaque(false);
// statusPanel布局
statusPanel.setPreferredSize(new Dimension(0, 30));
statusPanel.add(image);
statusPanel.add(statusText);
statusPanel.add(step);
statusPanel.setOpaque(false);
}
/**
* 更新状态栏UI和悔棋状态
* @param i 步数
*/
public void updateUI(int i) {
switch (i % 2) {
case 1 : statusText.setText("黑棋"); image.setIcon(blackIcon); break;
case 0 : statusText.setText("白棋"); image.setIcon(whiteIcon); break;
}
step.setText("第 " + i + " 步");
// 若一步未下 则不能悔棋
if (i == 1 || i == 2)
withdraw.setEnabled(false);
else
withdraw.setEnabled(true);
}
/**
* 游戏面板背景
*/
@Override
protected void paintComponent(Graphics g) {
g.drawImage(Tool.background, 0, 0, getSize().width, getSize().height, this);
}
}
4.网格重新布局:
package gobang;
import java.awt.GridBagConstraints;
import java.awt.Insets;
/**
* 重新设计网格袋布局
*/
public class GBC extends GridBagConstraints {
private static final long serialVersionUID = 1L;
public GBC (int gridx, int gridy) {
this.gridx = gridx;
this.gridy = gridy;
}
public GBC (int gridx, int gridy, int gridwidth, int gridheight) {
this.gridx = gridx;
this.gridy = gridy;
this.gridwidth = gridwidth;
this.gridheight = gridheight;
}
public GBC setAnchor (int anchor) {
this.anchor = anchor;
return this;
}
public GBC setFill (int fill) {
this.fill = fill;
return this;
}
public GBC setWeight (double weightx, double weighty) {
this.weightx = weightx;
this.weighty = weighty;
return this;
}
public GBC setInsets (int distance) {
this.insets = new Insets(distance, distance, distance, distance);
return this;
}
public GBC setInsetsUpandDown (int distance) {
this.insets = new Insets(distance, distance / 2, distance, distance / 2);
return this;
}
public GBC setIpad (int ipadx, int ipady) {
this.ipadx = ipadx;
this.ipady = ipady;
return this;
}
}
5.控制各个类:
package gobang;
import java.awt.BorderLayout;
import javax.swing.JFrame;
/**
* Gobang类 项目的入口
* 控制各个类
*/
public class Gobang extends JFrame {
private static final long serialVersionUID = 1L;
private Board board; // 棋盘
private GamePanel panel; // 游戏面板
private GobangListener listener; // 监听器
/**
* 构造器 初始化各个类 设置监听器
*/
public Gobang () {
new Tool(this); // 初始化静态工具类
board = new Board(); // 初始化棋盘
panel = new GamePanel(); // 初始化面板
// 设置状态
setTitle("gobang");
setSize(Tool.GAMEWIDTH, Tool.GAMEHEIGHT);
setResizable(false);
setLocation(Tool.GAMELOCATION);
setVisible(true);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// 设置布局
setLayout(new BorderLayout());
add(board, BorderLayout.CENTER);
add(panel, BorderLayout.EAST);
// 设置监听器
listener = new GobangListener(this);
addMouseListener(listener);
}
/**
* 返回棋盘
*/
public Board getBoard() {
return board;
}
/**
* 返回面板
*/
public GamePanel getPanel() {
return panel;
}
/**
* 返回监听器
*/
public GobangListener getListener() {
return listener;
}
/**
* 主函数 项目入口
*/
public static void main(String[] args) {
new Gobang();
}
}
二.对弈实现:
1.检测游戏状态:
package gobang;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Point;
import java.awt.Toolkit;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.ArrayList;
/**
* GobangListener类
* 各按钮等监听器的设置
* 各弹出面板的控制
* 游戏状态的检测
*/
public class GobangListener extends MouseAdapter {
private int step; // 步数
private boolean enable; // 棋盘是否可以下棋
private Gobang gobang; // Gobang实例
private WinFrame winFrame; // 胜利frame
private SettingFrame settingFrame; // 设置frame
private AI ai; // ai实例
private Cursor whiteChessCursor; // 白棋鼠标
private Cursor blackChessCursor; // 黑棋鼠标
private Record last; // 玩家上一步
private ArrayList<Record> record; // 棋局记录
/**
* 构造器 初始化和设置监听器
* @param gobang Gobang类实例
*/
public GobangListener (Gobang gobang) {
// 初始化
this.gobang = gobang;
winFrame = new WinFrame();
settingFrame = new SettingFrame();
ai = new AI(Color.WHITE);
record = new ArrayList<>();
step = 1;
enable = true;
Toolkit tk = Toolkit.getDefaultToolkit();
whiteChessCursor = tk.createCustomCursor(Tool.whiteChess, new Point(10, 10), "norm");
blackChessCursor = tk.createCustomCursor(Tool.blackChess, new Point(10, 10), "norm");
// 设置监听器
winFrame.exit.addActionListener((event) -> {
// 退出游戏
System.exit(0);
});
winFrame.again.addActionListener((event) -> {
// 再来一盘
gobang.setEnabled(true);
restart();
winFrame.setVisible(false);
});
winFrame.addWindowListener(new WindowAdapter() {
// 窗口关闭后复盘 显示下棋顺序
@Override
public void windowClosing(WindowEvent e) {
gobang.setEnabled(true);
gobang.getPanel().withdraw.setEnabled(false); // 此时不能悔棋
enable = false; // 此时不能下棋
Tool.reviewflag = true; // 复盘 显示下棋顺序
}
});
gobang.getPanel().exit.addActionListener((event) -> {
// 退出游戏
System.exit(0);
});
gobang.getPanel().start.addActionListener((event) -> {
// 开始游戏
restart();
});
gobang.getPanel().setting.addActionListener((event) -> {
// 设置 弹出设置frame
gobang.setEnabled(false);
settingFrame.setVisible(true);
});
gobang.getPanel().withdraw.addActionListener((event) -> {
// 悔棋
if (Tool.model == Tool.PVP) { // 人人模式移除last
step--;
record.remove(last);
}
else { // 人机模式移除last和ai的last
step = step - 2;
record.remove(ai.getLast());
record.remove(last);
gobang.getBoard().getState()[ai.getLast().getI()][ai.getLast().getJ()] = null;
}
int i = last.getI();
int j = last.getJ();
// 更新上一步
if (record.size() == 0)
last = null;
else
last = record.get(record.size() - 1);
gobang.getBoard().getState()[i][j] = null;
updateUI();
gobang.getPanel().withdraw.setEnabled(false); // 设置悔棋按钮不可用
gobang.getPanel().withdraw.setIcon(null);
gobang.getPanel().withdraw.setForeground(Color.BLACK);
});
gobang.getPanel().defeat.addActionListener((event) -> {
// 认输 弹出胜利frame
gobang.setEnabled(false);
winFrame.setWinner(step);
winFrame.setVisible(true);
});
settingFrame.certain.addActionListener((event) -> {
// 确认设置
gobang.setEnabled(true);
saveSetting(); // 保存设置
settingFrame.setVisible(false);
});
settingFrame.cancel.addActionListener((event) -> {
// 取消设置
gobang.setEnabled(true);
// 恢复设置前的状态
settingFrame.setModel(Tool.model);
settingFrame.setFirst(Tool.first);
settingFrame.setShow(Tool.show);
settingFrame.setVisible(false);
if (Tool.model == Tool.PVE) { // 若为人机模式 则可改变先手
settingFrame.firstP.setEnabled(true);
settingFrame.firstE.setEnabled(true);
}
else { // 若为人人模式 则不能改变先手
settingFrame.firstP.setEnabled(false);
settingFrame.firstE.setEnabled(false);
}
});
settingFrame.addWindowListener(new WindowAdapter() {
// 设置窗口关闭 与确认设置相同
public void windowClosing(WindowEvent e) {
gobang.setEnabled(true);
saveSetting();
settingFrame.setVisible(false);
}
});
// 更新UI
updateUI();
}
/**
* 保存设置
*/
private void saveSetting() {
int m = Tool.model;
Tool.model = settingFrame.getModel();
int f = Tool.first;
Tool.first = settingFrame.getFirst();
Tool.show = settingFrame.getShow();
// 如果游戏模式或人机模式下先手改变 则重开游戏
if (Tool.model != m || (Tool.model == Tool.PVE && Tool.first != f)) {
restart();
}
}
/**
* ai应对玩家下在(i,j)位置的棋
* @param i 横坐标
* @param j 纵坐标
*/
private void playwithAI (int i, int j) {
Record r = ai.play(gobang.getBoard().getState(), i, j);
record.add(r); // 记录这一步棋
gobang.getBoard().setChess(r.getI(), r.getJ(), r.getColor());
step++;
updateUI();
check(r.getI(), r.getJ()); // 检查是否5子相连
}
/**
* 重写mouseReleased方法
*/
@Override
public void mouseReleased (MouseEvent e) {
// 不能控制棋盘 直接返回
if (!enable)
return ;
// 获取当前点位置对应棋盘的横纵坐标
int i = Tool.geti(e.getY());
int j = Tool.getj(e.getX());
// 如不在棋盘范围内 则直接返回
if (i < 0 || j < 0 || i >= Tool.ROWNUM || j>= Tool.COLNUM)
return ;
Color c = step % 2 == 1 ? Color.BLACK : Color.WHITE; // 棋子颜色通过步数奇偶判断
boolean flag = gobang.getBoard().setChess(i, j, c); // 落子是否有效
if (flag) {
last = new Record(i, j); // 玩家上一步
last.setColor(c);
record.add(last);
step++;
updateUI();
if (!check(i, j) && Tool.model == Tool.PVE) // 如果游戏继续并且是PVE模式
playwithAI(i, j);
}
}
/**
* 检查游戏是否结束
* @param i 横坐标
* @param j 纵坐标
* @return 若胜利或平局 则返回true 游戏继续则返回false
*/
private boolean check(int i, int j) {
if(checkWin(i, j) || step == Tool.ROWNUM * Tool.COLNUM + 1) {
// 胜利或者棋盘已满(平局) 则弹出胜利frame
gobang.setEnabled(false);
winFrame.setWinner(step);
winFrame.setVisible(true);
return true;
}
return false;
}
/**
* 更新UI 设置鼠标图标和游戏面板状态
*/
private void updateUI () {
if (step % 2 == 1)
gobang.getBoard().setCursor(blackChessCursor);
else
gobang.getBoard().setCursor(whiteChessCursor);
gobang.getPanel().updateUI(step);
}
/**
* 重新开始
*/
private void restart() {
// 复位
step = 1;
enable = true;
Tool.reviewflag = false;
gobang.getBoard().restart();
record.clear();
if (Tool.model == Tool.PVE && Tool.first == Tool.FIRSTE) {
// PVE并且ai先手
ai.setColor(Color.BLACK);
// 第一步正中央
Record r = new Record(Tool.ROWNUM / 2, Tool.COLNUM / 2);
r.setColor(Color.BLACK);
record.add(r);
step++;
gobang.getBoard().setChess(Tool.ROWNUM / 2, Tool.COLNUM / 2, Color.BLACK);
ai.setFirst(gobang.getBoard().getState());
} else if (Tool.model == Tool.PVE && Tool.first == Tool.FIRSTP) {
// PVE并且玩家先手
ai.setColor(Color.WHITE);
}
updateUI();
}
/**
* 返回棋局记录
*/
public ArrayList<Record> getRecord() {
return record;
}
/**
* 检查是否获胜 判断(i,j)四个方向上是否有五子相连
* @param i 横坐标
* @param j 纵坐标
* @return 若胜利返回true 否则返回false
*/
private Boolean checkWin (int i, int j) {
int x;
int y;
int count;
Chess[][] state = gobang.getBoard().getState();
Color c = state[i][j].getColor();
// 竖向
count = 1;
x = i - 1;
y = j;
while (x >= 0 && state[x][y] != null && state[x][y].getColor().equals(c)) {
count++;
x--;
}
x = i + 1;
y = j;
while (x < Tool.ROWNUM && state[x][y] != null && state[x][y].getColor().equals(c)) {
count++;
x++;
}
if (count >= 5)
return true;
// 横向
count = 1;
x = i;
y = j - 1;
while (y >= 0 && state[x][y] != null && state[x][y].getColor().equals(c)) {
count++;
y--;
}
x = i;
y = j + 1;
while (y < Tool.COLNUM && state[x][y] != null && state[x][y].getColor().equals(c)) {
count++;
y++;
}
if (count >= 5)
return true;
// 右上-左下
count = 1;
x = i - 1;
y = j + 1;
while (x >= 0 && y < Tool.COLNUM && state[x][y] != null && state[x][y].getColor().equals(c)) {
count++;
x--;
y++;
}
x = i + 1;
y = j - 1;
while (x < Tool.ROWNUM && y >= 0 && state[x][y] != null && state[x][y].getColor().equals(c)) {
count++;
x++;
y--;
}
if (count >= 5)
return true;
// 左上-右下
count = 1;
x = i - 1;
y = j - 1;
while (x >= 0 && y >= 0 && state[x][y] != null && state[x][y].getColor().equals(c)) {
count++;
x--;
y--;
}
x = i + 1;
y = j + 1;
while (x < Tool.ROWNUM && y < Tool.COLNUM && state[x][y] != null && state[x][y].getColor().equals(c)) {
count++;
x++;
y++;
}
if (count >= 5)
return true;
return false;
}
}
2.记录棋子:
package gobang;
import java.awt.Color;
/**
* Record类 记录一个棋子的颜色和位置
*/
class Record {
private Color color; // 颜色
private int i; // 横坐标
private int j; // 纵坐标
/**
* 构造器
* @param color 颜色
*/
public Record (Color color) {
this.color = color;
}
/**
* 构造器
* @param i 横坐标
* @param j 纵坐标
*/
public Record (int i, int j) {
this.i = i;
this.j = j;
}
/**
* 获取横坐标
*/
public int getI() {
return i;
}
/**
* 设置横坐标
* @param i 横坐标
*/
public void setI(int i) {
this.i = i;
}
/**
* 获取纵坐标
*/
public int getJ() {
return j;
}
/**
* 设置纵坐标
* @param j 纵坐标
*/
public void setJ(int j) {
this.j = j;
}
/**
* 获取颜色
*/
public Color getColor() {
return color;
}
/**
* 设置颜色
* @param color 颜色
*/
public void setColor(Color color) {
this.color = color;
}
}
3.对弈设置:
package gobang;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.GridBagLayout;
import java.awt.Image;
import javax.swing.ButtonGroup;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
/**
* SettingFrame类 设置frame
* 点击游戏设置时弹出
*/
public class SettingFrame extends JFrame {
private static final long serialVersionUID = 1L;
private JLabel model; // 游戏模式
private JRadioButton PVE; // 人机
private JRadioButton PVP; // 人人
private JLabel first; // 先手
JRadioButton firstP; // 玩家先手
JRadioButton firstE; // 电脑先手
private JLabel show; // 显示方式
private JRadioButton showlast; // 只标记上一步
private JRadioButton showall; // 标记所有棋子(1, 2, 3...)
JButton certain; // 确认键
JButton cancel; // 取消键
/**
* 构造器 初始化
*/
public SettingFrame () {
// 设置属性
setTitle("setting");
setSize(Tool.SETTINWIDTH, Tool.SETTINGHEIGHT);
setLocation(Tool.SETTINGLOCATION);
setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
// 设置背景
JPanel panel = new JPanel() {
private static final long serialVersionUID = 1L;
@Override
protected void paintComponent(Graphics g) {
g.drawImage(Tool.settingframe, 0, 0, getSize().width, getSize().height, this);
}
} ;
panel.setOpaque(false);
// 模式选项
model = new JLabel("模式");
Tool.initComponent(model, Tool.getFont(Tool.fontwei, Font.PLAIN, 24));
PVE = new JRadioButton("人机对战");
Tool.initComponent(PVE, Tool.getFont(Tool.fontwei, Font.PLAIN, 24));
PVP = new JRadioButton("人人对战");
Tool.initComponent(PVP, Tool.getFont(Tool.fontwei, Font.PLAIN, 24));
ButtonGroup bgmodel = new ButtonGroup();
PVE.setSelected(true);
bgmodel.add(PVE);
bgmodel.add(PVP);
// 先手选项
first = new JLabel("先手");
Tool.initComponent(first, Tool.getFont(Tool.fontwei, Font.PLAIN, 24));
firstP = new JRadioButton("玩家先行");
Tool.initComponent(firstP, Tool.getFont(Tool.fontwei, Font.PLAIN, 24));
firstE = new JRadioButton("电脑先行");
Tool.initComponent(firstE, Tool.getFont(Tool.fontwei, Font.PLAIN, 24));
ButtonGroup bgfirst = new ButtonGroup();
firstP.setSelected(true);
bgfirst.add(firstP);
bgfirst.add(firstE);
// 显示选项
show = new JLabel("显示");
Tool.initComponent(show, Tool.getFont(Tool.fontwei, Font.PLAIN, 24));
showlast = new JRadioButton("标记一步");
Tool.initComponent(showlast, Tool.getFont(Tool.fontwei, Font.PLAIN, 24));
showall = new JRadioButton("标记所有");
Tool.initComponent(showall, Tool.getFont(Tool.fontwei, Font.PLAIN, 24));
ButtonGroup bgshow = new ButtonGroup();
showlast.setSelected(true);
bgshow.add(showlast);
bgshow.add(showall);
// 确认按钮
certain = new JButton("确定");
Tool.initComponent(certain, Tool.getFont(Tool.fontkai, Font.PLAIN, 20));
certain.setIcon(new ImageIcon(Tool.blackChess.getScaledInstance(Tool.ICONSIZE, Tool.ICONSIZE, Image.SCALE_DEFAULT)));
// 取消按钮
cancel = new JButton("取消");
Tool.initComponent(cancel, Tool.getFont(Tool.fontkai, Font.PLAIN, 20));
cancel.setIcon(new ImageIcon(Tool.whiteChess.getScaledInstance(Tool.ICONSIZE, Tool.ICONSIZE, Image.SCALE_DEFAULT)));
// 设置布局
add(panel);
panel.setLayout(new GridBagLayout());
panel.add(model, new GBC(0, 0).setInsets(5));
panel.add(PVE, new GBC(1, 0).setInsets(5));
panel.add(PVP, new GBC(2, 0).setInsets(5));
panel.add(first, new GBC(0, 1).setInsets(5));
panel.add(firstP, new GBC(1, 1).setInsets(5));
panel.add(firstE, new GBC(2, 1).setInsets(5));
panel.add(show, new GBC(0, 2).setInsets(5));
panel.add(showlast, new GBC(1, 2).setInsets(5));
panel.add(showall, new GBC(2, 2).setInsets(5));
panel.add(certain, new GBC(1, 3).setInsets(5));
panel.add(cancel, new GBC(2, 3).setInsets(5));
// 选择PVP时禁用先手选项
PVP.addActionListener((event) -> {
firstP.setEnabled(false);
firstE.setEnabled(false);
});
// 选择PVE时启用先手选项
PVE.addActionListener((event) -> {
firstP.setEnabled(true);
firstE.setEnabled(true);
});
}
/**
* 获取模式
*/
public int getModel() {
return PVP.isSelected() ? Tool.PVP : Tool.PVE;
}
/**
* 获取先手
* @return
*/
public int getFirst() {
return firstP.isSelected() ? Tool.FIRSTP : Tool.FIRSTE;
}
/**
* 获取显示
* @return
*/
public int getShow() {
return showall.isSelected() ? Tool.SHOWALL : Tool.SHOWLAST;
}
/**
* 设置模式
* @param i 模式
*/
public void setModel(int i) {
switch (i) {
case Tool.PVE : PVE.setSelected(true); break;
case Tool.PVP : PVP.setSelected(true); break;
}
}
/**
* 设置先手
* @param i 先手
*/
public void setFirst(int i) {
switch (i) {
case Tool.FIRSTP : firstP.setSelected(true); break;
case Tool.FIRSTE : firstE.setSelected(true); break;
}
}
/**
* 设置显示
* @param i 显示
*/
public void setShow(int i) {
switch (i) {
case Tool.SHOWLAST : showlast.setSelected(true); break;
case Tool.SHOWALL : showall.setSelected(true); break;
}
}
}
4.AI对弈的实现:
package gobang;
import java.awt.Color;
import java.util.HashMap;
/**
* AI类 权值法实现电脑下棋
*/
public class AI {
private HashMap<Integer, Integer> weightmap; // 权值哈希表
private int[][] weightE; // 存储电脑权值
private int[][] weightP; // 存储玩家权值
private Color colorE; // 电脑棋子颜色
private Color colorP; // 玩家棋子颜色
private Chess[][] state; // 棋盘状态
private Record last; // 记录电脑上一步棋
/**
* 构造器
* @param c 电脑的棋子颜色
*/
public AI (Color c) {
setColor(c);
weightmap = new HashMap<>();
weightE = new int[Tool.ROWNUM][Tool.COLNUM];
weightP = new int[Tool.ROWNUM][Tool.COLNUM];
// 哈希表
// key的第一个数字代表某一方向上连子的数量 范围 1 - 5
// key的第二个数字代表该方向上两端可延伸的方向数量 范围 0 - 2
// value为所赋权值
weightmap.put(10, 0);
weightmap.put(11, 5);
weightmap.put(12, 8);
weightmap.put(20, 0);
weightmap.put(21, 15);
weightmap.put(22, 18);
weightmap.put(30, 0);
weightmap.put(31, 30);
weightmap.put(32, 100);
weightmap.put(40, 0);
weightmap.put(41, 100);
weightmap.put(42, 200);
weightmap.put(50, 1000);
weightmap.put(51, 1000);
weightmap.put(52, 1000);
}
/**
* 电脑先行 第一步下在棋盘中央
* @param state 棋局状态 棋子的二维数组
*/
public void setFirst (Chess[][] state) {
this.state = state;
int i = Tool.ROWNUM / 2;
int j = Tool.COLNUM / 2;
updateWeight(i, j);
Record r = new Record(colorE);
r.setI(i);
r.setJ(j);
last = r;
}
/**
* 玩家先行
* @param state 棋局状态
* @param x 玩家上一步的横坐标
* @param y 玩家上一步的纵坐标
* @return 记录电脑下棋的位置和颜色
*/
public Record play (Chess[][] state, int x, int y) {
this.state = state;
// 更新权值表
updateWeight(x, y);
Record r = new Record(colorE);
int max = 0; // 记录电脑或玩家的最大权值
int opposite = 0; // 记录最大权值所在位置对手的权值
// 遍历两个权值二维数组 找到最大权值
// 如果权值相等 选择对手权值较大的点
for (int i = 0; i < Tool.ROWNUM; i++) {
for (int j = 0; j < Tool.COLNUM; j++) {
if (weightP[i][j] > max) {
max = weightP[i][j];
r.setI(i);
r.setJ(j);
opposite = weightE[i][j];
} else if (weightP[i][j] == max && weightE[i][j] > opposite) {
max = weightP[i][j];
r.setI(i);
r.setJ(j);
opposite = weightE[i][j];
}
if (weightE[i][j] > max) {
max = weightE[i][j];
r.setI(i);
r.setJ(j);
opposite = weightP[i][j];
} else if (weightE[i][j] == max && weightP[i][j] > opposite) {
max = weightE[i][j];
r.setI(i);
r.setJ(j);
opposite = weightP[i][j];
}
}
}
// 记录电脑的上一步棋
last = r;
// 更新权值表
updateWeight(r.getI(), r.getJ());
return r;
}
/**
* 更新权值表
* @param i 上一步棋的横坐标
* @param j 上一步棋的纵坐标
*/
private void updateWeight (int i, int j) {
// 遍历棋盘的每一个位置 探测该点权值
for (int p = 0; p < Tool.ROWNUM; p++) {
for (int q = 0; q < Tool.COLNUM; q++) {
if (state[p][q] != null)
continue;
weightE[p][q] = getWeight(p, q, colorE);
weightP[p][q] = getWeight(p, q, colorP);
}
}
// 有棋子的点权值置为0
weightE[i][j] = 0;
weightP[i][j] = 0;
}
/**
* 探测(i,j)位置的权值大小
* @param i 横坐标
* @param j 纵坐标
* @param c 该点颜色
* @return 返回4个方向权值大小之和作为该点的权值
*/
private int getWeight (int i, int j, Color c) {
int weight = 0;
int count; // 某一方向上连子数量 关键字的第一位
int p;
int q;
int flag; // 某一方向上可延伸的方向数 关键字第二位
// 竖向
flag = 0;
count = 1;
p = i - 1;
q = j;
while (p >= 0 && state[p][q] != null && state[p][q].getColor().equals(c)) {
count++;
p--;
}
// 如果上方没有棋子 则上方可以延伸 flag+1
if (p >= 0 && state[p][q] == null)
flag++;
p = i + 1;
q = j;
while (p < Tool.ROWNUM && state[p][q] != null && state[p][q].getColor().equals(c)) {
count++;
p++;
}
// 如果下方没有棋子 则下方可以延伸 flag+1
if (p < Tool.ROWNUM && state[p][q] == null)
flag++;
// 连子数超过5 按5计算
if (count > 5)
count = 5;
// 从哈希表中取出权值
weight += weightmap.get(count * 10 + flag);
// 其他方向以此类推
// 横向
flag = 0;
count = 1;
p = i;
q = j - 1;
while (q >= 0 && state[p][q] != null && state[p][q].getColor().equals(c)) {
count++;
q--;
}
if (q >= 0 && state[p][q] == null)
flag++;
p = i;
q = j + 1;
while (q < Tool.COLNUM && state[p][q] != null && state[p][q].getColor().equals(c)) {
count++;
q++;
}
if (q < Tool.COLNUM && state[p][q] == null)
flag++;
if (count > 5)
count = 5;
weight += weightmap.get(count * 10 + flag);
// 右上-左下
flag = 0;
count = 1;
p = i - 1;
q = j + 1;
while (p >= 0 && q < Tool.COLNUM && state[p][q] != null && state[p][q].getColor().equals(c)) {
count++;
p--;
q++;
}
if (p >= 0 && q < Tool.COLNUM && state[p][q] == null)
flag++;
p = i + 1;
q = j - 1;
while (p < Tool.ROWNUM && q >= 0 && state[p][q] != null && state[p][q].getColor().equals(c)) {
count++;
p++;
q--;
}
if (p < Tool.ROWNUM && q >= 0 && state[p][q] == null)
flag++;
if (count > 5)
count = 5;
weight += weightmap.get(count * 10 + flag);
// 左上-右下
flag = 0;
count = 1;
p = i - 1;
q = j - 1;
while (p >= 0 && q >= 0 && state[p][q] != null && state[p][q].getColor().equals(c)) {
count++;
p--;
q--;
}
if (p >= 0 && q >= 0 && state[p][q] == null)
flag++;
p = i + 1;
q = j + 1;
while (p < Tool.ROWNUM && q < Tool.COLNUM && state[p][q] != null && state[p][q].getColor().equals(c)) {
count++;
p++;
q++;
}
if (p < Tool.ROWNUM && q < Tool.COLNUM && state[p][q] == null)
flag++;
if (count > 5)
count = 5;
weight += weightmap.get(count * 10 + flag);
return weight;
}
/**
* 返回电脑的上一步
*/
public Record getLast() {
return last;
}
/**
* 设置电脑棋子颜色与玩家棋子颜色
* @param c 电脑棋子颜色
*/
public void setColor(Color c) {
colorE = c;
if (c.equals(Color.WHITE))
colorP = Color.BLACK;
else
colorP = Color.WHITE;
}
}
5.结果判断:
package gobang;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.GridBagLayout;
import java.awt.Image;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
/**
* WinFrame类 设置frame
* 获胜或认输时弹出
*/
public class WinFrame extends JFrame {
private static final long serialVersionUID = 1L;
JButton again; // 再来一盘
JButton exit; // 退出游戏
private JLabel winner; // 获胜者
private static String whitewin = "白棋获胜!";
private static String blackwin = "黑棋获胜!";
private static String draw = "平局!";
/**
* 构造器 初始化
*/
public WinFrame () {
// 设置属性
setTitle("win");
setSize(Tool.WINWIDTH, Tool.WINHEIGHT);
setLocation(Tool.WINLOCATION);
setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
// 设置背景
JPanel panel = new JPanel() {
private static final long serialVersionUID = 1L;
@Override
protected void paintComponent(Graphics g) {
g.drawImage(Tool.winframe, 0, 0, getSize().width, getSize().height, this);
}
} ;
panel.setOpaque(false);
// 胜利者
winner = new JLabel();
winner.setFont(Tool.getFont(Tool.fontkai, Font.BOLD, 36));
// 再来一盘
again = new JButton("再来一盘");
Tool.initComponent(again, Tool.getFont(Tool.fontkai, Font.PLAIN, 20));
again.setIcon(new ImageIcon(Tool.blackChess.getScaledInstance(Tool.ICONSIZE, Tool.ICONSIZE, Image.SCALE_DEFAULT)));
// 退出游戏
exit = new JButton("退出游戏");
Tool.initComponent(exit, Tool.getFont(Tool.fontkai, Font.PLAIN, 20));
exit.setIcon(new ImageIcon(Tool.whiteChess.getScaledInstance(Tool.ICONSIZE, Tool.ICONSIZE, Image.SCALE_DEFAULT)));
// 设置布局
add(panel);
panel.setLayout(new GridBagLayout());
panel.add(winner, new GBC(0, 0, 2, 1).setInsets(15));
panel.add(again, new GBC(0, 1).setInsets(10));
panel.add(exit, new GBC(1, 1).setInsets(10));
}
/**
* 设置胜利者
* @param step 步数
*/
public void setWinner (int step) {
// 根据步数奇偶性判断胜利者
if (step % 2 == 1)
winner.setText(whitewin);
else
winner.setText(blackwin);
}
/**
* 平局时
*/
public void setDraw() {
winner.setText(draw);
}
}
三.界面:
1.图像设置:
package gobang;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontFormatException;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.SwingConstants;
/**
* Tool类 静态工具类
* 只包含静态域和静态方法
*/
public class Tool {
static Gobang game; // Gobang实例
static final int ROWNUM = 15; // 行数
static final int COLNUM = 15; // 列数
static final int BOARDSIZE = 750; // 棋盘大小
static final int MARGIN = 25; // 棋盘边距
static final int CELL = 50; // 每个格子大小
static final int CHESSSIZE = 45; // 棋子图片大小
static final int BOARDPOINTSIZE = 10; // 棋盘点大小(4个角+中央点)
static final int ICONSIZE = 15; // 图标大小(按钮旁边的黑白棋子)
static final int LARGEBUTTONWIDTH = 155;// 大按钮的宽(开始游戏...)
static final int LARGEBUTTONHEIGHT = 36;// 大按钮的高
static final int SMALLBUTTONWIDTH = 75; // 小按钮的宽(悔棋...)
static final int SMALLBUTTONHEIGHT = 24;// 小按钮的高
static final int GAMEWIDTH = 950; // 主窗口的宽
static final int GAMEHEIGHT = 800; // 主窗口的高
static final Dimension PANELDIMENSION = new Dimension(180, 0); // 游戏面板显示位置
static final Point GAMELOCATION = new Point(500, 100); // 主窗口显示位置
static final Point WINLOCATION = new Point(710, 420); // 胜利窗口显示位置
static final Point SETTINGLOCATION = new Point(710, 420); // 设置窗口显示位置
static final int SETTINWIDTH = 400; // 设置窗口宽
static final int SETTINGHEIGHT = 200; // 设置窗口高
static final int WINWIDTH = 300; // 胜利窗口宽
static final int WINHEIGHT = 180; // 胜利窗口高
static final int TITLEWIDTH = 100; // 标题图片宽
static final int TTITLEHEIGHT = 300; // 标题图片高
static final int RECTSIZE = 10; // 方形标记尺寸
static final int NUMOFFSETX = -5; // 数字标记x向偏移量
static final int NUMOFFSETY = 5; // 数字标记y向偏移量
static final int FONTSIZE = 20; // 数字标记字号
static final int PVE = 0; // PVE定义为0
static final int PVP = 1; // PVP定义为1
static int model; // 模式变量
static final int FIRSTP = 0; // 玩家先手定义为0
static final int FIRSTE = 1; // 电脑先手定义为1
static int first; // 先手变量
static final int SHOWLAST = 0; // 只显示上一步定义为0
static final int SHOWALL = 1; // 显示所有定义为1
static int show; // 显示变量
static boolean reviewflag; // 复盘标志
static String fontwei = "res\\STXINWEI.TTF"; // 华文新魏字体路径
static String fontkai = "res\\STXINGKA.TTF"; // 华文行楷字体路径
static Image blackChess = new ImageIcon("res\\black.png").getImage(); // 黑棋图片
static Image whiteChess = new ImageIcon("res\\white.png").getImage(); // 白棋图片
static Image select = new ImageIcon("res\\select.png").getImage(); // 按钮被选中图片
static Image board = new ImageIcon("res\\board.jpg").getImage(); // 棋盘背景
static Image settingframe = new ImageIcon("res\\settingframe.png").getImage(); // 设置背景
static Image winframe = new ImageIcon("res\\winframe.png").getImage(); // 获胜背景
static Image background = new ImageIcon("res\\background.png").getImage(); // 面板背景
static Image gobang = new ImageIcon("res\\gobang.png").getImage(); // 五子棋标题图片
/**
* 构造器
* @param gobang Gobang类实例
*/
public Tool(Gobang gobang) {
game = gobang;
}
/**
* 初始化组件 设置背景透明 无边框 字体
* @param x 组件
* @param f 字体
*/
static void initComponent (JComponent x, Font f) {
x.setBackground(new Color(255, 255, 255));
x.setFont(f);
x.setOpaque(false);
x.setBorder(BorderFactory.createLineBorder(Color.BLACK, 0));
}
/**
* 设置按钮被选中效果
* @param b 按钮
* @param width 图片宽
* @param height 图片高
*/
static void setEntered (JButton b, int width, int height) {
b.setIcon(new ImageIcon(Tool.select.getScaledInstance(width, height, Image.SCALE_DEFAULT)));
b.setHorizontalTextPosition(SwingConstants.CENTER);
b.setForeground(Color.WHITE); // 字体颜色
}
/**
* 设置按钮未选中效果
* @param b 按钮
*/
static void setExited (JButton b) {
b.setIcon(null);
b.setForeground(Color.BLACK); // 字体颜色
}
/**
* 为按钮添加鼠标监听器 实现选中效果
* @param b 按钮
* @param width 图片宽
* @param height 图片高
*/
static void addMListener(JButton b, int width, int height) {
b.addMouseListener(new MouseAdapter() {
@Override
public void mouseEntered(MouseEvent arg0) {
Tool.setEntered(b, width, height);
super.mouseEntered(arg0);
}
@Override
public void mouseExited(MouseEvent arg0) {
Tool.setExited(b);
super.mouseExited(arg0);
}
});
}
/**
* 获取字体
* @param path 字体路径
* @param type 字体类型(plain,bold...)
* @param size 字体大小
* @return 生成字体
*/
static Font getFont(String path, int type, int size) {
Font font = null;
File file = new File(path);
try {
font = Font.createFont(Font.TRUETYPE_FONT, file);
font = font.deriveFont(type, size);
} catch (FontFormatException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return font;
}
/**
* 仅为上一步棋添加方形标记
* @param g 画笔
* @param r 记录
*/
private static void showlast (Graphics2D g, ArrayList<Record> r) {
Record last = r.get(r.size()-1);
if (last == null)
return ;
int x = last.getI();
int y = last.getJ();
Color color = last.getColor().equals(Color.WHITE) ? Color.DARK_GRAY : Color.LIGHT_GRAY;
g.setColor(color);
g.setStroke(new BasicStroke(2.0f));
g.drawRect(CELL * y - RECTSIZE / 2 + MARGIN, CELL * x - RECTSIZE / 2 + MARGIN, RECTSIZE, RECTSIZE);
}
/**
* 为所有棋子添加步数标记
* @param g 画笔
* @param r 记录
*/
private static void showall(Graphics2D g, ArrayList<Record> r) {
int i = 1;
g.setFont(getFont(fontwei, Font.BOLD, FONTSIZE));
int x;
int y;
for (Record item : r) {
Color c = item.getColor().equals(Color.WHITE) ? Color.DARK_GRAY : Color.LIGHT_GRAY;
g.setColor(c);
if (i > 99) // 三位数
x = CELL * item.getJ() + MARGIN + NUMOFFSETX * 3;
else if (i > 9) // 两位数
x = CELL * item.getJ() + MARGIN + NUMOFFSETX * 2;
else // 一位数
x = CELL * item.getJ() + MARGIN + NUMOFFSETX * 1;
y = CELL * item.getI() + MARGIN + NUMOFFSETY;
g.drawString(String.valueOf(i++), x, y);
}
}
/**
* 显示标记
* @param g 画笔
*/
static void show (Graphics g) {
if (game.getListener() == null)
return ;
ArrayList<Record> r = game.getListener().getRecord();
if (r == null || r.size() == 0)
return ;
Graphics2D g2 = (Graphics2D) g;
if (show == 1 || reviewflag) // 当选项显示所有被选中或游戏结束复盘时
showall(g2, r);
else
showlast(g2, r);
// 立即更新UI
game.getBoard().updateUI();
}
/**
* 通过点的横坐标计算对应棋子的列数
* @param x 横坐标
* @return 列
*/
static int getj (int x) {
return (int) Math.round(1.0 * (x - MARGIN) / CELL);
}
/**
* 通过点点纵坐标计算对应棋子的行数
* @param y 纵坐标
* @return 行
*/
static int geti (int y) {
return (int) Math.round(1.0 * (y - MARGIN * 2) / CELL);
}
}
运行结果截图: