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
+542
View File
@@ -0,0 +1,542 @@
<!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>Asteroids</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { width: 100%; height: 100%; overflow: hidden; background: #000; 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();
}
// Space adventure melody
const melody = [
{n: 330, d: 0.3}, {n: 392, d: 0.3}, {n: 440, d: 0.3}, {n: 523, d: 0.6},
{n: 440, d: 0.3}, {n: 392, d: 0.3}, {n: 330, d: 0.6},
{n: 294, d: 0.3}, {n: 330, d: 0.3}, {n: 392, d: 0.3}, {n: 440, d: 0.6},
{n: 392, d: 0.3}, {n: 330, d: 0.3}, {n: 294, d: 0.6}, {n: 0, d: 0.3}
];
const bass = [
{n: 82, d: 0.6}, {n: 98, d: 0.6}, {n: 110, d: 0.6}, {n: 131, d: 0.6},
{n: 73, d: 0.6}, {n: 82, d: 0.6}, {n: 98, d: 0.6}, {n: 110, d: 0.6}
];
let melodyIdx = 0, bassIdx = 0, nextMelody = 0, nextBass = 0;
function playTone(freq, dur, type = 'square', vol = 0.1) {
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 === 'shoot') {
osc.frequency.setValueAtTime(800, audioCtx.currentTime);
osc.frequency.exponentialRampToValueAtTime(200, audioCtx.currentTime + 0.1);
gain.gain.setValueAtTime(0.15, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.1);
} else if (type === 'explode') {
osc.frequency.setValueAtTime(150, audioCtx.currentTime);
osc.frequency.exponentialRampToValueAtTime(50, audioCtx.currentTime + 0.3);
gain.gain.setValueAtTime(0.25, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.3);
osc.type = 'sawtooth';
} else if (type === 'die') {
osc.frequency.setValueAtTime(400, audioCtx.currentTime);
osc.frequency.exponentialRampToValueAtTime(80, audioCtx.currentTime + 0.5);
gain.gain.setValueAtTime(0.3, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.5);
osc.type = 'sawtooth';
}
osc.connect(gain);
gain.connect(audioCtx.destination);
osc.start();
osc.stop(audioCtx.currentTime + 0.6);
}
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.08);
nextMelody = now + note.d;
melodyIdx = (melodyIdx + 1) % melody.length;
}
if (now >= nextBass) {
const note = bass[bassIdx];
playTone(note.n, note.d, 'triangle', 0.12);
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;
function resize() {
W = window.innerWidth;
H = window.innerHeight;
canvas.width = W;
canvas.height = H;
safeTop = H * 0.1;
safeBottom = H * 0.9;
}
resize();
window.addEventListener('resize', resize);
let gameRunning = false;
let gameOver = false;
let score = 0;
let lives = 3;
let level = 1;
let gameTime = 0;
const ship = { x: 0, y: 0, angle: 0, dx: 0, dy: 0, size: 20, invincible: 0 };
let asteroids = [];
let bullets = [];
let particles = [];
let keys = { left: false, right: false, up: false, shoot: false };
let lastShot = 0;
let touchTarget = null;
function spawnAsteroids(count, size = 'large') {
for (let i = 0; i < count; i++) {
let x, y;
do {
x = Math.random() * W;
y = safeTop + Math.random() * (safeBottom - safeTop);
} while (Math.hypot(x - ship.x, y - ship.y) < 150);
const speed = size === 'large' ? 0.8 : size === 'medium' ? 1.2 : 1.8;
const r = size === 'large' ? 40 : size === 'medium' ? 25 : 15;
const angle = Math.random() * Math.PI * 2;
asteroids.push({
x, y, r, size,
dx: Math.cos(angle) * speed,
dy: Math.sin(angle) * speed,
vertices: generateAsteroidShape(r)
});
}
}
function generateAsteroidShape(r) {
const vertices = [];
const points = 8 + Math.floor(Math.random() * 4);
for (let i = 0; i < points; i++) {
const angle = (i / points) * Math.PI * 2;
const dist = r * (0.7 + Math.random() * 0.3);
vertices.push({ x: Math.cos(angle) * dist, y: Math.sin(angle) * dist });
}
return vertices;
}
function init() {
ship.x = W / 2;
ship.y = (safeTop + safeBottom) / 2;
ship.angle = -Math.PI / 2;
ship.dx = 0;
ship.dy = 0;
ship.invincible = 180;
asteroids = [];
bullets = [];
particles = [];
score = 0;
lives = 3;
level = 1;
gameTime = 0;
spawnAsteroids(3);
gameOver = false;
gameRunning = true;
startMusic();
}
function shoot() {
if (Date.now() - lastShot < 250) return;
lastShot = Date.now();
bullets.push({
x: ship.x + Math.cos(ship.angle) * ship.size,
y: ship.y + Math.sin(ship.angle) * ship.size,
dx: Math.cos(ship.angle) * 8,
dy: Math.sin(ship.angle) * 8,
life: 60
});
playSfx('shoot');
}
function addParticles(x, y, count, color) {
for (let i = 0; i < count; i++) {
const angle = Math.random() * Math.PI * 2;
const speed = 1 + Math.random() * 3;
particles.push({
x, y,
dx: Math.cos(angle) * speed,
dy: Math.sin(angle) * speed,
life: 30 + Math.random() * 20,
color
});
}
}
document.addEventListener('keydown', e => {
initAudio();
if (e.key === 'ArrowLeft' || e.key === 'a') keys.left = true;
if (e.key === 'ArrowRight' || e.key === 'd') keys.right = true;
if (e.key === 'ArrowUp' || e.key === 'w') keys.up = true;
if (e.key === ' ') { keys.shoot = true; e.preventDefault(); }
if ((e.key === ' ' || e.key === 'Enter') && gameOver) init();
});
document.addEventListener('keyup', e => {
if (e.key === 'ArrowLeft' || e.key === 'a') keys.left = false;
if (e.key === 'ArrowRight' || e.key === 'd') keys.right = false;
if (e.key === 'ArrowUp' || e.key === 'w') keys.up = false;
if (e.key === ' ') keys.shoot = false;
});
canvas.addEventListener('click', e => {
initAudio();
if (gameOver) { init(); return; }
shoot();
});
canvas.addEventListener('mousemove', e => {
if (!gameRunning || gameOver) return;
touchTarget = { x: e.clientX, y: e.clientY };
});
canvas.addEventListener('touchstart', e => {
e.preventDefault();
initAudio();
if (gameOver) { init(); return; }
touchTarget = { x: e.touches[0].clientX, y: e.touches[0].clientY };
shoot();
});
canvas.addEventListener('touchmove', e => {
e.preventDefault();
touchTarget = { x: e.touches[0].clientX, y: e.touches[0].clientY };
});
canvas.addEventListener('touchend', () => {
touchTarget = null;
});
function update() {
if (!gameRunning || gameOver) return;
gameTime++;
// Ship rotation (keyboard)
if (keys.left) ship.angle -= 0.08;
if (keys.right) ship.angle += 0.08;
// Touch controls - point toward target
if (touchTarget) {
const targetAngle = Math.atan2(touchTarget.y - ship.y, touchTarget.x - ship.x);
let diff = targetAngle - ship.angle;
while (diff > Math.PI) diff -= Math.PI * 2;
while (diff < -Math.PI) diff += Math.PI * 2;
ship.angle += diff * 0.1;
keys.up = true;
} else if (!keys.up) {
keys.up = false;
}
// Thrust
if (keys.up) {
ship.dx += Math.cos(ship.angle) * 0.15;
ship.dy += Math.sin(ship.angle) * 0.15;
}
// Friction
ship.dx *= 0.99;
ship.dy *= 0.99;
// Move ship
ship.x += ship.dx;
ship.y += ship.dy;
// Wrap around
if (ship.x < 0) ship.x = W;
if (ship.x > W) ship.x = 0;
if (ship.y < safeTop) ship.y = safeBottom;
if (ship.y > safeBottom) ship.y = safeTop;
// Auto-shoot on keyboard
if (keys.shoot) shoot();
// Update invincibility
if (ship.invincible > 0) ship.invincible--;
// Update bullets
for (let i = bullets.length - 1; i >= 0; i--) {
const b = bullets[i];
b.x += b.dx;
b.y += b.dy;
b.life--;
// Wrap
if (b.x < 0) b.x = W;
if (b.x > W) b.x = 0;
if (b.y < safeTop) b.y = safeBottom;
if (b.y > safeBottom) b.y = safeTop;
if (b.life <= 0) bullets.splice(i, 1);
}
// Update asteroids
for (let i = asteroids.length - 1; i >= 0; i--) {
const a = asteroids[i];
a.x += a.dx;
a.y += a.dy;
// Wrap
if (a.x < -a.r) a.x = W + a.r;
if (a.x > W + a.r) a.x = -a.r;
if (a.y < safeTop - a.r) a.y = safeBottom + a.r;
if (a.y > safeBottom + a.r) a.y = safeTop - a.r;
// Check bullet collision
for (let j = bullets.length - 1; j >= 0; j--) {
const b = bullets[j];
if (Math.hypot(b.x - a.x, b.y - a.y) < a.r) {
bullets.splice(j, 1);
addParticles(a.x, a.y, 10, '#ff0');
playSfx('explode');
// Split asteroid
if (a.size === 'large') {
score += 20;
for (let k = 0; k < 2; k++) {
asteroids.push({
x: a.x, y: a.y, r: 25, size: 'medium',
dx: (Math.random() - 0.5) * 2.5,
dy: (Math.random() - 0.5) * 2.5,
vertices: generateAsteroidShape(25)
});
}
} else if (a.size === 'medium') {
score += 50;
for (let k = 0; k < 2; k++) {
asteroids.push({
x: a.x, y: a.y, r: 15, size: 'small',
dx: (Math.random() - 0.5) * 3,
dy: (Math.random() - 0.5) * 3,
vertices: generateAsteroidShape(15)
});
}
} else {
score += 100;
}
asteroids.splice(i, 1);
break;
}
}
}
// Check ship collision (generous for children)
if (ship.invincible <= 0) {
for (const a of asteroids) {
if (Math.hypot(ship.x - a.x, ship.y - a.y) < a.r + ship.size - 15) {
lives--;
ship.invincible = 180;
addParticles(ship.x, ship.y, 20, '#0ff');
playSfx('die');
if (lives <= 0) {
gameOver = true;
gameRunning = false;
musicPlaying = false;
}
break;
}
}
}
// Update particles
for (let i = particles.length - 1; i >= 0; i--) {
const p = particles[i];
p.x += p.dx;
p.y += p.dy;
p.life--;
if (p.life <= 0) particles.splice(i, 1);
}
// Spawn more asteroids when cleared
if (asteroids.length === 0) {
level++;
// Progressive difficulty - more asteroids, but capped for fairness
const count = Math.min(3 + level, 8);
spawnAsteroids(count);
}
updateMusic();
}
function draw() {
// Background
ctx.fillStyle = '#000';
ctx.fillRect(0, 0, W, H);
// Safe zones
ctx.fillStyle = '#111';
ctx.fillRect(0, 0, W, safeTop);
ctx.fillRect(0, safeBottom, W, H - safeBottom);
// Stars
ctx.fillStyle = '#fff';
for (let i = 0; i < 50; i++) {
const x = (i * 137) % W;
const y = safeTop + ((i * 251) % (safeBottom - safeTop));
ctx.fillRect(x, y, 2, 2);
}
// Header
ctx.fillStyle = '#fff';
ctx.font = 'bold 22px Arial';
ctx.textAlign = 'left';
ctx.fillText(`Score: ${score}`, 20, 35);
ctx.textAlign = 'center';
ctx.fillText(`Level ${level}`, W / 2, 35);
ctx.textAlign = 'right';
ctx.fillText(`Lives: ${'*'.repeat(lives)}`, W - 20, 35);
// Particles
for (const p of particles) {
ctx.fillStyle = p.color;
ctx.globalAlpha = p.life / 50;
ctx.fillRect(p.x - 2, p.y - 2, 4, 4);
}
ctx.globalAlpha = 1;
// Asteroids
ctx.strokeStyle = '#888';
ctx.lineWidth = 2;
for (const a of asteroids) {
ctx.beginPath();
ctx.moveTo(a.x + a.vertices[0].x, a.y + a.vertices[0].y);
for (let i = 1; i < a.vertices.length; i++) {
ctx.lineTo(a.x + a.vertices[i].x, a.y + a.vertices[i].y);
}
ctx.closePath();
ctx.stroke();
}
// Bullets
ctx.fillStyle = '#ff0';
for (const b of bullets) {
ctx.beginPath();
ctx.arc(b.x, b.y, 3, 0, Math.PI * 2);
ctx.fill();
}
// Ship
if (ship.invincible <= 0 || Math.floor(ship.invincible / 5) % 2 === 0) {
ctx.save();
ctx.translate(ship.x, ship.y);
ctx.rotate(ship.angle);
ctx.strokeStyle = '#0ff';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(ship.size, 0);
ctx.lineTo(-ship.size * 0.7, -ship.size * 0.6);
ctx.lineTo(-ship.size * 0.4, 0);
ctx.lineTo(-ship.size * 0.7, ship.size * 0.6);
ctx.closePath();
ctx.stroke();
// Thrust flame
if (keys.up) {
ctx.fillStyle = '#f80';
ctx.beginPath();
ctx.moveTo(-ship.size * 0.5, -ship.size * 0.3);
ctx.lineTo(-ship.size * 0.5, ship.size * 0.3);
ctx.lineTo(-ship.size - Math.random() * 10, 0);
ctx.closePath();
ctx.fill();
}
ctx.restore();
}
if (gameOver) {
ctx.fillStyle = 'rgba(0,0,0,0.85)';
ctx.fillRect(0, 0, W, H);
ctx.fillStyle = '#fff';
ctx.font = 'bold 40px Arial';
ctx.textAlign = 'center';
ctx.fillText('GAME OVER', W / 2, H / 2 - 50);
ctx.font = '24px Arial';
ctx.fillText(`Final Score: ${score}`, W / 2, H / 2);
ctx.fillStyle = '#0ff';
ctx.beginPath();
ctx.roundRect(W / 2 - 100, H / 2 + 30, 200, 50, 10);
ctx.fill();
ctx.fillStyle = '#000';
ctx.font = 'bold 18px Arial';
ctx.fillText('TAP TO PLAY AGAIN', W / 2, H / 2 + 62);
}
}
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>
+536
View File
@@ -0,0 +1,536 @@
<!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>Block Drop</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { width: 100%; height: 100%; overflow: hidden; background: #1a1a2e; 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();
}
// Classic puzzle melody
const melody = [
{n: 659, d: 0.2}, {n: 494, d: 0.1}, {n: 523, d: 0.1}, {n: 587, d: 0.2},
{n: 523, d: 0.1}, {n: 494, d: 0.1}, {n: 440, d: 0.2}, {n: 440, d: 0.1},
{n: 523, d: 0.1}, {n: 659, d: 0.2}, {n: 587, d: 0.1}, {n: 523, d: 0.1},
{n: 494, d: 0.3}, {n: 523, d: 0.1}, {n: 587, d: 0.2}, {n: 659, d: 0.2},
{n: 523, d: 0.2}, {n: 440, d: 0.2}, {n: 440, d: 0.3}, {n: 0, d: 0.2}
];
const bass = [
{n: 165, d: 0.4}, {n: 110, d: 0.4}, {n: 131, d: 0.4}, {n: 165, d: 0.4},
{n: 147, d: 0.4}, {n: 110, d: 0.4}, {n: 131, 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.1) {
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 === 'move') {
osc.frequency.value = 200;
gain.gain.setValueAtTime(0.1, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.05);
} else if (type === 'rotate') {
osc.frequency.value = 400;
gain.gain.setValueAtTime(0.12, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.08);
} else if (type === 'drop') {
osc.frequency.setValueAtTime(300, audioCtx.currentTime);
osc.frequency.exponentialRampToValueAtTime(100, audioCtx.currentTime + 0.15);
gain.gain.setValueAtTime(0.2, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.15);
} else if (type === 'clear') {
osc.frequency.setValueAtTime(523, audioCtx.currentTime);
osc.frequency.setValueAtTime(659, audioCtx.currentTime + 0.1);
osc.frequency.setValueAtTime(784, audioCtx.currentTime + 0.2);
gain.gain.setValueAtTime(0.25, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.3);
} else if (type === 'gameover') {
osc.frequency.setValueAtTime(400, audioCtx.currentTime);
osc.frequency.exponentialRampToValueAtTime(100, audioCtx.currentTime + 0.5);
gain.gain.setValueAtTime(0.3, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.5);
osc.type = 'sawtooth';
}
osc.connect(gain);
gain.connect(audioCtx.destination);
osc.start();
osc.stop(audioCtx.currentTime + 0.6);
}
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.08);
nextMelody = now + note.d;
melodyIdx = (melodyIdx + 1) % melody.length;
}
if (now >= nextBass) {
const note = bass[bassIdx];
playTone(note.n, note.d, 'triangle', 0.12);
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;
function resize() {
W = window.innerWidth;
H = window.innerHeight;
canvas.width = W;
canvas.height = H;
safeTop = H * 0.1;
safeBottom = H * 0.9;
}
resize();
window.addEventListener('resize', resize);
const COLS = 10;
const ROWS = 20;
let cellSize, boardX, boardY, boardW, boardH;
function calcBoard() {
const availH = safeBottom - safeTop - 40;
const availW = W - 40;
cellSize = Math.min(availW / COLS, availH / ROWS);
boardW = cellSize * COLS;
boardH = cellSize * ROWS;
boardX = (W - boardW) / 2;
boardY = safeTop + 20 + (availH - boardH) / 2;
}
const SHAPES = [
{ shape: [[1,1,1,1]], color: '#00f0f0' }, // I
{ shape: [[1,1],[1,1]], color: '#f0f000' }, // O
{ shape: [[0,1,0],[1,1,1]], color: '#a000f0' }, // T
{ shape: [[1,0,0],[1,1,1]], color: '#0000f0' }, // J
{ shape: [[0,0,1],[1,1,1]], color: '#f0a000' }, // L
{ shape: [[0,1,1],[1,1,0]], color: '#00f000' }, // S
{ shape: [[1,1,0],[0,1,1]], color: '#f00000' } // Z
];
let board = [];
let currentPiece = null;
let pieceX = 0, pieceY = 0;
let score = 0;
let level = 1;
let lines = 0;
let gameRunning = false;
let gameOver = false;
let dropTimer = 0;
let dropInterval = 60; // Very slow start (60 frames = 1 second at 60fps)
function createBoard() {
board = [];
for (let r = 0; r < ROWS; r++) {
board.push(new Array(COLS).fill(null));
}
}
function newPiece() {
const idx = Math.floor(Math.random() * SHAPES.length);
currentPiece = {
shape: SHAPES[idx].shape.map(row => [...row]),
color: SHAPES[idx].color
};
pieceX = Math.floor((COLS - currentPiece.shape[0].length) / 2);
pieceY = 0;
if (!isValidPosition(pieceX, pieceY, currentPiece.shape)) {
gameOver = true;
gameRunning = false;
musicPlaying = false;
playSfx('gameover');
}
}
function isValidPosition(x, y, shape) {
for (let r = 0; r < shape.length; r++) {
for (let c = 0; c < shape[r].length; c++) {
if (shape[r][c]) {
const newX = x + c;
const newY = y + r;
if (newX < 0 || newX >= COLS || newY >= ROWS) return false;
if (newY >= 0 && board[newY][newX]) return false;
}
}
}
return true;
}
function rotatePiece() {
const rows = currentPiece.shape.length;
const cols = currentPiece.shape[0].length;
const rotated = [];
for (let c = 0; c < cols; c++) {
rotated.push([]);
for (let r = rows - 1; r >= 0; r--) {
rotated[c].push(currentPiece.shape[r][c]);
}
}
// Try rotation with wall kicks
const kicks = [0, 1, -1, 2, -2];
for (const kick of kicks) {
if (isValidPosition(pieceX + kick, pieceY, rotated)) {
currentPiece.shape = rotated;
pieceX += kick;
playSfx('rotate');
return;
}
}
}
function movePiece(dx) {
if (isValidPosition(pieceX + dx, pieceY, currentPiece.shape)) {
pieceX += dx;
playSfx('move');
}
}
function dropPiece() {
if (isValidPosition(pieceX, pieceY + 1, currentPiece.shape)) {
pieceY++;
return true;
}
return false;
}
function hardDrop() {
while (dropPiece()) {}
lockPiece();
playSfx('drop');
}
function lockPiece() {
for (let r = 0; r < currentPiece.shape.length; r++) {
for (let c = 0; c < currentPiece.shape[r].length; c++) {
if (currentPiece.shape[r][c]) {
const boardY = pieceY + r;
const boardX = pieceX + c;
if (boardY >= 0) {
board[boardY][boardX] = currentPiece.color;
}
}
}
}
clearLines();
newPiece();
}
function clearLines() {
let cleared = 0;
for (let r = ROWS - 1; r >= 0; r--) {
if (board[r].every(cell => cell !== null)) {
board.splice(r, 1);
board.unshift(new Array(COLS).fill(null));
cleared++;
r++;
}
}
if (cleared > 0) {
const points = [0, 100, 300, 500, 800];
score += points[cleared] * level;
lines += cleared;
playSfx('clear');
// Level up every 10 lines
const newLevel = Math.floor(lines / 10) + 1;
if (newLevel > level) {
level = newLevel;
// Gradual speed increase - starts very slow
dropInterval = Math.max(10, 60 - (level - 1) * 5);
}
}
}
function init() {
calcBoard();
createBoard();
score = 0;
level = 1;
lines = 0;
dropInterval = 60; // 1 second drop at start
dropTimer = 0;
gameOver = false;
gameRunning = true;
newPiece();
startMusic();
}
let keys = { left: false, right: false, down: false };
let keyRepeat = { left: 0, right: 0, down: 0 };
document.addEventListener('keydown', e => {
initAudio();
if (e.key === 'ArrowLeft' || e.key === 'a') keys.left = true;
if (e.key === 'ArrowRight' || e.key === 'd') keys.right = true;
if (e.key === 'ArrowDown' || e.key === 's') keys.down = true;
if (e.key === 'ArrowUp' || e.key === 'w') { rotatePiece(); e.preventDefault(); }
if (e.key === ' ') { hardDrop(); e.preventDefault(); }
if ((e.key === ' ' || e.key === 'Enter') && gameOver) init();
});
document.addEventListener('keyup', e => {
if (e.key === 'ArrowLeft' || e.key === 'a') { keys.left = false; keyRepeat.left = 0; }
if (e.key === 'ArrowRight' || e.key === 'd') { keys.right = false; keyRepeat.right = 0; }
if (e.key === 'ArrowDown' || e.key === 's') { keys.down = false; keyRepeat.down = 0; }
});
let touchStartX = 0, touchStartY = 0;
canvas.addEventListener('click', e => {
initAudio();
if (gameOver) init();
});
canvas.addEventListener('touchstart', e => {
e.preventDefault();
initAudio();
if (gameOver) { init(); return; }
touchStartX = e.touches[0].clientX;
touchStartY = e.touches[0].clientY;
});
canvas.addEventListener('touchmove', e => {
e.preventDefault();
const dx = e.touches[0].clientX - touchStartX;
const dy = e.touches[0].clientY - touchStartY;
if (Math.abs(dx) > 30) {
movePiece(dx > 0 ? 1 : -1);
touchStartX = e.touches[0].clientX;
}
if (dy > 30) {
dropPiece();
touchStartY = e.touches[0].clientY;
}
});
canvas.addEventListener('touchend', e => {
const dx = e.changedTouches[0].clientX - touchStartX;
const dy = e.changedTouches[0].clientY - touchStartY;
// Tap to rotate
if (Math.abs(dx) < 10 && Math.abs(dy) < 10) {
rotatePiece();
}
// Swipe up for hard drop
if (dy < -50 && Math.abs(dx) < 30) {
hardDrop();
}
});
function update() {
if (!gameRunning || gameOver) return;
// Key repeat
if (keys.left) {
keyRepeat.left++;
if (keyRepeat.left === 1 || (keyRepeat.left > 15 && keyRepeat.left % 3 === 0)) {
movePiece(-1);
}
}
if (keys.right) {
keyRepeat.right++;
if (keyRepeat.right === 1 || (keyRepeat.right > 15 && keyRepeat.right % 3 === 0)) {
movePiece(1);
}
}
if (keys.down) {
keyRepeat.down++;
if (keyRepeat.down % 3 === 0) {
if (!dropPiece()) lockPiece();
}
}
// Auto drop
dropTimer++;
if (dropTimer >= dropInterval) {
dropTimer = 0;
if (!dropPiece()) {
lockPiece();
}
}
updateMusic();
}
function draw() {
calcBoard();
// Background
ctx.fillStyle = '#1a1a2e';
ctx.fillRect(0, 0, W, H);
// Safe zones
ctx.fillStyle = '#16213e';
ctx.fillRect(0, 0, W, safeTop);
ctx.fillRect(0, safeBottom, W, H - safeBottom);
// Header
ctx.fillStyle = '#fff';
ctx.font = 'bold 20px Arial';
ctx.textAlign = 'left';
ctx.fillText(`Score: ${score}`, 15, 35);
ctx.textAlign = 'center';
ctx.fillText(`Level ${level}`, W / 2, 35);
ctx.textAlign = 'right';
ctx.fillText(`Lines: ${lines}`, W - 15, 35);
// Board background
ctx.fillStyle = '#0f0f23';
ctx.fillRect(boardX, boardY, boardW, boardH);
// Grid
ctx.strokeStyle = '#222';
ctx.lineWidth = 1;
for (let r = 0; r <= ROWS; r++) {
ctx.beginPath();
ctx.moveTo(boardX, boardY + r * cellSize);
ctx.lineTo(boardX + boardW, boardY + r * cellSize);
ctx.stroke();
}
for (let c = 0; c <= COLS; c++) {
ctx.beginPath();
ctx.moveTo(boardX + c * cellSize, boardY);
ctx.lineTo(boardX + c * cellSize, boardY + boardH);
ctx.stroke();
}
// Placed blocks
for (let r = 0; r < ROWS; r++) {
for (let c = 0; c < COLS; c++) {
if (board[r][c]) {
ctx.fillStyle = board[r][c];
ctx.fillRect(boardX + c * cellSize + 1, boardY + r * cellSize + 1, cellSize - 2, cellSize - 2);
}
}
}
// Current piece
if (currentPiece && !gameOver) {
ctx.fillStyle = currentPiece.color;
for (let r = 0; r < currentPiece.shape.length; r++) {
for (let c = 0; c < currentPiece.shape[r].length; c++) {
if (currentPiece.shape[r][c]) {
const x = boardX + (pieceX + c) * cellSize + 1;
const y = boardY + (pieceY + r) * cellSize + 1;
ctx.fillRect(x, y, cellSize - 2, cellSize - 2);
}
}
}
// Ghost piece
let ghostY = pieceY;
while (isValidPosition(pieceX, ghostY + 1, currentPiece.shape)) {
ghostY++;
}
if (ghostY > pieceY) {
ctx.strokeStyle = currentPiece.color;
ctx.lineWidth = 2;
ctx.globalAlpha = 0.3;
for (let r = 0; r < currentPiece.shape.length; r++) {
for (let c = 0; c < currentPiece.shape[r].length; c++) {
if (currentPiece.shape[r][c]) {
const x = boardX + (pieceX + c) * cellSize + 2;
const y = boardY + (ghostY + r) * cellSize + 2;
ctx.strokeRect(x, y, cellSize - 4, cellSize - 4);
}
}
}
ctx.globalAlpha = 1;
}
}
// Controls hint
ctx.fillStyle = '#666';
ctx.font = '14px Arial';
ctx.textAlign = 'center';
ctx.fillText('Swipe/Arrows: Move | Tap/Up: Rotate | Swipe Up/Space: Drop', W / 2, safeBottom + 25);
if (gameOver) {
ctx.fillStyle = 'rgba(0,0,0,0.85)';
ctx.fillRect(0, 0, W, H);
ctx.fillStyle = '#fff';
ctx.font = 'bold 40px Arial';
ctx.textAlign = 'center';
ctx.fillText('GAME OVER', W / 2, H / 2 - 50);
ctx.font = '24px Arial';
ctx.fillText(`Score: ${score}`, W / 2, H / 2);
ctx.fillText(`Lines: ${lines}`, W / 2, H / 2 + 35);
ctx.fillStyle = '#00f0f0';
ctx.beginPath();
ctx.roundRect(W / 2 - 100, H / 2 + 60, 200, 50, 10);
ctx.fill();
ctx.fillStyle = '#000';
ctx.font = 'bold 18px Arial';
ctx.fillText('TAP TO PLAY AGAIN', W / 2, H / 2 + 92);
}
}
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>
+426
View File
@@ -0,0 +1,426 @@
<!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>Brick Breaker</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body {
width: 100%; height: 100%; overflow: hidden;
background: #1a1a2e; touch-action: none;
}
canvas { display: block; }
</style>
</head>
<body>
<canvas id="game"></canvas>
<script>
// Audio Context
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();
}
// Catchy melody - Super Mario style bouncy tune
const melodyNotes = [
{n: 659, d: 0.15}, {n: 659, d: 0.15}, {n: 0, d: 0.15}, {n: 659, d: 0.15},
{n: 0, d: 0.15}, {n: 523, d: 0.15}, {n: 659, d: 0.3}, {n: 784, d: 0.3},
{n: 0, d: 0.3}, {n: 392, d: 0.3}, {n: 0, d: 0.3},
{n: 523, d: 0.2}, {n: 0, d: 0.1}, {n: 392, d: 0.2}, {n: 0, d: 0.1}, {n: 330, d: 0.2},
{n: 0, d: 0.1}, {n: 440, d: 0.2}, {n: 494, d: 0.2}, {n: 466, d: 0.1}, {n: 440, d: 0.2},
{n: 392, d: 0.2}, {n: 659, d: 0.2}, {n: 784, d: 0.2}, {n: 880, d: 0.2},
{n: 698, d: 0.15}, {n: 784, d: 0.15}, {n: 0, d: 0.15}, {n: 659, d: 0.2},
{n: 523, d: 0.15}, {n: 587, d: 0.15}, {n: 494, d: 0.2}, {n: 0, d: 0.2}
];
const bassNotes = [
{n: 131, d: 0.3}, {n: 165, d: 0.3}, {n: 196, d: 0.3}, {n: 165, d: 0.3},
{n: 131, d: 0.3}, {n: 165, d: 0.3}, {n: 196, d: 0.3}, {n: 247, d: 0.3},
{n: 175, d: 0.3}, {n: 220, d: 0.3}, {n: 262, d: 0.3}, {n: 220, d: 0.3},
{n: 196, d: 0.3}, {n: 247, d: 0.3}, {n: 294, d: 0.3}, {n: 247, d: 0.3}
];
let melodyIndex = 0, bassIndex = 0;
let nextMelodyTime = 0, nextBassTime = 0;
function playTone(freq, duration, type = 'square', volume = 0.15) {
if (!audioCtx || !musicEnabled || freq === 0) return;
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
osc.type = type;
osc.frequency.value = freq;
gain.gain.setValueAtTime(volume, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + duration * 0.9);
osc.connect(gain);
gain.connect(audioCtx.destination);
osc.start();
osc.stop(audioCtx.currentTime + duration);
}
function playSfx(type) {
if (!audioCtx || !sfxEnabled) return;
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
if (type === 'hit') {
osc.frequency.setValueAtTime(520, audioCtx.currentTime);
osc.frequency.exponentialRampToValueAtTime(320, 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 === 'break') {
osc.frequency.setValueAtTime(800, audioCtx.currentTime);
osc.frequency.exponentialRampToValueAtTime(200, audioCtx.currentTime + 0.15);
gain.gain.setValueAtTime(0.25, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.15);
osc.type = 'sawtooth';
} else if (type === 'die') {
osc.frequency.setValueAtTime(400, audioCtx.currentTime);
osc.frequency.exponentialRampToValueAtTime(100, audioCtx.currentTime + 0.4);
gain.gain.setValueAtTime(0.3, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.4);
osc.type = 'sawtooth';
} else if (type === 'win') {
osc.frequency.setValueAtTime(523, audioCtx.currentTime);
osc.frequency.setValueAtTime(659, audioCtx.currentTime + 0.1);
osc.frequency.setValueAtTime(784, audioCtx.currentTime + 0.2);
gain.gain.setValueAtTime(0.2, audioCtx.currentTime);
gain.gain.setValueAtTime(0.2, audioCtx.currentTime + 0.2);
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.4);
osc.type = 'square';
}
osc.connect(gain);
gain.connect(audioCtx.destination);
osc.start();
osc.stop(audioCtx.currentTime + 0.5);
}
function updateMusic() {
if (!audioCtx || !musicEnabled || !musicPlaying) return;
const now = audioCtx.currentTime;
if (now >= nextMelodyTime) {
const note = melodyNotes[melodyIndex];
playTone(note.n, note.d, 'square', 0.12);
nextMelodyTime = now + note.d;
melodyIndex = (melodyIndex + 1) % melodyNotes.length;
}
if (now >= nextBassTime) {
const note = bassNotes[bassIndex];
playTone(note.n, note.d, 'triangle', 0.2);
nextBassTime = now + note.d;
bassIndex = (bassIndex + 1) % bassNotes.length;
}
}
function startMusic() {
if (!musicPlaying && musicEnabled) {
initAudio();
musicPlaying = true;
nextMelodyTime = audioCtx.currentTime;
nextBassTime = audioCtx.currentTime;
}
}
// Game setup
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
let W, H, safeTop, safeBottom, gameH, gameTop;
function resize() {
W = window.innerWidth;
H = window.innerHeight;
canvas.width = W;
canvas.height = H;
safeTop = H * 0.1;
safeBottom = H * 0.9;
gameH = safeBottom - safeTop;
gameTop = safeTop;
}
resize();
window.addEventListener('resize', resize);
// Game state
let gameRunning = false;
let gameOver = false;
let score = 0;
let lives = 5;
let level = 1;
let ballSpeed = 3;
// Paddle
const paddle = { w: 120, h: 15 };
paddle.x = W / 2 - paddle.w / 2;
paddle.y = safeBottom - 50;
// Ball
const ball = { r: 10, x: W / 2, y: safeBottom - 100, dx: 0, dy: 0 };
// Bricks
let bricks = [];
const brickRows = 5;
const brickCols = 8;
const brickH = 25;
const brickPadding = 6;
const colors = ['#ff6b6b', '#feca57', '#48dbfb', '#ff9ff3', '#54a0ff'];
function initBricks() {
bricks = [];
const brickW = (W - brickPadding * (brickCols + 1)) / brickCols;
for (let r = 0; r < brickRows; r++) {
for (let c = 0; c < brickCols; c++) {
bricks.push({
x: c * (brickW + brickPadding) + brickPadding,
y: gameTop + 40 + r * (brickH + brickPadding),
w: brickW,
h: brickH,
color: colors[r],
alive: true
});
}
}
}
function resetBall() {
ball.x = paddle.x + paddle.w / 2;
ball.y = paddle.y - ball.r - 5;
const angle = -Math.PI / 2 + (Math.random() - 0.5) * Math.PI / 3;
ball.dx = Math.cos(angle) * ballSpeed;
ball.dy = Math.sin(angle) * ballSpeed;
}
function init() {
score = 0;
lives = 5;
level = 1;
ballSpeed = 3; // Start very slow
paddle.x = W / 2 - paddle.w / 2;
paddle.y = safeBottom - 50;
initBricks();
resetBall();
gameOver = false;
gameRunning = true;
startMusic();
}
// Controls
let targetX = W / 2;
let keys = { left: false, right: false };
document.addEventListener('keydown', e => {
if (e.key === 'ArrowLeft' || e.key === 'a') keys.left = true;
if (e.key === 'ArrowRight' || e.key === 'd') keys.right = true;
if (e.key === ' ' || e.key === 'Enter') {
if (gameOver) init();
}
initAudio();
});
document.addEventListener('keyup', e => {
if (e.key === 'ArrowLeft' || e.key === 'a') keys.left = false;
if (e.key === 'ArrowRight' || e.key === 'd') keys.right = false;
});
canvas.addEventListener('mousemove', e => {
targetX = e.clientX;
});
canvas.addEventListener('click', e => {
initAudio();
if (gameOver) init();
});
canvas.addEventListener('touchstart', e => {
e.preventDefault();
initAudio();
if (gameOver) init();
targetX = e.touches[0].clientX;
});
canvas.addEventListener('touchmove', e => {
e.preventDefault();
targetX = e.touches[0].clientX;
});
function update() {
if (!gameRunning || gameOver) return;
// Paddle movement
if (keys.left) paddle.x -= 10;
if (keys.right) paddle.x += 10;
// Mouse/touch follow
const diff = targetX - (paddle.x + paddle.w / 2);
paddle.x += diff * 0.15;
// Bounds
if (paddle.x < 0) paddle.x = 0;
if (paddle.x + paddle.w > W) paddle.x = W - paddle.w;
// Ball movement
ball.x += ball.dx;
ball.y += ball.dy;
// Wall collision
if (ball.x - ball.r < 0 || ball.x + ball.r > W) {
ball.dx = -ball.dx;
ball.x = Math.max(ball.r, Math.min(W - ball.r, ball.x));
}
if (ball.y - ball.r < gameTop) {
ball.dy = -ball.dy;
ball.y = gameTop + ball.r;
}
// Paddle collision (generous hitbox)
if (ball.dy > 0 &&
ball.y + ball.r >= paddle.y - 5 &&
ball.y < paddle.y + paddle.h &&
ball.x >= paddle.x - 15 &&
ball.x <= paddle.x + paddle.w + 15) {
ball.dy = -Math.abs(ball.dy);
const hitPos = (ball.x - paddle.x) / paddle.w;
ball.dx = (hitPos - 0.5) * ballSpeed * 2;
ball.y = paddle.y - ball.r - 1;
playSfx('hit');
}
// Brick collision
for (let brick of bricks) {
if (!brick.alive) continue;
if (ball.x + ball.r > brick.x - 5 &&
ball.x - ball.r < brick.x + brick.w + 5 &&
ball.y + ball.r > brick.y - 5 &&
ball.y - ball.r < brick.y + brick.h + 5) {
brick.alive = false;
ball.dy = -ball.dy;
score += 10;
playSfx('break');
// Check level complete
if (bricks.every(b => !b.alive)) {
level++;
ballSpeed = Math.min(8, 3 + level * 0.5); // Gradual speed increase
initBricks();
resetBall();
playSfx('win');
}
break;
}
}
// Ball lost
if (ball.y > H) {
lives--;
playSfx('die');
if (lives <= 0) {
gameOver = true;
gameRunning = false;
} else {
resetBall();
}
}
updateMusic();
}
function draw() {
// Background
ctx.fillStyle = '#1a1a2e';
ctx.fillRect(0, 0, W, H);
// Safe zone indicators (subtle)
ctx.fillStyle = '#16213e';
ctx.fillRect(0, 0, W, safeTop);
ctx.fillRect(0, safeBottom, W, H - safeBottom);
// Score and lives
ctx.fillStyle = '#fff';
ctx.font = 'bold 20px Arial';
ctx.textAlign = 'left';
ctx.fillText(`Score: ${score}`, 15, 30);
ctx.textAlign = 'right';
ctx.fillText(`Lives: ${'❤️'.repeat(lives)}`, W - 15, 30);
ctx.textAlign = 'center';
ctx.fillText(`Level ${level}`, W / 2, 30);
// Bricks
for (let brick of bricks) {
if (!brick.alive) continue;
ctx.fillStyle = brick.color;
ctx.beginPath();
ctx.roundRect(brick.x, brick.y, brick.w, brick.h, 5);
ctx.fill();
}
// Paddle
ctx.fillStyle = '#00ffff';
ctx.shadowColor = '#00ffff';
ctx.shadowBlur = 15;
ctx.beginPath();
ctx.roundRect(paddle.x, paddle.y, paddle.w, paddle.h, 8);
ctx.fill();
ctx.shadowBlur = 0;
// Ball
ctx.fillStyle = '#fff';
ctx.shadowColor = '#fff';
ctx.shadowBlur = 10;
ctx.beginPath();
ctx.arc(ball.x, ball.y, ball.r, 0, Math.PI * 2);
ctx.fill();
ctx.shadowBlur = 0;
// Game over screen
if (gameOver) {
ctx.fillStyle = 'rgba(0,0,0,0.8)';
ctx.fillRect(0, 0, W, H);
ctx.fillStyle = '#fff';
ctx.font = 'bold 36px Arial';
ctx.textAlign = 'center';
ctx.fillText('GAME OVER', W / 2, H / 2 - 40);
ctx.font = '24px Arial';
ctx.fillText(`Final Score: ${score}`, W / 2, H / 2 + 10);
// Play again button
ctx.fillStyle = '#00ffff';
ctx.beginPath();
ctx.roundRect(W / 2 - 100, H / 2 + 40, 200, 50, 10);
ctx.fill();
ctx.fillStyle = '#000';
ctx.font = 'bold 20px Arial';
ctx.fillText('TAP TO PLAY AGAIN', W / 2, H / 2 + 72);
}
}
function gameLoop() {
update();
draw();
requestAnimationFrame(gameLoop);
}
// Window interface
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; };
// Start
init();
gameLoop();
</script>
</body>
</html>
+487
View File
@@ -0,0 +1,487 @@
<!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>Bridge Builder</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { width: 100%; height: 100%; overflow: hidden; background: #87ceeb; 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();
}
// Construction melody
const melody = [
{n: 392, d: 0.3}, {n: 440, d: 0.3}, {n: 494, d: 0.3}, {n: 523, d: 0.6},
{n: 494, d: 0.3}, {n: 440, d: 0.3}, {n: 392, d: 0.6},
{n: 349, d: 0.3}, {n: 392, d: 0.3}, {n: 440, d: 0.3}, {n: 494, d: 0.6},
{n: 523, d: 0.3}, {n: 587, d: 0.3}, {n: 523, d: 0.6}, {n: 0, d: 0.3}
];
const bass = [
{n: 98, d: 0.6}, {n: 110, d: 0.6}, {n: 131, d: 0.6}, {n: 110, d: 0.6},
{n: 87, d: 0.6}, {n: 98, d: 0.6}, {n: 110, d: 0.6}, {n: 131, d: 0.6}
];
let melodyIdx = 0, bassIdx = 0, nextMelody = 0, nextBass = 0;
function playTone(freq, dur, type = 'square', vol = 0.08) {
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 === 'build') {
osc.frequency.value = 500;
gain.gain.setValueAtTime(0.1, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.1);
} else if (type === 'release') {
osc.frequency.setValueAtTime(400, audioCtx.currentTime);
osc.frequency.exponentialRampToValueAtTime(200, audioCtx.currentTime + 0.15);
gain.gain.setValueAtTime(0.15, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.15);
} else if (type === 'success') {
osc.frequency.setValueAtTime(523, audioCtx.currentTime);
osc.frequency.setValueAtTime(659, audioCtx.currentTime + 0.15);
osc.frequency.setValueAtTime(784, audioCtx.currentTime + 0.3);
gain.gain.setValueAtTime(0.2, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.45);
} else if (type === 'fail') {
osc.frequency.setValueAtTime(300, audioCtx.currentTime);
osc.frequency.exponentialRampToValueAtTime(100, audioCtx.currentTime + 0.3);
gain.gain.setValueAtTime(0.2, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.3);
osc.type = 'sawtooth';
}
osc.connect(gain);
gain.connect(audioCtx.destination);
osc.start();
osc.stop(audioCtx.currentTime + 0.5);
}
function updateMusic() {
if (!audioCtx || !musicEnabled || !musicPlaying) return;
const now = audioCtx.currentTime;
if (now >= nextMelody) {
const note = melody[melodyIdx];
playTone(note.n, note.d, 'triangle', 0.06);
nextMelody = now + note.d;
melodyIdx = (melodyIdx + 1) % melody.length;
}
if (now >= nextBass) {
const note = bass[bassIdx];
playTone(note.n, note.d, 'triangle', 0.1);
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;
function resize() {
W = window.innerWidth;
H = window.innerHeight;
canvas.width = W;
canvas.height = H;
safeTop = H * 0.1;
safeBottom = H * 0.9;
}
resize();
window.addEventListener('resize', resize);
let gameRunning = false;
let gameOver = false;
let levelComplete = false;
let score = 0;
let level = 1;
// Game state
let building = false;
let bridgeAngle = 0;
let bridgeLength = 0;
let testingBridge = false;
let car = null;
let leftCliff = { x: 0, y: 0, w: 0, h: 0 };
let rightCliff = { x: 0, y: 0, w: 0, h: 0 };
let gap = 0;
function generateLevel() {
const availH = safeBottom - safeTop;
const cliffY = safeTop + availH * 0.6;
const cliffH = safeBottom - cliffY + 50;
// Gap increases with level
gap = 100 + level * 30 + Math.random() * 50;
const leftW = 80 + Math.random() * 60;
const rightW = 80 + Math.random() * 60;
leftCliff = { x: 0, y: cliffY, w: W / 2 - gap / 2, h: cliffH };
rightCliff = { x: W / 2 + gap / 2, y: cliffY, w: W - (W / 2 + gap / 2), h: cliffH };
bridgeAngle = 0;
bridgeLength = 0;
building = false;
testingBridge = false;
car = null;
levelComplete = false;
}
function init() {
score = 0;
level = 1;
gameOver = false;
gameRunning = true;
generateLevel();
startMusic();
}
function startBuilding() {
if (testingBridge || levelComplete || gameOver) return;
building = true;
bridgeLength = 0;
playSfx('build');
}
function stopBuilding() {
if (!building) return;
building = false;
testingBridge = true;
playSfx('release');
// Start the car
car = {
x: leftCliff.w - 40,
y: leftCliff.y - 20,
w: 40,
h: 20,
vx: 2,
vy: 0,
onBridge: false,
finished: false
};
}
document.addEventListener('keydown', e => {
initAudio();
if (e.key === ' ' || e.key === 'Enter') {
e.preventDefault();
if (gameOver) init();
else if (levelComplete) {
level++;
generateLevel();
} else if (!building && !testingBridge) {
startBuilding();
}
}
});
document.addEventListener('keyup', e => {
if (e.key === ' ' || e.key === 'Enter') {
if (building) stopBuilding();
}
});
canvas.addEventListener('mousedown', e => {
initAudio();
if (gameOver) init();
else if (levelComplete) { level++; generateLevel(); }
else if (!building && !testingBridge) startBuilding();
});
canvas.addEventListener('mouseup', () => {
if (building) stopBuilding();
});
canvas.addEventListener('touchstart', e => {
e.preventDefault();
initAudio();
if (gameOver) init();
else if (levelComplete) { level++; generateLevel(); }
else if (!building && !testingBridge) startBuilding();
});
canvas.addEventListener('touchend', e => {
e.preventDefault();
if (building) stopBuilding();
});
function update() {
if (!gameRunning || gameOver) return;
// Build bridge
if (building) {
bridgeLength += 2;
// Slight random wobble while building
bridgeAngle = Math.sin(bridgeLength * 0.1) * 0.02;
}
// Test bridge with car
if (testingBridge && car) {
const bridgeStartX = leftCliff.w;
const bridgeStartY = leftCliff.y;
const bridgeEndX = bridgeStartX + bridgeLength;
const bridgeEndY = bridgeStartY + Math.tan(bridgeAngle) * bridgeLength;
// Car physics
car.vy += 0.3; // Gravity
car.x += car.vx;
car.y += car.vy;
// Check if car is on cliff
if (car.x + car.w > 0 && car.x < leftCliff.w && car.y + car.h >= leftCliff.y) {
car.y = leftCliff.y - car.h;
car.vy = 0;
}
// Check if car is on bridge
if (car.x + car.w > bridgeStartX && car.x < bridgeEndX) {
const t = (car.x + car.w / 2 - bridgeStartX) / bridgeLength;
const bridgeY = bridgeStartY + t * (bridgeEndY - bridgeStartY);
if (car.y + car.h >= bridgeY - 5) {
car.y = bridgeY - car.h;
car.vy = 0;
car.onBridge = true;
}
}
// Check if car reached other side
if (car.x >= rightCliff.x && car.y + car.h >= rightCliff.y - 10) {
car.y = rightCliff.y - car.h;
car.vy = 0;
car.vx = 0;
car.finished = true;
// Success!
levelComplete = true;
const bonus = Math.max(0, 100 - Math.abs(bridgeLength - gap));
score += 100 + bonus + level * 10;
playSfx('success');
}
// Check if car fell
if (car.y > H) {
// Failed!
gameOver = true;
gameRunning = false;
musicPlaying = false;
playSfx('fail');
}
}
updateMusic();
}
function draw() {
// Sky gradient
const skyGrad = ctx.createLinearGradient(0, 0, 0, H);
skyGrad.addColorStop(0, '#87ceeb');
skyGrad.addColorStop(1, '#e0f7fa');
ctx.fillStyle = skyGrad;
ctx.fillRect(0, 0, W, H);
// Safe zones
ctx.fillStyle = '#6bb9f0';
ctx.fillRect(0, 0, W, safeTop);
ctx.fillStyle = '#5d4037';
ctx.fillRect(0, safeBottom, W, H - safeBottom);
// Clouds
ctx.fillStyle = '#fff';
for (let i = 0; i < 3; i++) {
const x = 100 + i * 200;
const y = safeTop + 40 + i * 20;
ctx.beginPath();
ctx.arc(x, y, 25, 0, Math.PI * 2);
ctx.arc(x + 30, y - 10, 30, 0, Math.PI * 2);
ctx.arc(x + 60, y, 25, 0, Math.PI * 2);
ctx.fill();
}
// Header
ctx.fillStyle = '#333';
ctx.font = 'bold 22px Arial';
ctx.textAlign = 'left';
ctx.fillText(`Score: ${score}`, 20, 35);
ctx.textAlign = 'center';
ctx.fillText(`Level ${level}`, W / 2, 35);
ctx.textAlign = 'right';
ctx.fillText(`Gap: ${Math.round(gap)}px`, W - 20, 35);
// Water/void
ctx.fillStyle = '#1e88e5';
ctx.fillRect(leftCliff.w, leftCliff.y + 30, rightCliff.x - leftCliff.w, safeBottom - leftCliff.y);
// Left cliff
ctx.fillStyle = '#5d4037';
ctx.fillRect(leftCliff.x, leftCliff.y, leftCliff.w, leftCliff.h);
ctx.fillStyle = '#8bc34a';
ctx.fillRect(leftCliff.x, leftCliff.y - 15, leftCliff.w, 20);
// Right cliff
ctx.fillStyle = '#5d4037';
ctx.fillRect(rightCliff.x, rightCliff.y, rightCliff.w, rightCliff.h);
ctx.fillStyle = '#8bc34a';
ctx.fillRect(rightCliff.x, rightCliff.y - 15, rightCliff.w, 20);
// Bridge
if (bridgeLength > 0) {
const bridgeStartX = leftCliff.w;
const bridgeStartY = leftCliff.y;
ctx.save();
ctx.translate(bridgeStartX, bridgeStartY);
ctx.rotate(bridgeAngle);
ctx.fillStyle = '#8B4513';
ctx.fillRect(0, -8, bridgeLength, 8);
// Bridge planks
ctx.fillStyle = '#654321';
for (let i = 0; i < bridgeLength; i += 15) {
ctx.fillRect(i, -8, 3, 8);
}
ctx.restore();
}
// Building indicator
if (!testingBridge && !levelComplete && !gameOver) {
ctx.fillStyle = building ? '#4caf50' : '#ff9800';
ctx.font = 'bold 18px Arial';
ctx.textAlign = 'center';
ctx.fillText(
building ? `Building: ${bridgeLength}px (Release to test!)` : 'Hold to build bridge!',
W / 2, leftCliff.y - 50
);
// Target marker
ctx.strokeStyle = '#4caf50';
ctx.lineWidth = 2;
ctx.setLineDash([5, 5]);
ctx.beginPath();
ctx.moveTo(leftCliff.w, leftCliff.y - 20);
ctx.lineTo(rightCliff.x, rightCliff.y - 20);
ctx.stroke();
ctx.setLineDash([]);
}
// Car
if (car) {
ctx.fillStyle = '#e74c3c';
ctx.fillRect(car.x, car.y, car.w, car.h);
// Wheels
ctx.fillStyle = '#333';
ctx.beginPath();
ctx.arc(car.x + 8, car.y + car.h, 6, 0, Math.PI * 2);
ctx.arc(car.x + car.w - 8, car.y + car.h, 6, 0, Math.PI * 2);
ctx.fill();
// Window
ctx.fillStyle = '#87ceeb';
ctx.fillRect(car.x + 10, car.y + 3, car.w - 15, car.h - 8);
}
// Controls hint
ctx.fillStyle = '#666';
ctx.font = '14px Arial';
ctx.textAlign = 'center';
ctx.fillText('Hold Space/Tap to build bridge, release to test!', W / 2, safeBottom + 25);
if (levelComplete) {
ctx.fillStyle = 'rgba(0,0,0,0.7)';
ctx.fillRect(0, 0, W, H);
ctx.fillStyle = '#4caf50';
ctx.font = 'bold 36px Arial';
ctx.textAlign = 'center';
ctx.fillText('LEVEL COMPLETE!', W / 2, H / 2 - 50);
ctx.fillStyle = '#fff';
ctx.font = '24px Arial';
ctx.fillText(`Bridge: ${bridgeLength}px | Gap: ${Math.round(gap)}px`, W / 2, H / 2);
ctx.fillStyle = '#4caf50';
ctx.beginPath();
ctx.roundRect(W / 2 - 100, H / 2 + 30, 200, 50, 10);
ctx.fill();
ctx.fillStyle = '#fff';
ctx.font = 'bold 18px Arial';
ctx.fillText('NEXT LEVEL', W / 2, H / 2 + 62);
}
if (gameOver) {
ctx.fillStyle = 'rgba(0,0,0,0.85)';
ctx.fillRect(0, 0, W, H);
ctx.fillStyle = '#e74c3c';
ctx.font = 'bold 36px Arial';
ctx.textAlign = 'center';
ctx.fillText('BRIDGE TOO SHORT!', W / 2, H / 2 - 50);
ctx.fillStyle = '#fff';
ctx.font = '24px Arial';
ctx.fillText(`Final Score: ${score}`, W / 2, H / 2);
ctx.fillStyle = '#2196f3';
ctx.beginPath();
ctx.roundRect(W / 2 - 100, H / 2 + 30, 200, 50, 10);
ctx.fill();
ctx.fillStyle = '#fff';
ctx.font = 'bold 18px Arial';
ctx.fillText('TAP TO RESTART', W / 2, H / 2 + 62);
}
}
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 && !levelComplete) { gameRunning = true; startMusic(); } };
window.setMusicEnabled = (v) => { musicEnabled = v; if (!v) musicPlaying = false; };
window.setSfxEnabled = (v) => { sfxEnabled = v; };
init();
gameLoop();
</script>
</body>
</html>
+557
View File
@@ -0,0 +1,557 @@
<!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>Connect Four</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { width: 100%; height: 100%; overflow: hidden; background: #1a237e; 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();
}
// Fun board game melody
const melody = [
{n: 523, d: 0.25}, {n: 587, d: 0.25}, {n: 659, d: 0.25}, {n: 698, d: 0.5},
{n: 659, d: 0.25}, {n: 587, d: 0.25}, {n: 523, d: 0.5},
{n: 440, d: 0.25}, {n: 494, d: 0.25}, {n: 523, d: 0.25}, {n: 587, d: 0.5},
{n: 523, d: 0.25}, {n: 494, d: 0.25}, {n: 440, d: 0.5}, {n: 0, d: 0.25}
];
const bass = [
{n: 131, d: 0.5}, {n: 165, d: 0.5}, {n: 131, d: 0.5}, {n: 110, d: 0.5},
{n: 110, d: 0.5}, {n: 131, d: 0.5}, {n: 110, d: 0.5}, {n: 98, d: 0.5}
];
let melodyIdx = 0, bassIdx = 0, nextMelody = 0, nextBass = 0;
function playTone(freq, dur, type = 'square', vol = 0.08) {
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 === 'drop') {
osc.frequency.setValueAtTime(600, audioCtx.currentTime);
osc.frequency.exponentialRampToValueAtTime(200, audioCtx.currentTime + 0.15);
gain.gain.setValueAtTime(0.2, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.15);
} else if (type === 'win') {
osc.frequency.setValueAtTime(523, audioCtx.currentTime);
osc.frequency.setValueAtTime(659, audioCtx.currentTime + 0.15);
osc.frequency.setValueAtTime(784, audioCtx.currentTime + 0.3);
osc.frequency.setValueAtTime(1047, audioCtx.currentTime + 0.45);
gain.gain.setValueAtTime(0.25, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.6);
} else if (type === 'lose') {
osc.frequency.setValueAtTime(400, audioCtx.currentTime);
osc.frequency.setValueAtTime(300, audioCtx.currentTime + 0.15);
osc.frequency.setValueAtTime(200, audioCtx.currentTime + 0.3);
gain.gain.setValueAtTime(0.2, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.45);
} else if (type === 'draw') {
osc.frequency.value = 350;
gain.gain.setValueAtTime(0.15, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.3);
}
osc.type = 'square';
osc.connect(gain);
gain.connect(audioCtx.destination);
osc.start();
osc.stop(audioCtx.currentTime + 0.7);
}
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.06);
nextMelody = now + note.d;
melodyIdx = (melodyIdx + 1) % melody.length;
}
if (now >= nextBass) {
const note = bass[bassIdx];
playTone(note.n, note.d, 'triangle', 0.1);
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;
function resize() {
W = window.innerWidth;
H = window.innerHeight;
canvas.width = W;
canvas.height = H;
safeTop = H * 0.1;
safeBottom = H * 0.9;
}
resize();
window.addEventListener('resize', resize);
const COLS = 7;
const ROWS = 6;
let board = [];
let currentPlayer = 1; // 1 = player (red), 2 = CPU (yellow)
let gameOver = false;
let winner = 0;
let gameRunning = false;
let hoverCol = -1;
let cpuThinking = false;
let winningCells = [];
let aiDifficulty = 0.5; // Start easy, increase over time
function createBoard() {
board = [];
for (let r = 0; r < ROWS; r++) {
board.push(new Array(COLS).fill(0));
}
}
function init() {
createBoard();
currentPlayer = 1;
gameOver = false;
winner = 0;
winningCells = [];
cpuThinking = false;
aiDifficulty = 0.3; // Easy start
gameRunning = true;
startMusic();
}
function getLowestRow(col) {
for (let r = ROWS - 1; r >= 0; r--) {
if (board[r][col] === 0) return r;
}
return -1;
}
function dropPiece(col, player) {
const row = getLowestRow(col);
if (row === -1) return false;
board[row][col] = player;
playSfx('drop');
return true;
}
function checkWin(player) {
// Horizontal
for (let r = 0; r < ROWS; r++) {
for (let c = 0; c <= COLS - 4; c++) {
if (board[r][c] === player && board[r][c+1] === player &&
board[r][c+2] === player && board[r][c+3] === player) {
winningCells = [[r,c], [r,c+1], [r,c+2], [r,c+3]];
return true;
}
}
}
// Vertical
for (let r = 0; r <= ROWS - 4; r++) {
for (let c = 0; c < COLS; c++) {
if (board[r][c] === player && board[r+1][c] === player &&
board[r+2][c] === player && board[r+3][c] === player) {
winningCells = [[r,c], [r+1,c], [r+2,c], [r+3,c]];
return true;
}
}
}
// Diagonal down-right
for (let r = 0; r <= ROWS - 4; r++) {
for (let c = 0; c <= COLS - 4; c++) {
if (board[r][c] === player && board[r+1][c+1] === player &&
board[r+2][c+2] === player && board[r+3][c+3] === player) {
winningCells = [[r,c], [r+1,c+1], [r+2,c+2], [r+3,c+3]];
return true;
}
}
}
// Diagonal up-right
for (let r = 3; r < ROWS; r++) {
for (let c = 0; c <= COLS - 4; c++) {
if (board[r][c] === player && board[r-1][c+1] === player &&
board[r-2][c+2] === player && board[r-3][c+3] === player) {
winningCells = [[r,c], [r-1,c+1], [r-2,c+2], [r-3,c+3]];
return true;
}
}
}
return false;
}
function isBoardFull() {
return board[0].every(cell => cell !== 0);
}
function evaluateWindow(window, player) {
const opp = player === 1 ? 2 : 1;
const playerCount = window.filter(c => c === player).length;
const oppCount = window.filter(c => c === opp).length;
const emptyCount = window.filter(c => c === 0).length;
if (playerCount === 4) return 100;
if (playerCount === 3 && emptyCount === 1) return 5;
if (playerCount === 2 && emptyCount === 2) return 2;
if (oppCount === 3 && emptyCount === 1) return -4;
return 0;
}
function scorePosition(player) {
let score = 0;
// Center column preference
const centerCol = board.map(row => row[3]).filter(c => c === player).length;
score += centerCol * 3;
// Horizontal
for (let r = 0; r < ROWS; r++) {
for (let c = 0; c <= COLS - 4; c++) {
const window = [board[r][c], board[r][c+1], board[r][c+2], board[r][c+3]];
score += evaluateWindow(window, player);
}
}
// Vertical
for (let c = 0; c < COLS; c++) {
for (let r = 0; r <= ROWS - 4; r++) {
const window = [board[r][c], board[r+1][c], board[r+2][c], board[r+3][c]];
score += evaluateWindow(window, player);
}
}
// Diagonals
for (let r = 0; r <= ROWS - 4; r++) {
for (let c = 0; c <= COLS - 4; c++) {
const window = [board[r][c], board[r+1][c+1], board[r+2][c+2], board[r+3][c+3]];
score += evaluateWindow(window, player);
}
}
for (let r = 3; r < ROWS; r++) {
for (let c = 0; c <= COLS - 4; c++) {
const window = [board[r][c], board[r-1][c+1], board[r-2][c+2], board[r-3][c+3]];
score += evaluateWindow(window, player);
}
}
return score;
}
function cpuMove() {
if (gameOver || currentPlayer !== 2) return;
cpuThinking = true;
setTimeout(() => {
// Sometimes make random move (easier for kids)
if (Math.random() > aiDifficulty) {
const validCols = [];
for (let c = 0; c < COLS; c++) {
if (getLowestRow(c) !== -1) validCols.push(c);
}
const col = validCols[Math.floor(Math.random() * validCols.length)];
dropPiece(col, 2);
} else {
// Smart move
let bestScore = -Infinity;
let bestCol = 3;
for (let c = 0; c < COLS; c++) {
const row = getLowestRow(c);
if (row === -1) continue;
board[row][c] = 2;
// Check for winning move
if (checkWin(2)) {
board[row][c] = 0;
winningCells = [];
bestCol = c;
break;
}
// Check if blocking player win
board[row][c] = 1;
if (checkWin(1)) {
board[row][c] = 0;
winningCells = [];
bestCol = c;
break;
}
board[row][c] = 2;
const score = scorePosition(2);
board[row][c] = 0;
if (score > bestScore) {
bestScore = score;
bestCol = c;
}
}
winningCells = [];
dropPiece(bestCol, 2);
}
cpuThinking = false;
if (checkWin(2)) {
gameOver = true;
winner = 2;
gameRunning = false;
playSfx('lose');
} else if (isBoardFull()) {
gameOver = true;
winner = 0;
gameRunning = false;
playSfx('draw');
} else {
currentPlayer = 1;
}
// Gradually increase difficulty
aiDifficulty = Math.min(0.8, aiDifficulty + 0.02);
}, 800);
}
function playerMove(col) {
if (gameOver || currentPlayer !== 1 || cpuThinking) return;
if (getLowestRow(col) === -1) return;
dropPiece(col, 1);
if (checkWin(1)) {
gameOver = true;
winner = 1;
gameRunning = false;
playSfx('win');
} else if (isBoardFull()) {
gameOver = true;
winner = 0;
gameRunning = false;
playSfx('draw');
} else {
currentPlayer = 2;
cpuMove();
}
}
canvas.addEventListener('mousemove', e => {
const availH = safeBottom - safeTop - 60;
const availW = W - 40;
const cellSize = Math.min(availW / COLS, availH / ROWS);
const boardW = cellSize * COLS;
const boardX = (W - boardW) / 2;
hoverCol = Math.floor((e.clientX - boardX) / cellSize);
if (hoverCol < 0 || hoverCol >= COLS) hoverCol = -1;
});
canvas.addEventListener('click', e => {
initAudio();
if (gameOver) { init(); return; }
const availH = safeBottom - safeTop - 60;
const availW = W - 40;
const cellSize = Math.min(availW / COLS, availH / ROWS);
const boardW = cellSize * COLS;
const boardX = (W - boardW) / 2;
const col = Math.floor((e.clientX - boardX) / cellSize);
if (col >= 0 && col < COLS) playerMove(col);
});
canvas.addEventListener('touchstart', e => {
e.preventDefault();
initAudio();
if (gameOver) { init(); return; }
const availH = safeBottom - safeTop - 60;
const availW = W - 40;
const cellSize = Math.min(availW / COLS, availH / ROWS);
const boardW = cellSize * COLS;
const boardX = (W - boardW) / 2;
const col = Math.floor((e.touches[0].clientX - boardX) / cellSize);
if (col >= 0 && col < COLS) playerMove(col);
});
document.addEventListener('keydown', e => {
initAudio();
if ((e.key === ' ' || e.key === 'Enter') && gameOver) init();
if (e.key >= '1' && e.key <= '7' && !gameOver && currentPlayer === 1) {
playerMove(parseInt(e.key) - 1);
}
});
function update() {
updateMusic();
}
function draw() {
// Background
ctx.fillStyle = '#1a237e';
ctx.fillRect(0, 0, W, H);
// Safe zones
ctx.fillStyle = '#0d1657';
ctx.fillRect(0, 0, W, safeTop);
ctx.fillRect(0, safeBottom, W, H - safeBottom);
// Header
ctx.fillStyle = '#fff';
ctx.font = 'bold 24px Arial';
ctx.textAlign = 'center';
ctx.fillText('CONNECT FOUR', W / 2, 40);
const turnText = gameOver ? '' : (currentPlayer === 1 ? 'Your Turn (Red)' : 'CPU Thinking...');
ctx.font = '18px Arial';
ctx.fillStyle = currentPlayer === 1 ? '#ef5350' : '#ffca28';
ctx.fillText(turnText, W / 2, 65);
// Calculate board
const availH = safeBottom - safeTop - 80;
const availW = W - 40;
const cellSize = Math.min(availW / COLS, availH / ROWS);
const boardW = cellSize * COLS;
const boardH = cellSize * ROWS;
const boardX = (W - boardW) / 2;
const boardY = safeTop + 80 + (availH - boardH) / 2;
// Board background
ctx.fillStyle = '#1565c0';
ctx.beginPath();
ctx.roundRect(boardX - 10, boardY - 10, boardW + 20, boardH + 20, 10);
ctx.fill();
// Hover indicator
if (hoverCol >= 0 && hoverCol < COLS && currentPlayer === 1 && !gameOver && !cpuThinking) {
ctx.fillStyle = 'rgba(239, 83, 80, 0.5)';
ctx.beginPath();
ctx.arc(boardX + hoverCol * cellSize + cellSize / 2, boardY - 25, cellSize / 3, 0, Math.PI * 2);
ctx.fill();
}
// Cells
for (let r = 0; r < ROWS; r++) {
for (let c = 0; c < COLS; c++) {
const x = boardX + c * cellSize + cellSize / 2;
const y = boardY + r * cellSize + cellSize / 2;
const radius = cellSize * 0.4;
// Hole
ctx.fillStyle = '#0d47a1';
ctx.beginPath();
ctx.arc(x, y, radius, 0, Math.PI * 2);
ctx.fill();
// Piece
if (board[r][c] !== 0) {
const isWinning = winningCells.some(cell => cell[0] === r && cell[1] === c);
if (board[r][c] === 1) {
ctx.fillStyle = isWinning ? '#ff8a80' : '#ef5350';
} else {
ctx.fillStyle = isWinning ? '#fff59d' : '#ffca28';
}
ctx.beginPath();
ctx.arc(x, y, radius - 2, 0, Math.PI * 2);
ctx.fill();
// Highlight
ctx.fillStyle = 'rgba(255,255,255,0.3)';
ctx.beginPath();
ctx.arc(x - radius * 0.2, y - radius * 0.2, radius * 0.3, 0, Math.PI * 2);
ctx.fill();
}
}
}
// Controls hint
ctx.fillStyle = '#90caf9';
ctx.font = '14px Arial';
ctx.textAlign = 'center';
ctx.fillText('Tap column or press 1-7 to drop', W / 2, safeBottom + 25);
// Game over
if (gameOver) {
ctx.fillStyle = 'rgba(0,0,0,0.8)';
ctx.fillRect(0, 0, W, H);
ctx.font = 'bold 36px Arial';
ctx.textAlign = 'center';
if (winner === 1) {
ctx.fillStyle = '#4caf50';
ctx.fillText('YOU WIN!', W / 2, H / 2 - 30);
} else if (winner === 2) {
ctx.fillStyle = '#f44336';
ctx.fillText('CPU WINS!', W / 2, H / 2 - 30);
} else {
ctx.fillStyle = '#ffca28';
ctx.fillText("IT'S A DRAW!", W / 2, H / 2 - 30);
}
ctx.fillStyle = '#1565c0';
ctx.beginPath();
ctx.roundRect(W / 2 - 100, H / 2 + 20, 200, 50, 10);
ctx.fill();
ctx.fillStyle = '#fff';
ctx.font = 'bold 18px Arial';
ctx.fillText('TAP TO PLAY AGAIN', W / 2, H / 2 + 52);
}
}
function gameLoop() {
update();
draw();
requestAnimationFrame(gameLoop);
}
window.gameScore = 0;
Object.defineProperty(window, 'gameScore', { get: () => winner === 1 ? 100 : 0 });
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>
+361
View File
@@ -0,0 +1,361 @@
<!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>Dino Runner</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { width: 100%; height: 100%; overflow: hidden; background: #f7f7f7; touch-action: none; }
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();
}
// Happy running melody
const melody = [
{n: 523, d: 0.15}, {n: 587, d: 0.15}, {n: 659, d: 0.15}, {n: 698, d: 0.15},
{n: 784, d: 0.3}, {n: 659, d: 0.15}, {n: 523, d: 0.15},
{n: 587, d: 0.15}, {n: 659, d: 0.15}, {n: 587, d: 0.15}, {n: 523, d: 0.3},
{n: 494, d: 0.15}, {n: 523, d: 0.15}, {n: 587, d: 0.15}, {n: 659, d: 0.15},
{n: 698, d: 0.3}, {n: 784, d: 0.3}, {n: 880, d: 0.3}, {n: 784, d: 0.15},
{n: 698, d: 0.15}, {n: 659, d: 0.3}, {n: 0, d: 0.3}
];
const bass = [
{n: 131, d: 0.3}, {n: 165, d: 0.3}, {n: 196, d: 0.3}, {n: 165, d: 0.3},
{n: 147, d: 0.3}, {n: 175, d: 0.3}, {n: 196, d: 0.3}, {n: 220, d: 0.3},
{n: 131, d: 0.3}, {n: 165, d: 0.3}, {n: 196, d: 0.6}
];
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 === 'jump') {
osc.frequency.setValueAtTime(300, audioCtx.currentTime);
osc.frequency.exponentialRampToValueAtTime(600, audioCtx.currentTime + 0.15);
gain.gain.setValueAtTime(0.2, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.15);
} else if (type === 'die') {
osc.frequency.setValueAtTime(400, audioCtx.currentTime);
osc.frequency.exponentialRampToValueAtTime(80, audioCtx.currentTime + 0.5);
gain.gain.setValueAtTime(0.3, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.5);
} else if (type === 'point') {
osc.frequency.value = 880;
gain.gain.setValueAtTime(0.15, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.1);
}
osc.type = 'square';
osc.connect(gain);
gain.connect(audioCtx.destination);
osc.start();
osc.stop(audioCtx.currentTime + 0.6);
}
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, groundY, safeTop;
function resize() {
W = window.innerWidth;
H = window.innerHeight;
canvas.width = W;
canvas.height = H;
safeTop = H * 0.1;
groundY = H * 0.75;
}
resize();
window.addEventListener('resize', resize);
let gameRunning = false;
let gameOver = false;
let score = 0;
let highScore = 0;
let gameSpeed = 2.5;
let spawnTimer = 0;
let spawnInterval = 200;
const dino = { x: 80, y: 0, w: 50, h: 60, vy: 0, jumping: false };
const gravity = 0.5;
const jumpForce = -13;
let obstacles = [];
let clouds = [];
let groundOffset = 0;
function init() {
score = 0;
gameSpeed = 2.5;
spawnInterval = 200;
spawnTimer = 0;
dino.y = groundY - dino.h;
dino.vy = 0;
dino.jumping = false;
obstacles = [];
clouds = [];
for (let i = 0; i < 5; i++) {
clouds.push({ x: Math.random() * W, y: safeTop + 30 + Math.random() * 80, w: 60 + Math.random() * 40 });
}
gameOver = false;
gameRunning = true;
startMusic();
}
function jump() {
if (!dino.jumping && gameRunning && !gameOver) {
dino.vy = jumpForce;
dino.jumping = true;
playSfx('jump');
}
}
document.addEventListener('keydown', e => {
if (e.key === ' ' || e.key === 'ArrowUp') {
e.preventDefault();
initAudio();
if (gameOver) init();
else jump();
}
});
canvas.addEventListener('click', () => { initAudio(); if (gameOver) init(); else jump(); });
canvas.addEventListener('touchstart', e => { e.preventDefault(); initAudio(); if (gameOver) init(); else jump(); });
function spawnObstacle() {
const type = Math.random() < 0.7 ? 'cactus' : 'bird';
const h = type === 'cactus' ? 40 + Math.random() * 25 : 30;
const y = type === 'bird' ? groundY - 80 - Math.random() * 60 : groundY - h;
obstacles.push({ x: W + 50, y, w: 30, h, type });
}
function update() {
if (!gameRunning || gameOver) return;
// Dino physics
dino.vy += gravity;
dino.y += dino.vy;
if (dino.y >= groundY - dino.h) {
dino.y = groundY - dino.h;
dino.vy = 0;
dino.jumping = false;
}
// Spawn obstacles
spawnTimer++;
if (spawnTimer >= spawnInterval) {
spawnObstacle();
spawnTimer = 0;
spawnInterval = Math.max(80, 200 - score / 3);
}
// Move obstacles
for (let i = obstacles.length - 1; i >= 0; i--) {
obstacles[i].x -= gameSpeed;
if (obstacles[i].x + obstacles[i].w < 0) {
obstacles.splice(i, 1);
score++;
playSfx('point');
}
}
// Move clouds
clouds.forEach(c => {
c.x -= gameSpeed * 0.3;
if (c.x + c.w < 0) c.x = W + c.w;
});
// Ground scroll
groundOffset = (groundOffset + gameSpeed) % 20;
// Collision (generous hitbox)
for (let obs of obstacles) {
const padding = 12;
if (dino.x + dino.w - padding > obs.x + padding &&
dino.x + padding < obs.x + obs.w - padding &&
dino.y + dino.h - padding > obs.y + padding) {
gameOver = true;
gameRunning = false;
musicPlaying = false;
if (score > highScore) highScore = score;
playSfx('die');
return;
}
}
// Increase speed gradually
if (gameSpeed < 10) gameSpeed += 0.001;
updateMusic();
}
function draw() {
// Sky
ctx.fillStyle = '#87CEEB';
ctx.fillRect(0, 0, W, H);
// Safe zone
ctx.fillStyle = '#6BB9F0';
ctx.fillRect(0, 0, W, safeTop);
// Score
ctx.fillStyle = '#333';
ctx.font = 'bold 24px Arial';
ctx.textAlign = 'right';
ctx.fillText(`Score: ${score}`, W - 20, 35);
ctx.font = '16px Arial';
ctx.fillText(`Best: ${highScore}`, W - 20, 55);
// Clouds
ctx.fillStyle = '#fff';
clouds.forEach(c => {
ctx.beginPath();
ctx.arc(c.x, c.y, 20, 0, Math.PI * 2);
ctx.arc(c.x + 25, c.y - 10, 25, 0, Math.PI * 2);
ctx.arc(c.x + 50, c.y, 20, 0, Math.PI * 2);
ctx.fill();
});
// Ground
ctx.fillStyle = '#8B4513';
ctx.fillRect(0, groundY, W, H - groundY);
ctx.fillStyle = '#228B22';
ctx.fillRect(0, groundY - 10, W, 15);
// Ground detail
ctx.fillStyle = '#654321';
for (let x = -groundOffset; x < W; x += 20) {
ctx.fillRect(x, groundY + 5, 2, 10);
}
// Obstacles
obstacles.forEach(obs => {
if (obs.type === 'cactus') {
ctx.fillStyle = '#228B22';
ctx.fillRect(obs.x, obs.y, obs.w, obs.h);
ctx.fillRect(obs.x - 8, obs.y + 10, 8, 15);
ctx.fillRect(obs.x + obs.w, obs.y + 15, 8, 12);
} else {
ctx.fillStyle = '#8B4513';
ctx.beginPath();
ctx.ellipse(obs.x + obs.w / 2, obs.y + obs.h / 2, obs.w / 2, obs.h / 2, 0, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#FFA500';
ctx.beginPath();
ctx.moveTo(obs.x, obs.y + obs.h / 2);
ctx.lineTo(obs.x - 10, obs.y + obs.h / 2 - 5);
ctx.lineTo(obs.x - 10, obs.y + obs.h / 2 + 5);
ctx.fill();
}
});
// Dino
ctx.fillStyle = '#2ECC71';
ctx.fillRect(dino.x, dino.y, dino.w, dino.h);
// Eye
ctx.fillStyle = '#fff';
ctx.beginPath();
ctx.arc(dino.x + dino.w - 12, dino.y + 15, 8, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#000';
ctx.beginPath();
ctx.arc(dino.x + dino.w - 10, dino.y + 15, 4, 0, Math.PI * 2);
ctx.fill();
// Legs
const legOffset = dino.jumping ? 0 : Math.sin(Date.now() / 80) * 5;
ctx.fillStyle = '#2ECC71';
ctx.fillRect(dino.x + 8, dino.y + dino.h, 10, 12 + legOffset);
ctx.fillRect(dino.x + dino.w - 18, dino.y + dino.h, 10, 12 - legOffset);
if (gameOver) {
ctx.fillStyle = 'rgba(0,0,0,0.7)';
ctx.fillRect(0, 0, W, H);
ctx.fillStyle = '#fff';
ctx.font = 'bold 40px Arial';
ctx.textAlign = 'center';
ctx.fillText('GAME OVER', W / 2, H / 2 - 40);
ctx.font = '28px Arial';
ctx.fillText(`Score: ${score}`, W / 2, H / 2 + 10);
ctx.fillStyle = '#2ECC71';
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>
+496
View File
@@ -0,0 +1,496 @@
<!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>Fruit Slicer</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { width: 100%; height: 100%; overflow: hidden; background: #2d1b4e; 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();
}
// Upbeat fruity melody
const melody = [
{n: 523, d: 0.2}, {n: 587, d: 0.2}, {n: 659, d: 0.2}, {n: 698, d: 0.4},
{n: 659, d: 0.2}, {n: 587, d: 0.2}, {n: 523, d: 0.4},
{n: 698, d: 0.2}, {n: 784, d: 0.2}, {n: 880, d: 0.2}, {n: 784, d: 0.4},
{n: 698, d: 0.2}, {n: 659, d: 0.2}, {n: 587, d: 0.4}, {n: 0, d: 0.2}
];
const bass = [
{n: 131, d: 0.4}, {n: 165, d: 0.4}, {n: 175, d: 0.4}, {n: 165, d: 0.4},
{n: 147, d: 0.4}, {n: 131, d: 0.4}, {n: 165, d: 0.4}, {n: 196, d: 0.4}
];
let melodyIdx = 0, bassIdx = 0, nextMelody = 0, nextBass = 0;
function playTone(freq, dur, type = 'square', vol = 0.1) {
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 === 'slice') {
osc.frequency.setValueAtTime(800, audioCtx.currentTime);
osc.frequency.exponentialRampToValueAtTime(400, audioCtx.currentTime + 0.1);
gain.gain.setValueAtTime(0.15, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.1);
} else if (type === 'bomb') {
osc.frequency.setValueAtTime(200, audioCtx.currentTime);
osc.frequency.exponentialRampToValueAtTime(50, audioCtx.currentTime + 0.3);
gain.gain.setValueAtTime(0.3, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.3);
osc.type = 'sawtooth';
} else if (type === 'miss') {
osc.frequency.value = 150;
gain.gain.setValueAtTime(0.1, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.2);
}
osc.connect(gain);
gain.connect(audioCtx.destination);
osc.start();
osc.stop(audioCtx.currentTime + 0.4);
}
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.08);
nextMelody = now + note.d;
melodyIdx = (melodyIdx + 1) % melody.length;
}
if (now >= nextBass) {
const note = bass[bassIdx];
playTone(note.n, note.d, 'triangle', 0.12);
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;
function resize() {
W = window.innerWidth;
H = window.innerHeight;
canvas.width = W;
canvas.height = H;
safeTop = H * 0.1;
safeBottom = H * 0.9;
}
resize();
window.addEventListener('resize', resize);
const FRUITS = [
{ emoji: '🍎', color: '#ef4444', points: 10 },
{ emoji: '🍊', color: '#f97316', points: 10 },
{ emoji: '🍋', color: '#eab308', points: 10 },
{ emoji: '🍇', color: '#8b5cf6', points: 15 },
{ emoji: '🍉', color: '#22c55e', points: 15 },
{ emoji: '🍓', color: '#dc2626', points: 20 }
];
let gameRunning = false;
let gameOver = false;
let score = 0;
let lives = 3;
let combo = 0;
let spawnTimer = 0;
let gameTime = 0;
let fruits = [];
let slices = [];
let particles = [];
let trail = [];
function init() {
fruits = [];
slices = [];
particles = [];
trail = [];
score = 0;
lives = 3;
combo = 0;
spawnTimer = 0;
gameTime = 0;
gameOver = false;
gameRunning = true;
startMusic();
}
function spawnFruit() {
const isBomb = gameTime > 300 && Math.random() < 0.15; // No bombs early
const x = 50 + Math.random() * (W - 100);
const vx = (Math.random() - 0.5) * 4;
const vy = -12 - Math.random() * 4;
if (isBomb) {
fruits.push({
x, y: H + 50,
vx, vy,
size: 50,
isBomb: true,
sliced: false
});
} else {
const type = FRUITS[Math.floor(Math.random() * FRUITS.length)];
fruits.push({
x, y: H + 50,
vx, vy,
size: 60,
type,
sliced: false,
rotation: 0,
rotSpeed: (Math.random() - 0.5) * 0.1
});
}
}
function addParticles(x, y, color, count = 10) {
for (let i = 0; i < count; i++) {
const angle = Math.random() * Math.PI * 2;
const spd = 2 + Math.random() * 4;
particles.push({
x, y,
dx: Math.cos(angle) * spd,
dy: Math.sin(angle) * spd,
life: 30,
color,
size: 5 + Math.random() * 5
});
}
}
function addSlice(f) {
const halfSize = f.size / 2;
slices.push({
x: f.x - halfSize / 2, y: f.y,
vx: f.vx - 3, vy: f.vy - 2,
size: halfSize,
type: f.type,
rotation: f.rotation,
rotSpeed: -0.2
});
slices.push({
x: f.x + halfSize / 2, y: f.y,
vx: f.vx + 3, vy: f.vy - 2,
size: halfSize,
type: f.type,
rotation: f.rotation,
rotSpeed: 0.2
});
}
let isSlicing = false;
let lastX = 0, lastY = 0;
function startSlice(x, y) {
isSlicing = true;
lastX = x;
lastY = y;
trail = [{ x, y, time: Date.now() }];
}
function moveSlice(x, y) {
if (!isSlicing || gameOver) return;
trail.push({ x, y, time: Date.now() });
if (trail.length > 20) trail.shift();
// Check for fruit hits
for (let i = fruits.length - 1; i >= 0; i--) {
const f = fruits[i];
if (f.sliced) continue;
const dx = f.x - x;
const dy = f.y - y;
const dist = Math.sqrt(dx * dx + dy * dy);
if (dist < f.size / 2 + 20) { // Generous hitbox
if (f.isBomb) {
// Hit bomb!
lives--;
f.sliced = true;
addParticles(f.x, f.y, '#333', 20);
playSfx('bomb');
combo = 0;
if (lives <= 0) {
gameOver = true;
gameRunning = false;
musicPlaying = false;
}
} else {
// Slice fruit!
f.sliced = true;
combo++;
score += f.type.points * Math.min(combo, 5);
addSlice(f);
addParticles(f.x, f.y, f.type.color, 15);
playSfx('slice');
}
fruits.splice(i, 1);
}
}
lastX = x;
lastY = y;
}
function endSlice() {
isSlicing = false;
}
canvas.addEventListener('mousedown', e => { initAudio(); if (gameOver) init(); else startSlice(e.clientX, e.clientY); });
canvas.addEventListener('mousemove', e => moveSlice(e.clientX, e.clientY));
canvas.addEventListener('mouseup', endSlice);
canvas.addEventListener('touchstart', e => {
e.preventDefault();
initAudio();
if (gameOver) { init(); return; }
startSlice(e.touches[0].clientX, e.touches[0].clientY);
});
canvas.addEventListener('touchmove', e => {
e.preventDefault();
moveSlice(e.touches[0].clientX, e.touches[0].clientY);
});
canvas.addEventListener('touchend', endSlice);
document.addEventListener('keydown', e => {
initAudio();
if ((e.key === ' ' || e.key === 'Enter') && gameOver) init();
});
function update() {
if (!gameRunning || gameOver) return;
gameTime++;
// Spawn fruits
spawnTimer++;
const spawnInterval = Math.max(30, 90 - gameTime / 100);
if (spawnTimer >= spawnInterval) {
spawnFruit();
spawnTimer = 0;
// Sometimes spawn multiple
if (Math.random() < 0.3 && gameTime > 200) {
setTimeout(() => spawnFruit(), 100);
}
}
// Update fruits
for (let i = fruits.length - 1; i >= 0; i--) {
const f = fruits[i];
f.x += f.vx;
f.vy += 0.3; // Gravity
f.y += f.vy;
if (!f.isBomb) f.rotation += f.rotSpeed;
// Check if missed (fell off screen)
if (f.y > H + 100) {
if (!f.sliced && !f.isBomb) {
// Missed a fruit!
lives--;
combo = 0;
playSfx('miss');
if (lives <= 0) {
gameOver = true;
gameRunning = false;
musicPlaying = false;
}
}
fruits.splice(i, 1);
}
}
// Update slices
for (let i = slices.length - 1; i >= 0; i--) {
const s = slices[i];
s.x += s.vx;
s.vy += 0.4;
s.y += s.vy;
s.rotation += s.rotSpeed;
if (s.y > H + 100) slices.splice(i, 1);
}
// Update particles
for (let i = particles.length - 1; i >= 0; i--) {
const p = particles[i];
p.x += p.dx;
p.y += p.dy;
p.dy += 0.2;
p.life--;
if (p.life <= 0) particles.splice(i, 1);
}
// Clean old trail points
const now = Date.now();
trail = trail.filter(t => now - t.time < 100);
updateMusic();
}
function draw() {
// Background gradient
const grad = ctx.createLinearGradient(0, 0, 0, H);
grad.addColorStop(0, '#2d1b4e');
grad.addColorStop(1, '#1a0f2e');
ctx.fillStyle = grad;
ctx.fillRect(0, 0, W, H);
// Safe zones
ctx.fillStyle = '#1a0f2e';
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, 40);
ctx.textAlign = 'right';
ctx.fillText(`Lives: ${'❤️'.repeat(lives)}`, W - 20, 40);
if (combo > 1) {
ctx.textAlign = 'center';
ctx.fillStyle = '#ffd700';
ctx.fillText(`${combo}x COMBO!`, W / 2, 40);
}
// Particles
for (const p of particles) {
ctx.fillStyle = p.color;
ctx.globalAlpha = p.life / 30;
ctx.beginPath();
ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
ctx.fill();
}
ctx.globalAlpha = 1;
// Slices
for (const s of slices) {
ctx.save();
ctx.translate(s.x, s.y);
ctx.rotate(s.rotation);
ctx.font = `${s.size}px Arial`;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(s.type.emoji, 0, 0);
ctx.restore();
}
// Fruits
for (const f of fruits) {
ctx.save();
ctx.translate(f.x, f.y);
if (!f.isBomb) ctx.rotate(f.rotation);
ctx.font = `${f.size}px Arial`;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(f.isBomb ? '💣' : f.type.emoji, 0, 0);
ctx.restore();
}
// Slice trail
if (trail.length > 1) {
ctx.strokeStyle = '#fff';
ctx.lineWidth = 4;
ctx.lineCap = 'round';
ctx.beginPath();
ctx.moveTo(trail[0].x, trail[0].y);
for (let i = 1; i < trail.length; i++) {
ctx.lineTo(trail[i].x, trail[i].y);
}
ctx.stroke();
// Glow
ctx.strokeStyle = 'rgba(255,255,255,0.3)';
ctx.lineWidth = 12;
ctx.stroke();
}
// Controls hint
ctx.fillStyle = '#666';
ctx.font = '14px Arial';
ctx.textAlign = 'center';
ctx.fillText('Swipe to slice fruits! Avoid bombs!', W / 2, safeBottom + 25);
if (gameOver) {
ctx.fillStyle = 'rgba(0,0,0,0.85)';
ctx.fillRect(0, 0, W, H);
ctx.fillStyle = '#fff';
ctx.font = 'bold 40px Arial';
ctx.textAlign = 'center';
ctx.fillText('GAME OVER', W / 2, H / 2 - 50);
ctx.font = '28px Arial';
ctx.fillText(`Final Score: ${score}`, W / 2, H / 2);
ctx.fillStyle = '#ef4444';
ctx.beginPath();
ctx.roundRect(W / 2 - 100, H / 2 + 30, 200, 50, 10);
ctx.fill();
ctx.fillStyle = '#fff';
ctx.font = 'bold 18px Arial';
ctx.fillText('TAP TO PLAY AGAIN', W / 2, H / 2 + 62);
}
}
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>
+450
View File
@@ -0,0 +1,450 @@
<!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>Geometry Runner</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { width: 100%; height: 100%; overflow: hidden; background: #1a1a2e; 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();
}
// Energetic electronic melody
const melody = [
{n: 523, d: 0.15}, {n: 523, d: 0.15}, {n: 659, d: 0.15}, {n: 523, d: 0.15},
{n: 784, d: 0.3}, {n: 659, d: 0.3},
{n: 523, d: 0.15}, {n: 523, d: 0.15}, {n: 659, d: 0.15}, {n: 523, d: 0.15},
{n: 698, d: 0.3}, {n: 659, d: 0.3},
{n: 587, d: 0.15}, {n: 587, d: 0.15}, {n: 698, d: 0.15}, {n: 587, d: 0.15},
{n: 880, d: 0.3}, {n: 784, d: 0.3}, {n: 0, d: 0.15}
];
const bass = [
{n: 131, d: 0.3}, {n: 131, d: 0.3}, {n: 165, d: 0.3}, {n: 165, d: 0.3},
{n: 175, d: 0.3}, {n: 175, d: 0.3}, {n: 147, d: 0.3}, {n: 147, d: 0.3}
];
let melodyIdx = 0, bassIdx = 0, nextMelody = 0, nextBass = 0;
function playTone(freq, dur, type = 'square', vol = 0.1) {
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 === 'jump') {
osc.frequency.setValueAtTime(400, audioCtx.currentTime);
osc.frequency.exponentialRampToValueAtTime(800, audioCtx.currentTime + 0.1);
gain.gain.setValueAtTime(0.15, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.1);
} else if (type === 'die') {
osc.frequency.setValueAtTime(400, audioCtx.currentTime);
osc.frequency.exponentialRampToValueAtTime(80, audioCtx.currentTime + 0.4);
gain.gain.setValueAtTime(0.25, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.4);
osc.type = 'sawtooth';
} else if (type === 'coin') {
osc.frequency.setValueAtTime(880, audioCtx.currentTime);
osc.frequency.setValueAtTime(1047, audioCtx.currentTime + 0.05);
gain.gain.setValueAtTime(0.15, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.15);
}
osc.connect(gain);
gain.connect(audioCtx.destination);
osc.start();
osc.stop(audioCtx.currentTime + 0.5);
}
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.08);
nextMelody = now + note.d;
melodyIdx = (melodyIdx + 1) % melody.length;
}
if (now >= nextBass) {
const note = bass[bassIdx];
playTone(note.n, note.d, 'triangle', 0.12);
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, groundY;
function resize() {
W = window.innerWidth;
H = window.innerHeight;
canvas.width = W;
canvas.height = H;
safeTop = H * 0.1;
safeBottom = H * 0.9;
groundY = safeBottom - 50;
}
resize();
window.addEventListener('resize', resize);
let gameRunning = false;
let gameOver = false;
let score = 0;
let distance = 0;
let speed = 3;
let spawnTimer = 0;
const player = { x: 100, y: 0, size: 35, vy: 0, jumping: false, rotation: 0 };
const gravity = 0.5;
const jumpForce = -12;
let obstacles = [];
let coins = [];
let particles = [];
let groundOffset = 0;
let bgOffset = 0;
function init() {
player.y = groundY - player.size;
player.vy = 0;
player.jumping = false;
player.rotation = 0;
obstacles = [];
coins = [];
particles = [];
score = 0;
distance = 0;
speed = 3;
spawnTimer = 0;
gameOver = false;
gameRunning = true;
startMusic();
}
function jump() {
if (!player.jumping && gameRunning && !gameOver) {
player.vy = jumpForce;
player.jumping = true;
playSfx('jump');
}
}
function spawnObstacle() {
const type = Math.random() < 0.7 ? 'spike' : 'block';
const h = type === 'spike' ? 35 : 40 + Math.random() * 20;
const w = type === 'spike' ? 30 : 35;
obstacles.push({
x: W + 50,
y: groundY - h,
w,
h,
type
});
// Sometimes spawn a coin
if (Math.random() < 0.4) {
coins.push({
x: W + 50 + Math.random() * 100,
y: groundY - 80 - Math.random() * 60,
size: 20
});
}
}
function addParticles(x, y, color) {
for (let i = 0; i < 10; i++) {
const angle = Math.random() * Math.PI * 2;
const spd = 2 + Math.random() * 3;
particles.push({
x, y,
dx: Math.cos(angle) * spd,
dy: Math.sin(angle) * spd,
life: 30,
color
});
}
}
document.addEventListener('keydown', e => {
initAudio();
if (e.key === ' ' || e.key === 'ArrowUp' || e.key === 'w') {
e.preventDefault();
if (gameOver) init();
else jump();
}
});
canvas.addEventListener('click', () => { initAudio(); if (gameOver) init(); else jump(); });
canvas.addEventListener('touchstart', e => { e.preventDefault(); initAudio(); if (gameOver) init(); else jump(); });
function update() {
if (!gameRunning || gameOver) return;
// Player physics
player.vy += gravity;
player.y += player.vy;
if (player.y >= groundY - player.size) {
player.y = groundY - player.size;
player.vy = 0;
player.jumping = false;
}
// Rotation while jumping
if (player.jumping) {
player.rotation += 0.15;
} else {
player.rotation = 0;
}
// Spawn obstacles
spawnTimer++;
const spawnInterval = Math.max(80, 180 - distance / 50);
if (spawnTimer >= spawnInterval) {
spawnObstacle();
spawnTimer = 0;
}
// Move obstacles
for (let i = obstacles.length - 1; i >= 0; i--) {
obstacles[i].x -= speed;
if (obstacles[i].x + obstacles[i].w < 0) {
obstacles.splice(i, 1);
score += 10;
}
}
// Move coins
for (let i = coins.length - 1; i >= 0; i--) {
coins[i].x -= speed;
if (coins[i].x + coins[i].size < 0) {
coins.splice(i, 1);
}
}
// Collision with obstacles (generous hitbox)
for (const obs of obstacles) {
const padding = 10;
if (player.x + player.size - padding > obs.x + padding &&
player.x + padding < obs.x + obs.w - padding &&
player.y + player.size - padding > obs.y + padding) {
gameOver = true;
gameRunning = false;
musicPlaying = false;
addParticles(player.x + player.size / 2, player.y + player.size / 2, '#ff6b6b');
playSfx('die');
return;
}
}
// Collect coins
for (let i = coins.length - 1; i >= 0; i--) {
const c = coins[i];
const dx = (player.x + player.size / 2) - c.x;
const dy = (player.y + player.size / 2) - c.y;
if (Math.sqrt(dx * dx + dy * dy) < player.size / 2 + c.size / 2) {
coins.splice(i, 1);
score += 50;
addParticles(c.x, c.y, '#ffd700');
playSfx('coin');
}
}
// Update particles
for (let i = particles.length - 1; i >= 0; i--) {
const p = particles[i];
p.x += p.dx;
p.y += p.dy;
p.dy += 0.2;
p.life--;
if (p.life <= 0) particles.splice(i, 1);
}
// Update distance and speed
distance++;
if (speed < 8) speed += 0.001;
// Background scroll
groundOffset = (groundOffset + speed) % 40;
bgOffset = (bgOffset + speed * 0.3) % 200;
updateMusic();
}
function draw() {
// Background gradient
const grad = ctx.createLinearGradient(0, safeTop, 0, groundY);
grad.addColorStop(0, '#1a1a2e');
grad.addColorStop(1, '#16213e');
ctx.fillStyle = grad;
ctx.fillRect(0, 0, W, H);
// Safe zones
ctx.fillStyle = '#0f0f1a';
ctx.fillRect(0, 0, W, safeTop);
ctx.fillRect(0, safeBottom, W, H - safeBottom);
// Background shapes
ctx.fillStyle = 'rgba(255,255,255,0.05)';
for (let i = 0; i < 5; i++) {
const x = ((i * 200) - bgOffset) % W + W;
ctx.beginPath();
ctx.moveTo(x, groundY - 100);
ctx.lineTo(x + 80, groundY);
ctx.lineTo(x - 80, groundY);
ctx.fill();
}
// Ground
ctx.fillStyle = '#4ade80';
ctx.fillRect(0, groundY, W, safeBottom - groundY);
// Ground pattern
ctx.fillStyle = '#22c55e';
for (let x = -groundOffset; x < W; x += 40) {
ctx.fillRect(x, groundY, 2, 10);
}
// Header
ctx.fillStyle = '#fff';
ctx.font = 'bold 22px Arial';
ctx.textAlign = 'left';
ctx.fillText(`Score: ${score}`, 20, 35);
ctx.textAlign = 'right';
ctx.fillText(`Distance: ${Math.floor(distance / 10)}m`, W - 20, 35);
// Particles
for (const p of particles) {
ctx.fillStyle = p.color;
ctx.globalAlpha = p.life / 30;
ctx.fillRect(p.x - 3, p.y - 3, 6, 6);
}
ctx.globalAlpha = 1;
// Coins
ctx.fillStyle = '#ffd700';
for (const c of coins) {
ctx.beginPath();
ctx.arc(c.x, c.y, c.size / 2, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#ffed4a';
ctx.beginPath();
ctx.arc(c.x - 3, c.y - 3, c.size / 4, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#ffd700';
}
// Obstacles
for (const obs of obstacles) {
if (obs.type === 'spike') {
ctx.fillStyle = '#ef4444';
ctx.beginPath();
ctx.moveTo(obs.x + obs.w / 2, obs.y);
ctx.lineTo(obs.x + obs.w, obs.y + obs.h);
ctx.lineTo(obs.x, obs.y + obs.h);
ctx.closePath();
ctx.fill();
} else {
ctx.fillStyle = '#ef4444';
ctx.fillRect(obs.x, obs.y, obs.w, obs.h);
}
}
// Player
ctx.save();
ctx.translate(player.x + player.size / 2, player.y + player.size / 2);
ctx.rotate(player.rotation);
ctx.fillStyle = '#00d4ff';
ctx.shadowColor = '#00d4ff';
ctx.shadowBlur = 15;
ctx.fillRect(-player.size / 2, -player.size / 2, player.size, player.size);
ctx.shadowBlur = 0;
// Eyes
ctx.fillStyle = '#fff';
ctx.fillRect(-player.size / 4, -player.size / 4, 8, 8);
ctx.fillRect(player.size / 8, -player.size / 4, 8, 8);
ctx.restore();
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('GAME OVER', W / 2, H / 2 - 50);
ctx.font = '24px Arial';
ctx.fillText(`Score: ${score}`, W / 2, H / 2);
ctx.fillText(`Distance: ${Math.floor(distance / 10)}m`, W / 2, H / 2 + 35);
ctx.fillStyle = '#00d4ff';
ctx.beginPath();
ctx.roundRect(W / 2 - 100, H / 2 + 60, 200, 50, 10);
ctx.fill();
ctx.fillStyle = '#000';
ctx.font = 'bold 18px Arial';
ctx.fillText('TAP TO PLAY AGAIN', W / 2, H / 2 + 92);
}
// Controls hint
if (!gameOver) {
ctx.fillStyle = '#666';
ctx.font = '14px Arial';
ctx.textAlign = 'center';
ctx.fillText('Tap or Space to jump', W / 2, safeBottom + 25);
}
}
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>
+356
View File
@@ -0,0 +1,356 @@
<!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>Lights Out</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { width: 100%; height: 100%; overflow: hidden; background: #1a1a2e; 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();
}
// Relaxing puzzle melody
const melody = [
{n: 392, d: 0.5}, {n: 440, d: 0.5}, {n: 494, d: 0.5}, {n: 523, d: 1.0},
{n: 494, d: 0.5}, {n: 440, d: 0.5}, {n: 392, d: 1.0},
{n: 330, d: 0.5}, {n: 349, d: 0.5}, {n: 392, d: 0.5}, {n: 440, d: 1.0},
{n: 392, d: 0.5}, {n: 349, d: 0.5}, {n: 330, d: 1.0}, {n: 0, d: 0.5}
];
const bass = [
{n: 98, d: 1.0}, {n: 110, d: 1.0}, {n: 131, d: 1.0}, {n: 110, d: 1.0},
{n: 82, d: 1.0}, {n: 87, d: 1.0}, {n: 98, d: 1.0}, {n: 110, d: 1.0}
];
let melodyIdx = 0, bassIdx = 0, nextMelody = 0, nextBass = 0;
function playTone(freq, dur, type = 'triangle', vol = 0.06) {
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 === 'toggle') {
osc.frequency.value = 440;
gain.gain.setValueAtTime(0.12, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.1);
} else if (type === 'win') {
osc.frequency.setValueAtTime(523, audioCtx.currentTime);
osc.frequency.setValueAtTime(659, audioCtx.currentTime + 0.15);
osc.frequency.setValueAtTime(784, audioCtx.currentTime + 0.3);
osc.frequency.setValueAtTime(1047, audioCtx.currentTime + 0.45);
gain.gain.setValueAtTime(0.2, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.6);
}
osc.type = 'square';
osc.connect(gain);
gain.connect(audioCtx.destination);
osc.start();
osc.stop(audioCtx.currentTime + 0.7);
}
function updateMusic() {
if (!audioCtx || !musicEnabled || !musicPlaying) return;
const now = audioCtx.currentTime;
if (now >= nextMelody) {
const note = melody[melodyIdx];
playTone(note.n, note.d, 'triangle', 0.05);
nextMelody = now + note.d;
melodyIdx = (melodyIdx + 1) % melody.length;
}
if (now >= nextBass) {
const note = bass[bassIdx];
playTone(note.n, note.d, 'triangle', 0.08);
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;
function resize() {
W = window.innerWidth;
H = window.innerHeight;
canvas.width = W;
canvas.height = H;
safeTop = H * 0.1;
safeBottom = H * 0.9;
}
resize();
window.addEventListener('resize', resize);
const SIZE = 5;
let grid = [];
let moves = 0;
let level = 1;
let gameOver = false;
let gameRunning = false;
function createGrid() {
grid = [];
for (let r = 0; r < SIZE; r++) {
grid.push(new Array(SIZE).fill(false));
}
}
function shuffleGrid(numMoves) {
// Make a solvable puzzle by performing random toggles
for (let i = 0; i < numMoves; i++) {
const r = Math.floor(Math.random() * SIZE);
const c = Math.floor(Math.random() * SIZE);
toggle(r, c, false); // Don't count these as moves
}
}
function toggle(row, col, countMove = true) {
const directions = [[0, 0], [-1, 0], [1, 0], [0, -1], [0, 1]];
for (const [dr, dc] of directions) {
const nr = row + dr;
const nc = col + dc;
if (nr >= 0 && nr < SIZE && nc >= 0 && nc < SIZE) {
grid[nr][nc] = !grid[nr][nc];
}
}
if (countMove) {
moves++;
playSfx('toggle');
}
}
function isWin() {
return grid.every(row => row.every(cell => !cell));
}
function init() {
createGrid();
moves = 0;
gameOver = false;
gameRunning = true;
// Easy start - fewer toggles needed
shuffleGrid(3 + level);
startMusic();
}
function nextLevel() {
level++;
createGrid();
shuffleGrid(3 + level);
moves = 0;
gameOver = false;
}
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);
});
function handleClick(x, y) {
if (gameOver) {
if (level < 10) {
nextLevel();
} else {
level = 1;
init();
}
return;
}
// Calculate grid position
const availH = safeBottom - safeTop - 80;
const availW = W - 40;
const cellSize = Math.min(availW / SIZE, availH / SIZE);
const gridSize = cellSize * SIZE;
const gridX = (W - gridSize) / 2;
const gridY = safeTop + 60 + (availH - gridSize) / 2;
const col = Math.floor((x - gridX) / cellSize);
const row = Math.floor((y - gridY) / cellSize);
if (row >= 0 && row < SIZE && col >= 0 && col < SIZE) {
toggle(row, col);
if (isWin()) {
gameOver = true;
playSfx('win');
}
}
}
document.addEventListener('keydown', e => {
initAudio();
if ((e.key === ' ' || e.key === 'Enter') && gameOver) {
if (level < 10) nextLevel();
else { level = 1; init(); }
}
if (e.key === 'r' || e.key === 'R') init();
});
function update() {
updateMusic();
}
function draw() {
// Background
ctx.fillStyle = '#1a1a2e';
ctx.fillRect(0, 0, W, H);
// Safe zones
ctx.fillStyle = '#16213e';
ctx.fillRect(0, 0, W, safeTop);
ctx.fillRect(0, safeBottom, W, H - safeBottom);
// Header
ctx.fillStyle = '#fff';
ctx.font = 'bold 24px Arial';
ctx.textAlign = 'center';
ctx.fillText('LIGHTS OUT', W / 2, 40);
ctx.font = '18px Arial';
ctx.textAlign = 'left';
ctx.fillText(`Level: ${level}`, 20, 35);
ctx.textAlign = 'right';
ctx.fillText(`Moves: ${moves}`, W - 20, 35);
// Calculate grid
const availH = safeBottom - safeTop - 80;
const availW = W - 40;
const cellSize = Math.min(availW / SIZE, availH / SIZE);
const gridSize = cellSize * SIZE;
const gridX = (W - gridSize) / 2;
const gridY = safeTop + 60 + (availH - gridSize) / 2;
// Instructions
ctx.fillStyle = '#888';
ctx.font = '16px Arial';
ctx.textAlign = 'center';
ctx.fillText('Turn off all the lights!', W / 2, gridY - 15);
// Draw grid
for (let r = 0; r < SIZE; r++) {
for (let c = 0; c < SIZE; c++) {
const x = gridX + c * cellSize;
const y = gridY + r * cellSize;
const padding = 4;
// Cell background
ctx.fillStyle = '#0f0f23';
ctx.beginPath();
ctx.roundRect(x + padding, y + padding, cellSize - padding * 2, cellSize - padding * 2, 8);
ctx.fill();
// Light
if (grid[r][c]) {
// Light ON - yellow glow
ctx.fillStyle = '#ffd700';
ctx.shadowColor = '#ffd700';
ctx.shadowBlur = 20;
ctx.beginPath();
ctx.roundRect(x + padding + 4, y + padding + 4, cellSize - padding * 2 - 8, cellSize - padding * 2 - 8, 6);
ctx.fill();
ctx.shadowBlur = 0;
} else {
// Light OFF - dark
ctx.fillStyle = '#2a2a4a';
ctx.beginPath();
ctx.roundRect(x + padding + 4, y + padding + 4, cellSize - padding * 2 - 8, cellSize - padding * 2 - 8, 6);
ctx.fill();
}
}
}
// Count lights on
const lightsOn = grid.flat().filter(c => c).length;
ctx.fillStyle = '#888';
ctx.font = '16px Arial';
ctx.textAlign = 'center';
ctx.fillText(`Lights remaining: ${lightsOn}`, W / 2, gridY + gridSize + 30);
// Controls hint
ctx.fillStyle = '#666';
ctx.font = '14px Arial';
ctx.fillText('Tap a light to toggle it and its neighbors', W / 2, safeBottom + 25);
// Win screen
if (gameOver) {
ctx.fillStyle = 'rgba(0,0,0,0.85)';
ctx.fillRect(0, 0, W, H);
ctx.fillStyle = '#ffd700';
ctx.font = 'bold 36px Arial';
ctx.textAlign = 'center';
ctx.fillText('LEVEL COMPLETE!', W / 2, H / 2 - 50);
ctx.fillStyle = '#fff';
ctx.font = '24px Arial';
ctx.fillText(`Solved in ${moves} moves`, W / 2, H / 2);
ctx.fillStyle = '#ffd700';
ctx.beginPath();
ctx.roundRect(W / 2 - 100, H / 2 + 30, 200, 50, 10);
ctx.fill();
ctx.fillStyle = '#000';
ctx.font = 'bold 18px Arial';
ctx.fillText(level < 10 ? 'NEXT LEVEL' : 'PLAY AGAIN', W / 2, H / 2 + 62);
}
}
function gameLoop() {
update();
draw();
requestAnimationFrame(gameLoop);
}
window.gameScore = 0;
Object.defineProperty(window, 'gameScore', { get: () => level * 100 - moves });
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>
+539
View File
@@ -0,0 +1,539 @@
<!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>Lunar Lander</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { width: 100%; height: 100%; overflow: hidden; background: #000; 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();
}
// Atmospheric space melody
const melody = [
{n: 220, d: 0.8}, {n: 262, d: 0.8}, {n: 294, d: 0.8}, {n: 330, d: 1.2},
{n: 294, d: 0.8}, {n: 262, d: 0.8}, {n: 220, d: 1.2},
{n: 196, d: 0.8}, {n: 220, d: 0.8}, {n: 262, d: 0.8}, {n: 294, d: 1.2}, {n: 0, d: 0.8}
];
const bass = [
{n: 55, d: 1.6}, {n: 65, d: 1.6}, {n: 73, d: 1.6}, {n: 65, d: 1.6}
];
let melodyIdx = 0, bassIdx = 0, nextMelody = 0, nextBass = 0;
function playTone(freq, dur, type = 'triangle', vol = 0.06) {
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 === 'thrust') {
osc.frequency.value = 80 + Math.random() * 20;
gain.gain.setValueAtTime(0.1, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.1);
osc.type = 'sawtooth';
} else if (type === 'land') {
osc.frequency.setValueAtTime(400, audioCtx.currentTime);
osc.frequency.setValueAtTime(600, audioCtx.currentTime + 0.1);
osc.frequency.setValueAtTime(800, audioCtx.currentTime + 0.2);
gain.gain.setValueAtTime(0.2, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.3);
} else if (type === 'crash') {
osc.frequency.setValueAtTime(200, audioCtx.currentTime);
osc.frequency.exponentialRampToValueAtTime(50, audioCtx.currentTime + 0.5);
gain.gain.setValueAtTime(0.3, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.5);
osc.type = 'sawtooth';
}
osc.connect(gain);
gain.connect(audioCtx.destination);
osc.start();
osc.stop(audioCtx.currentTime + 0.6);
}
function updateMusic() {
if (!audioCtx || !musicEnabled || !musicPlaying) return;
const now = audioCtx.currentTime;
if (now >= nextMelody) {
const note = melody[melodyIdx];
playTone(note.n, note.d, 'triangle', 0.05);
nextMelody = now + note.d;
melodyIdx = (melodyIdx + 1) % melody.length;
}
if (now >= nextBass) {
const note = bass[bassIdx];
playTone(note.n, note.d, 'triangle', 0.08);
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;
function resize() {
W = window.innerWidth;
H = window.innerHeight;
canvas.width = W;
canvas.height = H;
safeTop = H * 0.1;
safeBottom = H * 0.9;
}
resize();
window.addEventListener('resize', resize);
let gameRunning = false;
let gameOver = false;
let landed = false;
let score = 0;
let level = 1;
let fuel = 100;
const lander = { x: 0, y: 0, w: 40, h: 50, vx: 0, vy: 0, angle: 0, thrusting: false };
const gravity = 0.02; // Very low gravity for easy landing
const thrust = 0.08;
const rotSpeed = 0.05;
let terrain = [];
let landingPads = [];
let stars = [];
let particles = [];
let keys = { left: false, right: false, up: false };
let touchThrust = false;
function generateTerrain() {
terrain = [];
landingPads = [];
stars = [];
// Generate stars
for (let i = 0; i < 100; i++) {
stars.push({
x: Math.random() * W,
y: safeTop + Math.random() * (safeBottom - safeTop - 100)
});
}
// Generate terrain
const segments = 20;
const segWidth = W / segments;
let y = safeBottom - 50;
for (let i = 0; i <= segments; i++) {
terrain.push({ x: i * segWidth, y });
// Add variation
if (i < segments) {
const variation = Math.random() * 40 - 20;
y = Math.max(safeBottom - 150, Math.min(safeBottom - 30, y + variation));
}
}
// Create landing pads (flat areas)
const padCount = 2 + Math.min(level, 3);
const usedIndices = new Set();
for (let p = 0; p < padCount; p++) {
let idx;
do {
idx = 2 + Math.floor(Math.random() * (segments - 4));
} while (usedIndices.has(idx) || usedIndices.has(idx - 1) || usedIndices.has(idx + 1));
usedIndices.add(idx);
// Flatten area for pad
const padY = terrain[idx].y;
terrain[idx].y = padY;
terrain[idx + 1].y = padY;
landingPads.push({
x: idx * segWidth,
y: padY,
w: segWidth,
multiplier: p === 0 ? 1 : (2 + p) // Smaller pads = more points
});
}
}
function init() {
lander.x = W / 2 - lander.w / 2;
lander.y = safeTop + 50;
lander.vx = 0;
lander.vy = 0;
lander.angle = 0;
lander.thrusting = false;
fuel = 100;
particles = [];
generateTerrain();
gameOver = false;
landed = false;
gameRunning = true;
startMusic();
}
function nextLevel() {
level++;
init();
}
function addParticles(x, y, color, count = 5) {
for (let i = 0; i < count; i++) {
const angle = Math.random() * Math.PI * 2;
const spd = 1 + Math.random() * 2;
particles.push({
x, y,
dx: Math.cos(angle) * spd,
dy: Math.sin(angle) * spd,
life: 20 + Math.random() * 10,
color
});
}
}
document.addEventListener('keydown', e => {
initAudio();
if (e.key === 'ArrowLeft' || e.key === 'a') keys.left = true;
if (e.key === 'ArrowRight' || e.key === 'd') keys.right = true;
if (e.key === 'ArrowUp' || e.key === 'w' || e.key === ' ') { keys.up = true; e.preventDefault(); }
if ((e.key === ' ' || e.key === 'Enter') && gameOver) {
if (landed) nextLevel();
else init();
}
});
document.addEventListener('keyup', e => {
if (e.key === 'ArrowLeft' || e.key === 'a') keys.left = false;
if (e.key === 'ArrowRight' || e.key === 'd') keys.right = false;
if (e.key === 'ArrowUp' || e.key === 'w' || e.key === ' ') keys.up = false;
});
canvas.addEventListener('click', e => {
initAudio();
if (gameOver) {
if (landed) nextLevel();
else init();
return;
}
});
canvas.addEventListener('touchstart', e => {
e.preventDefault();
initAudio();
if (gameOver) {
if (landed) nextLevel();
else init();
return;
}
const x = e.touches[0].clientX;
if (x < W / 3) keys.left = true;
else if (x > W * 2 / 3) keys.right = true;
else touchThrust = true;
});
canvas.addEventListener('touchend', e => {
keys.left = false;
keys.right = false;
touchThrust = false;
});
canvas.addEventListener('touchmove', e => {
e.preventDefault();
});
function update() {
if (!gameRunning || gameOver) return;
// Rotation
if (keys.left) lander.angle -= rotSpeed;
if (keys.right) lander.angle += rotSpeed;
// Thrust
lander.thrusting = (keys.up || touchThrust) && fuel > 0;
if (lander.thrusting) {
lander.vx += Math.sin(lander.angle) * thrust;
lander.vy -= Math.cos(lander.angle) * thrust;
fuel -= 0.3;
if (Math.random() < 0.3) {
addParticles(
lander.x + lander.w / 2 - Math.sin(lander.angle) * 25,
lander.y + lander.h / 2 + Math.cos(lander.angle) * 25,
'#ff8800', 2
);
playSfx('thrust');
}
}
// Gravity
lander.vy += gravity;
// Move
lander.x += lander.vx;
lander.y += lander.vy;
// Wrap horizontally
if (lander.x < -lander.w) lander.x = W;
if (lander.x > W) lander.x = -lander.w;
// Check landing/crash
const bottomY = lander.y + lander.h;
// Check landing pads
for (const pad of landingPads) {
if (lander.x + lander.w > pad.x && lander.x < pad.x + pad.w) {
if (bottomY >= pad.y) {
// Check for safe landing
const speed = Math.sqrt(lander.vx * lander.vx + lander.vy * lander.vy);
const angleDeg = Math.abs(lander.angle * 180 / Math.PI);
if (speed < 1.5 && angleDeg < 20) { // Very generous for kids
// Safe landing!
landed = true;
gameOver = true;
score += Math.floor((100 + fuel) * pad.multiplier);
playSfx('land');
} else {
// Crash
gameOver = true;
addParticles(lander.x + lander.w / 2, lander.y + lander.h / 2, '#ff4444', 20);
playSfx('crash');
}
gameRunning = false;
musicPlaying = false;
return;
}
}
}
// Check terrain collision
for (let i = 0; i < terrain.length - 1; i++) {
const t1 = terrain[i];
const t2 = terrain[i + 1];
if (lander.x + lander.w > t1.x && lander.x < t2.x) {
// Interpolate terrain height
const t = (lander.x + lander.w / 2 - t1.x) / (t2.x - t1.x);
const terrainY = t1.y + (t2.y - t1.y) * t;
if (bottomY >= terrainY) {
// Crash on terrain
gameOver = true;
gameRunning = false;
musicPlaying = false;
addParticles(lander.x + lander.w / 2, lander.y + lander.h / 2, '#ff4444', 20);
playSfx('crash');
return;
}
}
}
// Update particles
for (let i = particles.length - 1; i >= 0; i--) {
const p = particles[i];
p.x += p.dx;
p.y += p.dy;
p.life--;
if (p.life <= 0) particles.splice(i, 1);
}
updateMusic();
}
function draw() {
// Background
ctx.fillStyle = '#000';
ctx.fillRect(0, 0, W, H);
// Safe zones
ctx.fillStyle = '#111';
ctx.fillRect(0, 0, W, safeTop);
ctx.fillRect(0, safeBottom, W, H - safeBottom);
// Stars
ctx.fillStyle = '#fff';
for (const star of stars) {
ctx.fillRect(star.x, star.y, 2, 2);
}
// Header
ctx.fillStyle = '#fff';
ctx.font = 'bold 20px Arial';
ctx.textAlign = 'left';
ctx.fillText(`Score: ${score}`, 20, 35);
ctx.textAlign = 'center';
ctx.fillText(`Level ${level}`, W / 2, 35);
ctx.textAlign = 'right';
ctx.fillText(`Fuel: ${Math.floor(fuel)}%`, W - 20, 35);
// Terrain
ctx.fillStyle = '#555';
ctx.beginPath();
ctx.moveTo(0, H);
for (const t of terrain) {
ctx.lineTo(t.x, t.y);
}
ctx.lineTo(W, H);
ctx.closePath();
ctx.fill();
// Landing pads
ctx.fillStyle = '#4ade80';
for (const pad of landingPads) {
ctx.fillRect(pad.x, pad.y - 5, pad.w, 5);
ctx.fillStyle = '#fff';
ctx.font = '12px Arial';
ctx.textAlign = 'center';
ctx.fillText(`x${pad.multiplier}`, pad.x + pad.w / 2, pad.y - 10);
ctx.fillStyle = '#4ade80';
}
// Particles
for (const p of particles) {
ctx.fillStyle = p.color;
ctx.globalAlpha = p.life / 30;
ctx.fillRect(p.x - 2, p.y - 2, 4, 4);
}
ctx.globalAlpha = 1;
// Lander
ctx.save();
ctx.translate(lander.x + lander.w / 2, lander.y + lander.h / 2);
ctx.rotate(lander.angle);
// Body
ctx.fillStyle = '#ddd';
ctx.beginPath();
ctx.moveTo(0, -lander.h / 2);
ctx.lineTo(-lander.w / 2, lander.h / 2);
ctx.lineTo(lander.w / 2, lander.h / 2);
ctx.closePath();
ctx.fill();
// Legs
ctx.strokeStyle = '#888';
ctx.lineWidth = 3;
ctx.beginPath();
ctx.moveTo(-lander.w / 2 + 5, lander.h / 2);
ctx.lineTo(-lander.w / 2 - 5, lander.h / 2 + 10);
ctx.moveTo(lander.w / 2 - 5, lander.h / 2);
ctx.lineTo(lander.w / 2 + 5, lander.h / 2 + 10);
ctx.stroke();
// Thrust flame
if (lander.thrusting) {
ctx.fillStyle = '#ff8800';
ctx.beginPath();
ctx.moveTo(-10, lander.h / 2);
ctx.lineTo(10, lander.h / 2);
ctx.lineTo(0, lander.h / 2 + 20 + Math.random() * 10);
ctx.closePath();
ctx.fill();
}
ctx.restore();
// Speed indicator
const speed = Math.sqrt(lander.vx * lander.vx + lander.vy * lander.vy);
ctx.fillStyle = speed < 1.5 ? '#4ade80' : '#ef4444';
ctx.font = '16px Arial';
ctx.textAlign = 'left';
ctx.fillText(`Speed: ${speed.toFixed(1)}`, 20, safeBottom + 25);
// Controls hint
ctx.fillStyle = '#666';
ctx.textAlign = 'center';
ctx.fillText('Left/Right: Rotate | Up/Tap center: Thrust', W / 2, safeBottom + 25);
if (gameOver) {
ctx.fillStyle = 'rgba(0,0,0,0.85)';
ctx.fillRect(0, 0, W, H);
ctx.font = 'bold 36px Arial';
ctx.textAlign = 'center';
if (landed) {
ctx.fillStyle = '#4ade80';
ctx.fillText('PERFECT LANDING!', W / 2, H / 2 - 40);
ctx.fillStyle = '#fff';
ctx.font = '24px Arial';
ctx.fillText(`Score: ${score}`, W / 2, H / 2 + 10);
ctx.fillStyle = '#4ade80';
ctx.beginPath();
ctx.roundRect(W / 2 - 100, H / 2 + 40, 200, 50, 10);
ctx.fill();
ctx.fillStyle = '#000';
ctx.font = 'bold 18px Arial';
ctx.fillText('NEXT LEVEL', W / 2, H / 2 + 72);
} else {
ctx.fillStyle = '#ef4444';
ctx.fillText('CRASH!', W / 2, H / 2 - 40);
ctx.fillStyle = '#fff';
ctx.font = '24px Arial';
ctx.fillText('Land slower and level', W / 2, H / 2 + 10);
ctx.fillStyle = '#ef4444';
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('TRY 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>
+535
View File
@@ -0,0 +1,535 @@
<!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>Maze Chomper</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { width: 100%; height: 100%; overflow: hidden; background: #000; 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();
}
// Classic arcade melody
const melody = [
{n: 523, d: 0.15}, {n: 587, d: 0.15}, {n: 659, d: 0.15}, {n: 784, d: 0.3},
{n: 659, d: 0.15}, {n: 587, d: 0.15}, {n: 523, d: 0.3},
{n: 392, d: 0.15}, {n: 440, d: 0.15}, {n: 494, d: 0.15}, {n: 523, d: 0.3},
{n: 494, d: 0.15}, {n: 440, d: 0.15}, {n: 392, d: 0.3}, {n: 0, d: 0.15}
];
const bass = [
{n: 131, d: 0.3}, {n: 165, d: 0.3}, {n: 196, d: 0.3}, {n: 165, d: 0.3},
{n: 98, d: 0.3}, {n: 110, d: 0.3}, {n: 131, d: 0.3}, {n: 165, d: 0.3}
];
let melodyIdx = 0, bassIdx = 0, nextMelody = 0, nextBass = 0;
function playTone(freq, dur, type = 'square', vol = 0.1) {
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 === 'chomp') {
osc.frequency.setValueAtTime(400, audioCtx.currentTime);
osc.frequency.setValueAtTime(300, audioCtx.currentTime + 0.05);
gain.gain.setValueAtTime(0.12, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.1);
} else if (type === 'power') {
osc.frequency.setValueAtTime(523, audioCtx.currentTime);
osc.frequency.setValueAtTime(659, audioCtx.currentTime + 0.1);
osc.frequency.setValueAtTime(784, audioCtx.currentTime + 0.2);
gain.gain.setValueAtTime(0.2, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.3);
} else if (type === 'ghost') {
osc.frequency.value = 800;
gain.gain.setValueAtTime(0.15, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.2);
} else if (type === 'die') {
osc.frequency.setValueAtTime(400, audioCtx.currentTime);
osc.frequency.exponentialRampToValueAtTime(100, audioCtx.currentTime + 0.4);
gain.gain.setValueAtTime(0.25, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.4);
osc.type = 'sawtooth';
}
osc.connect(gain);
gain.connect(audioCtx.destination);
osc.start();
osc.stop(audioCtx.currentTime + 0.5);
}
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.07);
nextMelody = now + note.d;
melodyIdx = (melodyIdx + 1) % melody.length;
}
if (now >= nextBass) {
const note = bass[bassIdx];
playTone(note.n, note.d, 'triangle', 0.1);
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;
function resize() {
W = window.innerWidth;
H = window.innerHeight;
canvas.width = W;
canvas.height = H;
safeTop = H * 0.1;
safeBottom = H * 0.9;
}
resize();
window.addEventListener('resize', resize);
// Simple maze layout (0 = path, 1 = wall)
const MAZE = [
[1,1,1,1,1,1,1,1,1,1,1,1,1],
[1,0,0,0,0,0,1,0,0,0,0,0,1],
[1,0,1,1,1,0,1,0,1,1,1,0,1],
[1,0,0,0,0,0,0,0,0,0,0,0,1],
[1,0,1,1,1,0,1,0,1,1,1,0,1],
[1,0,0,0,0,0,1,0,0,0,0,0,1],
[1,1,1,0,1,1,1,1,1,0,1,1,1],
[1,0,0,0,0,0,0,0,0,0,0,0,1],
[1,0,1,1,1,0,1,0,1,1,1,0,1],
[1,0,0,0,0,0,1,0,0,0,0,0,1],
[1,1,1,1,1,1,1,1,1,1,1,1,1]
];
const ROWS = MAZE.length;
const COLS = MAZE[0].length;
let cellSize, mazeX, mazeY;
let player = { x: 1, y: 1, dir: { x: 0, y: 0 }, nextDir: { x: 0, y: 0 }, mouthOpen: 0 };
let ghosts = [];
let dots = [];
let powerPellets = [];
let score = 0;
let lives = 3;
let level = 1;
let gameOver = false;
let gameRunning = false;
let powerMode = false;
let powerTimer = 0;
function calcMaze() {
const availH = safeBottom - safeTop - 60;
const availW = W - 40;
cellSize = Math.min(availW / COLS, availH / ROWS);
mazeX = (W - cellSize * COLS) / 2;
mazeY = safeTop + 40 + (availH - cellSize * ROWS) / 2;
}
function initDots() {
dots = [];
powerPellets = [];
for (let r = 0; r < ROWS; r++) {
for (let c = 0; c < COLS; c++) {
if (MAZE[r][c] === 0) {
// Power pellets in corners
if ((r === 1 || r === ROWS - 2) && (c === 1 || c === COLS - 2)) {
powerPellets.push({ x: c, y: r });
} else {
dots.push({ x: c, y: r });
}
}
}
}
}
function initGhosts() {
ghosts = [
{ x: 6, y: 5, dir: { x: 0, y: -1 }, color: '#ff0000', scared: false, speed: 0.03 },
{ x: 6, y: 5, dir: { x: 1, y: 0 }, color: '#00ffff', scared: false, speed: 0.025 },
{ x: 6, y: 5, dir: { x: -1, y: 0 }, color: '#ffb8ff', scared: false, speed: 0.02 }
];
}
function init() {
calcMaze();
player.x = 1;
player.y = 1;
player.dir = { x: 0, y: 0 };
player.nextDir = { x: 0, y: 0 };
initDots();
initGhosts();
score = 0;
lives = 3;
level = 1;
powerMode = false;
powerTimer = 0;
gameOver = false;
gameRunning = true;
startMusic();
}
function canMove(x, y) {
const col = Math.floor(x);
const row = Math.floor(y);
if (row < 0 || row >= ROWS || col < 0 || col >= COLS) return false;
return MAZE[row][col] === 0;
}
function setDirection(dx, dy) {
player.nextDir = { x: dx, y: dy };
}
document.addEventListener('keydown', e => {
initAudio();
if (e.key === 'ArrowLeft' || e.key === 'a') setDirection(-1, 0);
if (e.key === 'ArrowRight' || e.key === 'd') setDirection(1, 0);
if (e.key === 'ArrowUp' || e.key === 'w') setDirection(0, -1);
if (e.key === 'ArrowDown' || e.key === 's') setDirection(0, 1);
if ((e.key === ' ' || e.key === 'Enter') && gameOver) init();
});
let touchStartX = 0, touchStartY = 0;
canvas.addEventListener('touchstart', e => {
e.preventDefault();
initAudio();
if (gameOver) { init(); return; }
touchStartX = e.touches[0].clientX;
touchStartY = e.touches[0].clientY;
});
canvas.addEventListener('touchend', e => {
const dx = e.changedTouches[0].clientX - touchStartX;
const dy = e.changedTouches[0].clientY - touchStartY;
if (Math.abs(dx) > Math.abs(dy)) {
setDirection(dx > 0 ? 1 : -1, 0);
} else {
setDirection(0, dy > 0 ? 1 : -1);
}
});
canvas.addEventListener('click', e => {
initAudio();
if (gameOver) init();
});
function update() {
if (!gameRunning || gameOver) return;
const speed = 0.06;
// Try to turn
const testX = player.x + player.nextDir.x * speed;
const testY = player.y + player.nextDir.y * speed;
if (canMove(testX + 0.5, testY + 0.5)) {
player.dir = { ...player.nextDir };
}
// Move player
const newX = player.x + player.dir.x * speed;
const newY = player.y + player.dir.y * speed;
if (canMove(newX + 0.5, newY + 0.5)) {
player.x = newX;
player.y = newY;
}
// Animate mouth
player.mouthOpen = (player.mouthOpen + 0.2) % (Math.PI * 2);
// Collect dots
for (let i = dots.length - 1; i >= 0; i--) {
const d = dots[i];
if (Math.abs(player.x + 0.5 - d.x - 0.5) < 0.5 && Math.abs(player.y + 0.5 - d.y - 0.5) < 0.5) {
dots.splice(i, 1);
score += 10;
playSfx('chomp');
}
}
// Collect power pellets
for (let i = powerPellets.length - 1; i >= 0; i--) {
const p = powerPellets[i];
if (Math.abs(player.x + 0.5 - p.x - 0.5) < 0.5 && Math.abs(player.y + 0.5 - p.y - 0.5) < 0.5) {
powerPellets.splice(i, 1);
score += 50;
powerMode = true;
powerTimer = 300;
ghosts.forEach(g => g.scared = true);
playSfx('power');
}
}
// Power mode timer
if (powerMode) {
powerTimer--;
if (powerTimer <= 0) {
powerMode = false;
ghosts.forEach(g => g.scared = false);
}
}
// Move ghosts
ghosts.forEach(ghost => {
// Simple AI: mostly random, occasionally chase
if (Math.random() < 0.02) {
const dirs = [{ x: 1, y: 0 }, { x: -1, y: 0 }, { x: 0, y: 1 }, { x: 0, y: -1 }];
const validDirs = dirs.filter(d =>
canMove(ghost.x + d.x * 0.5 + 0.5, ghost.y + d.y * 0.5 + 0.5)
);
if (validDirs.length > 0) {
ghost.dir = validDirs[Math.floor(Math.random() * validDirs.length)];
}
}
const gSpeed = ghost.scared ? ghost.speed * 0.5 : ghost.speed;
const newGX = ghost.x + ghost.dir.x * gSpeed;
const newGY = ghost.y + ghost.dir.y * gSpeed;
if (canMove(newGX + 0.5, newGY + 0.5)) {
ghost.x = newGX;
ghost.y = newGY;
} else {
// Turn randomly
const dirs = [{ x: 1, y: 0 }, { x: -1, y: 0 }, { x: 0, y: 1 }, { x: 0, y: -1 }];
const validDirs = dirs.filter(d =>
canMove(ghost.x + d.x * 0.5 + 0.5, ghost.y + d.y * 0.5 + 0.5)
);
if (validDirs.length > 0) {
ghost.dir = validDirs[Math.floor(Math.random() * validDirs.length)];
}
}
// Collision with player
const dx = ghost.x - player.x;
const dy = ghost.y - player.y;
if (Math.sqrt(dx * dx + dy * dy) < 0.7) {
if (ghost.scared) {
// Eat ghost
ghost.x = 6;
ghost.y = 5;
ghost.scared = false;
score += 200;
playSfx('ghost');
} else {
// Die
lives--;
playSfx('die');
if (lives <= 0) {
gameOver = true;
gameRunning = false;
musicPlaying = false;
} else {
// Reset positions
player.x = 1;
player.y = 1;
player.dir = { x: 0, y: 0 };
initGhosts();
}
}
}
});
// Level complete
if (dots.length === 0 && powerPellets.length === 0) {
level++;
initDots();
initGhosts();
player.x = 1;
player.y = 1;
player.dir = { x: 0, y: 0 };
// Ghosts get faster
ghosts.forEach(g => g.speed += 0.005);
}
updateMusic();
}
function draw() {
calcMaze();
// Background
ctx.fillStyle = '#000';
ctx.fillRect(0, 0, W, H);
// Safe zones
ctx.fillStyle = '#111';
ctx.fillRect(0, 0, W, safeTop);
ctx.fillRect(0, safeBottom, W, H - safeBottom);
// Header
ctx.fillStyle = '#fff';
ctx.font = 'bold 22px Arial';
ctx.textAlign = 'left';
ctx.fillText(`Score: ${score}`, 20, 35);
ctx.textAlign = 'center';
ctx.fillText(`Level ${level}`, W / 2, 35);
ctx.textAlign = 'right';
ctx.fillText(`Lives: ${'●'.repeat(lives)}`, W - 20, 35);
// Draw maze
for (let r = 0; r < ROWS; r++) {
for (let c = 0; c < COLS; c++) {
const x = mazeX + c * cellSize;
const y = mazeY + r * cellSize;
if (MAZE[r][c] === 1) {
ctx.fillStyle = '#00f';
ctx.fillRect(x, y, cellSize, cellSize);
}
}
}
// Draw dots
ctx.fillStyle = '#ffb8ae';
for (const d of dots) {
const x = mazeX + (d.x + 0.5) * cellSize;
const y = mazeY + (d.y + 0.5) * cellSize;
ctx.beginPath();
ctx.arc(x, y, cellSize * 0.1, 0, Math.PI * 2);
ctx.fill();
}
// Draw power pellets
ctx.fillStyle = '#ffb8ae';
for (const p of powerPellets) {
const x = mazeX + (p.x + 0.5) * cellSize;
const y = mazeY + (p.y + 0.5) * cellSize;
ctx.beginPath();
ctx.arc(x, y, cellSize * 0.25, 0, Math.PI * 2);
ctx.fill();
}
// Draw ghosts
for (const ghost of ghosts) {
const x = mazeX + (ghost.x + 0.5) * cellSize;
const y = mazeY + (ghost.y + 0.5) * cellSize;
const size = cellSize * 0.4;
ctx.fillStyle = ghost.scared ? '#0000ff' : ghost.color;
ctx.beginPath();
ctx.arc(x, y - size * 0.2, size, Math.PI, 0);
ctx.lineTo(x + size, y + size * 0.5);
for (let i = 0; i < 3; i++) {
const wx = x + size - (i + 0.5) * (size * 2 / 3);
ctx.lineTo(wx, y + size * 0.2);
ctx.lineTo(wx - size / 3, y + size * 0.5);
}
ctx.closePath();
ctx.fill();
// Eyes
ctx.fillStyle = '#fff';
ctx.beginPath();
ctx.arc(x - size * 0.3, y - size * 0.3, size * 0.2, 0, Math.PI * 2);
ctx.arc(x + size * 0.3, y - size * 0.3, size * 0.2, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = ghost.scared ? '#fff' : '#00f';
ctx.beginPath();
ctx.arc(x - size * 0.3, y - size * 0.3, size * 0.1, 0, Math.PI * 2);
ctx.arc(x + size * 0.3, y - size * 0.3, size * 0.1, 0, Math.PI * 2);
ctx.fill();
}
// Draw player (Pac-Man)
const px = mazeX + (player.x + 0.5) * cellSize;
const py = mazeY + (player.y + 0.5) * cellSize;
const pSize = cellSize * 0.4;
const mouthAngle = Math.abs(Math.sin(player.mouthOpen)) * 0.5;
let angle = 0;
if (player.dir.x === 1) angle = 0;
else if (player.dir.x === -1) angle = Math.PI;
else if (player.dir.y === 1) angle = Math.PI / 2;
else if (player.dir.y === -1) angle = -Math.PI / 2;
ctx.fillStyle = '#ff0';
ctx.beginPath();
ctx.arc(px, py, pSize, angle + mouthAngle, angle + Math.PI * 2 - mouthAngle);
ctx.lineTo(px, py);
ctx.closePath();
ctx.fill();
// Controls hint
ctx.fillStyle = '#666';
ctx.font = '14px Arial';
ctx.textAlign = 'center';
ctx.fillText('Swipe or Arrow Keys to move', W / 2, safeBottom + 25);
if (gameOver) {
ctx.fillStyle = 'rgba(0,0,0,0.85)';
ctx.fillRect(0, 0, W, H);
ctx.fillStyle = '#ff0';
ctx.font = 'bold 40px Arial';
ctx.textAlign = 'center';
ctx.fillText('GAME OVER', W / 2, H / 2 - 50);
ctx.fillStyle = '#fff';
ctx.font = '24px Arial';
ctx.fillText(`Final Score: ${score}`, W / 2, H / 2);
ctx.fillStyle = '#ff0';
ctx.beginPath();
ctx.roundRect(W / 2 - 100, H / 2 + 30, 200, 50, 10);
ctx.fill();
ctx.fillStyle = '#000';
ctx.font = 'bold 18px Arial';
ctx.fillText('TAP TO PLAY AGAIN', W / 2, H / 2 + 62);
}
}
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>
+343
View File
@@ -0,0 +1,343 @@
<!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>Memory Match</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { width: 100%; height: 100%; overflow: hidden; background: #667eea; 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();
}
// Gentle puzzle melody
const melody = [
{n: 392, d: 0.3}, {n: 440, d: 0.3}, {n: 494, d: 0.3}, {n: 523, d: 0.6},
{n: 494, d: 0.3}, {n: 440, d: 0.3}, {n: 392, d: 0.6},
{n: 349, d: 0.3}, {n: 392, d: 0.3}, {n: 440, d: 0.3}, {n: 494, d: 0.6},
{n: 523, d: 0.3}, {n: 587, d: 0.3}, {n: 523, d: 0.6}, {n: 0, d: 0.3}
];
const bass = [
{n: 98, d: 0.6}, {n: 110, d: 0.6}, {n: 131, d: 0.6}, {n: 110, d: 0.6},
{n: 87, d: 0.6}, {n: 98, d: 0.6}, {n: 110, d: 0.6}, {n: 131, d: 0.6}
];
let melodyIdx = 0, bassIdx = 0, nextMelody = 0, nextBass = 0;
function playTone(freq, dur, type = 'square', vol = 0.1) {
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 === 'flip') {
osc.frequency.value = 600;
gain.gain.setValueAtTime(0.15, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.1);
} else if (type === 'match') {
osc.frequency.setValueAtTime(523, audioCtx.currentTime);
osc.frequency.setValueAtTime(659, audioCtx.currentTime + 0.1);
osc.frequency.setValueAtTime(784, audioCtx.currentTime + 0.2);
gain.gain.setValueAtTime(0.2, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.3);
} else if (type === 'wrong') {
osc.frequency.setValueAtTime(300, audioCtx.currentTime);
osc.frequency.setValueAtTime(200, audioCtx.currentTime + 0.15);
gain.gain.setValueAtTime(0.2, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.3);
} else if (type === 'win') {
osc.frequency.setValueAtTime(523, audioCtx.currentTime);
osc.frequency.setValueAtTime(659, audioCtx.currentTime + 0.15);
osc.frequency.setValueAtTime(784, audioCtx.currentTime + 0.3);
osc.frequency.setValueAtTime(1047, audioCtx.currentTime + 0.45);
gain.gain.setValueAtTime(0.25, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.6);
}
osc.type = 'square';
osc.connect(gain);
gain.connect(audioCtx.destination);
osc.start();
osc.stop(audioCtx.currentTime + 0.7);
}
function updateMusic() {
if (!audioCtx || !musicEnabled || !musicPlaying) return;
const now = audioCtx.currentTime;
if (now >= nextMelody) {
const note = melody[melodyIdx];
playTone(note.n, note.d, 'triangle', 0.08);
nextMelody = now + note.d;
melodyIdx = (melodyIdx + 1) % melody.length;
}
if (now >= nextBass) {
const note = bass[bassIdx];
playTone(note.n, note.d, 'triangle', 0.12);
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;
function resize() {
W = window.innerWidth;
H = window.innerHeight;
canvas.width = W;
canvas.height = H;
safeTop = H * 0.1;
safeBottom = H * 0.9;
}
resize();
window.addEventListener('resize', resize);
const symbols = ['🌟', '🎈', '🍎', '🚗', '🐱', '🌈'];
let cards = [];
let flipped = [];
let matched = [];
let moves = 0;
let canFlip = true;
let gameOver = false;
let gameRunning = false;
function shuffle(arr) {
for (let i = arr.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[arr[i], arr[j]] = [arr[j], arr[i]];
}
return arr;
}
function initCards() {
const pairs = shuffle([...symbols, ...symbols]);
const cols = 4, rows = 3;
const gameH = safeBottom - safeTop - 60;
const gameW = W - 40;
const cardW = Math.min((gameW - (cols - 1) * 10) / cols, 90);
const cardH = Math.min((gameH - (rows - 1) * 10) / rows, 110);
const totalW = cols * cardW + (cols - 1) * 10;
const totalH = rows * cardH + (rows - 1) * 10;
const startX = (W - totalW) / 2;
const startY = safeTop + 40 + (gameH - totalH) / 2;
cards = [];
for (let r = 0; r < rows; r++) {
for (let c = 0; c < cols; c++) {
cards.push({
x: startX + c * (cardW + 10),
y: startY + r * (cardH + 10),
w: cardW,
h: cardH,
symbol: pairs[r * cols + c],
flipped: false,
matched: false
});
}
}
}
function init() {
initCards();
flipped = [];
matched = [];
moves = 0;
canFlip = true;
gameOver = false;
gameRunning = true;
startMusic();
}
function handleClick(x, y) {
if (gameOver) { init(); return; }
if (!canFlip) return;
for (let i = 0; i < cards.length; i++) {
const c = cards[i];
if (x >= c.x && x <= c.x + c.w && y >= c.y && y <= c.y + c.h) {
if (c.matched || c.flipped) continue;
c.flipped = true;
flipped.push(i);
playSfx('flip');
if (flipped.length === 2) {
moves++;
canFlip = false;
const [a, b] = flipped;
if (cards[a].symbol === cards[b].symbol) {
cards[a].matched = true;
cards[b].matched = true;
matched.push(a, b);
flipped = [];
canFlip = true;
playSfx('match');
if (matched.length === cards.length) {
gameOver = true;
gameRunning = false;
playSfx('win');
}
} else {
setTimeout(() => {
cards[a].flipped = false;
cards[b].flipped = false;
flipped = [];
canFlip = true;
playSfx('wrong');
}, 1200);
}
}
break;
}
}
}
document.addEventListener('keydown', e => {
initAudio();
if ((e.key === ' ' || e.key === 'Enter') && 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); });
function update() {
updateMusic();
}
function draw() {
// Background gradient
const grad = ctx.createLinearGradient(0, 0, W, H);
grad.addColorStop(0, '#667eea');
grad.addColorStop(1, '#764ba2');
ctx.fillStyle = grad;
ctx.fillRect(0, 0, W, H);
// Safe zones
ctx.fillStyle = 'rgba(0,0,0,0.2)';
ctx.fillRect(0, 0, W, safeTop);
ctx.fillRect(0, safeBottom, W, H - safeBottom);
// Header
ctx.fillStyle = '#fff';
ctx.font = 'bold 22px Arial';
ctx.textAlign = 'left';
ctx.fillText(`Moves: ${moves}`, 20, 35);
ctx.textAlign = 'right';
ctx.fillText(`Pairs: ${matched.length / 2}/${cards.length / 2}`, W - 20, 35);
ctx.textAlign = 'center';
ctx.fillText('MEMORY MATCH', W / 2, 35);
// Cards
cards.forEach(c => {
// Shadow
ctx.fillStyle = 'rgba(0,0,0,0.3)';
ctx.beginPath();
ctx.roundRect(c.x + 3, c.y + 3, c.w, c.h, 10);
ctx.fill();
if (c.flipped || c.matched) {
// Face up
ctx.fillStyle = c.matched ? '#4ade80' : '#fff';
ctx.beginPath();
ctx.roundRect(c.x, c.y, c.w, c.h, 10);
ctx.fill();
ctx.font = `${c.w * 0.5}px Arial`;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(c.symbol, c.x + c.w / 2, c.y + c.h / 2);
} else {
// Face down
const cardGrad = ctx.createLinearGradient(c.x, c.y, c.x + c.w, c.y + c.h);
cardGrad.addColorStop(0, '#4facfe');
cardGrad.addColorStop(1, '#00f2fe');
ctx.fillStyle = cardGrad;
ctx.beginPath();
ctx.roundRect(c.x, c.y, c.w, c.h, 10);
ctx.fill();
ctx.fillStyle = 'rgba(255,255,255,0.3)';
ctx.font = `${c.w * 0.4}px Arial`;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText('?', c.x + c.w / 2, c.y + c.h / 2);
}
});
if (gameOver) {
ctx.fillStyle = 'rgba(0,0,0,0.85)';
ctx.fillRect(0, 0, W, H);
ctx.fillStyle = '#fff';
ctx.font = 'bold 40px Arial';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText('🎉 YOU WIN! 🎉', W / 2, H / 2 - 50);
ctx.font = '24px Arial';
ctx.fillText(`Completed in ${moves} moves`, W / 2, H / 2);
ctx.fillStyle = '#4ade80';
ctx.beginPath();
ctx.roundRect(W / 2 - 100, H / 2 + 30, 200, 50, 10);
ctx.fill();
ctx.fillStyle = '#000';
ctx.font = 'bold 18px Arial';
ctx.fillText('TAP TO PLAY AGAIN', W / 2, H / 2 + 55);
}
}
function gameLoop() {
update();
draw();
requestAnimationFrame(gameLoop);
}
window.gameScore = 0;
Object.defineProperty(window, 'gameScore', { get: () => Math.max(0, 1000 - moves * 10) });
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>
+578
View File
@@ -0,0 +1,578 @@
<!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>Platformer</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { width: 100%; height: 100%; overflow: hidden; background: #87ceeb; 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();
}
// Cheerful platformer melody
const melody = [
{n: 523, d: 0.2}, {n: 587, d: 0.2}, {n: 659, d: 0.2}, {n: 784, d: 0.4},
{n: 659, d: 0.2}, {n: 523, d: 0.2}, {n: 587, d: 0.4},
{n: 440, d: 0.2}, {n: 494, d: 0.2}, {n: 523, d: 0.2}, {n: 659, d: 0.4},
{n: 587, d: 0.2}, {n: 523, d: 0.2}, {n: 494, 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: 110, d: 0.4}, {n: 131, d: 0.4}, {n: 147, d: 0.4}, {n: 131, d: 0.4}
];
let melodyIdx = 0, bassIdx = 0, nextMelody = 0, nextBass = 0;
function playTone(freq, dur, type = 'square', vol = 0.1) {
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 === 'jump') {
osc.frequency.setValueAtTime(300, audioCtx.currentTime);
osc.frequency.exponentialRampToValueAtTime(600, audioCtx.currentTime + 0.15);
gain.gain.setValueAtTime(0.15, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.15);
} else if (type === 'coin') {
osc.frequency.setValueAtTime(880, audioCtx.currentTime);
osc.frequency.setValueAtTime(1047, audioCtx.currentTime + 0.08);
gain.gain.setValueAtTime(0.15, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.15);
} else if (type === 'win') {
osc.frequency.setValueAtTime(523, audioCtx.currentTime);
osc.frequency.setValueAtTime(659, audioCtx.currentTime + 0.15);
osc.frequency.setValueAtTime(784, audioCtx.currentTime + 0.3);
osc.frequency.setValueAtTime(1047, audioCtx.currentTime + 0.45);
gain.gain.setValueAtTime(0.2, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.6);
} else if (type === 'die') {
osc.frequency.setValueAtTime(400, audioCtx.currentTime);
osc.frequency.exponentialRampToValueAtTime(100, audioCtx.currentTime + 0.4);
gain.gain.setValueAtTime(0.25, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.4);
osc.type = 'sawtooth';
}
osc.connect(gain);
gain.connect(audioCtx.destination);
osc.start();
osc.stop(audioCtx.currentTime + 0.7);
}
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.08);
nextMelody = now + note.d;
melodyIdx = (melodyIdx + 1) % melody.length;
}
if (now >= nextBass) {
const note = bass[bassIdx];
playTone(note.n, note.d, 'triangle', 0.12);
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;
function resize() {
W = window.innerWidth;
H = window.innerHeight;
canvas.width = W;
canvas.height = H;
safeTop = H * 0.1;
safeBottom = H * 0.9;
}
resize();
window.addEventListener('resize', resize);
let gameRunning = false;
let gameOver = false;
let levelComplete = false;
let score = 0;
let level = 1;
const player = { x: 50, y: 0, w: 30, h: 40, vx: 0, vy: 0, onGround: false, facingRight: true };
const gravity = 0.4;
const jumpForce = -10;
const moveSpeed = 4;
let platforms = [];
let coins = [];
let enemies = [];
let flag = null;
let cameraX = 0;
function generateLevel() {
platforms = [];
coins = [];
enemies = [];
const groundY = safeBottom - 40;
// Ground
platforms.push({ x: 0, y: groundY, w: 2000 + level * 500, h: 50 });
// Generate platforms
let lastX = 100;
const levelLength = 1500 + level * 300;
while (lastX < levelLength) {
const gap = 80 + Math.random() * 100;
const height = groundY - 80 - Math.random() * 150;
const width = 80 + Math.random() * 100;
platforms.push({ x: lastX + gap, y: height, w: width, h: 20 });
// Add coin on platform
if (Math.random() < 0.7) {
coins.push({ x: lastX + gap + width / 2, y: height - 30, collected: false });
}
// Add enemy on some platforms (not at start)
if (Math.random() < 0.3 && lastX > 300) {
enemies.push({
x: lastX + gap + 20,
y: height - 30,
w: 30,
h: 30,
startX: lastX + gap + 10,
endX: lastX + gap + width - 40,
dir: 1,
speed: 1 + level * 0.2
});
}
lastX += gap + width;
}
// Flag at end
flag = { x: levelLength, y: groundY - 100, w: 40, h: 100 };
// Reset player
player.x = 50;
player.y = groundY - player.h - 10;
player.vx = 0;
player.vy = 0;
player.onGround = true;
cameraX = 0;
}
function init() {
score = 0;
level = 1;
gameOver = false;
levelComplete = false;
gameRunning = true;
generateLevel();
startMusic();
}
function nextLevel() {
level++;
levelComplete = false;
generateLevel();
}
let keys = { left: false, right: false, jump: false };
document.addEventListener('keydown', e => {
initAudio();
if (e.key === 'ArrowLeft' || e.key === 'a') keys.left = true;
if (e.key === 'ArrowRight' || e.key === 'd') keys.right = true;
if (e.key === 'ArrowUp' || e.key === 'w' || e.key === ' ') {
keys.jump = true;
e.preventDefault();
}
if ((e.key === ' ' || e.key === 'Enter') && (gameOver || levelComplete)) {
if (levelComplete) nextLevel();
else init();
}
});
document.addEventListener('keyup', e => {
if (e.key === 'ArrowLeft' || e.key === 'a') keys.left = false;
if (e.key === 'ArrowRight' || e.key === 'd') keys.right = false;
if (e.key === 'ArrowUp' || e.key === 'w' || e.key === ' ') keys.jump = false;
});
canvas.addEventListener('touchstart', e => {
e.preventDefault();
initAudio();
if (gameOver || levelComplete) {
if (levelComplete) nextLevel();
else init();
return;
}
const x = e.touches[0].clientX;
if (x < W / 3) keys.left = true;
else if (x > W * 2 / 3) keys.right = true;
else keys.jump = true;
});
canvas.addEventListener('touchend', e => {
keys.left = false;
keys.right = false;
keys.jump = false;
});
canvas.addEventListener('click', e => {
initAudio();
if (gameOver || levelComplete) {
if (levelComplete) nextLevel();
else init();
}
});
function update() {
if (!gameRunning || gameOver || levelComplete) return;
// Horizontal movement
if (keys.left) {
player.vx = -moveSpeed;
player.facingRight = false;
} else if (keys.right) {
player.vx = moveSpeed;
player.facingRight = true;
} else {
player.vx *= 0.8;
}
// Jump
if (keys.jump && player.onGround) {
player.vy = jumpForce;
player.onGround = false;
playSfx('jump');
}
// Gravity
player.vy += gravity;
// Move horizontally
player.x += player.vx;
if (player.x < 0) player.x = 0;
// Horizontal collision
for (const p of platforms) {
if (player.x + player.w > p.x - cameraX && player.x < p.x - cameraX + p.w &&
player.y + player.h > p.y && player.y < p.y + p.h) {
if (player.vx > 0) player.x = p.x - cameraX - player.w;
else if (player.vx < 0) player.x = p.x - cameraX + p.w;
}
}
// Move vertically
player.y += player.vy;
player.onGround = false;
// Vertical collision
for (const p of platforms) {
if (player.x + player.w > p.x - cameraX && player.x < p.x - cameraX + p.w &&
player.y + player.h > p.y && player.y < p.y + p.h) {
if (player.vy > 0) {
player.y = p.y - player.h;
player.vy = 0;
player.onGround = true;
} else if (player.vy < 0) {
player.y = p.y + p.h;
player.vy = 0;
}
}
}
// Fall off screen
if (player.y > H) {
gameOver = true;
gameRunning = false;
musicPlaying = false;
playSfx('die');
return;
}
// Collect coins
for (const c of coins) {
if (!c.collected) {
const cx = c.x - cameraX;
const cy = c.y;
if (player.x + player.w > cx - 15 && player.x < cx + 15 &&
player.y + player.h > cy - 15 && player.y < cy + 15) {
c.collected = true;
score += 100;
playSfx('coin');
}
}
}
// Update enemies
for (const e of enemies) {
e.x += e.dir * e.speed;
if (e.x < e.startX || e.x > e.endX) {
e.dir *= -1;
}
// Enemy collision
const ex = e.x - cameraX;
if (player.x + player.w - 5 > ex && player.x + 5 < ex + e.w &&
player.y + player.h > e.y && player.y < e.y + e.h) {
// Check if stomping
if (player.vy > 0 && player.y + player.h < e.y + e.h / 2) {
e.y = 9999; // Remove enemy
player.vy = -8;
score += 50;
playSfx('jump');
} else {
gameOver = true;
gameRunning = false;
musicPlaying = false;
playSfx('die');
return;
}
}
}
// Check flag
if (flag) {
const fx = flag.x - cameraX;
if (player.x + player.w > fx && player.x < fx + flag.w) {
levelComplete = true;
score += 500;
playSfx('win');
}
}
// Camera follow
const targetCameraX = player.x + player.w / 2 + cameraX - W / 3;
cameraX += (targetCameraX - cameraX) * 0.1;
if (cameraX < 0) cameraX = 0;
updateMusic();
}
function draw() {
// Sky
ctx.fillStyle = '#87ceeb';
ctx.fillRect(0, 0, W, H);
// Safe zones
ctx.fillStyle = '#6bb9f0';
ctx.fillRect(0, 0, W, safeTop);
ctx.fillRect(0, safeBottom, W, H - safeBottom);
// Clouds
ctx.fillStyle = '#fff';
for (let i = 0; i < 5; i++) {
const x = ((i * 300 - cameraX * 0.2) % (W + 200)) - 100;
const y = safeTop + 30 + (i * 50) % 80;
ctx.beginPath();
ctx.arc(x, y, 25, 0, Math.PI * 2);
ctx.arc(x + 30, y - 10, 30, 0, Math.PI * 2);
ctx.arc(x + 60, y, 25, 0, Math.PI * 2);
ctx.fill();
}
// Header
ctx.fillStyle = '#333';
ctx.font = 'bold 22px Arial';
ctx.textAlign = 'left';
ctx.fillText(`Score: ${score}`, 20, 35);
ctx.textAlign = 'center';
ctx.fillText(`Level ${level}`, W / 2, 35);
ctx.textAlign = 'right';
ctx.fillText(`Coins: ${coins.filter(c => c.collected).length}/${coins.length}`, W - 20, 35);
// Platforms
for (const p of platforms) {
const x = p.x - cameraX;
if (x + p.w > 0 && x < W) {
if (p.h > 30) {
// Ground
ctx.fillStyle = '#8B4513';
ctx.fillRect(x, p.y, p.w, p.h);
ctx.fillStyle = '#228B22';
ctx.fillRect(x, p.y, p.w, 15);
} else {
// Platform
ctx.fillStyle = '#8B4513';
ctx.fillRect(x, p.y, p.w, p.h);
ctx.fillStyle = '#654321';
ctx.fillRect(x, p.y, p.w, 5);
}
}
}
// Coins
ctx.fillStyle = '#ffd700';
for (const c of coins) {
if (!c.collected) {
const x = c.x - cameraX;
if (x > -20 && x < W + 20) {
ctx.beginPath();
ctx.arc(x, c.y, 12, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#ffed4a';
ctx.beginPath();
ctx.arc(x - 3, c.y - 3, 5, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#ffd700';
}
}
}
// Enemies
ctx.fillStyle = '#e74c3c';
for (const e of enemies) {
const x = e.x - cameraX;
if (x > -50 && x < W + 50 && e.y < H) {
ctx.fillRect(x, e.y, e.w, e.h);
// Eyes
ctx.fillStyle = '#fff';
ctx.beginPath();
ctx.arc(x + 8, e.y + 10, 5, 0, Math.PI * 2);
ctx.arc(x + e.w - 8, e.y + 10, 5, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#000';
ctx.beginPath();
ctx.arc(x + 8 + e.dir * 2, e.y + 10, 2, 0, Math.PI * 2);
ctx.arc(x + e.w - 8 + e.dir * 2, e.y + 10, 2, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#e74c3c';
}
}
// Flag
if (flag) {
const fx = flag.x - cameraX;
if (fx > -50 && fx < W + 50) {
ctx.fillStyle = '#8B4513';
ctx.fillRect(fx + 5, flag.y, 8, flag.h);
ctx.fillStyle = '#2ecc71';
ctx.beginPath();
ctx.moveTo(fx + 13, flag.y);
ctx.lineTo(fx + 50, flag.y + 25);
ctx.lineTo(fx + 13, flag.y + 50);
ctx.closePath();
ctx.fill();
}
}
// Player
ctx.fillStyle = '#3498db';
ctx.fillRect(player.x, player.y, player.w, player.h);
// Eyes
ctx.fillStyle = '#fff';
const eyeOffset = player.facingRight ? 5 : -5;
ctx.beginPath();
ctx.arc(player.x + player.w / 2 + eyeOffset, player.y + 12, 6, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#000';
ctx.beginPath();
ctx.arc(player.x + player.w / 2 + eyeOffset + (player.facingRight ? 2 : -2), player.y + 12, 3, 0, Math.PI * 2);
ctx.fill();
// Touch controls hint
ctx.fillStyle = '#666';
ctx.font = '14px Arial';
ctx.textAlign = 'center';
ctx.fillText('Left side: Move Left | Center: Jump | Right side: Move Right', W / 2, safeBottom + 25);
if (levelComplete) {
ctx.fillStyle = 'rgba(0,0,0,0.85)';
ctx.fillRect(0, 0, W, H);
ctx.fillStyle = '#2ecc71';
ctx.font = 'bold 40px Arial';
ctx.textAlign = 'center';
ctx.fillText('LEVEL COMPLETE!', W / 2, H / 2 - 50);
ctx.fillStyle = '#fff';
ctx.font = '24px Arial';
ctx.fillText(`Score: ${score}`, W / 2, H / 2);
ctx.fillStyle = '#2ecc71';
ctx.beginPath();
ctx.roundRect(W / 2 - 100, H / 2 + 30, 200, 50, 10);
ctx.fill();
ctx.fillStyle = '#fff';
ctx.font = 'bold 18px Arial';
ctx.fillText('NEXT LEVEL', W / 2, H / 2 + 62);
}
if (gameOver) {
ctx.fillStyle = 'rgba(0,0,0,0.85)';
ctx.fillRect(0, 0, W, H);
ctx.fillStyle = '#e74c3c';
ctx.font = 'bold 40px Arial';
ctx.textAlign = 'center';
ctx.fillText('GAME OVER', W / 2, H / 2 - 50);
ctx.fillStyle = '#fff';
ctx.font = '24px Arial';
ctx.fillText(`Final Score: ${score}`, W / 2, H / 2);
ctx.fillStyle = '#3498db';
ctx.beginPath();
ctx.roundRect(W / 2 - 100, H / 2 + 30, 200, 50, 10);
ctx.fill();
ctx.fillStyle = '#fff';
ctx.font = 'bold 18px Arial';
ctx.fillText('TAP TO PLAY AGAIN', W / 2, H / 2 + 62);
}
}
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 && !levelComplete) { gameRunning = true; startMusic(); } };
window.setMusicEnabled = (v) => { musicEnabled = v; if (!v) musicPlaying = false; };
window.setSfxEnabled = (v) => { sfxEnabled = v; };
init();
gameLoop();
</script>
</body>
</html>
+347
View File
@@ -0,0 +1,347 @@
<!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>Pong</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { width: 100%; height: 100%; overflow: hidden; background: #000; touch-action: none; }
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();
}
// Retro arcade melody
const melody = [
{n: 392, d: 0.2}, {n: 440, d: 0.2}, {n: 494, d: 0.2}, {n: 523, d: 0.4},
{n: 494, d: 0.2}, {n: 440, d: 0.2}, {n: 392, d: 0.4},
{n: 330, d: 0.2}, {n: 349, d: 0.2}, {n: 392, d: 0.2}, {n: 440, d: 0.4},
{n: 392, d: 0.2}, {n: 349, d: 0.2}, {n: 330, d: 0.4},
{n: 294, d: 0.2}, {n: 330, d: 0.2}, {n: 349, d: 0.2}, {n: 392, d: 0.4},
{n: 440, d: 0.2}, {n: 494, d: 0.2}, {n: 523, d: 0.4}, {n: 0, d: 0.4}
];
const bass = [
{n: 98, d: 0.4}, {n: 98, d: 0.4}, {n: 110, d: 0.4}, {n: 110, d: 0.4},
{n: 82, d: 0.4}, {n: 82, d: 0.4}, {n: 98, d: 0.4}, {n: 98, d: 0.4},
{n: 73, d: 0.4}, {n: 73, d: 0.4}, {n: 82, d: 0.4}, {n: 82, d: 0.4},
{n: 98, d: 0.4}, {n: 110, d: 0.4}, {n: 131, d: 0.8}
];
let melodyIdx = 0, bassIdx = 0;
let 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 === 'hit') {
osc.frequency.value = 440;
gain.gain.setValueAtTime(0.2, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.1);
} else if (type === 'score') {
osc.frequency.setValueAtTime(523, audioCtx.currentTime);
osc.frequency.setValueAtTime(659, audioCtx.currentTime + 0.1);
gain.gain.setValueAtTime(0.2, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.2);
} else if (type === 'wall') {
osc.frequency.value = 220;
gain.gain.setValueAtTime(0.15, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.05);
}
osc.type = 'square';
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;
function resize() {
W = window.innerWidth;
H = window.innerHeight;
canvas.width = W;
canvas.height = H;
safeTop = H * 0.1;
safeBottom = H * 0.9;
}
resize();
window.addEventListener('resize', resize);
let gameRunning = false;
let gameOver = false;
let playerScore = 0;
let cpuScore = 0;
const winScore = 7;
const paddleW = 15;
const paddleH = 100;
const ballSize = 12;
let ballSpeed = 3;
const player = { y: 0, speed: 8 };
const cpu = { y: 0, speed: 2, mistakeChance: 0.35 };
const ball = { x: 0, y: 0, dx: 0, dy: 0 };
let targetY = H / 2;
let keys = { up: false, down: false };
function resetBall(towardPlayer = true) {
ball.x = W / 2;
ball.y = H / 2;
const angle = (Math.random() - 0.5) * Math.PI / 3;
ball.dx = (towardPlayer ? -1 : 1) * Math.cos(angle) * ballSpeed;
ball.dy = Math.sin(angle) * ballSpeed;
}
function init() {
playerScore = 0;
cpuScore = 0;
ballSpeed = 3;
cpu.speed = 2;
cpu.mistakeChance = 0.35;
player.y = H / 2 - paddleH / 2;
cpu.y = H / 2 - paddleH / 2;
resetBall(true);
gameOver = false;
gameRunning = true;
startMusic();
}
document.addEventListener('keydown', e => {
if (e.key === 'ArrowUp' || e.key === 'w') keys.up = true;
if (e.key === 'ArrowDown' || e.key === 's') keys.down = true;
if ((e.key === ' ' || e.key === 'Enter') && gameOver) init();
initAudio();
});
document.addEventListener('keyup', e => {
if (e.key === 'ArrowUp' || e.key === 'w') keys.up = false;
if (e.key === 'ArrowDown' || e.key === 's') keys.down = false;
});
canvas.addEventListener('mousemove', e => { targetY = e.clientY; });
canvas.addEventListener('click', () => { initAudio(); if (gameOver) init(); });
canvas.addEventListener('touchstart', e => {
e.preventDefault();
initAudio();
if (gameOver) init();
targetY = e.touches[0].clientY;
});
canvas.addEventListener('touchmove', e => {
e.preventDefault();
targetY = e.touches[0].clientY;
});
function update() {
if (!gameRunning || gameOver) return;
// Player movement
if (keys.up) player.y -= player.speed;
if (keys.down) player.y += player.speed;
player.y += (targetY - player.y - paddleH / 2) * 0.1;
player.y = Math.max(safeTop, Math.min(safeBottom - paddleH, player.y));
// CPU movement (makes mistakes)
const cpuTarget = ball.y - paddleH / 2;
let dir = Math.sign(cpuTarget - cpu.y);
if (Math.random() < cpu.mistakeChance) dir = -dir;
if (Math.abs(cpuTarget - cpu.y) > 15) cpu.y += dir * cpu.speed;
cpu.y = Math.max(safeTop, Math.min(safeBottom - paddleH, cpu.y));
// Ball movement
ball.x += ball.dx;
ball.y += ball.dy;
// Top/bottom walls
if (ball.y - ballSize < safeTop || ball.y + ballSize > safeBottom) {
ball.dy = -ball.dy;
ball.y = Math.max(safeTop + ballSize, Math.min(safeBottom - ballSize, ball.y));
playSfx('wall');
}
// Player paddle
if (ball.dx < 0 && ball.x - ballSize <= 30 + paddleW &&
ball.x > 30 && ball.y >= player.y - 10 && ball.y <= player.y + paddleH + 10) {
ball.dx = Math.abs(ball.dx) * 1.02;
ball.dy += ((ball.y - player.y - paddleH / 2) / paddleH) * 3;
playSfx('hit');
}
// CPU paddle
if (ball.dx > 0 && ball.x + ballSize >= W - 30 - paddleW &&
ball.x < W - 30 && ball.y >= cpu.y - 10 && ball.y <= cpu.y + paddleH + 10) {
ball.dx = -Math.abs(ball.dx) * 1.02;
ball.dy += ((ball.y - cpu.y - paddleH / 2) / paddleH) * 3;
playSfx('hit');
}
// Scoring
if (ball.x < 0) {
cpuScore++;
playSfx('score');
if (cpuScore >= winScore) { gameOver = true; gameRunning = false; }
else resetBall(true);
}
if (ball.x > W) {
playerScore++;
playSfx('score');
if (cpu.mistakeChance > 0.15) cpu.mistakeChance -= 0.03;
if (cpu.speed < 3.5) cpu.speed += 0.15;
if (playerScore >= winScore) { gameOver = true; gameRunning = false; }
else resetBall(false);
}
updateMusic();
}
function draw() {
ctx.fillStyle = '#000';
ctx.fillRect(0, 0, W, H);
// Safe zones
ctx.fillStyle = '#111';
ctx.fillRect(0, 0, W, safeTop);
ctx.fillRect(0, safeBottom, W, H - safeBottom);
// Center line
ctx.setLineDash([15, 15]);
ctx.strokeStyle = '#333';
ctx.lineWidth = 4;
ctx.beginPath();
ctx.moveTo(W / 2, safeTop);
ctx.lineTo(W / 2, safeBottom);
ctx.stroke();
ctx.setLineDash([]);
// Scores
ctx.fillStyle = '#0f0';
ctx.font = 'bold 48px monospace';
ctx.textAlign = 'center';
ctx.fillText(playerScore, W / 4, safeTop - 15);
ctx.fillStyle = '#f00';
ctx.fillText(cpuScore, W * 3 / 4, safeTop - 15);
// Labels
ctx.font = '16px Arial';
ctx.fillStyle = '#0f0';
ctx.fillText('YOU', W / 4, 20);
ctx.fillStyle = '#f00';
ctx.fillText('CPU', W * 3 / 4, 20);
// Paddles
ctx.fillStyle = '#0f0';
ctx.shadowColor = '#0f0';
ctx.shadowBlur = 15;
ctx.fillRect(30, player.y, paddleW, paddleH);
ctx.fillStyle = '#f00';
ctx.shadowColor = '#f00';
ctx.fillRect(W - 30 - paddleW, cpu.y, paddleW, paddleH);
ctx.shadowBlur = 0;
// Ball
ctx.fillStyle = '#fff';
ctx.shadowColor = '#fff';
ctx.shadowBlur = 10;
ctx.beginPath();
ctx.arc(ball.x, ball.y, ballSize, 0, Math.PI * 2);
ctx.fill();
ctx.shadowBlur = 0;
if (gameOver) {
ctx.fillStyle = 'rgba(0,0,0,0.85)';
ctx.fillRect(0, 0, W, H);
ctx.fillStyle = playerScore >= winScore ? '#0f0' : '#f00';
ctx.font = 'bold 40px Arial';
ctx.textAlign = 'center';
ctx.fillText(playerScore >= winScore ? 'YOU WIN!' : 'CPU WINS', W / 2, H / 2 - 30);
ctx.fillStyle = '#fff';
ctx.font = '24px Arial';
ctx.fillText(`${playerScore} - ${cpuScore}`, W / 2, H / 2 + 20);
ctx.fillStyle = '#0f0';
ctx.beginPath();
ctx.roundRect(W / 2 - 100, H / 2 + 50, 200, 50, 10);
ctx.fill();
ctx.fillStyle = '#000';
ctx.font = 'bold 18px Arial';
ctx.fillText('TAP TO PLAY AGAIN', W / 2, H / 2 + 82);
}
}
function gameLoop() {
update();
draw();
requestAnimationFrame(gameLoop);
}
window.gameScore = 0;
Object.defineProperty(window, 'gameScore', { get: () => playerScore * 100 });
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>
+445
View File
@@ -0,0 +1,445 @@
<!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>2048</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { width: 100%; height: 100%; overflow: hidden; background: #faf8ef; 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();
}
// Calm puzzle melody
const melody = [
{n: 392, d: 0.4}, {n: 440, d: 0.4}, {n: 494, d: 0.4}, {n: 523, d: 0.8},
{n: 494, d: 0.4}, {n: 440, d: 0.4}, {n: 392, d: 0.8},
{n: 330, d: 0.4}, {n: 349, d: 0.4}, {n: 392, d: 0.4}, {n: 440, d: 0.8},
{n: 392, d: 0.4}, {n: 349, d: 0.4}, {n: 330, d: 0.8}, {n: 0, d: 0.4}
];
const bass = [
{n: 98, d: 0.8}, {n: 110, d: 0.8}, {n: 131, d: 0.8}, {n: 110, d: 0.8},
{n: 82, d: 0.8}, {n: 87, d: 0.8}, {n: 98, d: 0.8}, {n: 110, d: 0.8}
];
let melodyIdx = 0, bassIdx = 0, nextMelody = 0, nextBass = 0;
function playTone(freq, dur, type = 'square', vol = 0.08) {
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 === 'move') {
osc.frequency.value = 300;
gain.gain.setValueAtTime(0.1, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.05);
} else if (type === 'merge') {
osc.frequency.setValueAtTime(400, audioCtx.currentTime);
osc.frequency.setValueAtTime(600, audioCtx.currentTime + 0.05);
gain.gain.setValueAtTime(0.15, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.1);
} else if (type === 'win') {
osc.frequency.setValueAtTime(523, audioCtx.currentTime);
osc.frequency.setValueAtTime(659, audioCtx.currentTime + 0.15);
osc.frequency.setValueAtTime(784, audioCtx.currentTime + 0.3);
osc.frequency.setValueAtTime(1047, audioCtx.currentTime + 0.45);
gain.gain.setValueAtTime(0.2, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.6);
} else if (type === 'lose') {
osc.frequency.setValueAtTime(300, audioCtx.currentTime);
osc.frequency.exponentialRampToValueAtTime(100, audioCtx.currentTime + 0.4);
gain.gain.setValueAtTime(0.2, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.4);
}
osc.type = 'square';
osc.connect(gain);
gain.connect(audioCtx.destination);
osc.start();
osc.stop(audioCtx.currentTime + 0.7);
}
function updateMusic() {
if (!audioCtx || !musicEnabled || !musicPlaying) return;
const now = audioCtx.currentTime;
if (now >= nextMelody) {
const note = melody[melodyIdx];
playTone(note.n, note.d, 'triangle', 0.06);
nextMelody = now + note.d;
melodyIdx = (melodyIdx + 1) % melody.length;
}
if (now >= nextBass) {
const note = bass[bassIdx];
playTone(note.n, note.d, 'triangle', 0.1);
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;
function resize() {
W = window.innerWidth;
H = window.innerHeight;
canvas.width = W;
canvas.height = H;
safeTop = H * 0.1;
safeBottom = H * 0.9;
}
resize();
window.addEventListener('resize', resize);
const SIZE = 4;
let grid = [];
let score = 0;
let bestScore = 0;
let gameOver = false;
let won = false;
let gameRunning = false;
const COLORS = {
0: '#cdc1b4',
2: '#eee4da',
4: '#ede0c8',
8: '#f2b179',
16: '#f59563',
32: '#f67c5f',
64: '#f65e3b',
128: '#edcf72',
256: '#edcc61',
512: '#edc850',
1024: '#edc53f',
2048: '#edc22e'
};
const TEXT_COLORS = {
2: '#776e65',
4: '#776e65'
};
function createGrid() {
grid = [];
for (let r = 0; r < SIZE; r++) {
grid.push(new Array(SIZE).fill(0));
}
}
function addRandomTile() {
const empty = [];
for (let r = 0; r < SIZE; r++) {
for (let c = 0; c < SIZE; c++) {
if (grid[r][c] === 0) empty.push({r, c});
}
}
if (empty.length > 0) {
const {r, c} = empty[Math.floor(Math.random() * empty.length)];
grid[r][c] = Math.random() < 0.9 ? 2 : 4;
}
}
function init() {
createGrid();
score = 0;
gameOver = false;
won = false;
gameRunning = true;
addRandomTile();
addRandomTile();
startMusic();
}
function canMove() {
for (let r = 0; r < SIZE; r++) {
for (let c = 0; c < SIZE; c++) {
if (grid[r][c] === 0) return true;
if (c < SIZE - 1 && grid[r][c] === grid[r][c + 1]) return true;
if (r < SIZE - 1 && grid[r][c] === grid[r + 1][c]) return true;
}
}
return false;
}
function move(direction) {
if (gameOver) return false;
let moved = false;
let merged = false;
const rotateGrid = (times) => {
for (let t = 0; t < times; t++) {
const newGrid = [];
for (let r = 0; r < SIZE; r++) {
newGrid.push([]);
for (let c = 0; c < SIZE; c++) {
newGrid[r].push(grid[SIZE - 1 - c][r]);
}
}
grid = newGrid;
}
};
// Rotate so we always move left
const rotations = {left: 0, up: 1, right: 2, down: 3};
rotateGrid(rotations[direction]);
// Move and merge left
for (let r = 0; r < SIZE; r++) {
const row = grid[r].filter(v => v !== 0);
const newRow = [];
for (let i = 0; i < row.length; i++) {
if (i < row.length - 1 && row[i] === row[i + 1]) {
const mergedValue = row[i] * 2;
newRow.push(mergedValue);
score += mergedValue;
if (mergedValue === 2048 && !won) {
won = true;
playSfx('win');
}
i++;
merged = true;
} else {
newRow.push(row[i]);
}
}
while (newRow.length < SIZE) newRow.push(0);
for (let c = 0; c < SIZE; c++) {
if (grid[r][c] !== newRow[c]) moved = true;
grid[r][c] = newRow[c];
}
}
// Rotate back
rotateGrid((4 - rotations[direction]) % 4);
if (moved) {
addRandomTile();
if (merged) playSfx('merge');
else playSfx('move');
if (!canMove()) {
gameOver = true;
gameRunning = false;
playSfx('lose');
}
if (score > bestScore) bestScore = score;
}
return moved;
}
document.addEventListener('keydown', e => {
initAudio();
if (e.key === 'ArrowLeft' || e.key === 'a') { move('left'); e.preventDefault(); }
if (e.key === 'ArrowRight' || e.key === 'd') { move('right'); e.preventDefault(); }
if (e.key === 'ArrowUp' || e.key === 'w') { move('up'); e.preventDefault(); }
if (e.key === 'ArrowDown' || e.key === 's') { move('down'); e.preventDefault(); }
if ((e.key === ' ' || e.key === 'Enter') && gameOver) init();
});
let touchStartX = 0, touchStartY = 0;
canvas.addEventListener('click', e => {
initAudio();
if (gameOver) init();
});
canvas.addEventListener('touchstart', e => {
e.preventDefault();
initAudio();
if (gameOver) { init(); return; }
touchStartX = e.touches[0].clientX;
touchStartY = e.touches[0].clientY;
});
canvas.addEventListener('touchend', e => {
e.preventDefault();
const dx = e.changedTouches[0].clientX - touchStartX;
const dy = e.changedTouches[0].clientY - touchStartY;
if (Math.abs(dx) < 20 && Math.abs(dy) < 20) return;
if (Math.abs(dx) > Math.abs(dy)) {
move(dx > 0 ? 'right' : 'left');
} else {
move(dy > 0 ? 'down' : 'up');
}
});
canvas.addEventListener('mousedown', e => { touchStartX = e.clientX; touchStartY = e.clientY; });
canvas.addEventListener('mouseup', e => {
const dx = e.clientX - touchStartX;
const dy = e.clientY - touchStartY;
if (Math.abs(dx) < 20 && Math.abs(dy) < 20) return;
if (Math.abs(dx) > Math.abs(dy)) {
move(dx > 0 ? 'right' : 'left');
} else {
move(dy > 0 ? 'down' : 'up');
}
});
function update() {
updateMusic();
}
function draw() {
// Background
ctx.fillStyle = '#faf8ef';
ctx.fillRect(0, 0, W, H);
// Safe zones
ctx.fillStyle = '#f0e6d3';
ctx.fillRect(0, 0, W, safeTop);
ctx.fillRect(0, safeBottom, W, H - safeBottom);
// Header
ctx.fillStyle = '#776e65';
ctx.font = 'bold 28px Arial';
ctx.textAlign = 'center';
ctx.fillText('2048', W / 2, 40);
ctx.font = 'bold 18px Arial';
ctx.textAlign = 'left';
ctx.fillText(`Score: ${score}`, 20, 35);
ctx.textAlign = 'right';
ctx.fillText(`Best: ${bestScore}`, W - 20, 35);
// Calculate board size
const availH = safeBottom - safeTop - 40;
const availW = W - 40;
const boardSize = Math.min(availW, availH);
const cellPadding = 8;
const cellSize = (boardSize - cellPadding * (SIZE + 1)) / SIZE;
const boardX = (W - boardSize) / 2;
const boardY = safeTop + 20 + (availH - boardSize) / 2;
// Board background
ctx.fillStyle = '#bbada0';
ctx.beginPath();
ctx.roundRect(boardX, boardY, boardSize, boardSize, 10);
ctx.fill();
// Tiles
for (let r = 0; r < SIZE; r++) {
for (let c = 0; c < SIZE; c++) {
const x = boardX + cellPadding + c * (cellSize + cellPadding);
const y = boardY + cellPadding + r * (cellSize + cellPadding);
const value = grid[r][c];
ctx.fillStyle = COLORS[value] || '#3c3a32';
ctx.beginPath();
ctx.roundRect(x, y, cellSize, cellSize, 6);
ctx.fill();
if (value > 0) {
ctx.fillStyle = TEXT_COLORS[value] || '#f9f6f2';
const fontSize = value >= 1024 ? cellSize * 0.35 : value >= 128 ? cellSize * 0.4 : cellSize * 0.5;
ctx.font = `bold ${fontSize}px Arial`;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(value, x + cellSize / 2, y + cellSize / 2);
}
}
}
// Controls hint
ctx.fillStyle = '#776e65';
ctx.font = '14px Arial';
ctx.textAlign = 'center';
ctx.textBaseline = 'alphabetic';
ctx.fillText('Swipe or Arrow Keys to move tiles', W / 2, safeBottom + 25);
// Win message
if (won && !gameOver) {
ctx.fillStyle = 'rgba(237, 194, 46, 0.5)';
ctx.fillRect(boardX, boardY, boardSize, boardSize);
ctx.fillStyle = '#f9f6f2';
ctx.font = 'bold 36px Arial';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText('You Win!', W / 2, (safeTop + safeBottom) / 2);
ctx.font = '18px Arial';
ctx.fillText('Keep playing!', W / 2, (safeTop + safeBottom) / 2 + 40);
}
// Game over
if (gameOver) {
ctx.fillStyle = 'rgba(238, 228, 218, 0.85)';
ctx.fillRect(boardX, boardY, boardSize, boardSize);
ctx.fillStyle = '#776e65';
ctx.font = 'bold 36px Arial';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText('Game Over!', W / 2, (safeTop + safeBottom) / 2 - 30);
ctx.font = '24px Arial';
ctx.fillText(`Score: ${score}`, W / 2, (safeTop + safeBottom) / 2 + 10);
ctx.fillStyle = '#8f7a66';
ctx.beginPath();
ctx.roundRect(W / 2 - 80, (safeTop + safeBottom) / 2 + 40, 160, 45, 8);
ctx.fill();
ctx.fillStyle = '#f9f6f2';
ctx.font = 'bold 16px Arial';
ctx.fillText('TAP TO RESTART', W / 2, (safeTop + safeBottom) / 2 + 68);
}
}
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>
+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>