|
<!DOCTYPE html> |
|
<html> |
|
<head> |
|
<style> |
|
body { |
|
margin: 0; |
|
background: black; |
|
overflow: hidden; |
|
font-family: Arial, sans-serif; |
|
} |
|
.controls { |
|
position: fixed; |
|
top: 20px; |
|
left: 20px; |
|
z-index: 1; |
|
} |
|
.stats { |
|
position: fixed; |
|
top: 20px; |
|
right: 20px; |
|
color: #0f0; |
|
font-family: monospace; |
|
font-size: 16px; |
|
text-align: right; |
|
} |
|
button { |
|
background: #333; |
|
color: #0f0; |
|
border: 2px solid #0f0; |
|
padding: 10px 20px; |
|
margin: 5px; |
|
cursor: pointer; |
|
font-size: 14px; |
|
transition: 0.3s; |
|
} |
|
button:hover { |
|
background: #0f0; |
|
color: black; |
|
} |
|
</style> |
|
</head> |
|
<body> |
|
<div class="controls"> |
|
<button onclick="addParticles()">Add Particles</button> |
|
<button onclick="toggleAcceleration()">Toggle Acceleration</button> |
|
<button onclick="toggleCollisions()">Toggle Collisions</button> |
|
<button onclick="resetSimulation()">Reset</button> |
|
</div> |
|
<div class="stats"> |
|
<div>Speed: <span id="speedDisplay">0</span>%</div> |
|
<div>Particles: <span id="particleCount">0</span></div> |
|
<div>Collisions: <span id="collisionCount">0</span></div> |
|
</div> |
|
<canvas id="canvas"></canvas> |
|
<script> |
|
const canvas = document.getElementById('canvas'); |
|
const ctx = canvas.getContext('2d'); |
|
|
|
canvas.width = window.innerWidth; |
|
canvas.height = window.innerHeight; |
|
|
|
const centerX = canvas.width / 2; |
|
const centerY = canvas.height / 2; |
|
const radius = Math.min(canvas.width, canvas.height) / 3; |
|
|
|
let particles = []; |
|
let explosions = []; |
|
let isAccelerating = true; |
|
let collisionsEnabled = true; |
|
let baseSpeed = 0.01; |
|
let collisionCount = 0; |
|
|
|
class Explosion { |
|
constructor(x, y, energy) { |
|
this.x = x; |
|
this.y = y; |
|
this.particles = []; |
|
this.life = 1; |
|
|
|
|
|
const particleCount = Math.floor(energy * 100); |
|
for(let i = 0; i < particleCount; i++) { |
|
const angle = Math.random() * Math.PI * 2; |
|
const speed = Math.random() * energy * 10; |
|
this.particles.push({ |
|
x: x, |
|
y: y, |
|
vx: Math.cos(angle) * speed, |
|
vy: Math.sin(angle) * speed, |
|
life: 1 |
|
}); |
|
} |
|
} |
|
|
|
update() { |
|
this.particles.forEach(p => { |
|
p.x += p.vx; |
|
p.y += p.vy; |
|
p.life *= 0.95; |
|
p.vx *= 0.98; |
|
p.vy *= 0.98; |
|
}); |
|
this.particles = this.particles.filter(p => p.life > 0.01); |
|
this.life = this.particles.length > 0 ? 1 : 0; |
|
} |
|
|
|
draw() { |
|
this.particles.forEach(p => { |
|
ctx.beginPath(); |
|
const gradient = ctx.createRadialGradient(p.x, p.y, 0, p.x, p.y, 5); |
|
gradient.addColorStop(0, `rgba(255, 200, 0, ${p.life})`); |
|
gradient.addColorStop(1, 'rgba(255, 100, 0, 0)'); |
|
ctx.fillStyle = gradient; |
|
ctx.arc(p.x, p.y, 5, 0, Math.PI * 2); |
|
ctx.fill(); |
|
}); |
|
} |
|
} |
|
|
|
class Particle { |
|
constructor(clockwise) { |
|
this.angle = clockwise ? 0 : Math.PI; |
|
this.clockwise = clockwise; |
|
this.speed = baseSpeed; |
|
this.trail = []; |
|
this.maxTrailLength = 20; |
|
this.collided = false; |
|
} |
|
|
|
update() { |
|
if (isAccelerating && !this.collided) { |
|
this.speed += 0.0001; |
|
} |
|
this.angle += this.clockwise ? this.speed : -this.speed; |
|
|
|
this.x = centerX + Math.cos(this.angle) * radius; |
|
this.y = centerY + Math.sin(this.angle) * radius; |
|
|
|
this.trail.push({x: this.x, y: this.y}); |
|
if (this.trail.length > this.maxTrailLength) { |
|
this.trail.shift(); |
|
} |
|
} |
|
|
|
draw() { |
|
if (this.collided) return; |
|
|
|
|
|
if (this.trail.length > 1) { |
|
ctx.beginPath(); |
|
ctx.strokeStyle = this.clockwise ? |
|
`rgba(255,50,50,${this.speed})` : |
|
`rgba(50,50,255,${this.speed})`; |
|
ctx.lineWidth = 2; |
|
ctx.moveTo(this.trail[0].x, this.trail[0].y); |
|
for (let pos of this.trail) { |
|
ctx.lineTo(pos.x, pos.y); |
|
} |
|
ctx.stroke(); |
|
} |
|
|
|
|
|
ctx.beginPath(); |
|
ctx.fillStyle = this.clockwise ? 'red' : 'blue'; |
|
const particleSize = 3 + (this.speed * 20); |
|
ctx.arc(this.x, this.y, particleSize, 0, Math.PI * 2); |
|
ctx.fill(); |
|
|
|
|
|
ctx.beginPath(); |
|
const gradient = ctx.createRadialGradient( |
|
this.x, this.y, particleSize, |
|
this.x, this.y, particleSize + 5 |
|
); |
|
gradient.addColorStop(0, this.clockwise ? 'rgba(255,0,0,0.5)' : 'rgba(0,0,255,0.5)'); |
|
gradient.addColorStop(1, 'rgba(0,0,0,0)'); |
|
ctx.fillStyle = gradient; |
|
ctx.arc(this.x, this.y, particleSize + 5, 0, Math.PI * 2); |
|
ctx.fill(); |
|
} |
|
} |
|
|
|
function checkCollisions() { |
|
if (!collisionsEnabled) return; |
|
|
|
for (let i = 0; i < particles.length; i++) { |
|
for (let j = i + 1; j < particles.length; j++) { |
|
const p1 = particles[i]; |
|
const p2 = particles[j]; |
|
|
|
if (!p1.collided && !p2.collided && p1.clockwise !== p2.clockwise) { |
|
const dx = p1.x - p2.x; |
|
const dy = p1.y - p2.y; |
|
const distance = Math.sqrt(dx * dx + dy * dy); |
|
|
|
if (distance < 10) { |
|
|
|
explosions.push(new Explosion( |
|
(p1.x + p2.x) / 2, |
|
(p1.y + p2.y) / 2, |
|
(p1.speed + p2.speed) |
|
)); |
|
|
|
p1.collided = true; |
|
p2.collided = true; |
|
collisionCount++; |
|
document.getElementById('collisionCount').textContent = collisionCount; |
|
} |
|
} |
|
} |
|
} |
|
|
|
|
|
particles = particles.filter(p => !p.collided); |
|
} |
|
|
|
function drawAccelerator() { |
|
|
|
ctx.beginPath(); |
|
ctx.strokeStyle = '#333'; |
|
ctx.lineWidth = 15; |
|
ctx.arc(centerX, centerY, radius, 0, Math.PI * 2); |
|
ctx.stroke(); |
|
|
|
|
|
for (let i = 0; i < 8; i++) { |
|
const angle = (i / 8) * Math.PI * 2; |
|
const x = centerX + Math.cos(angle) * radius; |
|
const y = centerY + Math.sin(angle) * radius; |
|
|
|
ctx.beginPath(); |
|
ctx.fillStyle = isAccelerating ? '#0f0' : '#333'; |
|
ctx.arc(x, y, 4, 0, Math.PI * 2); |
|
ctx.fill(); |
|
} |
|
} |
|
|
|
function addParticles() { |
|
particles.push(new Particle(true)); |
|
particles.push(new Particle(false)); |
|
updateStats(); |
|
} |
|
|
|
function toggleAcceleration() { |
|
isAccelerating = !isAccelerating; |
|
} |
|
|
|
function toggleCollisions() { |
|
collisionsEnabled = !collisionsEnabled; |
|
} |
|
|
|
function resetSimulation() { |
|
particles = []; |
|
explosions = []; |
|
baseSpeed = 0.01; |
|
collisionCount = 0; |
|
updateStats(); |
|
} |
|
|
|
function updateStats() { |
|
const currentSpeed = particles.length > 0 ? |
|
Math.min(100, particles[0].speed * 1000).toFixed(1) : 0; |
|
document.getElementById('speedDisplay').textContent = currentSpeed; |
|
document.getElementById('particleCount').textContent = particles.length; |
|
document.getElementById('collisionCount').textContent = collisionCount; |
|
} |
|
|
|
function animate() { |
|
|
|
ctx.fillStyle = 'rgba(0, 0, 0, 0.1)'; |
|
ctx.fillRect(0, 0, canvas.width, canvas.height); |
|
|
|
drawAccelerator(); |
|
|
|
|
|
explosions.forEach(explosion => explosion.update()); |
|
explosions = explosions.filter(explosion => explosion.life > 0); |
|
explosions.forEach(explosion => explosion.draw()); |
|
|
|
|
|
particles.forEach(particle => { |
|
particle.update(); |
|
particle.draw(); |
|
}); |
|
|
|
checkCollisions(); |
|
updateStats(); |
|
requestAnimationFrame(animate); |
|
} |
|
|
|
window.addEventListener('resize', () => { |
|
canvas.width = window.innerWidth; |
|
canvas.height = window.innerHeight; |
|
centerX = canvas.width / 2; |
|
centerY = canvas.height / 2; |
|
radius = Math.min(canvas.width, canvas.height) / 3; |
|
}); |
|
|
|
|
|
addParticles(); |
|
animate(); |
|
</script> |
|
</body> |
|
</html> |