space-2 / index.html
arirajuns's picture
Add 2 files
944af14 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Spring Jump - Hooke's Law Physics Game</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.18.0/matter.min.js"></script>
<style>
#gameCanvas {
background: linear-gradient(to bottom, #87CEEB, #E0F7FA);
border-radius: 10px;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
}
.spring {
transition: height 0.1s ease-out;
}
.platform {
background: linear-gradient(to right, #4CAF50, #8BC34A);
border-radius: 5px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
}
.character {
background: linear-gradient(to bottom, #FF5722, #FF9800);
border-radius: 50% 50% 40% 40%;
position: relative;
}
.character::before {
content: "";
position: absolute;
width: 10px;
height: 10px;
background: #333;
border-radius: 50%;
top: 25%;
left: 30%;
}
.character::after {
content: "";
position: absolute;
width: 10px;
height: 10px;
background: #333;
border-radius: 50%;
top: 25%;
right: 30%;
}
.score-display {
background: rgba(255, 255, 255, 0.8);
border-radius: 20px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
.instructions {
background: rgba(255, 255, 255, 0.9);
border-radius: 10px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
</style>
</head>
<body class="bg-gray-100 min-h-screen flex flex-col items-center justify-center p-4">
<div class="w-full max-w-4xl">
<h1 class="text-4xl font-bold text-center text-orange-600 mb-2">Spring Jump</h1>
<p class="text-center text-gray-700 mb-6">Experience Hooke's Law in action! Hold space to compress the spring and release to jump.</p>
<div class="relative">
<canvas id="gameCanvas" width="800" height="500" class="w-full border-2 border-gray-300"></canvas>
<div class="instructions absolute top-4 left-4 p-3 max-w-xs">
<h3 class="font-bold text-gray-800 mb-2">How to Play:</h3>
<ul class="text-sm text-gray-700 list-disc pl-4">
<li>Hold <span class="font-mono bg-gray-200 px-1">SPACE</span> to compress spring</li>
<li>Release to jump</li>
<li>Longer compression = higher jump</li>
<li>Reach platforms to score points</li>
</ul>
</div>
<div class="score-display absolute top-4 right-4 px-4 py-2">
<p class="text-lg font-bold text-gray-800">Score: <span id="score">0</span></p>
<p class="text-sm text-gray-600">High Score: <span id="highScore">0</span></p>
</div>
</div>
<div class="mt-6 flex justify-between items-center">
<div class="flex items-center">
<div class="w-8 h-8 rounded-full bg-orange-500 mr-2"></div>
<span class="text-gray-700">Character</span>
</div>
<div class="flex items-center">
<div class="w-8 h-3 rounded bg-green-500 mr-2"></div>
<span class="text-gray-700">Platforms</span>
</div>
<div class="flex items-center">
<div class="w-2 h-8 bg-gray-800 mr-2 spring-icon"></div>
<span class="text-gray-700">Spring</span>
</div>
</div>
<div class="mt-6 bg-white p-4 rounded-lg shadow">
<h3 class="font-bold text-gray-800 mb-2">Physics Explained:</h3>
<p class="text-gray-700 mb-2">This game demonstrates <span class="font-bold">Hooke's Law</span> (F = -kx) where:</p>
<ul class="text-sm text-gray-700 list-disc pl-4 mb-3">
<li>F = Force applied by the spring</li>
<li>k = Spring constant (stiffness)</li>
<li>x = Displacement from equilibrium</li>
</ul>
<p class="text-gray-700">The more you compress the spring (increase x), the more force it applies, resulting in higher jumps!</p>
</div>
</div>
<script>
// Game setup
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
const scoreElement = document.getElementById('score');
const highScoreElement = document.getElementById('highScore');
// Matter.js modules
const { Engine, Render, Runner, Bodies, Composite, Body, Events, Mouse, MouseConstraint } = Matter;
// Create engine
const engine = Engine.create({
gravity: { x: 0, y: 1 }
});
// Game variables
let score = 0;
let highScore = localStorage.getItem('highScore') || 0;
let isCompressing = false;
let compressionStartTime = 0;
let maxCompression = 0;
const springConstant = 0.002;
const characterSize = 30;
let character, spring, ground;
let platforms = [];
let gameStarted = false;
// Initialize game
function initGame() {
// Clear previous bodies
Composite.clear(engine.world);
// Create ground
ground = Bodies.rectangle(canvas.width / 2, canvas.height - 10, canvas.width, 20, {
isStatic: true,
render: {
fillStyle: '#795548'
}
});
// Create character
character = Bodies.circle(canvas.width / 2, canvas.height - 60, characterSize, {
restitution: 0.3,
friction: 0.1,
render: {
fillStyle: '#FF5722'
}
});
// Create spring (visual only, physics handled separately)
spring = {
x: canvas.width / 2,
y: canvas.height - 40,
width: 5,
height: 20,
naturalHeight: 20,
compressedHeight: 5
};
// Create initial platforms
createPlatforms();
// Add all bodies to the world
Composite.add(engine.world, [ground, character, ...platforms]);
// Reset score
score = 0;
scoreElement.textContent = score;
highScoreElement.textContent = highScore;
gameStarted = true;
}
// Create platforms at varying heights
function createPlatforms() {
platforms = [];
// Starting platform
platforms.push(
Bodies.rectangle(canvas.width / 2, canvas.height - 30, 200, 20, {
isStatic: true,
render: {
fillStyle: '#4CAF50'
},
label: 'platform'
})
);
// Additional platforms
const platformCount = 8;
const minHeight = canvas.height / 2;
const maxHeight = 100;
for (let i = 0; i < platformCount; i++) {
const width = 100 + Math.random() * 100;
const x = 50 + Math.random() * (canvas.width - 100);
const y = minHeight - (i * ((minHeight - maxHeight) / platformCount));
platforms.push(
Bodies.rectangle(x, y, width, 15, {
isStatic: true,
render: {
fillStyle: '#8BC34A'
},
label: 'platform'
})
);
}
}
// Handle spring compression and jumping
function handleSpring() {
if (!gameStarted) return;
const characterPos = character.position;
// Check if character is on ground or platform
const isOnSurface = platforms.some(platform => {
return (
characterPos.y + characterSize >= platform.position.y - 10 &&
characterPos.y + characterSize <= platform.position.y + 10 &&
characterPos.x >= platform.position.x - platform.bounds.max.x + platform.position.x &&
characterPos.x <= platform.position.x + platform.bounds.max.x - platform.position.x
);
});
if (isOnSurface) {
// Position spring under character
spring.x = characterPos.x;
spring.y = characterPos.y + characterSize;
// Handle space key press for spring compression
if (isCompressing) {
const compressionTime = Date.now() - compressionStartTime;
const compressionRatio = Math.min(compressionTime / 1000, 1); // Max 1 second compression
// Calculate compressed height (visual)
spring.height = spring.naturalHeight - (compressionRatio * (spring.naturalHeight - spring.compressedHeight));
// Track max compression for jump force
maxCompression = Math.max(maxCompression, compressionRatio);
} else {
// Reset spring when not compressing
spring.height = spring.naturalHeight;
}
// Apply jump force when space is released after compression
if (!isCompressing && maxCompression > 0) {
// Calculate jump force based on Hooke's Law (F = -kx)
const jumpForce = -springConstant * (maxCompression * 1000);
// Apply impulse to character
Body.applyForce(character, character.position, {
x: 0,
y: jumpForce
});
maxCompression = 0;
}
} else {
// Hide spring when character is in the air
spring.height = 0;
}
}
// Check for platform collisions to score points
function checkPlatformCollisions() {
if (!gameStarted) return;
platforms.forEach(platform => {
if (
character.position.y + characterSize >= platform.position.y - 15 &&
character.position.y + characterSize <= platform.position.y + 5 &&
character.position.x >= platform.position.x - (platform.bounds.max.x - platform.position.x) &&
character.position.x <= platform.position.x + (platform.bounds.max.x - platform.position.x) &&
character.velocity.y > 0 // Only count when falling onto platform
) {
// Only score if this is a new platform (not the current standing one)
if (!platform.scored) {
score += 10;
scoreElement.textContent = score;
if (score > highScore) {
highScore = score;
highScoreElement.textContent = highScore;
localStorage.setItem('highScore', highScore);
}
platform.scored = true;
// Mark other platforms as not scored
platforms.forEach(p => {
if (p !== platform) p.scored = false;
});
}
}
});
}
// Game loop
function gameLoop() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Draw ground
ctx.fillStyle = '#795548';
ctx.fillRect(0, canvas.height - 10, canvas.width, 20);
// Draw platforms
platforms.forEach(platform => {
ctx.fillStyle = platform.render.fillStyle;
ctx.fillRect(
platform.position.x - (platform.bounds.max.x - platform.position.x),
platform.position.y - (platform.bounds.max.y - platform.position.y),
platform.bounds.max.x - platform.bounds.min.x,
platform.bounds.max.y - platform.bounds.min.y
);
});
// Draw spring
if (spring.height > 0) {
ctx.fillStyle = '#333';
ctx.fillRect(
spring.x - spring.width / 2,
spring.y,
spring.width,
spring.height
);
// Draw spring coils
const coils = 4;
const coilHeight = spring.height / coils;
for (let i = 0; i < coils; i++) {
ctx.beginPath();
ctx.arc(
spring.x,
spring.y + (i * coilHeight) + (coilHeight / 2),
spring.width * 1.5,
0,
Math.PI,
true
);
ctx.strokeStyle = '#333';
ctx.lineWidth = 2;
ctx.stroke();
}
}
// Draw character
ctx.fillStyle = character.render.fillStyle;
ctx.beginPath();
ctx.arc(character.position.x, character.position.y, characterSize, 0, Math.PI * 2);
ctx.fill();
// Draw character face
ctx.fillStyle = '#333';
ctx.beginPath();
ctx.arc(character.position.x - 10, character.position.y - 5, 5, 0, Math.PI * 2); // Left eye
ctx.arc(character.position.x + 10, character.position.y - 5, 5, 0, Math.PI * 2); // Right eye
ctx.fill();
// Draw mouth (smile when jumping, straight when compressing)
ctx.beginPath();
if (isCompressing) {
ctx.moveTo(character.position.x - 10, character.position.y + 5);
ctx.lineTo(character.position.x + 10, character.position.y + 5);
} else {
ctx.arc(character.position.x, character.position.y + 5, 10, 0, Math.PI);
}
ctx.strokeStyle = '#333';
ctx.lineWidth = 2;
ctx.stroke();
// Handle spring mechanics
handleSpring();
// Check for scoring
checkPlatformCollisions();
// Game over if character falls off screen
if (character.position.y > canvas.height + 100) {
gameStarted = false;
setTimeout(initGame, 1000);
}
requestAnimationFrame(gameLoop);
}
// Event listeners
window.addEventListener('keydown', (e) => {
if (e.code === 'Space') {
if (!isCompressing && gameStarted) {
isCompressing = true;
compressionStartTime = Date.now();
}
}
});
window.addEventListener('keyup', (e) => {
if (e.code === 'Space') {
isCompressing = false;
}
});
// Touch support for mobile
canvas.addEventListener('touchstart', () => {
if (!isCompressing && gameStarted) {
isCompressing = true;
compressionStartTime = Date.now();
}
});
canvas.addEventListener('touchend', () => {
isCompressing = false;
});
// Start the game
initGame();
gameLoop();
// Start Matter.js engine
const runner = Runner.create();
Runner.run(runner, engine);
</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=arirajuns/space-2" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>