<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Squirrel Adventure</title>
<style>
html, body {
margin: 0;
padding: 0;
background: #1b1b2b;
overflow: hidden;
font-family: monospace, sans-serif;
image-rendering: pixelated;
}
#ui {
position: fixed;
top: 0; left: 0; right: 0;
display: flex;
justify-content: space-between;
padding: 6px 12px;
color: #fff;
font-size: 14px;
text-shadow: 1px 1px 0 #000;
pointer-events: none;
z-index: 10;
}
#msg {
position: fixed;
top: 40px;
left: 50%;
transform: translateX(-50%);
color: #fff8d0;
background: rgba(0,0,0,0.6);
padding: 6px 14px;
border-radius: 8px;
font-size: 14px;
z-index: 10;
display: none;
}
canvas {
display: block;
background: #3c8f4a;
image-rendering: pixelated;
margin: 0 auto;
}
#help {
position: fixed;
bottom: 4px;
left: 0; right: 0;
text-align: center;
color: #eee;
font-size: 12px;
text-shadow: 1px 1px 0 #000;
z-index: 10;
}
</style>
</head>
<body>
<div id="ui">
<div id="p1stat">🐿️1 HP: 5</div>
<div id="p2stat">🐿️2 HP: 5</div>
</div>
<div id="msg"></div>
<canvas id="game" width="800" height="600"></canvas>
<div id="help">P1: WASD move, F attack | P2: Arrow keys move, / attack</div>
<script>
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
ctx.imageSmoothingEnabled = false;
const W = canvas.width, H = canvas.height;
const WORLD_W = 1600, WORLD_H = 1200;
function showMsg(text, ms=2200) {
const m = document.getElementById('msg');
m.textContent = text;
m.style.display = 'block';
clearTimeout(showMsg._t);
showMsg._t = setTimeout(() => { m.style.display = 'none'; }, ms);
}
let trees = [];
for (let i = 0; i < 40; i++) {
trees.push({ x: Math.random()*WORLD_W, y: Math.random()*WORLD_H, r: 18+Math.random()*10 });
}
let puzzleRock = { x: 500, y: 300, solved: false, r: 26 };
let treasure = { x: 500, y: 300, taken: false, active: false };
let launcher = { x: 1200, y: 900, taken: false };
let frog = { x: 900, y: 700, taken: false, hopT: 0, baseX: 900, baseY: 700 };
let cabin = { x: 250, y: 700, entered: false, inside: false };
let ferns = [];
for (let i = 0; i < 14; i++) {
let ang = Math.random() * Math.PI * 2;
let dist = 60 + Math.random() * 50;
ferns.push({ x: cabin.x + Math.cos(ang)*dist, y: cabin.y + Math.sin(ang)*dist, sway: Math.random()*Math.PI*2 });
}
let insideFern = { x: 130, y: 130, sway: 0 };
const ROOM_W = 260, ROOM_H = 220;
let craftTable = { x: 200, y: 80 };
let crafted = 0;
let bushes = [];
for (let i = 0; i < 24; i++) {
bushes.push({ x: Math.random()*WORLD_W, y: Math.random()*WORLD_H, r: 10+Math.random()*8 });
}
let flowers = [];
const flowerColors = ['#ff6b9d','#ffd93d','#ff8c42','#c774e8','#fff'];
for (let i = 0; i < 40; i++) {
flowers.push({ x: Math.random()*WORLD_W, y: Math.random()*WORLD_H, color: flowerColors[Math.floor(Math.random()*flowerColors.length)] });
}
let bears = [];
function spawnBear(x, y) {
bears.push({ x, y, hp: 3, alive: true, cd: 0 });
}
spawnBear(900, 300);
spawnBear(300, 800);
spawnBear(1300, 500);
spawnBear(700, 1000);
let bearSpawnTimer = 0;
const MAX_BEARS = 60;
function spawnRandomBear() {
let liveCount = bears.filter(b => b.alive).length;
if (liveCount >= MAX_BEARS) return;
for (let i = 0; i < 5; i++) {
if (bears.filter(b => b.alive).length >= MAX_BEARS) break;
let x = Math.random() * WORLD_W;
let y = Math.random() * WORLD_H;
spawnBear(x, y);
}
}
let players = {
1: {
x: 300, y: 300, w: 28, h: 28, color: '#c07a3e',
hp: 5, maxHp: 5, weapon: 'sword', dir: 'down', attackCd: 0, alive: true,
keys: { up: 'w', down: 's', left: 'a', right: 'd', attack: 'f' }
},
2: {
x: 380, y: 300, w: 28, h: 28, color: '#8a5a99',
hp: 5, maxHp: 5, weapon: 'sword', dir: 'down', attackCd: 0, alive: true,
keys: { up: 'ArrowUp', down: 'ArrowDown', left: 'ArrowLeft', right: 'ArrowRight', attack: '/' }
}
};
let keysDown = {};
window.addEventListener('keydown', e => {
keysDown[e.key] = true;
if (e.key === ' ') e.preventDefault();
});
window.addEventListener('keyup', e => { keysDown[e.key] = false; });
function tryCraft() {
if (!cabin.inside) return;
for (let p of [1, 2]) {
const pl = players[p];
if (!pl.alive) continue;
let d = Math.hypot(pl.x - craftTable.x, pl.y - craftTable.y);
if (d < 36) {
if (treasure.taken && crafted === 0) {
crafted = 1;
showMsg('✨ You crafted a shiny gem from acorns!');
} else if (crafted > 0) {
showMsg('You already crafted your gem!');
} else {
showMsg('You need acorns first! Find the treasure.');
}
}
}
}
window.addEventListener('keydown', e => {
if (e.key === 'c' || e.key === 'C') tryCraft();
});
function tryAttack(p) {
const pl = players[p];
if (pl.attackCd > 0 || !pl.alive) return;
pl.attackCd = pl.weapon === 'gun' ? 18 : 24;
pl.attacking = 10;
if (!puzzleRock.solved) {
let d = Math.hypot(pl.x - puzzleRock.x, pl.y - puzzleRock.y);
if (d < 50) {
puzzleRock.solved = true;
treasure.active = true;
showMsg('🧩 The rock cracked open! A treasure appeared!');
}
}
let range = 40;
for (let b of bears) {
if (!b.alive) continue;
let d = Math.hypot(pl.x - b.x, pl.y - b.y);
if (d < range) {
b.hp -= 1;
if (b.hp <= 0) {
b.alive = false;
showMsg('🐻 A bear was defeated!');
}
}
}
}
function update() {
for (let p of [1, 2]) {
const pl = players[p];
if (!pl.alive) continue;
const k = pl.keys;
let speed = 3;
let dx = 0, dy = 0;
if (keysDown[k.up]) { dy -= speed; pl.dir = 'up'; }
if (keysDown[k.down]) { dy += speed; pl.dir = 'down'; }
if (keysDown[k.left]) { dx -= speed; pl.dir = 'left'; }
if (keysDown[k.right]) { dx += speed; pl.dir = 'right'; }
pl.x = Math.max(20, Math.min(WORLD_W-20, pl.x + dx));
pl.y = Math.max(20, Math.min(WORLD_H-20, pl.y + dy));
if (keysDown[k.attack]) tryAttack(p);
if (pl.attackCd > 0) pl.attackCd--;
if (pl.attacking > 0) pl.attacking--;
if (!cabin.inside) {
let dCabin = Math.hypot(pl.x - cabin.x, pl.y - (cabin.y + 14));
if (dCabin < 24) {
if (!cabin.entered) {
cabin.entered = true;
showMsg('🏠 Welcome home, squirrel!');
}
cabin.inside = true;
pl.x = ROOM_W/2;
pl.y = ROOM_H - 30;
}
} else {
if (pl.y > ROOM_H - 20 && pl.x > ROOM_W/2 - 30 && pl.x < ROOM_W/2 + 30) {
cabin.inside = false;
pl.x = cabin.x;
pl.y = cabin.y + 40;
}
pl.x = Math.max(20, Math.min(ROOM_W-20, pl.x));
pl.y = Math.max(20, Math.min(ROOM_H-20, pl.y));
}
if (treasure.active && !treasure.taken) {
let d = Math.hypot(pl.x - treasure.x, pl.y - treasure.y);
if (d < 30) {
treasure.taken = true;
showMsg('🌰 Got a treasure of acorns!');
}
}
if (!frog.taken) {
let d = Math.hypot(pl.x - frog.x, pl.y - frog.y);
if (d < 30) {
frog.taken = true;
showMsg('🐸 Player ' + p + ' found a friendly frog!');
}
}
}
for (let b of bears) {
if (!b.alive) continue;
let target = null, bestD = Infinity;
for (let p of [1, 2]) {
const pl = players[p];
if (!pl.alive) continue;
let d = Math.hypot(pl.x - b.x, pl.y - b.y);
if (d < bestD) { bestD = d; target = pl; }
}
if (target && bestD < 400) {
let ang = Math.atan2(target.y - b.y, target.x - b.x);
b.x += Math.cos(ang) * 1.2;
b.y += Math.sin(ang) * 1.2;
if (bestD < 26 && b.cd <= 0) {
target.hp -= 1;
b.cd = 60;
if (target.hp <= 0) {
target.hp = 0;
target.alive = false;
showMsg('💀 Squirrel down!');
}
}
}
if (b.cd > 0) b.cd--;
}
bearSpawnTimer++;
if (bearSpawnTimer >= 60) {
bearSpawnTimer = 0;
spawnRandomBear();
}
document.getElementById('p1stat').textContent = '🐿️1 HP: ' + Math.max(0, players[1].hp) + ' 🗡️';
document.getElementById('p2stat').textContent = '🐿️2 HP: ' + Math.max(0, players[2].hp) + ' 🗡️';
}
function getCamera() {
const p1 = players[1], p2 = players[2];
let cx = (p1.x + p2.x) / 2;
let cy = (p1.y + p2.y) / 2;
let camX = Math.max(W/2, Math.min(WORLD_W - W/2, cx));
let camY = Math.max(H/2, Math.min(WORLD_H - H/2, cy));
return { x: camX - W/2, y: camY - H/2 };
}
function drawSquirrel(pl, isAlive) {
const s = pl.w;
ctx.save();
if (!isAlive) ctx.globalAlpha = 0.3;
ctx.fillStyle = pl.color;
ctx.fillRect(pl.x - s/2, pl.y - s/2, s, s);
ctx.fillStyle = '#d4534f';
ctx.fillRect(pl.x - s/2 - 6, pl.y - 6, 8, 16);
ctx.fillStyle = '#fff';
ctx.fillRect(pl.x - 6, pl.y - 4, 4, 4);
ctx.fillRect(pl.x + 2, pl.y - 4, 4, 4);
ctx.fillStyle = pl.color;
ctx.beginPath();
ctx.arc(pl.x + s/2 + 4, pl.y - s/2, 10, 0, Math.PI*2);
ctx.fill();
ctx.strokeStyle = '#ccc';
ctx.lineWidth = 4;
let wx = pl.x, wy = pl.y;
let ext = pl.attacking > 0 ? 26 : 16;
if (pl.dir === 'up') wy -= ext;
if (pl.dir === 'down') wy += ext;
if (pl.dir === 'left') wx -= ext;
if (pl.dir === 'right') wx += ext;
ctx.beginPath();
ctx.moveTo(pl.x, pl.y);
ctx.lineTo(wx, wy);
ctx.stroke();
ctx.restore();
}
let frameCount = 0;
function drawInsideRoom() {
ctx.clearRect(0, 0, W, H);
ctx.fillStyle = '#6b4a30';
ctx.fillRect(0, 0, W, H);
ctx.save();
ctx.translate(W/2 - ROOM_W/2, H/2 - ROOM_H/2);
ctx.fillStyle = '#8a6238';
ctx.fillRect(0, 0, ROOM_W, ROOM_H);
ctx.strokeStyle = '#6b4a30';
ctx.lineWidth = 6;
ctx.strokeRect(0, 0, ROOM_W, ROOM_H);
ctx.fillStyle = '#8a6238';
ctx.fillRect(ROOM_W/2 - 30, ROOM_H - 6, 60, 10);
ctx.fillStyle = '#4a2f18';
ctx.fillRect(craftTable.x - 24, craftTable.y - 16, 48, 32);
ctx.fillStyle = '#6b4a30';
ctx.fillRect(craftTable.x - 24, craftTable.y - 20, 48, 6);
ctx.font = '18px serif';
ctx.fillText(crafted > 0 ? '💎' : '🔨', craftTable.x - 9, craftTable.y + 4);
let swayAmt = Math.sin(frameCount*0.03 + insideFern.sway) * 4;
ctx.strokeStyle = '#2f7a3a';
ctx.lineWidth = 3;
for (let a = -1; a <= 1; a++) {
ctx.beginPath();
ctx.moveTo(insideFern.x, insideFern.y);
ctx.quadraticCurveTo(insideFern.x + a*8 + swayAmt, insideFern.y - 14, insideFern.x + a*16 + swayAmt, insideFern.y - 26);
ctx.stroke();
}
ctx.fillStyle = '#7a5230';
ctx.beginPath();
ctx.arc(insideFern.x, insideFern.y + 4, 8, 0, Math.PI*2);
ctx.fill();
drawSquirrel(players[1], players[1].alive);
drawSquirrel(players[2], players[2].alive);
ctx.restore();
}
function draw() {
frameCount++;
if (cabin.inside) { drawInsideRoom(); return; }
ctx.clearRect(0, 0, W, H);
const cam = getCamera();
ctx.fillStyle = '#3c8f4a';
ctx.fillRect(0, 0, W, H);
ctx.strokeStyle = 'rgba(0,0,0,0.05)';
for (let gx = -((cam.x)%40); gx < W; gx += 40) {
ctx.beginPath(); ctx.moveTo(gx,0); ctx.lineTo(gx,H); ctx.stroke();
}
for (let gy = -((cam.y)%40); gy < H; gy += 40) {
ctx.beginPath(); ctx.moveTo(0,gy); ctx.lineTo(W,gy); ctx.stroke();
}
ctx.save();
ctx.translate(-cam.x, -cam.y);
ctx.fillStyle = '#7a5230';
ctx.fillRect(cabin.x - 40, cabin.y - 30, 80, 60);
ctx.fillStyle = '#5a3a1e';
ctx.beginPath();
ctx.moveTo(cabin.x - 48, cabin.y - 30);
ctx.lineTo(cabin.x, cabin.y - 65);
ctx.lineTo(cabin.x + 48, cabin.y - 30);
ctx.closePath();
ctx.fill();
ctx.fillStyle = '#4a2f18';
ctx.fillRect(cabin.x - 10, cabin.y - 2, 20, 32);
ctx.fillStyle = '#bfe3f7';
ctx.fillRect(cabin.x - 30, cabin.y - 12, 14, 14);
ctx.fillRect(cabin.x + 16, cabin.y - 12, 14, 14);
for (let f of ferns) {
let swayAmt = Math.sin(frameCount*0.03 + f.sway) * 4;
ctx.strokeStyle = '#2f7a3a';
ctx.lineWidth = 3;
for (let a = -1; a <= 1; a++) {
ctx.beginPath();
ctx.moveTo(f.x, f.y);
ctx.quadraticCurveTo(f.x + a*8 + swayAmt, f.y - 12, f.x + a*14 + swayAmt, f.y - 22);
ctx.stroke();
}
}
for (let bu of bushes) {
ctx.fillStyle = '#387a3f';
ctx.beginPath();
ctx.arc(bu.x, bu.y, bu.r, 0, Math.PI*2);
ctx.fill();
ctx.fillStyle = '#2f6a35';
ctx.beginPath();
ctx.arc(bu.x - bu.r*0.4, bu.y + bu.r*0.3, bu.r*0.6, 0, Math.PI*2);
ctx.fill();
}
for (let fl of flowers) {
ctx.fillStyle = fl.color;
ctx.beginPath();
ctx.arc(fl.x, fl.y, 3, 0, Math.PI*2);
ctx.fill();
ctx.fillStyle = '#ffd93d';
ctx.beginPath();
ctx.arc(fl.x, fl.y, 1.2, 0, Math.PI*2);
ctx.fill();
}
for (let t of trees) {
ctx.fillStyle = '#6b4423';
ctx.fillRect(t.x-4, t.y, 8, 14);
ctx.fillStyle = '#2d6b34';
ctx.beginPath();
ctx.arc(t.x, t.y, t.r, 0, Math.PI*2);
ctx.fill();
}
if (!puzzleRock.solved) {
ctx.fillStyle = '#888';
ctx.beginPath();
ctx.arc(puzzleRock.x, puzzleRock.y, puzzleRock.r, 0, Math.PI*2);
ctx.fill();
ctx.fillStyle = '#fff';
ctx.font = '10px monospace';
ctx.fillText('?', puzzleRock.x-3, puzzleRock.y+4);
}
if (treasure.active && !treasure.taken) {
ctx.font = '26px serif';
ctx.fillText('🌰', treasure.x-13, treasure.y+10);
}
if (!frog.taken) {
frog.hopT += 0.05;
frog.y = frog.baseY + Math.sin(frog.hopT) * 6;
ctx.font = '26px serif';
ctx.fillText('🐸', frog.x-13, frog.y+10);
}
for (let b of bears) {
if (!b.alive) continue;
ctx.fillStyle = '#5a3825';
ctx.fillRect(b.x-16, b.y-16, 32, 32);
ctx.fillStyle = '#fff';
ctx.fillRect(b.x-8, b.y-6, 5, 5);
ctx.fillRect(b.x+3, b.y-6, 5, 5);
}
drawSquirrel(players[1], players[1].alive);
drawSquirrel(players[2], players[2].alive);
ctx.restore();
}
function loop() {
update();
draw();
requestAnimationFrame(loop);
}
loop();
</script>
</body>
</html>