Initial commit

This commit is contained in:
Allen
2026-04-30 02:14:25 +00:00
commit 7090e6f01d
243 changed files with 115122 additions and 0 deletions
+387
View File
@@ -0,0 +1,387 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<title>Whack-a-Mole</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { width: 100%; height: 100%; overflow: hidden; background: #8B4513; touch-action: none; font-family: Arial, sans-serif; }
canvas { display: block; }
</style>
</head>
<body>
<canvas id="game"></canvas>
<script>
const AudioContext = window.AudioContext || window.webkitAudioContext;
let audioCtx = null;
let musicEnabled = true;
let sfxEnabled = true;
let musicPlaying = false;
function initAudio() {
if (!audioCtx) audioCtx = new AudioContext();
if (audioCtx.state === 'suspended') audioCtx.resume();
}
// Playful carnival melody
const melody = [
{n: 523, d: 0.2}, {n: 659, d: 0.2}, {n: 784, d: 0.2}, {n: 659, d: 0.2},
{n: 523, d: 0.2}, {n: 659, d: 0.2}, {n: 784, d: 0.4},
{n: 698, d: 0.2}, {n: 659, d: 0.2}, {n: 587, d: 0.2}, {n: 523, d: 0.4},
{n: 587, d: 0.2}, {n: 659, d: 0.2}, {n: 698, d: 0.2}, {n: 784, d: 0.2},
{n: 880, d: 0.4}, {n: 784, d: 0.2}, {n: 698, d: 0.2}, {n: 659, d: 0.4}, {n: 0, d: 0.2}
];
const bass = [
{n: 131, d: 0.4}, {n: 165, d: 0.4}, {n: 196, d: 0.4}, {n: 165, d: 0.4},
{n: 175, d: 0.4}, {n: 147, d: 0.4}, {n: 131, d: 0.4}, {n: 165, d: 0.4},
{n: 196, d: 0.4}, {n: 220, d: 0.4}, {n: 196, d: 0.4}, {n: 165, d: 0.4}
];
let melodyIdx = 0, bassIdx = 0, nextMelody = 0, nextBass = 0;
function playTone(freq, dur, type = 'square', vol = 0.12) {
if (!audioCtx || !musicEnabled || freq === 0) return;
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
osc.type = type;
osc.frequency.value = freq;
gain.gain.setValueAtTime(vol, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + dur * 0.9);
osc.connect(gain);
gain.connect(audioCtx.destination);
osc.start();
osc.stop(audioCtx.currentTime + dur);
}
function playSfx(type) {
if (!audioCtx || !sfxEnabled) return;
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
if (type === 'whack') {
osc.frequency.setValueAtTime(200, audioCtx.currentTime);
osc.frequency.exponentialRampToValueAtTime(80, audioCtx.currentTime + 0.1);
gain.gain.setValueAtTime(0.3, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.1);
osc.type = 'sawtooth';
} else if (type === 'pop') {
osc.frequency.setValueAtTime(400, audioCtx.currentTime);
osc.frequency.exponentialRampToValueAtTime(800, audioCtx.currentTime + 0.1);
gain.gain.setValueAtTime(0.2, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.1);
osc.type = 'square';
} else if (type === 'miss') {
osc.frequency.value = 150;
gain.gain.setValueAtTime(0.15, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.2);
osc.type = 'triangle';
}
osc.connect(gain);
gain.connect(audioCtx.destination);
osc.start();
osc.stop(audioCtx.currentTime + 0.3);
}
function updateMusic() {
if (!audioCtx || !musicEnabled || !musicPlaying) return;
const now = audioCtx.currentTime;
if (now >= nextMelody) {
const note = melody[melodyIdx];
playTone(note.n, note.d, 'square', 0.1);
nextMelody = now + note.d;
melodyIdx = (melodyIdx + 1) % melody.length;
}
if (now >= nextBass) {
const note = bass[bassIdx];
playTone(note.n, note.d, 'triangle', 0.18);
nextBass = now + note.d;
bassIdx = (bassIdx + 1) % bass.length;
}
}
function startMusic() {
if (!musicPlaying && musicEnabled) {
initAudio();
musicPlaying = true;
nextMelody = audioCtx.currentTime;
nextBass = audioCtx.currentTime;
}
}
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
let W, H, safeTop, safeBottom, gameArea;
function resize() {
W = window.innerWidth;
H = window.innerHeight;
canvas.width = W;
canvas.height = H;
safeTop = H * 0.1;
safeBottom = H * 0.9;
gameArea = { x: 20, y: safeTop + 20, w: W - 40, h: safeBottom - safeTop - 40 };
}
resize();
window.addEventListener('resize', resize);
let gameRunning = false;
let gameOver = false;
let score = 0;
let timeLeft = 60;
let moleUpTime = 2500;
let lastMoleTime = 0;
let currentMole = -1;
let moleState = 'down'; // down, up, hit
let moleY = 0;
let holes = [];
function initHoles() {
holes = [];
const cols = 3, rows = 3;
const holeW = Math.min(gameArea.w / cols - 20, 100);
const holeH = holeW * 0.6;
const gapX = (gameArea.w - cols * holeW) / (cols + 1);
const gapY = (gameArea.h - rows * holeH) / (rows + 1);
for (let r = 0; r < rows; r++) {
for (let c = 0; c < cols; c++) {
holes.push({
x: gameArea.x + gapX + c * (holeW + gapX),
y: gameArea.y + gapY + r * (holeH + gapY),
w: holeW,
h: holeH
});
}
}
}
function init() {
score = 0;
timeLeft = 60;
moleUpTime = 2500;
currentMole = -1;
moleState = 'down';
moleY = 0;
initHoles();
gameOver = false;
gameRunning = true;
lastMoleTime = Date.now();
startMusic();
}
function showMole() {
if (moleState !== 'down') return;
currentMole = Math.floor(Math.random() * 9);
moleState = 'rising';
moleY = 0;
playSfx('pop');
}
function handleClick(x, y) {
if (gameOver) { init(); return; }
if (!gameRunning) return;
for (let i = 0; i < holes.length; i++) {
const h = holes[i];
if (x >= h.x && x <= h.x + h.w && y >= h.y && y <= h.y + h.h) {
if (i === currentMole && (moleState === 'up' || moleState === 'rising')) {
moleState = 'hit';
score += 10;
playSfx('whack');
setTimeout(() => {
moleState = 'down';
currentMole = -1;
}, 300);
} else {
playSfx('miss');
}
break;
}
}
}
document.addEventListener('keydown', e => {
initAudio();
if (e.key === ' ' || e.key === 'Enter') {
if (gameOver) init();
}
});
canvas.addEventListener('click', e => {
initAudio();
handleClick(e.clientX, e.clientY);
});
canvas.addEventListener('touchstart', e => {
e.preventDefault();
initAudio();
handleClick(e.touches[0].clientX, e.touches[0].clientY);
});
let lastTime = Date.now();
let timerAccum = 0;
function update() {
const now = Date.now();
const dt = now - lastTime;
lastTime = now;
if (!gameRunning || gameOver) return;
// Timer
timerAccum += dt;
if (timerAccum >= 1000) {
timeLeft--;
timerAccum = 0;
if (timeLeft <= 0) {
gameOver = true;
gameRunning = false;
musicPlaying = false;
}
}
// Mole animation
if (moleState === 'rising') {
moleY += 5;
if (moleY >= 100) {
moleY = 100;
moleState = 'up';
lastMoleTime = now;
}
} else if (moleState === 'up') {
if (now - lastMoleTime > moleUpTime) {
moleState = 'falling';
}
} else if (moleState === 'falling') {
moleY -= 5;
if (moleY <= 0) {
moleY = 0;
moleState = 'down';
currentMole = -1;
}
} else if (moleState === 'down') {
if (now - lastMoleTime > 500) {
showMole();
}
}
// Gradually speed up
if (moleUpTime > 1000) moleUpTime -= 0.5;
updateMusic();
}
function draw() {
// Background
ctx.fillStyle = '#5D4037';
ctx.fillRect(0, 0, W, H);
// Safe zones
ctx.fillStyle = '#4E342E';
ctx.fillRect(0, 0, W, safeTop);
ctx.fillRect(0, safeBottom, W, H - safeBottom);
// Header
ctx.fillStyle = '#fff';
ctx.font = 'bold 24px Arial';
ctx.textAlign = 'left';
ctx.fillText(`Score: ${score}`, 20, 35);
ctx.textAlign = 'right';
ctx.fillText(`Time: ${timeLeft}s`, W - 20, 35);
ctx.textAlign = 'center';
ctx.fillText('WHACK-A-MOLE', W / 2, 35);
// Grass background
ctx.fillStyle = '#4CAF50';
ctx.fillRect(gameArea.x, gameArea.y, gameArea.w, gameArea.h);
// Draw holes
holes.forEach((h, i) => {
// Hole shadow
ctx.fillStyle = '#2E1B0F';
ctx.beginPath();
ctx.ellipse(h.x + h.w / 2, h.y + h.h * 0.7, h.w / 2, h.h / 3, 0, 0, Math.PI * 2);
ctx.fill();
// Mole
if (i === currentMole && moleState !== 'down') {
const moleH = h.h * 0.8 * (moleY / 100);
const moleW = h.w * 0.7;
const moleX = h.x + (h.w - moleW) / 2;
const moleBottom = h.y + h.h * 0.7;
const moleTop = moleBottom - moleH;
// Mole body
ctx.fillStyle = moleState === 'hit' ? '#ff6b6b' : '#8D6E63';
ctx.beginPath();
ctx.ellipse(moleX + moleW / 2, moleTop + moleH / 2, moleW / 2, moleH / 2, 0, 0, Math.PI * 2);
ctx.fill();
// Eyes
if (moleY > 50) {
ctx.fillStyle = '#fff';
ctx.beginPath();
ctx.arc(moleX + moleW * 0.35, moleTop + moleH * 0.35, 8, 0, Math.PI * 2);
ctx.arc(moleX + moleW * 0.65, moleTop + moleH * 0.35, 8, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = moleState === 'hit' ? '#ff0000' : '#000';
ctx.beginPath();
ctx.arc(moleX + moleW * 0.35, moleTop + moleH * 0.35, 4, 0, Math.PI * 2);
ctx.arc(moleX + moleW * 0.65, moleTop + moleH * 0.35, 4, 0, Math.PI * 2);
ctx.fill();
// Nose
ctx.fillStyle = '#E91E63';
ctx.beginPath();
ctx.arc(moleX + moleW / 2, moleTop + moleH * 0.55, 6, 0, Math.PI * 2);
ctx.fill();
}
}
// Hole front (covers mole bottom)
ctx.fillStyle = '#3E2723';
ctx.beginPath();
ctx.ellipse(h.x + h.w / 2, h.y + h.h * 0.7, h.w / 2, h.h / 4, 0, 0, Math.PI);
ctx.fill();
});
if (gameOver) {
ctx.fillStyle = 'rgba(0,0,0,0.8)';
ctx.fillRect(0, 0, W, H);
ctx.fillStyle = '#fff';
ctx.font = 'bold 40px Arial';
ctx.textAlign = 'center';
ctx.fillText("TIME'S UP!", W / 2, H / 2 - 40);
ctx.font = '28px Arial';
ctx.fillText(`Final Score: ${score}`, W / 2, H / 2 + 10);
ctx.fillStyle = '#4CAF50';
ctx.beginPath();
ctx.roundRect(W / 2 - 100, H / 2 + 40, 200, 50, 10);
ctx.fill();
ctx.fillStyle = '#fff';
ctx.font = 'bold 18px Arial';
ctx.fillText('TAP TO PLAY AGAIN', W / 2, H / 2 + 72);
}
}
function gameLoop() {
update();
draw();
requestAnimationFrame(gameLoop);
}
window.gameScore = 0;
Object.defineProperty(window, 'gameScore', { get: () => score });
window.pauseGame = () => { gameRunning = false; musicPlaying = false; };
window.resumeGame = () => { if (!gameOver) { gameRunning = true; startMusic(); } };
window.setMusicEnabled = (v) => { musicEnabled = v; if (!v) musicPlaying = false; };
window.setSfxEnabled = (v) => { sfxEnabled = v; };
init();
gameLoop();
</script>
</body>
</html>