Spaces:
Sleeping
Sleeping
| import streamlit as st | |
| # Set wide layout | |
| st.set_page_config(layout="wide", page_title="Galaxian Snake Game", initial_sidebar_state="expanded") | |
| # Sidebar instructions | |
| st.sidebar.markdown("## Galaxian Snake Game Controls") | |
| st.sidebar.markdown( | |
| """ | |
| - **Controls:** | |
| - **Q:** Move Up–Left | |
| - **W:** Move Up | |
| - **E:** Move Up–Right | |
| - **A:** Move Left | |
| - **S:** (Center) Continue current direction | |
| - **D:** Move Right | |
| - **Z:** Move Down–Left | |
| - **X:** Move Down | |
| - **C:** Move Down–Right | |
| - **Rules:** | |
| - The snake moves on a grid and grows when it eats the alien food (👾). | |
| - You must avoid colliding with the walls or yourself. | |
| - Press **R** to restart after a game over. | |
| Have fun and enjoy the retro Galaxian vibe! | |
| """ | |
| ) | |
| # p5.js snake game embedded as an HTML string | |
| html_code = r""" | |
| <!DOCTYPE html> | |
| <html> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <title>Galaxian Snake Game</title> | |
| <!-- Include p5.js (with WEBGL disabled because we use 2D grid) --> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.6.0/p5.min.js"></script> | |
| <style> | |
| body { margin: 0; padding: 0; overflow: hidden; background: black; } | |
| canvas { display: block; margin: 0 auto; } | |
| </style> | |
| </head> | |
| <body> | |
| <script> | |
| /* | |
| Galaxian Snake Game | |
| - The snake moves on a grid. | |
| - Controls: Q, W, E, A, S, D, Z, X, C (with S meaning “no turn”). | |
| - Food is represented as a retro alien (👾). | |
| - A starfield background evokes classic Galaxian style. | |
| */ | |
| // Game configuration | |
| let tileSize = 20; | |
| let cols, rows; | |
| let snake; | |
| let direction; // {x: dx, y: dy} in grid units | |
| let food; | |
| let score; | |
| let gameOver; | |
| let moveCounter = 0; | |
| let moveDelay = 8; // lower => faster snake | |
| // Starfield for background | |
| let stars = []; | |
| const numStars = 150; | |
| function setup() { | |
| createCanvas(windowWidth, windowHeight); | |
| frameRate(30); | |
| cols = floor(width / tileSize); | |
| rows = floor(height / tileSize); | |
| resetGame(); | |
| // Create starfield | |
| for (let i = 0; i < numStars; i++) { | |
| stars.push({ | |
| x: random(width), | |
| y: random(height), | |
| speed: random(0.5, 2), | |
| size: random(1, 3) | |
| }); | |
| } | |
| } | |
| function resetGame() { | |
| snake = []; | |
| // Start snake in the middle with length 3 | |
| let startX = floor(cols / 2); | |
| let startY = floor(rows / 2); | |
| snake.push({x: startX, y: startY}); | |
| snake.push({x: startX - 1, y: startY}); | |
| snake.push({x: startX - 2, y: startY}); | |
| // Initial direction: moving right | |
| direction = {x: 1, y: 0}; | |
| placeFood(); | |
| score = 0; | |
| gameOver = false; | |
| moveCounter = 0; | |
| } | |
| // Place food (alien) at a random location not occupied by the snake | |
| function placeFood() { | |
| let valid = false; | |
| let newFood; | |
| while (!valid) { | |
| newFood = { | |
| x: floor(random(cols)), | |
| y: floor(random(rows)) | |
| }; | |
| valid = true; | |
| for (let seg of snake) { | |
| if (seg.x === newFood.x && seg.y === newFood.y) { | |
| valid = false; | |
| break; | |
| } | |
| } | |
| } | |
| food = newFood; | |
| } | |
| function draw() { | |
| // Draw Galaxian–style starfield background | |
| background(0); | |
| noStroke(); | |
| fill(255); | |
| for (let s of stars) { | |
| ellipse(s.x, s.y, s.size); | |
| s.y += s.speed; | |
| if (s.y > height) { | |
| s.y = 0; | |
| s.x = random(width); | |
| } | |
| } | |
| // Draw grid boundaries (optional: for visual style) | |
| // stroke(40); | |
| // for (let i = 0; i < cols; i++) { | |
| // line(i * tileSize, 0, i * tileSize, height); | |
| // } | |
| // for (let j = 0; j < rows; j++) { | |
| // line(0, j * tileSize, width, j * tileSize); | |
| // } | |
| // Game update if not over | |
| if (!gameOver) { | |
| moveCounter++; | |
| if (moveCounter >= moveDelay) { | |
| updateSnake(); | |
| moveCounter = 0; | |
| } | |
| } else { | |
| // Display Game Over message | |
| textAlign(CENTER, CENTER); | |
| textSize(64); | |
| fill(255, 50, 50); | |
| text("GAME OVER", width/2, height/2); | |
| textSize(32); | |
| text("Score: " + score + " (Press R to Restart)", width/2, height/2 + 50); | |
| } | |
| // Draw food as a retro alien emoji | |
| textSize(tileSize); | |
| textAlign(CENTER, CENTER); | |
| text("👾", food.x * tileSize + tileSize/2, food.y * tileSize + tileSize/2); | |
| // Draw snake as neon blocks | |
| for (let i = 0; i < snake.length; i++) { | |
| let seg = snake[i]; | |
| if (i == 0) { | |
| fill(0, 255, 0); // head bright green | |
| } else { | |
| fill(0, 180, 0); | |
| } | |
| rect(seg.x * tileSize, seg.y * tileSize, tileSize, tileSize); | |
| } | |
| // Draw score in top-left corner | |
| fill(255); | |
| textSize(16); | |
| textAlign(LEFT, TOP); | |
| text("Score: " + score, 10, 10); | |
| } | |
| // Update snake position and check collisions | |
| function updateSnake() { | |
| // Determine new head position | |
| let head = snake[0]; | |
| let newHead = { x: head.x + direction.x, y: head.y + direction.y }; | |
| // Check wall collisions | |
| if (newHead.x < 0 || newHead.x >= cols || newHead.y < 0 || newHead.y >= rows) { | |
| gameOver = true; | |
| return; | |
| } | |
| // Check self-collision | |
| for (let seg of snake) { | |
| if (seg.x === newHead.x && seg.y === newHead.y) { | |
| gameOver = true; | |
| return; | |
| } | |
| } | |
| // Add new head | |
| snake.unshift(newHead); | |
| // Check food collision | |
| if (newHead.x === food.x && newHead.y === food.y) { | |
| score += 10; | |
| placeFood(); | |
| // (Do not remove tail: snake grows) | |
| } else { | |
| // Remove tail (snake moves forward) | |
| snake.pop(); | |
| } | |
| } | |
| // Map key presses to direction changes using the 3x3 layout: | |
| // Q: (-1,-1), W: (0,-1), E: (1,-1) | |
| // A: (-1, 0), S: (0,0) [no change], D: (1,0) | |
| // Z: (-1, 1), X: (0,1), C: (1,1) | |
| function keyPressed() { | |
| if (gameOver && key.toLowerCase() === 'r') { | |
| resetGame(); | |
| return; | |
| } | |
| // Only process if game is not over | |
| if (gameOver) return; | |
| let newDir = null; | |
| let k = key.toLowerCase(); | |
| if (k === 'q') newDir = {x: -1, y: -1}; | |
| else if (k === 'w') newDir = {x: 0, y: -1}; | |
| else if (k === 'e') newDir = {x: 1, y: -1}; | |
| else if (k === 'a') newDir = {x: -1, y: 0}; | |
| else if (k === 's') newDir = {x: 0, y: 0}; // center: no change | |
| else if (k === 'd') newDir = {x: 1, y: 0}; | |
| else if (k === 'z') newDir = {x: -1, y: 1}; | |
| else if (k === 'x') newDir = {x: 0, y: 1}; | |
| else if (k === 'c') newDir = {x: 1, y: 1}; | |
| // If a valid key was pressed and newDir is not the "no-turn" (0,0) command, | |
| // update direction. Also disallow reversing (only if snake length > 1). | |
| if (newDir) { | |
| if (newDir.x === 0 && newDir.y === 0) { | |
| // "S" was pressed: no turn, so do nothing. | |
| return; | |
| } | |
| if (snake.length > 1) { | |
| // Prevent reversing (i.e., newDir should not be exactly opposite of current) | |
| if (newDir.x === -direction.x && newDir.y === -direction.y) { | |
| return; | |
| } | |
| } | |
| direction = newDir; | |
| } | |
| } | |
| // Resize canvas on window resize | |
| function windowResized() { | |
| resizeCanvas(windowWidth, windowHeight); | |
| cols = floor(width / tileSize); | |
| rows = floor(height / tileSize); | |
| } | |
| </script> | |
| </body> | |
| </html> | |
| """ | |
| st.components.v1.html(html_code, height=700, scrolling=False) | |