黄金矿工
by ThunderBolt64872 lines22.0 KB
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<title>黄金矿工</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body {
width: 100%; height: 100%;
overflow: hidden;
background: #1a0a00;
font-family: 'Arial', sans-serif;
touch-action: none;
-webkit-user-select: none;
user-select: none;
}
#gameContainer {
position: relative;
width: 100vw; height: 100vh;
display: flex;
align-items: center;
justify-content: center;
}
canvas {
display: block;
background: #2c1a0e;
}
/* HUD overlay */
#hud {
position: absolute;
top: 0; left: 0; right: 0;
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 16px;
background: linear-gradient(180deg, rgba(0,0,0,0.7) 0%, transparent 100%);
color: #fff;
font-size: 16px;
font-weight: bold;
z-index: 10;
pointer-events: none;
}
#hud span { text-shadow: 1px 1px 3px rgba(0,0,0,0.8); }
.hud-item { display: flex; align-items: center; gap: 6px; }
.hud-icon { font-size: 20px; }
/* Overlay screens */
.overlay {
position: absolute;
top: 0; left: 0; width: 100%; height: 100%;
background: rgba(0,0,0,0.75);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
z-index: 20;
color: #fff;
text-align: center;
}
.overlay.hidden { display: none; }
.overlay h1 {
font-size: 42px;
margin-bottom: 12px;
text-shadow: 0 0 20px #ffd700, 0 0 40px #ff8c00;
color: #ffd700;
}
.overlay h2 {
font-size: 28px;
margin-bottom: 8px;
color: #ffd700;
text-shadow: 0 0 10px #ff8c00;
}
.overlay p {
font-size: 18px;
margin-bottom: 20px;
color: #ddd;
max-width: 320px;
line-height: 1.5;
}
.overlay .score-display {
font-size: 36px;
color: #ffd700;
font-weight: bold;
margin: 10px 0 20px;
text-shadow: 0 0 15px #ff8c00;
}
.btn {
padding: 14px 48px;
font-size: 20px;
font-weight: bold;
border: none;
border-radius: 50px;
cursor: pointer;
background: linear-gradient(135deg, #ffd700, #ff8c00);
color: #1a0a00;
box-shadow: 0 4px 15px rgba(255,215,0,0.4);
transition: transform 0.15s, box-shadow 0.15s;
-webkit-tap-highlight-color: transparent;
}
.btn:hover { transform: scale(1.05); box-shadow: 0 6px 20px rgba(255,215,0,0.6); }
.btn:active { transform: scale(0.97); }
/* Level transition */
.overlay .level-tag {
display: inline-block;
background: #ff8c00;
color: #1a0a00;
padding: 4px 16px;
border-radius: 20px;
font-size: 14px;
font-weight: bold;
margin-bottom: 10px;
}
/* How to play hint */
.hint {
font-size: 14px;
color: #aaa;
margin-top: 16px;
}
</style>
</head>
<body>
<div id="gameContainer">
<canvas id="gameCanvas"></canvas>
<!-- HUD -->
<div id="hud">
<div class="hud-item"><span class="hud-icon">🪙</span><span id="scoreText">0</span></div>
<div class="hud-item"><span id="levelText">第 1 关</span></div>
<div class="hud-item"><span class="hud-icon">⏱</span><span id="timerText">30</span></div>
</div>
<!-- Start Screen -->
<div id="startScreen" class="overlay">
<h1>⛏ 黄金矿工</h1>
<p>控制钩子抓取地下的金块和钻石,在限时内尽量多得分!</p>
<button class="btn" id="startBtn">开始游戏</button>
<p class="hint">点击屏幕 / 按空格键 释放钩子</p>
</div>
<!-- Level Complete Screen -->
<div id="levelScreen" class="overlay hidden">
<div class="level-tag" id="levelTag">第 1 关</div>
<h2>时间到!</h2>
<div class="score-display" id="levelScore">0</div>
<button class="btn" id="nextBtn">下一关</button>
</div>
<!-- Game Over Screen -->
<div id="endScreen" class="overlay hidden">
<h1>游戏结束</h1>
<div class="score-display" id="finalScore">0</div>
<button class="btn" id="restartBtn">再来一次</button>
</div>
</div>
<script>
// ===================== GAME CONFIG =====================
const CONFIG = {
LEVELS: 2,
TIME_PER_LEVEL: 30,
HOOK_SWING_SPEED: 0.025,
HOOK_EXTEND_SPEED: 4,
HOOK_RETRACT_SPEED: 3,
HOOK_RETRACT_SPEED_LOADED: 1.2,
HOOK_MAX_LENGTH_RATIO: 0.85,
MIN_ANGLE: -Math.PI * 0.42,
MAX_ANGLE: Math.PI * 0.42,
GROUND_RATIO: 0.18, // ground starts at 18% from top
MINER_RATIO: 0.14, // miner Y position
};
// Item types: name, score, radius, weight (affects retract speed), color
const ITEM_TYPES = {
SMALL_GOLD: { name: '小金块', score: 50, radius: 16, weight: 1, color: '#FFD700', type: 'gold' },
MEDIUM_GOLD: { name: '中金块', score: 150, radius: 24, weight: 2, color: '#FFC107', type: 'gold' },
LARGE_GOLD: { name: '大金块', score: 300, radius: 34, weight: 3.5, color: '#FF9800', type: 'gold' },
DIAMOND: { name: '钻石', score: 500, radius: 14, weight: 0.5, color: '#00E5FF', type: 'diamond' },
ROCK: { name: '石头', score: 10, radius: 28, weight: 4, color: '#8D6E63', type: 'rock' },
};
// Level configs: what items to spawn
const LEVEL_CONFIG = [
{
items: [
{ type: 'SMALL_GOLD', count: 5 },
{ type: 'MEDIUM_GOLD', count: 3 },
{ type: 'LARGE_GOLD', count: 1 },
{ type: 'DIAMOND', count: 1 },
{ type: 'ROCK', count: 2 },
]
},
{
items: [
{ type: 'SMALL_GOLD', count: 3 },
{ type: 'MEDIUM_GOLD', count: 3 },
{ type: 'LARGE_GOLD', count: 2 },
{ type: 'DIAMOND', count: 2 },
{ type: 'ROCK', count: 4 },
]
},
];
// ===================== CANVAS SETUP =====================
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
function resizeCanvas() {
const ratio = window.devicePixelRatio || 1;
const w = window.innerWidth;
const h = window.innerHeight;
canvas.width = w * ratio;
canvas.height = h * ratio;
canvas.style.width = w + 'px';
canvas.style.height = h + 'px';
ctx.setTransform(ratio, 0, 0, ratio, 0, 0);
}
window.addEventListener('resize', resizeCanvas);
resizeCanvas();
// ===================== GAME STATE =====================
let game = {
state: 'idle', // idle | playing | levelEnd | gameOver
level: 1,
score: 0,
levelScore: 0,
timeLeft: CONFIG.TIME_PER_LEVEL,
timerInterval: null,
items: [],
hook: {
angle: 0,
swingDir: 1,
length: 0,
maxLength: 0,
state: 'swinging', // swinging | extending | retracting
tipX: 0,
tipY: 0,
grabbed: null,
},
particles: [],
};
// ===================== DOM REFS =====================
const scoreText = document.getElementById('scoreText');
const levelText = document.getElementById('levelText');
const timerText = document.getElementById('timerText');
const startScreen = document.getElementById('startScreen');
const levelScreen = document.getElementById('levelScreen');
const endScreen = document.getElementById('endScreen');
const levelTag = document.getElementById('levelTag');
const levelScoreEl = document.getElementById('levelScore');
const finalScoreEl = document.getElementById('finalScore');
// ===================== HELPERS =====================
function getW() { return window.innerWidth; }
function getH() { return window.innerHeight; }
function getMinerX() { return getW() / 2; }
function getMinerY() { return getH() * CONFIG.MINER_RATIO; }
function getGroundY() { return getH() * CONFIG.GROUND_RATIO; }
function dist(x1, y1, x2, y2) {
return Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2);
}
function rand(min, max) {
return Math.random() * (max - min) + min;
}
// ===================== ITEMS =====================
function spawnItems(levelIdx) {
const items = [];
const cfg = LEVEL_CONFIG[levelIdx];
const groundY = getGroundY() + 40;
const bottomY = getH() - 40;
const leftX = 50;
const rightX = getW() - 50;
cfg.items.forEach(entry => {
const template = ITEM_TYPES[entry.type];
for (let i = 0; i < entry.count; i++) {
let x, y, overlapping;
let attempts = 0;
// avoid overlap
do {
x = rand(leftX + template.radius, rightX - template.radius);
y = rand(groundY + template.radius, bottomY - template.radius);
overlapping = items.some(it => dist(x, y, it.x, it.y) < it.radius + template.radius + 10);
attempts++;
} while (overlapping && attempts < 50);
items.push({
...template,
x, y,
grabbed: false,
sparkle: Math.random() * Math.PI * 2,
});
}
});
return items;
}
// ===================== PARTICLES =====================
function addParticles(x, y, color, count) {
for (let i = 0; i < count; i++) {
game.particles.push({
x, y,
vx: rand(-3, 3),
vy: rand(-4, -1),
life: rand(20, 40),
maxLife: 40,
color,
size: rand(2, 5),
});
}
}
function updateParticles() {
game.particles = game.particles.filter(p => {
p.x += p.vx;
p.y += p.vy;
p.vy += 0.12;
p.life--;
return p.life > 0;
});
}
function drawParticles() {
game.particles.forEach(p => {
const alpha = p.life / p.maxLife;
ctx.globalAlpha = alpha;
ctx.fillStyle = p.color;
ctx.beginPath();
ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
ctx.fill();
});
ctx.globalAlpha = 1;
}
// ===================== DRAWING =====================
function drawBackground() {
const w = getW(), h = getH();
const groundY = getGroundY();
// Sky
const skyGrad = ctx.createLinearGradient(0, 0, 0, groundY);
skyGrad.addColorStop(0, '#87CEEB');
skyGrad.addColorStop(1, '#E8D5B7');
ctx.fillStyle = skyGrad;
ctx.fillRect(0, 0, w, groundY);
// Ground
const groundGrad = ctx.createLinearGradient(0, groundY, 0, h);
groundGrad.addColorStop(0, '#8B6914');
groundGrad.addColorStop(0.3, '#6B4E0A');
groundGrad.addColorStop(1, '#3E2A04');
ctx.fillStyle = groundGrad;
ctx.fillRect(0, groundY, w, h - groundY);
// Ground line
ctx.strokeStyle = '#5C8A2F';
ctx.lineWidth = 4;
ctx.beginPath();
ctx.moveTo(0, groundY);
ctx.lineTo(w, groundY);
ctx.stroke();
// Grass tufts
ctx.fillStyle = '#5C8A2F';
for (let x = 0; x < w; x += 30) {
ctx.beginPath();
ctx.moveTo(x, groundY);
ctx.lineTo(x + 5, groundY - 8);
ctx.lineTo(x + 10, groundY);
ctx.fill();
}
// Underground dirt texture (subtle dots)
ctx.fillStyle = 'rgba(139,105,20,0.3)';
for (let i = 0; i < 60; i++) {
const dx = (i * 137.5) % w;
const dy = groundY + 30 + (i * 97.3) % (h - groundY - 50);
ctx.beginPath();
ctx.arc(dx, dy, 2, 0, Math.PI * 2);
ctx.fill();
}
}
function drawMiner() {
const mx = getMinerX();
const my = getMinerY();
// Body
ctx.fillStyle = '#D32F2F';
ctx.fillRect(mx - 12, my - 10, 24, 30);
// Head
ctx.fillStyle = '#FFCCBC';
ctx.beginPath();
ctx.arc(mx, my - 20, 14, 0, Math.PI * 2);
ctx.fill();
// Hard hat
ctx.fillStyle = '#FFC107';
ctx.beginPath();
ctx.ellipse(mx, my - 28, 16, 8, 0, Math.PI, 0);
ctx.fill();
ctx.fillRect(mx - 16, my - 28, 32, 4);
// Hat light
ctx.fillStyle = '#FF5722';
ctx.beginPath();
ctx.arc(mx, my - 32, 4, 0, Math.PI * 2);
ctx.fill();
// Eyes
ctx.fillStyle = '#333';
ctx.beginPath();
ctx.arc(mx - 5, my - 22, 2, 0, Math.PI * 2);
ctx.arc(mx + 5, my - 22, 2, 0, Math.PI * 2);
ctx.fill();
// Arms
ctx.strokeStyle = '#FFCCBC';
ctx.lineWidth = 4;
ctx.beginPath();
ctx.moveTo(mx - 12, my);
ctx.lineTo(mx - 22, my + 12);
ctx.moveTo(mx + 12, my);
ctx.lineTo(mx + 22, my + 12);
ctx.stroke();
}
function drawRopeAndHook() {
const mx = getMinerX();
const my = getMinerY() + 15;
const hook = game.hook;
// Calculate hook tip
hook.tipX = mx + Math.sin(hook.angle) * hook.length;
hook.tipY = my + Math.cos(hook.angle) * hook.length;
// Rope
ctx.strokeStyle = '#8D6E63';
ctx.lineWidth = 3;
ctx.beginPath();
ctx.moveTo(mx, my);
ctx.lineTo(hook.tipX, hook.tipY);
ctx.stroke();
// Hook
const hs = 10; // hook size
ctx.strokeStyle = '#B0BEC5';
ctx.lineWidth = 3;
ctx.lineCap = 'round';
if (hook.grabbed) {
// Closed claw
ctx.beginPath();
ctx.moveTo(hook.tipX - hs, hook.tipY - 4);
ctx.lineTo(hook.tipX, hook.tipY + 4);
ctx.lineTo(hook.tipX + hs, hook.tipY - 4);
ctx.stroke();
} else {
// Open claw
ctx.beginPath();
ctx.moveTo(hook.tipX - hs * 0.7, hook.tipY);
ctx.lineTo(hook.tipX, hook.tipY + 8);
ctx.lineTo(hook.tipX + hs * 0.7, hook.tipY);
ctx.stroke();
}
ctx.lineCap = 'butt';
}
function drawItems() {
const now = Date.now() / 1000;
game.items.forEach(item => {
if (item.grabbed) return;
ctx.save();
ctx.translate(item.x, item.y);
if (item.type === 'diamond') {
// Diamond shape
const r = item.radius;
const sparkle = Math.sin(now * 3 + item.sparkle) * 0.15 + 1;
ctx.scale(sparkle, sparkle);
// Glow
ctx.shadowColor = '#00E5FF';
ctx.shadowBlur = 12;
ctx.fillStyle = item.color;
ctx.beginPath();
ctx.moveTo(0, -r);
ctx.lineTo(r * 0.7, 0);
ctx.lineTo(0, r);
ctx.lineTo(-r * 0.7, 0);
ctx.closePath();
ctx.fill();
// Highlight
ctx.fillStyle = 'rgba(255,255,255,0.5)';
ctx.beginPath();
ctx.moveTo(0, -r * 0.6);
ctx.lineTo(r * 0.3, -r * 0.1);
ctx.lineTo(0, r * 0.1);
ctx.lineTo(-r * 0.3, -r * 0.1);
ctx.closePath();
ctx.fill();
ctx.shadowBlur = 0;
} else if (item.type === 'gold') {
// Gold nugget (rounded rectangle-ish)
const r = item.radius;
const sparkle = Math.sin(now * 2 + item.sparkle) * 0.08 + 1;
ctx.scale(sparkle, sparkle);
// Glow
ctx.shadowColor = '#FFD700';
ctx.shadowBlur = 8;
ctx.fillStyle = item.color;
ctx.beginPath();
ctx.moveTo(-r * 0.8, -r * 0.5);
ctx.lineTo(r * 0.6, -r * 0.6);
ctx.lineTo(r * 0.9, r * 0.2);
ctx.lineTo(r * 0.3, r * 0.7);
ctx.lineTo(-r * 0.7, r * 0.6);
ctx.lineTo(-r * 0.95, r * 0);
ctx.closePath();
ctx.fill();
// Shine
ctx.fillStyle = 'rgba(255,255,255,0.35)';
ctx.beginPath();
ctx.ellipse(-r * 0.2, -r * 0.2, r * 0.35, r * 0.2, -0.3, 0, Math.PI * 2);
ctx.fill();
ctx.shadowBlur = 0;
} else if (item.type === 'rock') {
// Rock
const r = item.radius;
ctx.fillStyle = item.color;
ctx.beginPath();
ctx.moveTo(-r * 0.7, -r * 0.5);
ctx.lineTo(r * 0.3, -r * 0.7);
ctx.lineTo(r * 0.8, -r * 0.2);
ctx.lineTo(r * 0.6, r * 0.6);
ctx.lineTo(-r * 0.2, r * 0.7);
ctx.lineTo(-r * 0.9, r * 0.3);
ctx.closePath();
ctx.fill();
// Texture
ctx.strokeStyle = 'rgba(0,0,0,0.2)';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(-r * 0.3, -r * 0.3);
ctx.lineTo(r * 0.2, r * 0.1);
ctx.stroke();
}
ctx.restore();
});
}
// Draw grabbed item following hook
function drawGrabbedItem() {
const hook = game.hook;
if (!hook.grabbed) return;
const item = hook.grabbed;
ctx.save();
ctx.translate(hook.tipX, hook.tipY + item.radius * 0.5);
// Simple draw for grabbed
if (item.type === 'diamond') {
const r = item.radius * 0.9;
ctx.fillStyle = item.color;
ctx.shadowColor = '#00E5FF';
ctx.shadowBlur = 10;
ctx.beginPath();
ctx.moveTo(0, -r);
ctx.lineTo(r * 0.7, 0);
ctx.lineTo(0, r);
ctx.lineTo(-r * 0.7, 0);
ctx.closePath();
ctx.fill();
ctx.shadowBlur = 0;
} else if (item.type === 'gold') {
const r = item.radius * 0.9;
ctx.fillStyle = item.color;
ctx.shadowColor = '#FFD700';
ctx.shadowBlur = 8;
ctx.beginPath();
ctx.arc(0, 0, r * 0.7, 0, Math.PI * 2);
ctx.fill();
ctx.shadowBlur = 0;
} else {
const r = item.radius * 0.9;
ctx.fillStyle = item.color;
ctx.beginPath();
ctx.arc(0, 0, r * 0.65, 0, Math.PI * 2);
ctx.fill();
}
ctx.restore();
}
// ===================== HOOK LOGIC =====================
function updateHook() {
const hook = game.hook;
const mx = getMinerX();
const my = getMinerY() + 15;
hook.maxLength = getH() * CONFIG.HOOK_MAX_LENGTH_RATIO;
switch (hook.state) {
case 'swinging':
hook.angle += CONFIG.HOOK_SWING_SPEED * hook.swingDir;
if (hook.angle >= CONFIG.MAX_ANGLE) {
hook.angle = CONFIG.MAX_ANGLE;
hook.swingDir = -1;
} else if (hook.angle <= CONFIG.MIN_ANGLE) {
hook.angle = CONFIG.MIN_ANGLE;
hook.swingDir = 1;
}
hook.length = 30;
break;
case 'extending':
hook.length += CONFIG.HOOK_EXTEND_SPEED;
// Check collision with items
checkCollision();
// Check boundary
if (hook.length >= hook.maxLength || hook.tipX < 5 || hook.tipX > getW() - 5 || hook.tipY > getH() - 5) {
hook.state = 'retracting';
}
break;
case 'retracting':
const speed = hook.grabbed
? CONFIG.HOOK_RETRACT_SPEED_LOADED / hook.grabbed.weight
: CONFIG.HOOK_RETRACT_SPEED;
hook.length -= speed * 1.8;
// Move grabbed item with hook
if (hook.grabbed) {
hook.grabbed.x = hook.tipX;
hook.grabbed.y = hook.tipY + hook.grabbed.radius * 0.5;
}
if (hook.length <= 30) {
hook.length = 30;
if (hook.grabbed) {
// Score!
const item = hook.grabbed;
game.score += item.score;
game.levelScore += item.score;
addParticles(mx, my, item.color, 15);
hook.grabbed = null;
}
hook.state = 'swinging';
}
break;
}
}
function checkCollision() {
const hook = game.hook;
for (let item of game.items) {
if (item.grabbed) continue;
const d = dist(hook.tipX, hook.tipY, item.x, item.y);
if (d < item.radius + 8) {
item.grabbed = true;
hook.grabbed = item;
hook.state = 'retracting';
addParticles(item.x, item.y, item.color, 8);
break;
}
}
}
function releaseHook() {
if (game.state !== 'playing') return;
if (game.hook.state === 'swinging') {
game.hook.state = 'extending';
}
}
// ===================== GAME FLOW =====================
function startGame() {
game.score = 0;
game.level = 1;
startScreen.classList.add('hidden');
endScreen.classList.add('hidden');
startLevel(1);
}
function startLevel(lvl) {
game.level = lvl;
game.levelScore = 0;
game.timeLeft = CONFIG.TIME_PER_LEVEL;
game.state = 'playing';
game.items = spawnItems(lvl - 1);
game.hook = {
angle: 0,
swingDir: 1,
length: 30,
maxLength: getH() * CONFIG.HOOK_MAX_LENGTH_RATIO,
state: 'swinging',
tipX: getMinerX(),
tipY: getMinerY() + 45,
grabbed: null,
};
game.particles = [];
updateHUD();
// Timer
clearInterval(game.timerInterval);
game.timerInterval = setInterval(() => {
if (game.state !== 'playing') return;
game.timeLeft--;
updateHUD();
if (game.timeLeft <= 0) {
endLevel();
}
}, 1000);
}
function endLevel() {
clearInterval(game.timerInterval);
game.state = 'levelEnd';
if (game.level >= CONFIG.LEVELS) {
// Game over
finalScoreEl.textContent = game.score;
endScreen.classList.remove('hidden');
} else {
// Show level score
levelTag.textContent = `第 ${game.level} 关`;
levelScoreEl.textContent = `${game.levelScore} 分`;
levelScreen.classList.remove('hidden');
}
}
function nextLevel() {
levelScreen.classList.add('hidden');
startLevel(game.level + 1);
}
function restartGame() {
endScreen.classList.add('hidden');
startGame();
}
function updateHUD() {
scoreText.textContent = game.score;
levelText.textContent = `第 ${game.level} 关`;
timerText.textContent = Math.max(0, game.timeLeft);
// Timer color
if (game.timeLeft <= 5) {
timerText.style.color = '#FF5252';
} else if (game.timeLeft <= 10) {
timerText.style.color = '#FFB74D';
} else {
timerText.style.color = '#fff';
}
}
// ===================== SCORE POPUP =====================
let scorePopups = [];
function addScorePopup(x, y, score) {
scorePopups.push({
x, y, score,
life: 40,
vy: -2,
});
}
// Override score adding to show popup
const origHook = game.hook;
// We handle popups in the main loop after scoring
// ===================== MAIN LOOP =====================
function gameLoop() {
const w = getW(), h = getH();
ctx.clearRect(0, 0, w, h);
drawBackground();
drawItems();
if (game.state === 'playing') {
updateHook();
}
drawMiner();
drawRopeAndHook();
drawGrabbedItem();
updateParticles();
drawParticles();
// Score popups
scorePopups = scorePopups.filter(p => {
p.y += p.vy;
p.life--;
const alpha = p.life / 40;
ctx.globalAlpha = alpha;
ctx.fillStyle = '#FFD700';
ctx.font = 'bold 22px Arial';
ctx.textAlign = 'center';
ctx.fillText(`+${p.score}`, p.x, p.y);
ctx.globalAlpha = 1;
return p.life > 0;
});
// Detect score change for popup
if (game.hook.state === 'swinging' && game.hook.grabbed === null) {
// just after retract with item — popup handled via particles
}
requestAnimationFrame(gameLoop);
}
// ===================== INPUT =====================
// Click / Tap
canvas.addEventListener('click', () => releaseHook());
canvas.addEventListener('touchstart', (e) => {
e.preventDefault();
releaseHook();
}, { passive: false });
// Keyboard
document.addEventListener('keydown', (e) => {
if (e.code === 'Space' || e.key === ' ') {
e.preventDefault();
releaseHook();
}
});
// Buttons
document.getElementById('startBtn').addEventListener('click', startGame);
document.getElementById('nextBtn').addEventListener('click', nextLevel);
document.getElementById('restartBtn').addEventListener('click', restartGame);
// ===================== SCORE POPUP HOOK =====================
// Patch scoring to show popups
let _prevScore = 0;
const _origUpdateHook = updateHook;
// We use a simpler approach: check score delta each frame
setInterval(() => {
if (game.score > _prevScore) {
const diff = game.score - _prevScore;
addScorePopup(getMinerX(), getMinerY() - 20, diff);
_prevScore = game.score;
}
if (game.score < _prevScore) {
_prevScore = game.score; // reset
}
}, 50);
// ===================== START =====================
gameLoop();
</script>
</body>
</html>
Game Source: 黄金矿工
Creator: ThunderBolt64
Libraries: none
Complexity: complex (872 lines, 22.0 KB)
The full source code is displayed above on this page.
Remix Instructions
To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: game-thunderbolt64" to link back to the original. Then publish at arcadelab.ai/publish.