|
<!DOCTYPE html> |
|
<html lang="en"> |
|
<head> |
|
<meta charset="UTF-8"> |
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
<title>Chainsaw Massacre: 8-Bit Horror</title>WWWWWWWWWW |
|
<script src="https://cdn.tailwindcss.com"></script>WWWWW |
|
<style> |
|
body { |
|
margin: 0; |
|
overflow: hidden; |
|
background-color: #111; |
|
font-family: 'Press Start 2P', cursive; |
|
} |
|
canvas { |
|
display: block; |
|
} |
|
#ui { |
|
position: absolute; |
|
top: 0; |
|
left: 0; |
|
width: 100%; |
|
color: white; |
|
text-shadow: 2px 2px 0 #000; |
|
pointer-events: none; |
|
} |
|
#health-bar { |
|
width: 200px; |
|
height: 20px; |
|
border: 3px solid white; |
|
margin: 10px; |
|
} |
|
#health-fill { |
|
height: 100%; |
|
width: 100%; |
|
background-color: red; |
|
transition: width 0.3s; |
|
} |
|
#game-over { |
|
display: none; |
|
position: absolute; |
|
top: 50%; |
|
left: 50%; |
|
transform: translate(-50%, -50%); |
|
background-color: rgba(0, 0, 0, 0.8); |
|
padding: 20px; |
|
border: 5px solid red; |
|
text-align: center; |
|
} |
|
#start-screen { |
|
position: absolute; |
|
top: 0; |
|
left: 0; |
|
width: 100%; |
|
height: 100%; |
|
background-color: #000; |
|
display: flex; |
|
flex-direction: column; |
|
justify-content: center; |
|
align-items: center; |
|
color: white; |
|
z-index: 100; |
|
} |
|
@font-face { |
|
font-family: 'Press Start 2P'; |
|
src: url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap'); |
|
} |
|
.pixel-text { |
|
font-family: 'Press Start 2P', cursive; |
|
} |
|
.blood-particle { |
|
position: absolute; |
|
width: 5px; |
|
height: 5px; |
|
background-color: red; |
|
border-radius: 50%; |
|
pointer-events: none; |
|
} |
|
</style> |
|
</head> |
|
<body> |
|
<div id="start-screen" class="pixel-text"> |
|
<h1 class="text-4xl mb-8 text-red-600">CHAINSAW MASSACRE</h1> |
|
<p class="mb-4 text-yellow-400">8-BIT HORROR</p> |
|
<p class="mb-8 text-green-400">Find and eliminate all targets</p> |
|
<button id="start-btn" class="bg-red-600 hover:bg-red-700 text-white px-8 py-4 rounded-lg text-xl transition-all hover:scale-110"> |
|
START GAME |
|
</button> |
|
<div class="mt-12 text-xs text-gray-400"> |
|
<p>WASD to move | SPACE to attack | SHIFT to run</p> |
|
<p>Left click to chainsaw | Right click to hide</p> |
|
</div> |
|
</div> |
|
|
|
<div id="ui" class="pixel-text"> |
|
<div class="flex justify-between items-center p-4"> |
|
<div> |
|
<div id="health-bar"> |
|
<div id="health-fill"></div> |
|
</div> |
|
<div id="score" class="mt-2">KILLS: 0</div> |
|
</div> |
|
<div id="phase" class="text-xl">PHASE 1: HUNT KIDS</div> |
|
<div id="timer" class="text-xl">TIME: 00:00</div> |
|
</div> |
|
</div> |
|
|
|
<div id="game-over" class="pixel-text"> |
|
<h1 class="text-4xl text-red-600 mb-4">GAME OVER</h1> |
|
<p id="final-score" class="text-2xl mb-8">You killed 0 victims</p> |
|
<button id="restart-btn" class="bg-red-600 hover:bg-red-700 text-white px-8 py-4 rounded-lg text-xl"> |
|
PLAY AGAIN |
|
</button> |
|
</div> |
|
|
|
<canvas id="gameCanvas"></canvas> |
|
|
|
<script> |
|
|
|
const canvas = document.getElementById('gameCanvas'); |
|
const ctx = canvas.getContext('2d'); |
|
const startScreen = document.getElementById('start-screen'); |
|
const startBtn = document.getElementById('start-btn'); |
|
const gameOverScreen = document.getElementById('game-over'); |
|
const restartBtn = document.getElementById('restart-btn'); |
|
const healthFill = document.getElementById('health-fill'); |
|
const scoreDisplay = document.getElementById('score'); |
|
const phaseDisplay = document.getElementById('phase'); |
|
const timerDisplay = document.getElementById('timer'); |
|
const finalScoreDisplay = document.getElementById('final-score'); |
|
|
|
|
|
canvas.width = window.innerWidth; |
|
canvas.height = window.innerHeight; |
|
|
|
|
|
let gameRunning = false; |
|
let score = 0; |
|
let health = 100; |
|
let gameTime = 0; |
|
let gamePhase = 1; |
|
let timerInterval; |
|
|
|
|
|
const player = { |
|
x: canvas.width / 2, |
|
y: canvas.height / 2, |
|
size: 30, |
|
speed: 3, |
|
isAttacking: false, |
|
isHiding: false, |
|
direction: 0, |
|
chainsawSound: new Audio('data:audio/wav;base64,UklGRl9vT19XQVZFZm10IBAAAAABAAEAQB8AAEAfAAABAAgAZGF0YU...'), |
|
}; |
|
|
|
|
|
let kids = []; |
|
const KID_COUNT = 8; |
|
|
|
|
|
let police = []; |
|
const POLICE_COUNT = 5; |
|
|
|
|
|
const house = { |
|
walls: [ |
|
{x: 200, y: 200, width: 400, height: 300, color: '#8B4513'}, |
|
{x: 300, y: 150, width: 200, height: 50, color: '#A52A2A'}, |
|
{x: 600, y: 300, width: 100, height: 200, color: '#8B4513'}, |
|
{x: 200, y: 500, width: 200, height: 50, color: '#228B22'}, |
|
{x: 400, y: 500, width: 300, height: 50, color: '#228B22'}, |
|
], |
|
doors: [ |
|
{x: 350, y: 500, width: 50, height: 30, color: '#654321'} |
|
], |
|
windows: [ |
|
{x: 250, y: 250, width: 50, height: 50, color: '#87CEEB'}, |
|
{x: 450, y: 250, width: 50, height: 50, color: '#87CEEB'}, |
|
{x: 620, y: 350, width: 50, height: 50, color: '#87CEEB'} |
|
] |
|
}; |
|
|
|
|
|
const trees = [ |
|
{x: 100, y: 400, trunkWidth: 20, trunkHeight: 60, leavesWidth: 80, leavesHeight: 100}, |
|
{x: 700, y: 400, trunkWidth: 20, trunkHeight: 60, leavesWidth: 80, leavesHeight: 100}, |
|
{x: 150, y: 600, trunkWidth: 20, trunkHeight: 60, leavesWidth: 80, leavesHeight: 100}, |
|
{x: 650, y: 600, trunkWidth: 20, trunkHeight: 60, leavesWidth: 80, leavesHeight: 100} |
|
]; |
|
|
|
|
|
function initGame() { |
|
|
|
gameRunning = true; |
|
score = 0; |
|
health = 100; |
|
gameTime = 0; |
|
gamePhase = 1; |
|
kids = []; |
|
police = []; |
|
|
|
|
|
for (let i = 0; i < KID_COUNT; i++) { |
|
kids.push({ |
|
x: Math.random() * (canvas.width - 100) + 50, |
|
y: Math.random() * (canvas.height - 100) + 50, |
|
size: 20, |
|
speed: 1 + Math.random() * 1, |
|
direction: Math.random() * Math.PI * 2, |
|
changeDirectionTimer: 0, |
|
isAlive: true, |
|
screamSound: new Audio('data:audio/wav;base64,UklGRl9vT19XQVZFZm10IBAAAAABAAEAQB8AAEAfAAABAAgAZGF0YU...') |
|
}); |
|
} |
|
|
|
|
|
clearInterval(timerInterval); |
|
timerInterval = setInterval(updateTimer, 1000); |
|
|
|
|
|
startScreen.style.display = 'none'; |
|
gameOverScreen.style.display = 'none'; |
|
|
|
|
|
requestAnimationFrame(gameLoop); |
|
} |
|
|
|
|
|
function gameLoop() { |
|
if (!gameRunning) return; |
|
|
|
|
|
ctx.clearRect(0, 0, canvas.width, canvas.height); |
|
|
|
|
|
updatePlayer(); |
|
updateKids(); |
|
if (gamePhase >= 3) updatePolice(); |
|
|
|
|
|
drawWorld(); |
|
|
|
|
|
drawPlayer(); |
|
|
|
|
|
drawKids(); |
|
|
|
|
|
if (gamePhase >= 3) drawPolice(); |
|
|
|
|
|
checkPhaseTransitions(); |
|
|
|
|
|
if (health <= 0) { |
|
gameOver(); |
|
return; |
|
} |
|
|
|
|
|
requestAnimationFrame(gameLoop); |
|
} |
|
|
|
|
|
function updatePlayer() { |
|
|
|
const moveX = (keys['d'] ? 1 : 0) - (keys['a'] ? 1 : 0); |
|
const moveY = (keys['s'] ? 1 : 0) - (keys['w'] ? 1 : 0); |
|
|
|
if (moveX !== 0 || moveY !== 0) { |
|
player.direction = Math.atan2(moveY, moveX); |
|
const speed = keys['Shift'] ? player.speed * 1.5 : player.speed; |
|
player.x += Math.cos(player.direction) * speed; |
|
player.y += Math.sin(player.direction) * speed; |
|
|
|
|
|
player.x = Math.max(player.size, Math.min(canvas.width - player.size, player.x)); |
|
player.y = Math.max(player.size, Math.min(canvas.height - player.size, player.y)); |
|
} |
|
|
|
|
|
if (mouse.isAttacking && !player.isAttacking) { |
|
player.isAttacking = true; |
|
player.chainsawSound.play(); |
|
|
|
|
|
const targets = gamePhase >= 3 ? [...kids, ...police] : kids; |
|
for (let target of targets) { |
|
if (!target.isAlive) continue; |
|
|
|
const dist = Math.sqrt( |
|
Math.pow(target.x - player.x, 2) + |
|
Math.pow(target.y - player.y, 2) |
|
); |
|
|
|
if (dist < player.size + target.size) { |
|
|
|
target.isAlive = false; |
|
score++; |
|
scoreDisplay.textContent = `KILLS: ${score}`; |
|
|
|
|
|
createBlood(target.x, target.y); |
|
|
|
|
|
if (target.screamSound) target.screamSound.play(); |
|
} |
|
} |
|
|
|
setTimeout(() => { |
|
player.isAttacking = false; |
|
}, 500); |
|
} |
|
|
|
|
|
player.isHiding = mouse.isHiding; |
|
} |
|
|
|
|
|
function updateKids() { |
|
for (let kid of kids) { |
|
if (!kid.isAlive) continue; |
|
|
|
|
|
kid.changeDirectionTimer--; |
|
if (kid.changeDirectionTimer <= 0) { |
|
kid.direction = Math.random() * Math.PI * 2; |
|
kid.changeDirectionTimer = 30 + Math.random() * 60; |
|
} |
|
|
|
|
|
kid.x += Math.cos(kid.direction) * kid.speed; |
|
kid.y += Math.sin(kid.direction) * kid.speed; |
|
|
|
|
|
kid.x = Math.max(kid.size, Math.min(canvas.width - kid.size, kid.x)); |
|
kid.y = Math.max(kid.size, Math.min(canvas.height - kid.size, kid.y)); |
|
|
|
|
|
const distToPlayer = Math.sqrt( |
|
Math.pow(kid.x - player.x, 2) + |
|
Math.pow(kid.y - player.y, 2) |
|
); |
|
|
|
if (distToPlayer < 200) { |
|
|
|
const angleAway = Math.atan2(kid.y - player.y, kid.x - player.x); |
|
kid.x += Math.cos(angleAway) * kid.speed * 2; |
|
kid.y += Math.sin(angleAway) * kid.speed * 2; |
|
} |
|
} |
|
} |
|
|
|
|
|
function updatePolice() { |
|
if (police.length === 0) { |
|
|
|
for (let i = 0; i < POLICE_COUNT; i++) { |
|
police.push({ |
|
x: Math.random() * (canvas.width - 100) + 50, |
|
y: Math.random() * (canvas.height - 100) + 50, |
|
size: 25, |
|
speed: 2 + Math.random() * 1, |
|
direction: Math.random() * Math.PI * 2, |
|
changeDirectionTimer: 0, |
|
isAlive: true, |
|
shootTimer: 0 |
|
}); |
|
} |
|
} |
|
|
|
for (let cop of police) { |
|
if (!cop.isAlive) continue; |
|
|
|
|
|
cop.direction = Math.atan2(player.y - cop.y, player.x - cop.x); |
|
cop.x += Math.cos(cop.direction) * cop.speed; |
|
cop.y += Math.sin(cop.direction) * cop.speed; |
|
|
|
|
|
cop.shootTimer--; |
|
if (cop.shootTimer <= 0) { |
|
cop.shootTimer = 60 + Math.random() * 60; |
|
|
|
|
|
const distToPlayer = Math.sqrt( |
|
Math.pow(player.x - cop.x, 2) + |
|
Math.pow(player.y - cop.y, 2) |
|
); |
|
|
|
if (distToPlayer < 300 && !player.isHiding) { |
|
|
|
health -= 10; |
|
healthFill.style.width = `${health}%`; |
|
|
|
|
|
createBlood(player.x, player.y); |
|
} |
|
} |
|
} |
|
} |
|
|
|
|
|
function drawWorld() { |
|
|
|
ctx.fillStyle = '#228B22'; |
|
ctx.fillRect(0, 0, canvas.width, canvas.height); |
|
|
|
|
|
for (let wall of house.walls) { |
|
ctx.fillStyle = wall.color; |
|
ctx.fillRect(wall.x, wall.y, wall.width, wall.height); |
|
} |
|
|
|
|
|
for (let door of house.doors) { |
|
ctx.fillStyle = door.color; |
|
ctx.fillRect(door.x, door.y, door.width, door.height); |
|
} |
|
|
|
|
|
for (let window of house.windows) { |
|
ctx.fillStyle = window.color; |
|
ctx.fillRect(window.x, window.y, window.width, window.height); |
|
} |
|
|
|
|
|
for (let tree of trees) { |
|
|
|
ctx.fillStyle = '#8B4513'; |
|
ctx.fillRect( |
|
tree.x - tree.trunkWidth/2, |
|
tree.y - tree.trunkHeight/2, |
|
tree.trunkWidth, |
|
tree.trunkHeight |
|
); |
|
|
|
|
|
ctx.fillStyle = '#006400'; |
|
ctx.beginPath(); |
|
ctx.ellipse( |
|
tree.x, |
|
tree.y - tree.leavesHeight/2, |
|
tree.leavesWidth/2, |
|
tree.leavesHeight/2, |
|
0, 0, Math.PI * 2 |
|
); |
|
ctx.fill(); |
|
} |
|
} |
|
|
|
|
|
function drawPlayer() { |
|
|
|
ctx.fillStyle = player.isHiding ? '#555' : '#333'; |
|
ctx.beginPath(); |
|
ctx.arc(player.x, player.y, player.size, 0, Math.PI * 2); |
|
ctx.fill(); |
|
|
|
|
|
ctx.fillStyle = '#FFF'; |
|
ctx.beginPath(); |
|
ctx.arc( |
|
player.x + Math.cos(player.direction) * player.size/2, |
|
player.y + Math.sin(player.direction) * player.size/2, |
|
player.size/4, 0, Math.PI * 2 |
|
); |
|
ctx.fill(); |
|
|
|
|
|
if (player.isAttacking) { |
|
ctx.fillStyle = '#FF0000'; |
|
ctx.beginPath(); |
|
ctx.arc( |
|
player.x + Math.cos(player.direction) * player.size * 1.5, |
|
player.y + Math.sin(player.direction) * player.size * 1.5, |
|
player.size/2, 0, Math.PI * 2 |
|
); |
|
ctx.fill(); |
|
|
|
|
|
ctx.fillStyle = 'rgba(255, 0, 0, 0.5)'; |
|
ctx.beginPath(); |
|
ctx.moveTo(player.x, player.y); |
|
ctx.lineTo( |
|
player.x + Math.cos(player.direction) * player.size * 3, |
|
player.y + Math.sin(player.direction) * player.size * 3 |
|
); |
|
ctx.lineWidth = 10; |
|
ctx.stroke(); |
|
} |
|
} |
|
|
|
|
|
function drawKids() { |
|
for (let kid of kids) { |
|
if (!kid.isAlive) continue; |
|
|
|
|
|
ctx.fillStyle = '#FF69B4'; |
|
ctx.beginPath(); |
|
ctx.arc(kid.x, kid.y, kid.size, 0, Math.PI * 2); |
|
ctx.fill(); |
|
|
|
|
|
ctx.fillStyle = '#FFF'; |
|
ctx.beginPath(); |
|
ctx.arc(kid.x, kid.y - kid.size/3, kid.size/3, 0, Math.PI * 2); |
|
ctx.fill(); |
|
|
|
|
|
const distToPlayer = Math.sqrt( |
|
Math.pow(kid.x - player.x, 2) + |
|
Math.pow(kid.y - player.y, 2) |
|
); |
|
|
|
if (distToPlayer < 200) { |
|
ctx.fillStyle = '#FF0000'; |
|
ctx.beginPath(); |
|
ctx.arc(kid.x, kid.y - kid.size/3, kid.size/5, 0, Math.PI * 2); |
|
ctx.fill(); |
|
} |
|
} |
|
} |
|
|
|
|
|
function drawPolice() { |
|
for (let cop of police) { |
|
if (!cop.isAlive) continue; |
|
|
|
|
|
ctx.fillStyle = '#0000FF'; |
|
ctx.beginPath(); |
|
ctx.arc(cop.x, cop.y, cop.size, 0, Math.PI * 2); |
|
ctx.fill(); |
|
|
|
|
|
ctx.fillStyle = '#FFF'; |
|
ctx.beginPath(); |
|
ctx.arc(cop.x, cop.y - cop.size/3, cop.size/3, 0, Math.PI * 2); |
|
ctx.fill(); |
|
|
|
|
|
ctx.fillStyle = '#FFD700'; |
|
ctx.beginPath(); |
|
ctx.arc(cop.x, cop.y, cop.size/3, 0, Math.PI * 2); |
|
ctx.fill(); |
|
|
|
|
|
if (cop.shootTimer > 50) { |
|
ctx.strokeStyle = '#000'; |
|
ctx.lineWidth = 3; |
|
ctx.beginPath(); |
|
ctx.moveTo(cop.x, cop.y); |
|
ctx.lineTo( |
|
cop.x + Math.cos(cop.direction) * cop.size * 2, |
|
cop.y + Math.sin(cop.direction) * cop.size * 2 |
|
); |
|
ctx.stroke(); |
|
} |
|
} |
|
} |
|
|
|
|
|
function createBlood(x, y) { |
|
for (let i = 0; i < 10; i++) { |
|
const particle = document.createElement('div'); |
|
particle.className = 'blood-particle'; |
|
particle.style.left = `${x + (Math.random() * 20 - 10)}px`; |
|
particle.style.top = `${y + (Math.random() * 20 - 10)}px`; |
|
document.body.appendChild(particle); |
|
|
|
|
|
const angle = Math.random() * Math.PI * 2; |
|
const distance = 10 + Math.random() * 40; |
|
const duration = 300 + Math.random() * 700; |
|
|
|
particle.animate([ |
|
{ |
|
transform: `translate(0, 0)`, |
|
opacity: 1 |
|
}, |
|
{ |
|
transform: `translate(${Math.cos(angle) * distance}px, ${Math.sin(angle) * distance}px)`, |
|
opacity: 0 |
|
} |
|
], { |
|
duration: duration, |
|
easing: 'cubic-bezier(0.1, 0.8, 0.2, 1)' |
|
}); |
|
|
|
|
|
setTimeout(() => { |
|
particle.remove(); |
|
}, duration); |
|
} |
|
} |
|
|
|
|
|
function checkPhaseTransitions() { |
|
|
|
if (gamePhase === 1) { |
|
const aliveKids = kids.filter(kid => kid.isAlive).length; |
|
if (aliveKids === 0) { |
|
gamePhase = 2; |
|
phaseDisplay.textContent = 'PHASE 2: HIDE AND WAIT'; |
|
|
|
|
|
setTimeout(() => { |
|
gamePhase = 3; |
|
phaseDisplay.textContent = 'PHASE 3: FIGHT POLICE'; |
|
}, 10000); |
|
} |
|
} |
|
} |
|
|
|
|
|
function updateTimer() { |
|
gameTime++; |
|
const minutes = Math.floor(gameTime / 60).toString().padStart(2, '0'); |
|
const seconds = (gameTime % 60).toString().padStart(2, '0'); |
|
timerDisplay.textContent = `TIME: ${minutes}:${seconds}`; |
|
} |
|
|
|
|
|
function gameOver() { |
|
gameRunning = false; |
|
clearInterval(timerInterval); |
|
|
|
|
|
finalScoreDisplay.textContent = `You killed ${score} victims`; |
|
gameOverScreen.style.display = 'block'; |
|
} |
|
|
|
|
|
const keys = {}; |
|
const mouse = { |
|
x: 0, |
|
y: 0, |
|
isAttacking: false, |
|
isHiding: false |
|
}; |
|
|
|
window.addEventListener('keydown', (e) => { |
|
keys[e.key.toLowerCase()] = true; |
|
}); |
|
|
|
window.addEventListener('keyup', (e) => { |
|
keys[e.key.toLowerCase()] = false; |
|
}); |
|
|
|
canvas.addEventListener('mousemove', (e) => { |
|
mouse.x = e.clientX; |
|
mouse.y = e.clientY; |
|
|
|
|
|
player.direction = Math.atan2( |
|
e.clientY - player.y, |
|
e.clientX - player.x |
|
); |
|
}); |
|
|
|
canvas.addEventListener('mousedown', (e) => { |
|
if (e.button === 0) { |
|
mouse.isAttacking = true; |
|
} else if (e.button === 2) { |
|
mouse.isHiding = true; |
|
} |
|
}); |
|
|
|
canvas.addEventListener('mouseup', (e) => { |
|
if (e.button === 0) { |
|
mouse.isAttacking = false; |
|
} else if (e.button === 2) { |
|
mouse.isHiding = false; |
|
} |
|
}); |
|
|
|
canvas.addEventListener('contextmenu', (e) => { |
|
e.preventDefault(); |
|
}); |
|
|
|
|
|
window.addEventListener('resize', () => { |
|
canvas.width = window.innerWidth; |
|
canvas.height = window.innerHeight; |
|
}); |
|
|
|
|
|
startBtn.addEventListener('click', initGame); |
|
|
|
|
|
restartBtn.addEventListener('click', initGame); |
|
</script> |
|
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=MaxHeadspace/chainsaw-massacre-2d" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> |
|
</html> |