本遊戲改編自微信熱門網頁版「青蛙跳石頭」,移植為 Java Swing 桌面版。為專注邏輯實現,未使用圖片素材,改用彩色方塊代表青蛙。棋盤 7 個位置,初始左右各 3 只不同顏色方塊,中間空 1 格。目標:使左右兩邊方塊交換位置。
JFrame,負責視窗初始化、遊戲主循環(重繪執行緒)、事件分發(鍵盤/滑鼠)public class Main extends JFrame {
public static final int GAME_WIDTH = 420;
public static final int GAME_HEIGHT = 150;
// ... 位置常數
private Frog frog; // 遊戲模型
private DateFormat format = new SimpleDateFormat("mm:ss");
private String startTime, endTime;
private long elapsedTime;
private void launchFrame() {
// 視窗屬性設置
setLocation(GAME_X, GAME_Y);
setSize(GAME_WIDTH, GAME_HEIGHT);
setTitle("Frog Jump Game");
setResizable(false);
getContentPane().setBackground(Color.WHITE);
// 事件監聽器註冊
addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) { System.exit(0); }
});
addMouseListener(new MouseHandler());
addKeyListener(new KeyHandler());
setVisible(true);
// 獨立執行緒驅動重繪與計時
new Thread(new PaintThread()).start();
}
public void paint(Graphics g) {
super.paint(g);
// 繪製 7 個位置框
for (int i = 1; i < 8; i++) {
g.drawRect(i * 50 - 5, 45, 30, 30);
}
frog.draw(g); // 委託模型繪製
// 計時顯示
try {
elapsedTime = format.parse(endTime).getTime() - format.parse(startTime).getTime();
g.drawString("時間:" + format.format(new Date(elapsedTime)), 250, 40);
} catch (ParseException e) { e.printStackTrace(); }
// 操作提示
g.drawString("F2 重新開始", 100, 40);
g.drawString("規則:1.點擊方塊跳動", 85, 100);
g.drawString("2.不可後退,最多跨越 1 格", 120, 115);
g.drawString("3.左右交換位置獲勝", 120, 130);
}
// 重繪執行緒:每秒刷新一次
private class PaintThread implements Runnable {
public void run() {
while (true) {
repaint();
endTime = format.format(new Date());
try { Thread.sleep(1000); } catch (Exception e) { e.printStackTrace(); }
}
}
}
// 事件分發給模型處理
private class KeyHandler extends KeyAdapter {
public void keyReleased(KeyEvent e) { frog.keyReleased(e); }
}
private class MouseHandler extends MouseAdapter {
public void mouseClicked(MouseEvent e) { frog.mouseClicked(e); }
}
}
public class Frog {
// 內部類:方塊屬性封裝
private static class Block {
int x, y; // 座標
boolean blueTeam; // 陣營:true=藍(左),false=紅(右)
boolean visible; // 是否顯示(中間位置初始隱藏)
Block(int x, int y, boolean blue, boolean vis) { ... }
}
private static final int BLOCK_SIZE = 20;
private static final int TOTAL_BLOCKS = 7;
private static final int BASE_X = 50, BASE_Y = 50;
private List<Block> blocks = new ArrayList<>();
private Main mainRef;
public Frog(Main m) { this.mainRef = m; initBlocks(); }
private void initBlocks() {
blocks.clear();
for (int i = 1; i <= TOTAL_BLOCKS; i++) {
if (i <= TOTAL_BLOCKS / 2)
blocks.add(new Block(BASE_X * i, BASE_Y, true, true)); // 左側藍色
else if (i == TOTAL_BLOCKS / 2 + 1)
blocks.add(new Block(BASE_X * i, BASE_Y, true, false)); // 中間空位
else
blocks.add(new Block(BASE_X * i, BASE_Y, false, true)); // 右側紅色
}
}
public void draw(Graphics g) {
Color old = g.getColor();
for (Block b : blocks) {
if (b.visible) {
g.setColor(b.blueTeam ? Color.BLUE : Color.RED);
g.fillRect(b.x, b.y, BLOCK_SIZE, BLOCK_SIZE);
}
}
g.setColor(old);
}
// 勝負判定:左半邊全紅、中間空、右半邊全藍
public boolean isGameOver() {
for (int i = 0; i < TOTAL_BLOCKS; i++) {
Block b = blocks.get(i);
if (i < TOTAL_BLOCKS / 2) { if (b.blueTeam) return false; }
else if (i == TOTAL_BLOCKS / 2) { if (b.visible) return false; }
else { if (!b.blueTeam) return false; }
}
return true;
}
public void reset() {
initBlocks();
mainRef.repaint();
String now = mainRef.format.format(new Date());
mainRef.startTime = mainRef.endTime = now;
}
// 滑鼠點擊移動算法核心
public void mouseClicked(MouseEvent e) {
int x = e.getX(), y = e.getY();
// 點擊區域檢測:Y 在方塊垂直範圍內,X 對應 7 個位置之一
if (y > 50 && y < 70 && x % 50 <= 20) {
int idx = x / 50 - 1; // 轉換為 0-based 索引
Block clicked = blocks.get(idx);
if (!clicked.visible) return; // 點擊空位無效
if (clicked.blueTeam) { // 藍方:向右移動
tryMoveRight(idx);
} else { // 紅方:向左移動
tryMoveLeft(idx);
}
}
mainRef.repaint();
if (isGameOver()) showVictoryDialog();
}
private void tryMoveRight(int idx) {
// 相鄰右格有方塊,且再右格為空 → 跳躍兩格
if (idx < TOTAL_BLOCKS - 1 && blocks.get(idx + 1).visible) {
if (idx < TOTAL_BLOCKS - 2 && !blocks.get(idx + 2).visible) {
blocks.set(idx, new Block(BASE_X * (idx + 1), BASE_Y, true, false));
blocks.set(idx + 2, new Block(BASE_X * (idx + 3), BASE_Y, true, true));
return;
}
}
// 相鄰右格為空 → 移動一格
if (idx < TOTAL_BLOCKS - 1 && !blocks.get(idx + 1).visible) {
blocks.set(idx, new Block(BASE_X * (idx + 1), BASE_Y, true, false));
blocks.set(idx + 1, new Block(BASE_X * (idx + 2), BASE_Y, true, true));
}
}
private void tryMoveLeft(int idx) {
// 鏡像邏輯:向左移動
if (idx > 0 && blocks.get(idx - 1).visible) {
if (idx > 1 && !blocks.get(idx - 2).visible) {
blocks.set(idx, new Block(BASE_X * (idx + 1), BASE_Y, false, false));
blocks.set(idx - 2, new Block(BASE_X * (idx - 1), BASE_Y, false, true));
return;
}
}
if (idx > 0 && !blocks.get(idx - 1).visible) {
blocks.set(idx, new Block(BASE_X * (idx + 1), BASE_Y, false, false));
blocks.set(idx - 1, new Block(BASE_X * idx, BASE_Y, false, true));
}
}
private void showVictoryDialog() {
Object[] options = {"OK", "CANCEL"};
int choice = JOptionPane.showOptionDialog(null,
"完成遊戲\n用時:" + mainRef.format.format(new Date(mainRef.elapsedTime)),
"遊戲結束", JOptionPane.DEFAULT_OPTION, JOptionPane.WARNING_MESSAGE,
null, options, options[0]);
if (choice == JOptionPane.YES_OPTION) System.exit(0);
else reset();
}
public void keyReleased(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_F2) reset();
}
}
移動規則可抽象為狀態機:
當前位置 idx,目標方向 dir(+1 右 / -1 左)
1. 相鄰位置 idx+dir 有方塊?
是 → 檢查 idx+2*dir 是否為空?
是 → 跳躍兩格(原位變空,目標位置生成方塊)
否 → 不可移動
否 → 相鄰位置為空 → 移動一格
Main.java、Frog.java 放入同一目錄javac Main.java Frog.javajava Main