wormate-io-clone / script.js
dannyboy84's picture
make it 2 seconds for each piece
7aefad1 verified
// Game initialization
document.addEventListener('DOMContentLoaded', () => {
// Initialize game
const game = new WormateGame();
game.init();
// Initialize Feather Icons
if (typeof feather !== 'undefined') {
feather.replace();
}
});
class WormateGame {
constructor() {
this.canvas = document.getElementById('gameCanvas');
this.ctx = this.canvas.getContext('2d');
this.scoreDisplay = document.getElementById('score-display');
this.leaderboard = document.getElementById('leaderboard');
this.playBtn = document.getElementById('play-btn');
this.timeDisplay = document.getElementById('time-display');
this.gameStarted = false;
this.playerId = null;
this.players = {};
this.foods = [];
this.powerUps = [];
this.score = 0;
this.timeLeft = 60; // Starting time (seconds)
this.maxTime = 120; // Maximum time bank
this.timeDecayRate = 1.0; // Base decay rate (seconds per second)
this.timeDecayMultiplier = 0.01; // Increases as worm grows
this.gameTimer = null;
this.keys = {
ArrowUp: false,
ArrowDown: false,
ArrowLeft: false,
ArrowRight: false
};
}
init() {
this.resizeCanvas();
window.addEventListener('resize', () => this.resizeCanvas());
// Keyboard controls
window.addEventListener('keydown', (e) => {
if (this.keys.hasOwnProperty(e.key)) {
this.keys[e.key] = true;
e.preventDefault();
}
});
window.addEventListener('keyup', (e) => {
if (this.keys.hasOwnProperty(e.key)) {
this.keys[e.key] = false;
e.preventDefault();
}
});
// Start game button
this.playBtn.addEventListener('click', () => this.startGame());
// Start game loop
this.gameLoop();
}
resizeCanvas() {
this.canvas.width = window.innerWidth;
this.canvas.height = window.innerHeight;
}
startGame() {
const playerName = prompt('Enter your name:', 'Player' + Math.floor(Math.random() * 1000));
if (playerName) {
this.playerId = 'local-' + Date.now();
this.players[this.playerId] = {
id: this.playerId,
name: playerName,
color: this.getRandomColor(),
score: 0,
segments: [
{ x: this.canvas.width/2, y: this.canvas.height/2, radius: 15 },
{ x: this.canvas.width/2 - 20, y: this.canvas.height/2, radius: 14 },
{ x: this.canvas.width/2 - 40, y: this.canvas.height/2, radius: 13 }
]
};
// Generate initial food
for (let i = 0; i < 50; i++) {
const foodTypes = [
{ type: 'lollipop', radius: 12, color: '#FF5252', time: 2, rarity: 0.5 },
{ type: 'gummybear', radius: 10, color: '#FF4081', time: 2, rarity: 0.5 },
{ type: 'candycorn', radius: 8, color: '#FFD740', time: 2, rarity: 0.5 },
{ type: 'chocolate', radius: 14, color: '#7B3F00', time: 2, rarity: 0.1 },
{ type: 'marshmallow', radius: 9, color: '#FFFFFF', time: 2, rarity: 0.2 }
];
const randomFood = foodTypes[Math.floor(Math.random() * foodTypes.length)];
this.foods.push({
x: Math.random() * this.canvas.width,
y: Math.random() * this.canvas.height,
radius: randomFood.radius,
color: randomFood.color,
type: randomFood.type,
points: randomFood.points,
time: 2 // Fixed 2 seconds for all food types
,
time: 2 // Fixed 2 seconds for all food types
});
}
this.gameStarted = true;
this.playBtn.style.display = 'none';
this.timeLeft = 30; // Reset time
this.timeDisplay.textContent = `Time: ${this.timeLeft}s`;
this.startTimer();
}
}
gameLoop() {
if (this.gameStarted) {
this.update();
this.render();
}
requestAnimationFrame(() => this.gameLoop());
}
startTimer() {
clearInterval(this.gameTimer);
let lastTime = Date.now();
this.gameTimer = setInterval(() => {
const now = Date.now();
const delta = (now - lastTime) / 1000; // Convert to seconds
lastTime = now;
// Calculate time decay based on worm size
const decayRate = this.timeDecayRate +
(this.timeDecayMultiplier * this.players[this.playerId]?.segments.length || 0);
this.timeLeft = Math.max(0, this.timeLeft - (decayRate * delta));
this.timeDisplay.textContent = `Time: ${Math.floor(this.timeLeft)}s`;
if (this.timeLeft <= 0) {
this.gameOver();
}
// Visual feedback for time status
if (this.timeLeft <= 10) {
this.timeDisplay.classList.add('warning');
this.timeDisplay.style.animationDuration = `${0.5 / (11 - this.timeLeft)}s`;
} else if (this.timeLeft <= 30) {
this.timeDisplay.classList.add('warning');
this.timeDisplay.style.animationDuration = '1s';
} else {
this.timeDisplay.classList.remove('warning');
}
}, 1000);
}
update() {
// Move player worm
const player = this.players[this.playerId];
if (!player) return;
const head = player.segments[0];
let newX = head.x;
let newY = head.y;
if (this.keys.ArrowUp) newY -= 5;
if (this.keys.ArrowDown) newY += 5;
if (this.keys.ArrowLeft) newX -= 5;
if (this.keys.ArrowRight) newX += 5;
// Add new head position
player.segments.unshift({
x: newX,
y: newY,
radius: head.radius
});
// Remove tail segment
player.segments.pop();
// Check food collision
this.foods = this.foods.filter(food => {
const distance = Math.sqrt(
Math.pow(head.x - food.x, 2) +
Math.pow(head.y - food.y, 2)
);
if (distance < head.radius + food.radius) {
// Eat food - grow worm
const tail = player.segments[player.segments.length - 1];
for (let i = 0; i < 5; i++) {
player.segments.push({
x: tail.x,
y: tail.y,
radius: tail.radius * 0.95
});
}
// Add time for eating food with diminishing returns based on worm size
const timeGain = Math.max(1, food.time - (player.segments.length * 0.1));
this.timeLeft = Math.min(this.maxTime, this.timeLeft + timeGain);
// Increase time decay as worm grows (harder to maintain)
this.timeDecayMultiplier = 0.01 + (player.segments.length * 0.0005);
player.score += 1;
this.score = player.score;
this.scoreDisplay.textContent = `Score: ${this.score}`;
this.timeDisplay.textContent = `Time: ${this.timeLeft}s`;
return false; // Remove food
}
return true; // Keep food
});
// Add new food if needed
if (this.foods.length < 30 && Math.random() < 0.1) {
const foodTypes = [
{ type: 'lollipop', radius: 12, color: '#FF5252', points: 10, time: 2 },
{ type: 'gummybear', radius: 10, color: '#FF4081', points: 5, time: 2 },
{ type: 'candycorn', radius: 8, color: '#FFD740', points: 3, time: 2 },
{ type: 'chocolate', radius: 14, color: '#7B3F00', points: 15, time: 2 },
{ type: 'marshmallow', radius: 9, color: '#FFFFFF', points: 8, time: 2 }
];
const randomFood = foodTypes[Math.floor(Math.random() * foodTypes.length)];
this.foods.push({
x: Math.random() * this.canvas.width,
y: Math.random() * this.canvas.height,
radius: randomFood.radius,
color: randomFood.color,
type: randomFood.type,
points: randomFood.points
});
}
// Check wall collision
if (
head.x < -head.radius ||
head.x > this.canvas.width + head.radius ||
head.y < -head.radius ||
head.y > this.canvas.height + head.radius
) {
this.gameOver();
}
// Update leaderboard
this.updateLeaderboard();
}
render() {
// Clear canvas
this.ctx.fillStyle = '#1a1a2e';
this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
// Draw foods
this.foods.forEach(food => {
this.ctx.save();
this.ctx.translate(food.x, food.y);
switch(food.type) {
case 'lollipop':
// Lollipop head
this.ctx.fillStyle = food.color;
this.ctx.beginPath();
this.ctx.arc(0, 0, food.radius, 0, Math.PI * 2);
this.ctx.fill();
// Lollipop stick
this.ctx.strokeStyle = '#FFFFFF';
this.ctx.lineWidth = 2;
this.ctx.beginPath();
this.ctx.moveTo(0, food.radius);
this.ctx.lineTo(0, food.radius * 2);
this.ctx.stroke();
break;
case 'gummybear':
// Gummy bear body
this.ctx.fillStyle = food.color;
this.ctx.beginPath();
this.ctx.ellipse(0, 0, food.radius, food.radius * 1.2, 0, 0, Math.PI * 2);
this.ctx.fill();
// Eyes
this.ctx.fillStyle = '#000000';
this.ctx.beginPath();
this.ctx.arc(-food.radius/3, -food.radius/3, 2, 0, Math.PI * 2);
this.ctx.arc(food.radius/3, -food.radius/3, 2, 0, Math.PI * 2);
this.ctx.fill();
break;
case 'candycorn':
// Candy corn triangle
this.ctx.fillStyle = food.color;
this.ctx.beginPath();
this.ctx.moveTo(0, -food.radius);
this.ctx.lineTo(-food.radius, food.radius);
this.ctx.lineTo(food.radius, food.radius);
this.ctx.closePath();
this.ctx.fill();
break;
case 'chocolate':
// Chocolate bar
this.ctx.fillStyle = food.color;
this.ctx.fillRect(-food.radius, -food.radius/2, food.radius*2, food.radius);
break;
case 'marshmallow':
// Marshmallow
this.ctx.fillStyle = food.color;
this.ctx.beginPath();
this.ctx.arc(0, 0, food.radius, 0, Math.PI * 2);
this.ctx.fill();
// Shading
this.ctx.fillStyle = 'rgba(0,0,0,0.1)';
this.ctx.beginPath();
this.ctx.arc(0, -food.radius/3, food.radius/3, 0, Math.PI * 2);
this.ctx.fill();
break;
}
this.ctx.restore();
});
// Draw players
Object.values(this.players).forEach(player => {
// Draw worm segments
player.segments.forEach((segment, i) => {
const gradient = this.ctx.createRadialGradient(
segment.x, segment.y, 0,
segment.x, segment.y, segment.radius
);
gradient.addColorStop(0, player.color);
gradient.addColorStop(1, this.darkenColor(player.color, 0.3));
this.ctx.fillStyle = gradient;
this.ctx.beginPath();
this.ctx.arc(segment.x, segment.y, segment.radius, 0, Math.PI * 2);
this.ctx.fill();
// Draw eyes on head
if (i === 0 && player.segments.length > 1) {
const angle = Math.atan2(
player.segments[1].y - segment.y,
player.segments[1].x - segment.x
);
const eyeRadius = segment.radius * 0.3;
const eyeOffset = segment.radius * 0.6;
// Eyes
this.ctx.fillStyle = 'white';
this.ctx.beginPath();
this.ctx.arc(
segment.x + Math.cos(angle + Math.PI/2) * eyeOffset,
segment.y + Math.sin(angle + Math.PI/2) * eyeOffset,
eyeRadius, 0, Math.PI * 2
);
this.ctx.fill();
this.ctx.beginPath();
this.ctx.arc(
segment.x + Math.cos(angle - Math.PI/2) * eyeOffset,
segment.y + Math.sin(angle - Math.PI/2) * eyeOffset,
eyeRadius, 0, Math.PI * 2
);
this.ctx.fill();
// Pupils
this.ctx.fillStyle = 'black';
this.ctx.beginPath();
this.ctx.arc(
segment.x + Math.cos(angle + Math.PI/2) * eyeOffset + Math.cos(angle) * eyeRadius/2,
segment.y + Math.sin(angle + Math.PI/2) * eyeOffset + Math.sin(angle) * eyeRadius/2,
eyeRadius/2, 0, Math.PI * 2
);
this.ctx.fill();
this.ctx.beginPath();
this.ctx.arc(
segment.x + Math.cos(angle - Math.PI/2) * eyeOffset + Math.cos(angle) * eyeRadius/2,
segment.y + Math.sin(angle - Math.PI/2) * eyeOffset + Math.sin(angle) * eyeRadius/2,
eyeRadius/2, 0, Math.PI * 2
);
this.ctx.fill();
}
});
// Draw player name
if (player.segments.length > 0) {
const head = player.segments[0];
this.ctx.fillStyle = 'white';
this.ctx.font = '12px "Press Start 2P", cursive';
this.ctx.textAlign = 'center';
this.ctx.fillText(player.name, head.x, head.y - head.radius - 10);
}
});
}
updateLeaderboard() {
const sortedPlayers = Object.values(this.players).sort((a, b) => b.score - a.score);
this.leaderboard.innerHTML = '<h3>Leaderboard</h3>' +
sortedPlayers.map(p =>
`<div class="player-row ${p.id === this.playerId ? 'you' : ''}">
<span class="player-name">${p.name}</span>
<span class="player-score">${p.score}</span>
</div>`
).join('');
}
gameOver() {
alert(`Game Over! Your score: ${this.score}`);
this.gameStarted = false;
this.playBtn.style.display = 'block';
this.players = {};
this.foods = [];
this.score = 0;
this.scoreDisplay.textContent = `Score: 0`;
}
getRandomColor() {
const colors = [
'#FF5252', '#FF4081', '#E040FB', '#7C4DFF',
'#536DFE', '#448AFF', '#40C4FF', '#18FFFF',
'#64FFDA', '#69F0AE', '#B2FF59', '#EEFF41',
'#FFFF00', '#FFD740', '#FFAB40', '#FF6E40'
];
return colors[Math.floor(Math.random() * colors.length)];
}
darkenColor(color, amount) {
let r = parseInt(color.substr(1, 2), 16);
let g = parseInt(color.substr(3, 2), 16);
let b = parseInt(color.substr(5, 2), 16);
r = Math.max(0, Math.floor(r * (1 - amount)));
g = Math.max(0, Math.floor(g * (1 - amount)));
b = Math.max(0, Math.floor(b * (1 - amount)));
return `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`;
}
}