繼承 JPanel,實作 MouseListener、內部類實作 MouseMotionListener。
關鍵常數:
public static final int MARGIN = 30; // 邊距
public static final int GRID_SPAN = 35; // 格子間距
public static final int ROWS = 15; // 行數
public static final int COLS = 15; // 列數
棋子儲存:
Point[] chessList = new Point[(ROWS+1)*(COLS+1)];
int chessCount = 0; // 當前棋子數
boolean isBlack = true; // 黑棋先行
boolean gameOver = false; // 遊戲結束標記
int xIndex, yIndex; // 最後落子座標索引
// paintComponent 核心繪製邏輯
for (int i = 0; i < chessCount; i++) {
int xPos = chessList[i].getX() * GRID_SPAN + MARGIN;
int yPos = chessList[i].getY() * GRID_SPAN + MARGIN;
// 使用漸變繪製立體棋子效果
Color colortemp = chessList[i].getColor();
if (colortemp == Color.BLACK) {
RadialGradientPaint paint = new RadialGradientPaint(
xPos - 15 + 25, yPos - 15 + 10, 20,
new float[]{0f, 1f}, new Color[]{Color.WHITE, Color.BLACK});
((Graphics2D) g).setPaint(paint);
} else { // 白棋
RadialGradientPaint paint = new RadialGradientPaint(
xPos - 15 + 25, yPos - 15 + 10, 70,
new float[]{0f, 1f}, new Color[]{Color.WHITE, Color.BLACK});
((Graphics2D) g).setPaint(paint);
}
((Graphics2D) g).setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g.fill(new Ellipse2D.Float(xPos - 15, yPos - 15, 34, 35));
// 標記最後一步(紅框)
if (i == chessCount - 1) {
g.setColor(Color.RED);
g.drawRect(xPos - 15, yPos - 15, 34, 35);
}
}
addMouseMotionListener(new MouseMotionListener() {
public void mouseMoved(MouseEvent e) {
int x1 = (e.getX() - MARGIN + GRID_SPAN/2) / GRID_SPAN;
int y1 = (e.getY() - MARGIN + GRID_SPAN/2) / GRID_SPAN;
// 超出邊界、遊戲結束、已有棋子 → 預設游標
if (x1 < 0 || x1 > ROWS || y1 < 0 || y1 > COLS || gameOver || findChess(x1, y1)) {
setCursor(Cursor.getDefaultCursor());
} else {
setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
}
}
public void mouseDragged(MouseEvent e) {}
});
public void mousePressed(MouseEvent e) {
if (gameOver) return;
xIndex = (e.getX() - MARGIN + GRID_SPAN/2) / GRID_SPAN;
yIndex = (e.getY() - MARGIN + GRID_SPAN/2) / GRID_SPAN;
// 邊界檢查、重複落子檢查
if (xIndex < 0 || xIndex > ROWS || yIndex < 0 || yIndex > COLS) return;
if (findChess(xIndex, yIndex)) return;
// 新增棋子
chessList[chessCount++] = new Point(xIndex, yIndex, isBlack ? Color.BLACK : Color.WHITE);
repaint();
// 勝負判定
if (isWin()) {
JOptionPane.showMessageDialog(this, "恭喜," + (isBlack ? "黑棋" : "白棋") + "獲勝!");
gameOver = true;
}
isBlack = !isBlack; // 切換回合
}
private boolean isWin() {
int continueCount = 1;
Color c = isBlack ? Color.BLACK : Color.WHITE;
// 橫向(左右)
for (int x = xIndex - 1; x >= 0; x--)
if (getChess(x, yIndex, c) != null) continueCount++; else break;
for (int x = xIndex + 1; x <= COLS; x++)
if (getChess(x, yIndex, c) != null) continueCount++; else break;
if (continueCount >= 5) return true;
// 縱向(上下)
continueCount = 1;
for (int y = yIndex - 1; y >= 0; y--)
if (getChess(xIndex, y, c) != null) continueCount++; else break;
for (int y = yIndex + 1; y <= ROWS; y++)
if (getChess(xIndex, y, c) != null) continueCount++; else break;
if (continueCount >= 5) return true;
// 斜向:左上-右下 (\)
continueCount = 1;
for (int x = xIndex - 1, y = yIndex - 1; x >= 0 && y >= 0; x--, y--)
if (getChess(x, y, c) != null) continueCount++; else break;
for (int x = xIndex + 1, y = yIndex + 1; x <= COLS && y <= ROWS; x++, y++)
if (getChess(x, y, c) != null) continueCount++; else break;
if (continueCount >= 5) return true;
// 斜向:右上-左下 (/)
continueCount = 1;
for (int x = xIndex + 1, y = yIndex - 1; x <= COLS && y >= 0; x++, y--)
if (getChess(x, y, c) != null) continueCount++; else break;
for (int x = xIndex - 1, y = yIndex + 1; x >= 0 && y <= ROWS; x--, y++)
if (getChess(x, y, c) != null) continueCount++; else break;
if (continueCount >= 5) return true;
return false;
}
// 悔棋
public void goback() {
if (chessCount == 0) return;
chessList[chessCount - 1] = null;
chessCount--;
if (chessCount > 0) {
xIndex = chessList[chessCount - 1].getX();
yIndex = chessList[chessCount - 1].getY();
}
isBlack = !isBlack; // 切回上一手回合
repaint();
}
// 重新開始
public void restartGame() {
Arrays.fill(chessList, null);
isBlack = true;
gameOver = false;
chessCount = 0;
repaint();
}
public class Point {
private int x, y;
private Color color;
public static final int DIAMETER = 30;
public Point(int x, int y, Color color) {
this.x = x; this.y = y; this.color = color;
}
// getter 方法...
}
public class StartChessJFrame extends JFrame {
private ChessBoard chessBoard;
private JMenuBar menuBar;
private JMenu sysMenu;
private JMenuItem startItem, exitItem, backItem;
private JButton startBtn, exitBtn, backBtn;
public StartChessJFrame() {
// 選單列
menuBar = new JMenuBar();
sysMenu = new JMenu("系統");
startItem = new JMenuItem("重新開始");
exitItem = new JMenuItem("退出");
backItem = new JMenuItem("悔棋");
sysMenu.add(startItem); sysMenu.add(backItem); sysMenu.add(exitItem);
menuBar.add(sysMenu);
setJMenuBar(menuBar);
// 底部工具列
toolbar = new JPanel(new FlowLayout(FlowLayout.LEFT));
startBtn = new JButton("重新開始");
exitBtn = new JButton("退出");
backBtn = new JButton("悔棋");
toolbar.add(startBtn); toolbar.add(backBtn); toolbar.add(exitBtn);
add(toolbar, BorderLayout.SOUTH);
// 統一事件監聽
ActionListener lis = e -> {
Object src = e.getSource();
if (src == startItem || src == startBtn) chessBoard.restartGame();
else if (src == exitItem || src == exitBtn) System.exit(0);
else if (src == backItem || src == backBtn) chessBoard.goback();
};
startItem.addActionListener(lis); startBtn.addActionListener(lis);
backItem.addActionListener(lis); backBtn.addActionListener(lis);
exitItem.addActionListener(lis); exitBtn.addActionListener(lis);
add(chessBoard);
setDefaultCloseOperation(EXIT_ON_CLOSE);
pack(); setVisible(true);
}
}
Graphics2D + RadialGradientPaint + 反鋸齒實現立體棋子