UNPKG

claude-arcade

Version:

Add classic arcade games to your Claude Code workflow with Ctrl+G

430 lines (429 loc) 19.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.DinoGame = void 0; class DinoGame { constructor() { this.dino = { y: 0, vy: 0, ducking: false, duckTimer: 0 }; // variant: for cactus -> 0=small,1=tall,2=double; for bird -> 0=low,1=high; width used for collision this.obstacles = []; this.ground = []; this.score = 0; this.distance = 0; this.playing = false; this.gameOver = false; this.speed = 1; this.WIDTH = 70; this.HEIGHT = 15; this.GROUND_Y = this.HEIGHT - 3; this.GRAVITY = 0.6; // Reduced gravity for slower fall = more hang time this.JUMP_STRENGTH = -5.0; // Adjusted for balanced jump height with lower gravity this.DINO_X = 8; this.DUCK_DURATION = 10; // 10 frames at 50ms = 0.5 seconds } isPlaying() { return this.playing; } isGameOver() { return this.gameOver; } getScore() { return this.score; } generateGround() { const groundChars = ['_', '-', '.', '_', '_']; this.ground = []; for (let i = 0; i < this.WIDTH * 2; i++) { this.ground.push(groundChars[Math.floor(Math.random() * groundChars.length)]); } } reset() { this.dino = { y: 0, vy: 0, ducking: false, duckTimer: 0 }; this.obstacles = []; this.score = 0; this.distance = 0; this.gameOver = false; this.speed = 1.2; // Slightly faster for more challenge this.generateGround(); // Add first obstacle this.spawnObstacle(); } start() { this.playing = true; this.reset(); } stop() { this.playing = false; } spawnObstacle() { const lastObstacle = this.obstacles[this.obstacles.length - 1]; const minDistance = 35 + Math.random() * 15; // Slightly more consistent spacing if (!lastObstacle || lastObstacle.x < this.WIDTH - minDistance) { const type = Math.random() > 0.6 ? 'bird' : 'cactus'; if (type === 'bird') { const variant = Math.random() > 0.5 ? 0 : 1; // 0=low,1=high const height = variant === 0 ? 4 : 7; this.obstacles.push({ x: this.WIDTH + 5, type, height, variant, width: 5 }); // Updated width for new bird sprite } else { const variant = Math.random() < 0.15 ? 2 : (Math.random() < 0.5 ? 0 : 1); // favor single cacti const width = variant === 2 ? 5 : 3; this.obstacles.push({ x: this.WIDTH + 5, type, height: 0, variant, width }); } } } jump() { if (this.dino.y === 0) { this.dino.ducking = false; // Auto-release duck when jumping this.dino.vy = this.JUMP_STRENGTH; } } duck(enable) { if (this.dino.y === 0 && enable) { this.dino.ducking = true; this.dino.duckTimer = this.DUCK_DURATION; } } update() { if (this.gameOver) return; // Update distance and score this.distance += this.speed; this.score = Math.floor(this.distance / 2); // Increase speed over time this.speed = 1 + Math.floor(this.score / 100) * 0.2; // Update dino physics if (this.dino.y !== 0 || this.dino.vy !== 0) { this.dino.vy += this.GRAVITY; this.dino.y += this.dino.vy; // Clamp to ground if (this.dino.y >= 0) { this.dino.y = 0; this.dino.vy = 0; } } // Auto-release duck after timer expires if (this.dino.ducking) { this.dino.duckTimer--; if (this.dino.duckTimer <= 0) { this.dino.ducking = false; } } // Update obstacles for (let i = this.obstacles.length - 1; i >= 0; i--) { this.obstacles[i].x -= this.speed; // Remove off-screen obstacles if (this.obstacles[i].x < -5) { this.obstacles.splice(i, 1); } } // Spawn new obstacles this.spawnObstacle(); // Check collisions for (const obstacle of this.obstacles) { if (this.checkCollision(obstacle)) { this.gameOver = true; this.playing = false; return; } } } checkCollision(obstacle) { const dinoHeight = this.dino.ducking ? 2 : 5; const dinoBottomY = this.GROUND_Y + Math.floor(this.dino.y); const dinoTopY = dinoBottomY - dinoHeight + 1; const dinoWidth = 6; // Updated to match new sprite width if (obstacle.type === 'cactus') { // Cactus is on the ground, height 4 const cactusHeight = obstacle.variant === 1 ? 5 : 4; // tall variant higher const cactusBottomY = this.GROUND_Y; const cactusTopY = cactusBottomY - cactusHeight + 1; const cactusWidth = obstacle.width || 3; // More forgiving collision - only check the center part of dino if (obstacle.x < this.DINO_X + dinoWidth - 1 && obstacle.x + cactusWidth > this.DINO_X + 1 && dinoBottomY >= cactusTopY && dinoTopY <= cactusBottomY) { return true; } } else if (obstacle.type === 'bird') { // Bird is in the air - collision changes based on ducking state const birdBottomY = this.GROUND_Y - obstacle.height; const birdTopY = birdBottomY - 1; // 2 pixels tall // When ducking, dino is only 2 pixels tall vs 5 when standing // This allows ducking under low birds but still hits high birds if not careful if (obstacle.x < this.DINO_X + dinoWidth - 1 && obstacle.x + (obstacle.width || 3) > this.DINO_X + 1) { // Check if dino's hitbox overlaps with bird if (dinoBottomY >= birdTopY && dinoTopY <= birdBottomY) { return true; } } } return false; } draw() { const GREEN = '\x1b[32m', YELLOW = '\x1b[33m', RED = '\x1b[31m', RESET = '\x1b[0m'; const WHITE = '\x1b[37m', GRAY = '\x1b[90m', CYAN = '\x1b[36m'; let output = '\x1b[2J\x1b[1;1H'; // Score in top right const scoreText = `${this.score.toString().padStart(5, '0')}`; output += `\x1b[1;${this.WIDTH - 10}H` + GRAY + 'HI ' + WHITE + '00000' + RESET; output += `\x1b[2;${this.WIDTH - 10}H` + GRAY + scoreText.substring(0, 5) + RESET; // Draw game area // Position dino so its FEET are at ground level const dinoHeight = this.dino.ducking ? 2 : 5; const dinoBottomY = this.GROUND_Y + Math.floor(this.dino.y); const dinoTopY = dinoBottomY - dinoHeight + 1; const groundOffset = Math.floor(this.distance) % this.ground.length; for (let y = 0; y < this.HEIGHT; y++) { output += `\x1b[${y + 3};1H`; for (let x = 0; x < this.WIDTH; x++) { let char = ' '; let color = RESET; // Draw dino - improved graphics (now 6 wide) if (x >= this.DINO_X && x < this.DINO_X + 6 && y >= dinoTopY && y <= dinoBottomY) { const rowOffset = y - dinoTopY; // Which row of the dino sprite are we drawing? if (this.dino.ducking) { // Ducking dino (2 rows tall, 6 wide) - nicer crouched sprite if (rowOffset === 0) { if (x === this.DINO_X) char = ' '; else if (x === this.DINO_X + 1) char = '█'; else if (x === this.DINO_X + 2) char = '█'; else if (x === this.DINO_X + 3) char = '█'; else if (x === this.DINO_X + 4) char = '█'; else if (x === this.DINO_X + 5) char = '▄'; } else if (rowOffset === 1) { if (x === this.DINO_X) char = '▄'; else if (x === this.DINO_X + 1) char = '█'; else if (x === this.DINO_X + 2) char = '▄'; else if (x === this.DINO_X + 3) char = '▄'; else if (x === this.DINO_X + 4) char = ' '; else if (x === this.DINO_X + 5) char = '▄'; } } else { // Standing dino (5 rows tall, 6 wide) - nicer running sprite if (rowOffset === 0) { if (x === this.DINO_X + 3) char = '▄'; else if (x === this.DINO_X + 4) char = '▄'; else if (x === this.DINO_X + 5) char = '▄'; } else if (rowOffset === 1) { if (x === this.DINO_X + 2) char = '▄'; else if (x === this.DINO_X + 3) char = '█'; else if (x === this.DINO_X + 4) char = '█'; else if (x === this.DINO_X + 5) char = '█'; } else if (rowOffset === 2) { if (x === this.DINO_X + 1) char = '▄'; else if (x === this.DINO_X + 2) char = '█'; else if (x === this.DINO_X + 3) char = '█'; else if (x === this.DINO_X + 4) char = '█'; else if (x === this.DINO_X + 5) char = '█'; } else if (rowOffset === 3) { if (x === this.DINO_X) char = '▄'; else if (x === this.DINO_X + 1) char = '█'; else if (x === this.DINO_X + 2) char = '█'; else if (x === this.DINO_X + 3) char = '█'; else if (x === this.DINO_X + 4) char = ' '; } else if (rowOffset === 4) { if (x === this.DINO_X) char = '▀'; else if (x === this.DINO_X + 1) char = ' '; else if (x === this.DINO_X + 2) char = '▀'; else if (x === this.DINO_X + 3) char = ' '; else if (x === this.DINO_X + 4) char = '▀'; } } if (char !== ' ') color = GREEN; } // Draw obstacles (nicer sprites) for (const obstacle of this.obstacles) { const obstacleX = Math.floor(obstacle.x); if (obstacle.type === 'cactus') { const baseY = this.GROUND_Y; // bottom row of cactus sits on ground const variant = obstacle.variant || 0; // 0=small,1=tall,2=double if (variant === 0) { // Small cactus (3 wide x 4 tall centered on obstacleX+1) if (y === baseY - 3 && x === obstacleX + 1) { char = '╥'; color = GREEN; } if (y === baseY - 2 && x === obstacleX + 1) { char = '║'; color = GREEN; } if (y === baseY - 1 && (x === obstacleX || x === obstacleX + 1 || x === obstacleX + 2)) { char = x === obstacleX + 1 ? '║' : '╫'; color = GREEN; } if (y === baseY && x === obstacleX + 1) { char = '║'; color = GREEN; } } else if (variant === 1) { // Tall cactus (3 wide x 5 tall) if (y === baseY - 4 && x === obstacleX + 1) { char = '╥'; color = GREEN; } if (y === baseY - 3 && x === obstacleX + 1) { char = '║'; color = GREEN; } if (y === baseY - 2 && (x === obstacleX || x === obstacleX + 1 || x === obstacleX + 2)) { char = x === obstacleX + 1 ? '║' : '╫'; color = GREEN; } if (y === baseY - 1 && x === obstacleX + 1) { char = '║'; color = GREEN; } if (y === baseY && x === obstacleX + 1) { char = '║'; color = GREEN; } } else { // Double cactus (two small cacti separated by 2 spaces) width ~5 const left = obstacleX; const right = obstacleX + 3; // Left if (y === baseY - 3 && x === left + 1) { char = '╥'; color = GREEN; } if (y === baseY - 2 && x === left + 1) { char = '║'; color = GREEN; } if (y === baseY - 1 && (x === left || x === left + 1 || x === left + 2)) { char = x === left + 1 ? '║' : '╫'; color = GREEN; } if (y === baseY && x === left + 1) { char = '║'; color = GREEN; } // Right if (y === baseY - 3 && x === right + 1) { char = '╥'; color = GREEN; } if (y === baseY - 2 && x === right + 1) { char = '║'; color = GREEN; } if (y === baseY - 1 && (x === right || x === right + 1 || x === right + 2)) { char = x === right + 1 ? '║' : '╫'; color = GREEN; } if (y === baseY && x === right + 1) { char = '║'; color = GREEN; } } } else if (obstacle.type === 'bird') { // Nicer bird sprite (5x2) - pterodactyl style const birdY = this.GROUND_Y - obstacle.height; if (y === birdY && x >= obstacleX && x < obstacleX + 5) { if (x === obstacleX) char = ' '; else if (x === obstacleX + 1) char = '▄'; else if (x === obstacleX + 2) char = '█'; else if (x === obstacleX + 3) char = '▄'; else if (x === obstacleX + 4) char = ' '; color = RED; } else if (y === birdY + 1 && x >= obstacleX && x < obstacleX + 5) { if (x === obstacleX) char = '▀'; else if (x === obstacleX + 1) char = '▄'; else if (x === obstacleX + 2) char = ' '; else if (x === obstacleX + 3) char = '▄'; else if (x === obstacleX + 4) char = '▀'; color = RED; } } } // Draw ground if (y === this.GROUND_Y + 1) { const groundIdx = (x + groundOffset) % this.ground.length; char = this.ground[groundIdx]; color = GRAY; } output += color + char + RESET; } } output += `\x1b[${this.HEIGHT + 5};1H` + CYAN + '↑/SPACE: Jump | ↓: Duck | P: Restart | Q/Ctrl+C: Exit' + RESET; return output; } drawGameOver() { const RED = '\x1b[31m', YELLOW = '\x1b[33m', RESET = '\x1b[0m', GRAY = '\x1b[90m'; let output = '\x1b[2J\x1b[10;1H'; output += RED + '🦖 G A M E O V E R! 🦖\n\n' + RESET; output += GRAY + 'Your Score: ' + RESET + YELLOW + this.score.toString().padStart(5, '0') + '\n' + RESET; return output; } drawWelcome() { const GREEN = '\x1b[32m', YELLOW = '\x1b[33m', RESET = '\x1b[0m', CYAN = '\x1b[36m'; const GRAY = '\x1b[90m'; return '\x1b[2J\x1b[8;1H' + GREEN + '🦖 CHROME DINO GAME 🦖\n\n' + RESET + GRAY + 'Jump over cacti and duck under birds!\n\n' + RESET + YELLOW + 'Press ↑ or SPACE to jump\n' + RESET + YELLOW + 'Press ↓ to duck\n\n' + RESET + CYAN + 'Press P to start!\n' + RESET + CYAN + 'Press Q or Ctrl+C to exit back to Claude\n' + RESET; } } exports.DinoGame = DinoGame;