added sys digger
This commit is contained in:
227
sys_digger/game.js
Normal file
227
sys_digger/game.js
Normal file
@@ -0,0 +1,227 @@
|
||||
const canvas = document.getElementById('gameCanvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
const cysCanvas = document.getElementById('cystinstein-canvas');
|
||||
const cysCtx = cysCanvas.getContext('2d');
|
||||
|
||||
canvas.width = window.innerWidth;
|
||||
canvas.height = window.innerHeight;
|
||||
|
||||
const CX = canvas.width / 2;
|
||||
const CY = canvas.height / 2;
|
||||
|
||||
// ─── Leaderboard ─────────────────────────────────────────────────────────────
|
||||
const LEADERBOARD_KEY = 'sysdigger_leaderboard';
|
||||
const LEADERBOARD_MAX = 10;
|
||||
|
||||
function loadLeaderboard() {
|
||||
try {
|
||||
return JSON.parse(localStorage.getItem(LEADERBOARD_KEY)) || [];
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function saveLeaderboard(board) {
|
||||
localStorage.setItem(LEADERBOARD_KEY, JSON.stringify(board));
|
||||
}
|
||||
|
||||
function addEntry(name, score) {
|
||||
const board = loadLeaderboard();
|
||||
board.push({ name: name.trim() || 'Anonymous', score });
|
||||
board.sort((a, b) => b.score - a.score);
|
||||
const trimmed = board.slice(0, LEADERBOARD_MAX);
|
||||
saveLeaderboard(trimmed);
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
function renderLeaderboard(highlightEntry) {
|
||||
const board = loadLeaderboard();
|
||||
const tbody = document.getElementById('leaderboard-rows');
|
||||
tbody.innerHTML = '';
|
||||
|
||||
if (board.length === 0) {
|
||||
const tr = document.createElement('tr');
|
||||
tr.innerHTML = `<td colspan="3" style="color:#555;text-align:center;padding:12px">No scores yet</td>`;
|
||||
tbody.appendChild(tr);
|
||||
return;
|
||||
}
|
||||
|
||||
board.forEach((entry, i) => {
|
||||
const tr = document.createElement('tr');
|
||||
const isHighlight = highlightEntry &&
|
||||
entry.name === highlightEntry.name &&
|
||||
entry.score === highlightEntry.score;
|
||||
|
||||
if (isHighlight) tr.classList.add('highlight');
|
||||
else if (i === 0) tr.classList.add('rank-1');
|
||||
else if (i === 1) tr.classList.add('rank-2');
|
||||
else if (i === 2) tr.classList.add('rank-3');
|
||||
|
||||
const medal = i === 0 ? '🥇' : i === 1 ? '🥈' : i === 2 ? '🥉' : i + 1;
|
||||
tr.innerHTML = `
|
||||
<td class="rank-num">${medal}</td>
|
||||
<td style="text-align:left">${escapeHtml(entry.name)}</td>
|
||||
<td style="text-align:right">${entry.score}</td>`;
|
||||
tbody.appendChild(tr);
|
||||
});
|
||||
}
|
||||
|
||||
function escapeHtml(str) {
|
||||
return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
||||
}
|
||||
|
||||
function topScore() {
|
||||
const board = loadLeaderboard();
|
||||
return board.length > 0 ? board[0].score : null;
|
||||
}
|
||||
|
||||
// ─── State ───────────────────────────────────────────────────────────────────
|
||||
let score = 0;
|
||||
let docks = 0;
|
||||
let gameOver = false;
|
||||
let incomingAtoms = [];
|
||||
let anchorPoints = [];
|
||||
let particles = [];
|
||||
let clickEffects = [];
|
||||
let lastSpawn = 0;
|
||||
let spawnInterval = SPAWN_INTERVAL_START;
|
||||
|
||||
const stars = Array.from({ length: STAR_COUNT }, () => ({
|
||||
x: Math.random() * canvas.width,
|
||||
y: Math.random() * canvas.height,
|
||||
r: Math.random() * STAR_RADIUS_MAX,
|
||||
a: STAR_ALPHA_MIN + Math.random() * STAR_ALPHA_RANGE
|
||||
}));
|
||||
|
||||
// ─── Anchors ─────────────────────────────────────────────────────────────────
|
||||
function initAnchors() {
|
||||
anchorPoints = [];
|
||||
for (let i = 0; i < NUM_ANCHORS; i++) {
|
||||
const angle = (i / NUM_ANCHORS) * Math.PI * 2 - Math.PI / 2;
|
||||
anchorPoints.push({
|
||||
x: CX + Math.cos(angle) * ANCHOR_RADIUS,
|
||||
y: CY + Math.sin(angle) * ANCHOR_RADIUS,
|
||||
angle,
|
||||
active: false,
|
||||
blockTimer: 0,
|
||||
pulsePhase: Math.random() * Math.PI * 2
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Spawn ───────────────────────────────────────────────────────────────────
|
||||
function spawnAtom() {
|
||||
const available = anchorPoints.map((_, i) => i).filter(i => !anchorPoints[i].active);
|
||||
if (available.length === 0) return;
|
||||
const idx = available[Math.floor(Math.random() * available.length)];
|
||||
incomingAtoms.push(new IncomingAtom(idx));
|
||||
}
|
||||
|
||||
// ─── Input ───────────────────────────────────────────────────────────────────
|
||||
canvas.addEventListener('click', e => {
|
||||
if (gameOver) return;
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
const mx = e.clientX - rect.left;
|
||||
const my = e.clientY - rect.top;
|
||||
let hit = false;
|
||||
|
||||
anchorPoints.forEach((ap, i) => {
|
||||
if (!ap.active) return;
|
||||
if (Math.hypot(mx - ap.x, my - ap.y) > CLICK_RADIUS) return;
|
||||
|
||||
const atom = incomingAtoms.find(a => a.anchorIndex === i && !a.dead);
|
||||
if (atom) {
|
||||
atom.dead = true;
|
||||
ap.active = false;
|
||||
ap.blockTimer = BLOCK_TIMER_FRAMES;
|
||||
score++;
|
||||
document.getElementById('score').textContent = `Score: ${score}`;
|
||||
burst(ap.x, ap.y, '#00ff88');
|
||||
clickEffects.push({ x: ap.x, y: ap.y, radius: 5, life: 1 });
|
||||
hit = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (!hit) clickEffects.push({ x: mx, y: my, radius: 5, life: 0.5 });
|
||||
});
|
||||
|
||||
// ─── Game flow ───────────────────────────────────────────────────────────────
|
||||
function triggerGameOver() {
|
||||
gameOver = true;
|
||||
const best = topScore();
|
||||
const isNewRecord = best === null || score > best;
|
||||
|
||||
document.getElementById('final-score').textContent = `Score: ${score}`;
|
||||
document.getElementById('new-record').classList.toggle('visible', isNewRecord);
|
||||
|
||||
const input = document.getElementById('player-name');
|
||||
input.value = '';
|
||||
setTimeout(() => input.focus(), 50);
|
||||
|
||||
renderLeaderboard(null);
|
||||
document.getElementById('overlay').classList.add('active');
|
||||
}
|
||||
|
||||
function submitScore() {
|
||||
const name = document.getElementById('player-name').value.trim() || 'Anonymous';
|
||||
const entry = { name, score };
|
||||
addEntry(name, score);
|
||||
renderLeaderboard(entry);
|
||||
|
||||
const best = topScore();
|
||||
updateHudBest(best);
|
||||
|
||||
document.getElementById('name-form').style.display = 'none';
|
||||
}
|
||||
|
||||
function restartGame() {
|
||||
score = docks = 0;
|
||||
gameOver = false;
|
||||
incomingAtoms = [];
|
||||
particles = [];
|
||||
clickEffects = [];
|
||||
lastSpawn = 0;
|
||||
spawnInterval = SPAWN_INTERVAL_START;
|
||||
document.getElementById('score').textContent = 'Score: 0';
|
||||
document.getElementById('docks-count').textContent = '0 / 8 docks';
|
||||
document.getElementById('name-form').style.display = 'flex';
|
||||
document.getElementById('overlay').classList.remove('active');
|
||||
initAnchors();
|
||||
updateCystinstein();
|
||||
}
|
||||
|
||||
function updateHudBest(best) {
|
||||
document.getElementById('highscore').textContent = best !== null ? `Best: ${best}` : 'Best: —';
|
||||
}
|
||||
|
||||
// ─── Enter key on name input ──────────────────────────────────────────────────
|
||||
document.getElementById('player-name').addEventListener('keydown', e => {
|
||||
if (e.key === 'Enter') submitScore();
|
||||
});
|
||||
|
||||
// ─── Main loop ───────────────────────────────────────────────────────────────
|
||||
function gameLoop(ts) {
|
||||
drawBackground();
|
||||
|
||||
if (!gameOver && ts - lastSpawn > spawnInterval) {
|
||||
spawnAtom();
|
||||
lastSpawn = ts;
|
||||
spawnInterval = Math.max(SPAWN_INTERVAL_MIN, spawnInterval - SPAWN_DIFFICULTY_STEP);
|
||||
}
|
||||
|
||||
incomingAtoms = incomingAtoms.filter(a => !a.dead);
|
||||
incomingAtoms.forEach(a => { a.update(); a.draw(); });
|
||||
|
||||
drawCentralMolecule();
|
||||
drawAnchorPoints();
|
||||
drawParticles();
|
||||
drawClickEffects();
|
||||
|
||||
requestAnimationFrame(gameLoop);
|
||||
}
|
||||
|
||||
// ─── Boot ────────────────────────────────────────────────────────────────────
|
||||
updateHudBest(topScore());
|
||||
initAnchors();
|
||||
updateCystinstein();
|
||||
requestAnimationFrame(gameLoop);
|
||||
Reference in New Issue
Block a user