<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Super Smash Tree</title>
<style>
html, body {
margin: 0;
padding: 0;
background: linear-gradient(#87ceeb, #d6f0ff);
overflow: hidden;
font-family: sans-serif;
height: 100%;
}
#wrap {
position: relative;
width: 900px;
height: 550px;
margin: 10px auto;
background: linear-gradient(#a2d8f5, #eaf7ff);
border: 6px solid #333;
border-radius: 12px;
overflow: hidden;
}
canvas {
display: block;
background: transparent;
}
#hud {
width: 900px;
margin: 0 auto;
display: flex;
justify-content: space-between;
font-size: 20px;
font-weight: bold;
color: #222;
}
#p1info { color: #d1425a; }
#p2info { color: #2f6fd1; }
#msg {
position: absolute;
top: 40%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 48px;
font-weight: bold;
color: white;
text-shadow: 3px 3px 0 #000;
display: none;
}
#restartBtn {
position: absolute;
top: 58%;
left: 50%;
transform: translate(-50%, -50%);
padding: 10px 20px;
font-size: 20px;
border-radius: 10px;
border: none;
background: #ffd23f;
cursor: pointer;
display: none;
}
#instructions {
width: 900px;
margin: 6px auto;
text-align: center;
color: #333;
font-size: 14px;
}
</style>
</head>
<body>
<div id="hud">
<div id="p1info">Red: 0%</div>
<div style="text-align:center; color:#333;">SUPER SMASH TREE</div>
<div id="p2info">Blue: 0%</div>
</div>
<div id="wrap">
<canvas id="game" width="900" height="550"></canvas>
<div id="msg"></div>
<button id="restartBtn">Play Again</button>
</div>
<div id="instructions">
Player Roy (Red): A/D move, W jump, S punch, E side-B, R down-smash, F fireball, Q shield | Player Corrin (Blue): Arrows move/jump, Down punch, / side-B, ' down-smash, ] fireball, . shield
</div>
<script>
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
const W = canvas.width, H = canvas.height;
const gravity = 0.6;
const groundY = 470;
const platforms = [
{x: 0, y: groundY, w: 900, h: 80},
{x: 150, y: 340, w: 180, h: 20},
{x: 570, y: 340, w: 180, h: 20},
{x: 370, y: 220, w: 160, h: 20}
];
function makePlayer(x, color, name) {
return {
x: x, y: 300, w: 40, h: 50,
vx: 0, vy: 0,
onGround: false,
facing: 1,
damage: 0,
color: color,
name: name,
punching: 0,
sideB: 0,
sideBCooldown: 0,
downSmash: 0,
downSmashCooldown: 0,
fireCooldown: 0,
lives: 3,
invuln: 0
};
}
let p1 = makePlayer(250, '#2f6fd1', 'Roy');
let p2 = makePlayer(600, '#d1425a', 'Corrin');
let projectiles = [];
const keys = {};
document.addEventListener('keydown', e => { keys[e.code] = true; });
document.addEventListener('keyup', e => { keys[e.code] = false; });
let gameOver = false;
function resetGame() {
p1 = makePlayer(250, '#2f6fd1', 'Roy');
p2 = makePlayer(600, '#d1425a', 'Corrin');
projectiles = [];
gameOver = false;
document.getElementById('msg').style.display = 'none';
document.getElementById('restartBtn').style.display = 'none';
}
document.getElementById('restartBtn').addEventListener('click', resetGame);
function rectsOverlap(a, b) {
return a.x < b.x + b.w && a.x + a.w > b.x && a.y < b.y + b.h && a.y + a.h > b.y;
}
function tryHit(target, attacker, dmg, knock, vyKnock) {
if (target.invuln > 0) return;
if (target.counter > 0) {
const dir = attacker.x < target.x ? -1 : 1;
attacker.vx = dir * (knock * 1.8);
attacker.vy = -10 - attacker.damage * 0.12;
attacker.damage += dmg * 1.6;
attacker.invuln = 25;
target.invuln = 15;
return;
}
if (target.shielding) return;
const dir = target.x < attacker.x ? -1 : 1;
target.vx = dir * knock;
target.vy = vyKnock;
target.damage += dmg;
target.invuln = 20;
}
function updatePlayer(p, controls, other) {
if (keys[controls.left]) { p.vx = -4; p.facing = -1; }
else if (keys[controls.right]) { p.vx = 4; p.facing = 1; }
else { p.vx *= 0.8; }
if (keys[controls.jump] && p.onGround) {
p.vy = -14;
p.onGround = false;
}
if (keys[controls.punch] && p.punching <= 0 && !p.shielding) {
p.punching = 15;
p.upAir = !p.onGround;
}
if (p.punching > 0) p.punching--;
if (p.downSmashCooldown > 0) p.downSmashCooldown--;
if (keys[controls.downSmash] && p.downSmash <= 0 && p.downSmashCooldown <= 0 && p.onGround) {
p.downSmash = 20;
p.downSmashCooldown = 70;
}
if (p.downSmash > 0) p.downSmash--;
if (p.fireCooldown > 0) p.fireCooldown--;
if (keys[controls.fireball] && p.fireCooldown <= 0) {
p.fireCooldown = 50;
projectiles.push({x: p.x + p.w/2, y: p.y + 15, vx: p.facing * 8, owner: p, life: 90});
}
if (p.sidePunchCooldown > 0) p.sidePunchCooldown--;
if (controls.sidePunch && keys[controls.sidePunch] && p.sidePunch <= 0 && p.sidePunchCooldown <= 0 && !p.shielding) {
p.sidePunch = 10;
p.sidePunchCooldown = 25;
}
if (p.sidePunch > 0) p.sidePunch--;
if (p.counterCooldown > 0) p.counterCooldown--;
if (controls.counter && keys[controls.counter] && p.counter <= 0 && p.counterCooldown <= 0 && p.onGround) {
p.counter = 18;
p.counterCooldown = 55;
}
if (p.counter > 0) p.counter--;
p.shielding = false;
if (controls.shield && keys[controls.shield] && p.onGround && p.sideB <= 0 && p.counter <= 0) {
p.shielding = true;
p.vx *= 0.5;
}
if (p.sideBCooldown > 0) p.sideBCooldown--;
if (keys[controls.sideB] && p.sideB <= 0 && p.sideBCooldown <= 0) {
p.sideB = 18;
p.sideBCooldown = 60;
if (keys[controls.left]) p.facing = -1;
else if (keys[controls.right]) p.facing = 1;
}
if (p.sideB > 0) {
p.vx = p.facing * 12;
p.sideB--;
}
p.vy += gravity;
p.x += p.vx;
p.y += p.vy;
p.onGround = false;
for (const plat of platforms) {
if (p.vy >= 0 &&
p.x + p.w > plat.x && p.x < plat.x + plat.w &&
p.y + p.h > plat.y && p.y + p.h < plat.y + plat.h + p.vy + 1) {
p.y = plat.y - p.h;
p.vy = 0;
p.onGround = true;
}
}
if (p.invuln > 0) p.invuln--;
if (p.punching === 10) {
const reach = p.name === 'Roy' ? 40 : 30;
const hitbox = {
x: p.facing === 1 ? p.x + p.w : p.x - reach,
y: p.y + 10,
w: reach,
h: 30
};
if (rectsOverlap(hitbox, other)) {
const knock = 8 + other.damage * 0.15;
if (other.counter > 0) {
tryHit(other, p, 10, knock, -6);
} else if (other.invuln <= 0 && !other.shielding) {
if (p.upAir) {
other.vy = -10 - other.damage * 0.15;
other.vx = p.facing * knock * 0.4;
} else {
other.vx = p.facing * knock;
other.vy = -6 - other.damage * 0.1;
}
other.damage += 10;
other.invuln = 20;
}
}
}
if (p.sidePunch === 6) {
const reach = 26;
const hitbox = {
x: p.facing === 1 ? p.x + p.w : p.x - reach,
y: p.y + 18,
w: reach,
h: 20
};
if (rectsOverlap(hitbox, other)) {
tryHit(other, p, 6, 6 + other.damage * 0.12, -4 - other.damage * 0.08);
}
}
if (p.downSmash === 15) {
const hitboxL = {x: p.x - 35, y: p.y + 20, w: 35, h: 25};
const hitboxR = {x: p.x + p.w, y: p.y + 20, w: 35, h: 25};
if (rectsOverlap(hitboxL, other) || rectsOverlap(hitboxR, other)) {
const dir = other.x < p.x ? -1 : 1;
const knock = 10 + other.damage * 0.18;
tryHit(other, p, 12, knock, -8 - other.damage * 0.1);
}
}
if (p.sideB > 0) {
const reach = p.name === 'Roy' ? 15 : 0;
const sideBox = {x: p.x - reach, y: p.y, w: p.w + reach*2, h: p.h};
if (rectsOverlap(sideBox, other)) {
const knock = (p.name === 'Roy' ? 14 : 12) + other.damage * 0.2;
tryHit(other, p, (p.name === 'Roy' ? 16 : 14), knock, -8 - other.damage * 0.1);
p.sideB = 0;
}
}
for (const proj of projectiles) {
if (proj.owner !== p && rectsOverlap({x: proj.x-8, y: proj.y-8, w:16, h:16}, p) && p.invuln <= 0 && !p.shielding) {
const knock = 7 + p.damage * 0.12;
p.vx = (proj.vx > 0 ? 1 : -1) * knock;
p.vy = -5 - p.damage * 0.08;
p.damage += 8;
p.invuln = 18;
proj.life = 0;
}
}
if (p.y > H + 100) {
p.lives--;
p.damage = 0;
p.x = W/2 - 100 + (Math.random()*200);
p.y = 100;
p.vx = 0;
p.vy = 0;
p.invuln = 60;
}
}
function updateProjectiles() {
for (const proj of projectiles) {
proj.x += proj.vx;
proj.life--;
}
projectiles = projectiles.filter(pr => pr.life > 0 && pr.x > -20 && pr.x < W+20);
}
function drawProjectiles() {
for (const proj of projectiles) {
ctx.fillStyle = proj.owner.name === 'Corrin' ? '#7fd1ff' : '#ff8c42';
ctx.beginPath();
ctx.arc(proj.x, proj.y, 8, 0, Math.PI*2);
ctx.fill();
}
}
function draw() {
ctx.clearRect(0, 0, W, H);
ctx.fillStyle = '#6b4226';
for (const plat of platforms) {
ctx.fillStyle = plat.h > 50 ? '#5a8f3c' : '#8a5a34';
ctx.fillRect(plat.x, plat.y, plat.w, plat.h);
}
drawPlayer(p1);
drawPlayer(p2);
drawProjectiles();
document.getElementById('p1info').textContent = `Roy: ${p1.damage}% Lives: ${'★'.repeat(Math.max(p1.lives,0))}`;
document.getElementById('p2info').textContent = `Corrin: ${p2.damage}% Lives: ${'★'.repeat(Math.max(p2.lives,0))}`;
}
function drawPlayer(p) {
ctx.save();
if (p.invuln > 0 && Math.floor(p.invuln/4) % 2 === 0) {
ctx.globalAlpha = 0.4;
}
if (p.shielding) {
ctx.fillStyle = 'rgba(150,220,255,0.6)';
ctx.beginPath();
ctx.arc(p.x + p.w/2, p.y + p.h/2, 34, 0, Math.PI*2);
ctx.fill();
}
ctx.fillStyle = p.color;
ctx.fillRect(p.x, p.y, p.w, p.h);
ctx.fillStyle = '#ffe0bd';
ctx.beginPath();
ctx.arc(p.x + p.w/2, p.y - 8, 14, 0, Math.PI*2);
ctx.fill();
ctx.fillStyle = '#000';
const eyeOffset = p.facing === 1 ? 4 : -4;
ctx.beginPath();
ctx.arc(p.x + p.w/2 + eyeOffset, p.y - 8, 2.5, 0, Math.PI*2);
ctx.fill();
if (p.counter > 0) {
ctx.strokeStyle = 'gold';
ctx.lineWidth = 3;
ctx.beginPath();
ctx.arc(p.x + p.w/2, p.y + p.h/2, 28, 0, Math.PI*2);
ctx.stroke();
}
if (p.sidePunch > 0) {
ctx.fillStyle = '#ffcc33';
const armLen = 22;
const armX = p.facing === 1 ? p.x + p.w : p.x - armLen;
ctx.fillRect(armX, p.y + 20, armLen, 6);
}
if (p.punching > 0) {
if (p.name === 'Roy') {
ctx.fillStyle = '#ffcc33';
const armLen = 34;
const armX = p.facing === 1 ? p.x + p.w : p.x - armLen;
ctx.fillRect(armX, p.y + 10, armLen, 8);
} else {
ctx.fillStyle = '#7fd1ff';
const armX = p.facing === 1 ? p.x + p.w : p.x - 25;
ctx.fillRect(armX, p.y + 12, 25, 12);
}
}
if (p.sideB > 0) {
if (p.name === 'Roy') {
const swordX = p.facing === 1 ? p.x + p.w : p.x - 50;
ctx.fillStyle = 'orangered';
ctx.fillRect(swordX, p.y + 2, 50, 12);
ctx.fillStyle = 'yellow';
ctx.fillRect(swordX, p.y + 5, 50, 5);
for (let i = 0; i < 3; i++) {
ctx.fillStyle = i % 2 === 0 ? 'orange' : 'red';
const fx = p.facing === 1 ? swordX - i*10 : swordX + 50 + i*10;
ctx.beginPath();
ctx.arc(fx, p.y + 8 + Math.sin(p.sideB+i)*4, 6 - i, 0, Math.PI*2);
ctx.fill();
}
} else {
ctx.fillStyle = '#2fa8ff';
const swordX = p.facing === 1 ? p.x + p.w : p.x - 40;
ctx.beginPath();
ctx.moveTo(swordX, p.y + 10);
ctx.lineTo(swordX + (p.facing===1?40:-40)*-1+ (p.facing===1?40:0), p.y);
ctx.lineTo(swordX + (p.facing===1?40:0), p.y + 20);
ctx.closePath();
ctx.fill();
ctx.fillStyle = 'rgba(120,220,255,0.6)';
ctx.beginPath();
ctx.arc(swordX + (p.facing===1?20:-20), p.y+10, 14, 0, Math.PI*2);
ctx.fill();
}
}
if (p.downSmash > 0) {
ctx.strokeStyle = 'orange';
ctx.lineWidth = 4;
ctx.beginPath();
ctx.arc(p.x + p.w/2, p.y + p.h/2, 40, 0, Math.PI*2);
ctx.stroke();
}
ctx.restore();
}
function checkGameOver() {
if (p1.lives <= 0 || p2.lives <= 0) {
gameOver = true;
const msg = document.getElementById('msg');
if (p1.lives <= 0 && p2.lives <= 0) {
msg.textContent = "It's a Tie!";
} else if (p1.lives <= 0) {
msg.textContent = "Blue Wins!";
} else {
msg.textContent = "Red Wins!";
}
msg.style.display = 'block';
document.getElementById('restartBtn').style.display = 'block';
}
}
function loop() {
if (!gameOver) {
updatePlayer(p1, {left:'KeyA', right:'KeyD', jump:'KeyW', punch:'KeyS', sideB:'KeyE', shield:'KeyQ', downSmash:'KeyR', fireball:'KeyF', sidePunch:'KeyT', counter:'KeyC'}, p2);
updatePlayer(p2, {left:'ArrowLeft', right:'ArrowRight', jump:'ArrowUp', punch:'ArrowDown', sideB:'Slash', shield:'Period', downSmash:'Quote', fireball:'BracketRight'}, p1);
updateProjectiles();
checkGameOver();
}
draw();
requestAnimationFrame(loop);
}
loop();
</script>
</body>
</html>