Skip to content

Instantly share code, notes, and snippets.

@Jach
Created December 31, 2024 09:04
Show Gist options
  • Save Jach/7e7058b9b10c1c8dddfb9798ce7405a6 to your computer and use it in GitHub Desktop.
Save Jach/7e7058b9b10c1c8dddfb9798ce7405a6 to your computer and use it in GitHub Desktop.
Jumping Coyopotato Game, made 99.999% with Claude
<!DOCTYPE html>
<html>
<head>
<title>Jumping Potato Game</title>
<style>
body {
margin: 0;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
background: #87CEEB;
font-family: Arial, sans-serif;
}
#game-container {
width: 800px;
height: 400px;
background: #f0f0f0;
position: relative;
overflow: hidden;
border: 4px solid #654321;
}
#score {
position: absolute;
top: 20px;
right: 20px;
font-size: 24px;
color: #333;
}
#game-over {
display: none;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
text-align: center;
background: rgba(255, 255, 255, 0.9);
padding: 20px;
border-radius: 10px;
}
.ground {
position: absolute;
bottom: 0;
width: 100%;
height: 50px;
background: #654321;
}
#restart-btn {
padding: 10px 20px;
font-size: 18px;
cursor: pointer;
background: #4CAF50;
color: white;
border: none;
border-radius: 5px;
margin-top: 10px;
}
#restart-btn:hover {
background: #45a049;
}
</style>
</head>
<body>
<div id="game-container">
<div id="score">Score: 0</div>
<div class="ground"></div>
<div id="game-over">
<h2>Game Over!</h2>
<p>Final Score: <span id="final-score">0</span></p>
<button id="restart-btn">Play Again</button>
</div>
</div>
<script>
class Game {
constructor() {
this.container = document.getElementById('game-container');
this.scoreElement = document.getElementById('score');
this.gameOverElement = document.getElementById('game-over');
this.finalScoreElement = document.getElementById('final-score');
this.restartBtn = document.getElementById('restart-btn');
this.potato = null;
this.jumpArcPreview = null;
this.obstacles = [];
this.score = 0;
this.isGameOver = false;
this.groundHeight = 50;
this.gravity = 0.8;
this.obstacleSpeed = 6; // Increased speed
this.minObstacleDistance = 200; // Reduced minimum distance
this.init();
}
init() {
this.createPotato();
this.createJumpArcPreview();
this.setupEventListeners();
this.startGame();
}
createPotato() {
const potatoSvgOrig = `
<svg width="50" height="60" viewBox="0 0 50 60">
<g transform="translate(0,0)">
<path d="M25 5 Q40 5, 45 25 Q50 45, 25 55 Q0 45, 5 25 Q10 5, 25 5"
fill="#D2B48C" stroke="#8B4513" stroke-width="2"/>
<ellipse cx="20" cy="25" rx="3" ry="4" fill="#8B4513"/> <!-- left eye -->
<ellipse cx="30" cy="25" rx="3" ry="4" fill="#8B4513"/> <!-- right eye -->
<path d="M20 35 Q25 40, 30 35" fill="none" stroke="#8B4513" stroke-width="2"/> <!-- smile -->
</g>
</svg>
`;
// Coyopotato:
const potatoSvg = `
<svg width="80" height="70" viewBox="0 0 80 70">
<g transform="translate(5,5)">
<!-- Fluffy tail (behind potato) -->
<path d="M45 35
Q60 25, 70 15
L72 17
L68 20
L73 22
L69 25
L74 28
Q65 35, 45 35"
fill="#E6C097" stroke="#8B4513" stroke-width="2"/>
<!-- Main potato body -->
<path d="M25 15 Q40 15, 45 30 Q50 45, 25 55 Q0 45, 5 30 Q10 15, 25 15"
fill="#D2B48C" stroke="#8B4513" stroke-width="2"/>
<!-- Ears -->
<path d="M15 17 L10 5 L20 15 Z"
fill="#E6C097" stroke="#8B4513" stroke-width="2"/>
<path d="M35 17 L40 5 L30 15 Z"
fill="#E6C097" stroke="#8B4513" stroke-width="2"/>
<!-- Face -->
<ellipse cx="20" cy="30" rx="2" ry="3" fill="#8B4513"/> <!-- left eye -->
<ellipse cx="30" cy="30" rx="2" ry="3" fill="#8B4513"/> <!-- right eye -->
<path d="M25 35 L23 37 L27 37" fill="none" stroke="#8B4513" stroke-width="2"/> <!-- nose is mouth -->
<!-- Blush marks -->
<ellipse cx="15" cy="37" rx="4" ry="3" fill="#FFB6C1" opacity="0.5"/>
<ellipse cx="35" cy="37" rx="4" ry="3" fill="#FFB6C1" opacity="0.5"/>
</g>
</svg>
`;
this.potato = document.createElement('div');
this.potato.style.position = 'absolute';
this.potato.style.left = '50px';
this.potato.style.bottom = `${this.groundHeight}px`;
this.potato.style.width = '50px';
this.potato.style.height = '60px';
this.potato.innerHTML = potatoSvg;
this.container.appendChild(this.potato);
this.potatoProps = {
y: this.groundHeight,
velocity: 0,
isJumping: false
};
}
createObstacle() {
const rockSvg = `
<svg width="30" height="40" viewBox="0 0 30 40">
<path d="M5 35 L2 20 L10 10 L20 15 L25 25 L20 35 Z"
fill="#808080" stroke="#666666" stroke-width="2"/>
</svg>
`;
const obstacle = document.createElement('div');
obstacle.style.position = 'absolute';
obstacle.style.right = '0';
obstacle.style.bottom = `${this.groundHeight}px`;
obstacle.style.width = '30px';
obstacle.style.height = '40px';
obstacle.innerHTML = rockSvg;
this.container.appendChild(obstacle);
this.obstacles.push({
element: obstacle,
x: this.container.offsetWidth
});
}
setupEventListeners() {
this.spacePressed = false;
this.spaceHoldStartTime = 0;
document.addEventListener('keydown', (e) => {
if (e.code === 'Space') {
if (this.isGameOver) {
this.resetGame();
} else if (!this.spacePressed && !this.potatoProps.isJumping) {
this.spacePressed = true;
this.spaceHoldStartTime = Date.now();
}
}
});
document.addEventListener('keyup', (e) => {
if (e.code === 'Space' && this.spacePressed) {
const holdDuration = Date.now() - this.spaceHoldStartTime;
const baseVelocity = 12;
const maxVelocity = 20;
const maxHoldTime = 500; // max hold time in ms
// Calculate velocity based on hold duration
const velocityIncrease = Math.min(holdDuration / maxHoldTime, 1) * (maxVelocity - baseVelocity);
this.potatoProps.velocity = baseVelocity + velocityIncrease;
this.potatoProps.isJumping = true;
this.spacePressed = false;
}
});
this.restartBtn.addEventListener('click', () => {
this.resetGame();
});
}
updatePotato() {
// Visual feedback for charging jump
if (this.spacePressed) {
const holdDuration = Date.now() - this.spaceHoldStartTime;
const maxHoldTime = 500;
const chargeProgress = Math.min(holdDuration / maxHoldTime, 1);
// Make the potato squish down while charging
const scaleY = 1 - (chargeProgress * 0.2);
const scaleX = 1 + (chargeProgress * 0.2);
this.potato.style.transform = `scaleX(${scaleX}) scaleY(${scaleY})`;
} else if (this.potatoProps.isJumping) {
this.potato.style.transform = 'scale(1)';
this.potatoProps.y += this.potatoProps.velocity;
this.potatoProps.velocity -= 0.8;
if (this.potatoProps.y <= this.groundHeight) {
this.potatoProps.y = this.groundHeight;
this.potatoProps.velocity = 0;
this.potatoProps.isJumping = false;
}
this.potato.style.bottom = `${this.potatoProps.y}px`;
}
}
createJumpArcPreview() {
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svg.style.position = 'absolute';
svg.style.left = '0';
svg.style.top = '0';
svg.style.width = '100%';
svg.style.height = '100%';
svg.style.pointerEvents = 'none';
svg.style.display = 'none';
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
path.setAttribute('stroke', 'rgba(255, 255, 255, 0.7)');
path.setAttribute('stroke-width', '2');
path.setAttribute('fill', 'none');
path.setAttribute('stroke-dasharray', '5,5');
const landingMarker = document.createElementNS("http://www.w3.org/2000/svg", "circle");
landingMarker.setAttribute('r', '4');
landingMarker.setAttribute('fill', 'red');
svg.appendChild(path);
svg.appendChild(landingMarker);
this.container.appendChild(svg);
this.jumpArcPreview = { svg, path, landingMarker };
}
updateJumpArcPreview() {
if (this.spacePressed) {
const holdDuration = Date.now() - this.spaceHoldStartTime;
const maxHoldTime = 500;
const baseVelocity = 12;
const maxVelocity = 20;
const velocityIncrease = Math.min(holdDuration / maxHoldTime, 1) * (maxVelocity - baseVelocity);
const initialVelocity = baseVelocity + velocityIncrease;
// Calculate jump arc points
let points = [];
let x = parseInt(this.potato.style.left) + 25; // center of potato
let y = this.potatoProps.y;
let vy = initialVelocity;
let landingPoint = null;
let time = 0;
while (y >= this.groundHeight) {
points.push([x + (this.obstacleSpeed * time), this.container.offsetHeight - y]);
vy -= this.gravity;
y += vy;
time += 1;
if (y <= this.groundHeight && !landingPoint) {
landingPoint = [x + (this.obstacleSpeed * time), this.container.offsetHeight - this.groundHeight];
}
}
// Create SVG path
const pathData = points.map((p, i) =>
(i === 0 ? 'M' : 'L') + p[0] + ' ' + p[1]
).join(' ');
this.jumpArcPreview.path.setAttribute('d', pathData);
this.jumpArcPreview.path.setAttribute('stroke', 'rgba(50, 50, 50, 0.7)'); // Darker color
if (landingPoint) {
this.jumpArcPreview.landingMarker.setAttribute('cx', landingPoint[0]);
this.jumpArcPreview.landingMarker.setAttribute('cy', landingPoint[1]);
}
this.jumpArcPreview.svg.style.display = 'block';
} else {
this.jumpArcPreview.svg.style.display = 'none';
}
}
canSpawnObstacle() {
if (this.obstacles.length === 0) return true;
const lastObstacle = this.obstacles[this.obstacles.length - 1];
return lastObstacle.x < this.container.offsetWidth - this.minObstacleDistance;
}
updateObstacles() {
if (Math.random() < 0.025 && this.canSpawnObstacle()) {
this.createObstacle();
}
for (let i = this.obstacles.length - 1; i >= 0; i--) {
const obstacle = this.obstacles[i];
obstacle.x -= 5;
obstacle.element.style.right = `${this.container.offsetWidth - obstacle.x}px`;
// Check collision
if (this.checkCollision(obstacle)) {
this.gameOver();
return;
}
// Remove obstacles that are off screen
if (obstacle.x + 30 < 0) {
this.container.removeChild(obstacle.element);
this.obstacles.splice(i, 1);
this.score++;
this.scoreElement.textContent = `Score: ${this.score}`;
}
}
}
checkCollision(obstacle) {
const potatoRect = this.potato.getBoundingClientRect();
const obstacleRect = obstacle.element.getBoundingClientRect();
return !(potatoRect.right < obstacleRect.left ||
potatoRect.left > obstacleRect.right ||
potatoRect.bottom < obstacleRect.top ||
potatoRect.top > obstacleRect.bottom);
}
gameOver() {
this.isGameOver = true;
this.gameOverElement.style.display = 'block';
this.finalScoreElement.textContent = this.score;
}
resetGame() {
// Clear obstacles
this.obstacles.forEach(obstacle => {
this.container.removeChild(obstacle.element);
});
this.obstacles = [];
// Reset score and game state
this.score = 0;
this.scoreElement.textContent = 'Score: 0';
this.isGameOver = false;
this.gameOverElement.style.display = 'none';
// Reset potato position
this.potatoProps.y = this.groundHeight;
this.potatoProps.velocity = 0;
this.potatoProps.isJumping = false;
this.potato.style.bottom = `${this.groundHeight}px`;
}
startGame() {
const gameLoop = () => {
if (!this.isGameOver) {
this.updatePotato();
this.updateObstacles();
this.updateJumpArcPreview();
}
requestAnimationFrame(gameLoop);
};
gameLoop();
}
}
// Start the game
new Game();
</script>
</body>
</html>
@Jach
Copy link
Author

Jach commented Jan 2, 2025

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment