黄金矿工是经典街机游戏的代表,玩家控制一个摇摆的钩子抓取地下的金矿、钻石和障碍物。本文用Java实现黄金矿工游戏的核心算法,重点讲解物理抛物线运动模拟钩子的抓取轨迹,以及贪心策略在多目标场景下的最优选择决策。
游戏机制与物理建模
黄金矿工的核心算法挑战包括:
- 钩子摆动:钩子以固定点为圆心做简谐摆动
- 发射轨迹:按下按钮后钩子沿当前角度直线发射
- 物理抓取:抓到物品后根据重量调整收回速度
- 目标选择:多个物品同时可见时,选择最有价值的目标
物理抛物线与直线运动模拟
钩子的运动分为两个阶段:摆动阶段和发射阶段。摆动使用简谐运动模型,发射后沿直线运动直到碰到边界或物品。
import java.util.*;
/**
* 黄金矿工 - 物理引擎核心
* 模拟钩子摆动、发射、抓取和回收的完整物理过程
*/
public class MinerPhysics {
// 钩子状态
static class Hook {
double originX, originY; // 旋转中心(固定)
double angle; // 当前角度(弧度)
double length; // 当前绳长
double maxLength; // 最大绳长
double swingSpeed; // 摆动角速度
boolean extending; // 是否正在发射
boolean retracting; // 是否正在回收
double extendSpeed; // 发射速度
double retractBaseSpeed; // 基础回收速度
Item caughtItem; // 抓到的物品
Hook(double ox, double oy, double maxLen) {
this.originX = ox; this.originY = oy;
this.maxLength = maxLen;
this.length = 0;
this.angle = 0;
this.swingSpeed = 1.5; // 弧度/秒
this.extendSpeed = 300; // 像素/秒
this.retractBaseSpeed = 150;
}
/**
* 更新钩子摆动
*/
void updateSwing(double dt) {
if (extending || retracting) return;
angle += swingSpeed * dt;
// 限制摆动范围在 -60度 到 60度
if (angle > Math.PI / 3 || angle < -Math.PI / 3) {
swingSpeed = -swingSpeed;
}
}
/**
* 获取钩子末端坐标
*/
double getTipX() { return originX + Math.sin(angle) * length; }
double getTipY() { return originY + Math.cos(angle) * length; }
/**
* 发射钩子
*/
void extend(double dt) {
if (!extending) return;
length += extendSpeed * dt;
if (length >= maxLength) {
length = maxLength;
extending = false;
retracting = true; // 到达边界自动回收
}
}
/**
* 回收钩子,重量越大速度越慢
*/
void retract(double dt) {
if (!retracting) return;
double weight = (caughtItem != null) ? caughtItem.weight : 1.0;
double speed = retractBaseSpeed / Math.sqrt(weight);
length -= speed * dt;
if (length <= 0) {
length = 0;
retracting = false;
}
}
}
static class Item {
double x, y;
int value;
double weight;
double radius;
String type;
Item(double x, double y, int value, double weight, double radius, String type) {
this.x = x; this.y = y; this.value = value;
this.weight = weight; this.radius = radius; this.type = type;
}
/**
* 计算物品的"性价比":价值/重量比
*/
double getValueDensity() {
return value / weight;
}
}
}
贪心最优目标选择算法
当钩子沿直线运动时,可能穿过多个物品的碰撞范围。贪心策略选择当前角度下”性价比”最高的物品进行抓取。
/**
* 黄金矿工 - 贪心目标选择器
* 在钩子发射路径上选择最优目标
*/
public class MinerTargetSelector {
/**
* 贪心策略:选择发射路径上性价比最高的物品
* 性价比 = 价值 / (重量 * 距离)
* 同时考虑物品大小(大物品更容易被抓到)
*/
public MinerPhysics.Item selectBestTarget(
MinerPhysics.Hook hook,
List<MinerPhysics.Item> items) {
MinerPhysics.Item best = null;
double bestScore = -1;
for (MinerPhysics.Item item : items) {
// 计算钩子到物品的距离
double dx = item.x - hook.originX;
double dy = item.y - hook.originY;
double distance = Math.sqrt(dx * dx + dy * dy);
// 计算物品相对于钩子的角度
double itemAngle = Math.atan2(dx, dy);
double angleDiff = Math.abs(itemAngle - hook.angle);
// 如果角度差太大,钩子不会经过该物品
if (angleDiff > 0.1) continue; // 约5度容差
// 计算钩子尖端到物品中心的最近距离(垂直距离)
double hookTipX = hook.getTipX();
double hookTipY = hook.getTipY();
double distToLine = pointToLineDistance(
item.x, item.y,
hook.originX, hook.originY,
hookTipX, hookTipY
);
// 如果距离大于物品半径,抓不到
if (distToLine > item.radius) continue;
// 贪心评分:价值高、重量轻、距离近、体积大的物品优先
double score = item.value / (item.weight * 0.5 + distance * 0.01 + 1);
if (score > bestScore) {
bestScore = score;
best = item;
}
}
return best;
}
/**
* 计算点到线段的距离
*/
private double pointToLineDistance(double px, double py,
double x1, double y1, double x2, double y2) {
double dx = x2 - x1;
double dy = y2 - y1;
double len2 = dx * dx + dy * dy;
if (len2 == 0) return Math.sqrt((px-x1)*(px-x1) + (py-y1)*(py-y1));
double t = Math.max(0, Math.min(1, ((px-x1)*dx + (py-y1)*dy) / len2));
double projX = x1 + t * dx;
double projY = y1 + t * dy;
return Math.sqrt((px-projX)*(px-projX) + (py-projY)*(py-projY));
}
/**
* 多目标场景下的贪心策略:预测下一钩的最佳发射角度
* 枚举多个角度,选择能抓到最高分物品的角度
*/
public double findBestAngle(
MinerPhysics.Hook hook,
List<MinerPhysics.Item> items,
double angleRange, // 搜索角度范围
double step) { // 角度步长
double bestAngle = hook.angle;
double bestScore = -1;
double originalAngle = hook.angle;
for (double a = -angleRange; a <= angleRange; a += step) {
hook.angle = originalAngle + a;
MinerPhysics.Item target = selectBestTarget(hook, items);
if (target != null) {
double score = target.value / (target.weight + 1);
if (score > bestScore) {
bestScore = score;
bestAngle = hook.angle;
}
}
}
hook.angle = originalAngle; // 恢复原角度
return bestAngle;
}
}
完整可运行项目
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.util.List;
/**
* 黄金矿工完整游戏实现
*/
public class GoldMinerGame extends JPanel implements ActionListener, KeyListener {
private static final int WIDTH = 800;
private static final int HEIGHT = 600;
private static final int HOOK_ORIGIN_X = WIDTH / 2;
private static final int HOOK_ORIGIN_Y = 80;
private Timer timer;
private Hook hook = new Hook();
private List<Item> items = new ArrayList<>();
private Random rand = new Random();
private int score = 0;
private int targetScore = 650;
private int level = 1;
private boolean gameOver = false;
// 钩子状态
class Hook {
double angle = 0;
double length = 0;
double maxLength = 450;
double swingSpeed = 1.2;
boolean extending = false;
boolean retracting = false;
Item caught = null;
double getTipX() { return HOOK_ORIGIN_X + Math.sin(angle) * length; }
double getTipY() { return HOOK_ORIGIN_Y + Math.cos(angle) * length; }
}
class Item {
double x, y;
int value;
double weight;
double radius;
Color color;
String name;
Item(double x, double y, int value, double weight, double radius, Color color, String name) {
this.x = x; this.y = y; this.value = value;
this.weight = weight; this.radius = radius;
this.color = color; this.name = name;
}
}
public GoldMinerGame() {
setPreferredSize(new Dimension(WIDTH, HEIGHT));
setBackground(new Color(135, 206, 235)); // 天空蓝
addKeyListener(this);
setFocusable(true);
timer = new Timer(20, this);
generateLevel();
}
/**
* 生成关卡物品
* 贪心策略确保物品分布均匀且有价值梯度
*/
private void generateLevel() {
items.clear();
hook.caught = null;
hook.length = 0;
hook.extending = false;
hook.retracting = false;
// 大金块(高价值高重量)
for (int i = 0; i < 2 + level; i++) {
items.add(new Item(
rand.nextInt(WIDTH - 100) + 50,
rand.nextInt(300) + 200,
50 * (rand.nextInt(3) + 1), 8, 25,
Color.YELLOW, "大金块"
));
}
// 小金块(中等价值)
for (int i = 0; i < 5 + level * 2; i++) {
items.add(new Item(
rand.nextInt(WIDTH - 100) + 50,
rand.nextInt(300) + 200,
15 * (rand.nextInt(3) + 1), 3, 12,
new Color(255, 215, 0), "小金块"
));
}
// 钻石(高价值轻重量,最优目标)
for (int i = 0; i < 2 + level; i++) {
items.add(new Item(
rand.nextInt(WIDTH - 100) + 50,
rand.nextInt(300) + 200,
100 + rand.nextInt(100), 1, 8,
Color.CYAN, "钻石"
));
}
// 石头(障碍物,低价值高重量)
for (int i = 0; i < 3 + level; i++) {
items.add(new Item(
rand.nextInt(WIDTH - 100) + 50,
rand.nextInt(300) + 200,
5 + rand.nextInt(10), 15, 20,
Color.GRAY, "石头"
));
}
}
@Override
public void actionPerformed(ActionEvent e) {
if (gameOver) return;
double dt = 0.02;
// 摆动阶段
if (!hook.extending && !hook.retracting) {
hook.angle += hook.swingSpeed * dt;
if (hook.angle > Math.PI / 3 || hook.angle < -Math.PI / 3) {
hook.swingSpeed = -hook.swingSpeed;
}
}
// 发射阶段
if (hook.extending) {
hook.length += 250 * dt;
// 检测碰撞
double tipX = hook.getTipX();
double tipY = hook.getTipY();
for (Item item : items) {
double dist = Math.sqrt((tipX - item.x) * (tipX - item.x)
+ (tipY - item.y) * (tipY - item.y));
if (dist < item.radius + 5) {
hook.caught = item;
hook.extending = false;
hook.retracting = true;
break;
}
}
// 到达边界
if (hook.length >= hook.maxLength) {
hook.extending = false;
hook.retracting = true;
}
}
// 回收阶段
if (hook.retracting) {
double weight = (hook.caught != null) ? hook.caught.weight : 1;
double speed = 120 / Math.sqrt(weight);
hook.length -= speed * dt;
if (hook.length <= 0) {
hook.length = 0;
hook.retracting = false;
if (hook.caught != null) {
score += hook.caught.value;
items.remove(hook.caught);
hook.caught = null;
// 检查过关
if (score >= targetScore) {
level++;
targetScore += 200;
generateLevel();
}
// 检查是否还有物品
if (items.isEmpty()) {
generateLevel();
}
}
}
}
repaint();
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
// 绘制地面背景
g.setColor(new Color(194, 178, 128));
g.fillRect(0, 180, WIDTH, HEIGHT - 180);
// 绘制绳索
g.setColor(Color.BLACK);
int tipX = (int) hook.getTipX();
int tipY = (int) hook.getTipY();
g.drawLine(HOOK_ORIGIN_X, HOOK_ORIGIN_Y, tipX, tipY);
// 绘制钩子
g.setColor(Color.DARK_GRAY);
g.fillOval(tipX - 5, tipY - 5, 10, 10);
// 绘制被抓物品(跟随钩子)
if (hook.caught != null) {
Item item = hook.caught;
item.x = tipX;
item.y = tipY + item.radius;
drawItem(g, item);
}
// 绘制所有物品
for (Item item : items) {
if (item != hook.caught) {
drawItem(g, item);
}
}
// UI
g.setColor(Color.BLACK);
g.setFont(new Font("Microsoft YaHei", Font.BOLD, 18));
g.drawString("分数: " + score + " / 目标: " + targetScore, 20, 30);
g.drawString("关卡: " + level, 20, 55);
g.drawString("剩余物品: " + items.size(), 20, 80);
// 提示信息
if (!hook.extending && !hook.retracting) {
g.setColor(Color.RED);
g.drawString("按空格键发射钩子", WIDTH/2 - 80, 30);
}
}
private void drawItem(Graphics g, Item item) {
g.setColor(item.color);
g.fillOval((int)(item.x - item.radius), (int)(item.y - item.radius),
(int)(item.radius * 2), (int)(item.radius * 2));
g.setColor(Color.BLACK);
g.drawOval((int)(item.x - item.radius), (int)(item.y - item.radius),
(int)(item.radius * 2), (int)(item.radius * 2));
g.setColor(Color.WHITE);
g.setFont(new Font("Arial", Font.BOLD, 10));
g.drawString(String.valueOf(item.value), (int)item.x - 5, (int)item.y + 4);
}
@Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_SPACE) {
if (!hook.extending && !hook.retracting) {
hook.extending = true;
}
}
}
public void start() { timer.start(); }
@Override public void keyReleased(KeyEvent e) {}
@Override public void keyTyped(KeyEvent e) {}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame("黄金矿工 - 物理抛物线与贪心策略");
GoldMinerGame game = new GoldMinerGame();
frame.add(game);
frame.pack();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
game.start();
});
}
}
算法核心要点总结
物理模拟关键公式:
- 钩子末端坐标:
x = originX + sin(angle) * length,y = originY + cos(angle) * length - 简谐摆动:
angle += swingSpeed * dt,到达边界反弹 - 重量影响回收:
retractSpeed = baseSpeed / sqrt(weight),重物品大幅减速
贪心策略应用:
- 目标选择贪心:在钩子发射路径上,选择
value / weight比率最高的物品 - 角度预测贪心:枚举小范围角度,预测能抓到最高价值物品的发射时机
- 关卡生成贪心:确保每关都有足够的高性价比物品(钻石)作为策略奖励
时间复杂度分析:
– 碰撞检测:O(N),N为场上物品数
– 最优角度搜索:O(M × N),M为角度枚举数
– 摆动更新:O(1)
扩展方向
- 弹道预测线:绘制虚线预览钩子轨迹,帮助玩家决策
- 道具系统:炸弹(清除周围物品)、大力水(加速回收)等
- 多钩子协同:两个钩子同时工作,需要协调避免碰撞