Spaces:
Sleeping
Sleeping
| import streamlit as st | |
| from datetime import datetime | |
| # Initialize session state | |
| if 'scores' not in st.session_state: | |
| st.session_state.scores = [] | |
| # HTML/JS game code | |
| GAME_HTML = ''' | |
| <!DOCTYPE html> | |
| <html> | |
| <head> | |
| <style> | |
| body { margin: 0; } | |
| #game-container { | |
| width: 100%; | |
| height: 400px; | |
| background: black; | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| .lander { | |
| width: 32px; | |
| height: 32px; | |
| position: absolute; | |
| transform: translate(-50%, -50%); | |
| } | |
| .thrust-particle { | |
| width: 4px; | |
| height: 4px; | |
| background: red; | |
| position: absolute; | |
| border-radius: 50%; | |
| } | |
| .flag { | |
| width: 4px; | |
| height: 24px; | |
| background: yellow; | |
| position: absolute; | |
| bottom: 48px; | |
| } | |
| .hud { | |
| position: absolute; | |
| color: white; | |
| padding: 16px; | |
| font-family: sans-serif; | |
| } | |
| .controls { | |
| position: absolute; | |
| top: 16px; | |
| right: 16px; | |
| color: white; | |
| font-family: sans-serif; | |
| font-size: 14px; | |
| } | |
| .game-over { | |
| position: absolute; | |
| inset: 0; | |
| background: rgba(0,0,0,0.7); | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| color: white; | |
| font-family: sans-serif; | |
| text-align: center; | |
| } | |
| button { | |
| background: #a855f7; | |
| border: none; | |
| color: white; | |
| padding: 8px 16px; | |
| margin-top: 16px; | |
| cursor: pointer; | |
| border-radius: 4px; | |
| } | |
| button:hover { | |
| background: #9333ea; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div id="game-container"> | |
| <div class="hud"> | |
| <div id="fuel">Fuel: 100%</div> | |
| <div id="velocity">Velocity: 0 m/s</div> | |
| <div id="score"></div> | |
| </div> | |
| <div class="controls"> | |
| <div>β - Thrust</div> | |
| <div>β β - Move</div> | |
| <div>Land between flags</div> | |
| </div> | |
| </div> | |
| <script> | |
| class LunarLander { | |
| constructor() { | |
| this.container = document.getElementById('game-container'); | |
| this.position = { x: 200, y: 50 }; | |
| this.velocity = { x: 0, y: 0 }; | |
| this.fuel = 100; | |
| this.gameState = 'playing'; | |
| this.thrust = false; | |
| this.GRAVITY = 0.05; | |
| this.THRUST = 0.15; | |
| this.LANDING_SPEED = 3; | |
| this.groundPoints = [ | |
| {x: 0, y: 380}, {x: 100, y: 360}, {x: 150, y: 370}, | |
| {x: 200, y: 350}, {x: 300, y: 350}, {x: 350, y: 370}, | |
| {x: 400, y: 360}, {x: 450, y: 380}, {x: 500, y: 370} | |
| ]; | |
| this.setupGame(); | |
| } | |
| setupGame() { | |
| this.createLander(); | |
| this.createFlags(); | |
| this.createGround(); | |
| this.setupControls(); | |
| this.gameLoop(); | |
| } | |
| createLander() { | |
| this.lander = document.createElement('div'); | |
| this.lander.className = 'lander'; | |
| this.lander.style.background = '#a855f7'; | |
| this.container.appendChild(this.lander); | |
| } | |
| createFlags() { | |
| const flag1 = document.createElement('div'); | |
| flag1.className = 'flag'; | |
| flag1.style.left = '190px'; | |
| const flag2 = document.createElement('div'); | |
| flag2.className = 'flag'; | |
| flag2.style.left = '310px'; | |
| this.container.appendChild(flag1); | |
| this.container.appendChild(flag2); | |
| } | |
| createGround() { | |
| const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); | |
| svg.style.position = 'absolute'; | |
| svg.style.bottom = '0'; | |
| svg.style.width = '100%'; | |
| svg.style.height = '100%'; | |
| const path = document.createElementNS('http://www.w3.org/2000/svg', 'path'); | |
| const d = `M${this.groundPoints.map(p => `${p.x} ${p.y}`).join(' L')}`; | |
| path.setAttribute('d', d); | |
| path.setAttribute('stroke', 'white'); | |
| path.setAttribute('fill', 'none'); | |
| path.setAttribute('stroke-width', '2'); | |
| svg.appendChild(path); | |
| this.container.appendChild(svg); | |
| } | |
| setupControls() { | |
| document.addEventListener('keydown', (e) => { | |
| if (this.gameState !== 'playing' || this.fuel <= 0) return; | |
| switch (e.key) { | |
| case 'ArrowUp': | |
| this.thrust = true; | |
| this.velocity.y -= this.THRUST; | |
| this.fuel = Math.max(0, this.fuel - 0.5); | |
| this.updateThrust(); | |
| break; | |
| case 'ArrowLeft': | |
| this.velocity.x -= 0.1; | |
| this.fuel = Math.max(0, this.fuel - 0.2); | |
| break; | |
| case 'ArrowRight': | |
| this.velocity.x += 0.1; | |
| this.fuel = Math.max(0, this.fuel - 0.2); | |
| break; | |
| } | |
| }); | |
| document.addEventListener('keyup', (e) => { | |
| if (e.key === 'ArrowUp') { | |
| this.thrust = false; | |
| this.updateThrust(); | |
| } | |
| }); | |
| } | |
| updateThrust() { | |
| const particles = this.lander.querySelectorAll('.thrust-particle'); | |
| particles.forEach(p => p.remove()); | |
| if (this.thrust) { | |
| for (let i = 0; i < 4; i++) { | |
| const particle = document.createElement('div'); | |
| particle.className = 'thrust-particle'; | |
| particle.style.left = `${Math.random() * 8 - 4}px`; | |
| particle.style.top = `${Math.random() * 8}px`; | |
| this.lander.appendChild(particle); | |
| } | |
| } | |
| } | |
| endGame(won) { | |
| this.gameState = won ? 'won' : 'crashed'; | |
| const score = Math.floor(this.fuel * 100); | |
| const overlay = document.createElement('div'); | |
| overlay.className = 'game-over'; | |
| overlay.innerHTML = ` | |
| <div> | |
| <h2>${won ? 'Landing Successful!' : 'Crashed!'}</h2> | |
| <div>Score: ${score}</div> | |
| <button onclick="restartGame()">Play Again</button> | |
| </div> | |
| `; | |
| this.container.appendChild(overlay); | |
| if (won) { | |
| window.parent.postMessage({ | |
| type: 'score', | |
| score: score | |
| }, '*'); | |
| } | |
| } | |
| checkCollision() { | |
| for (let i = 0; i < this.groundPoints.length - 1; i++) { | |
| const p1 = this.groundPoints[i]; | |
| const p2 = this.groundPoints[i + 1]; | |
| if (this.position.x >= p1.x && this.position.x <= p2.x) { | |
| const groundY = p1.y + ((p2.y - p1.y) * (this.position.x - p1.x)) / (p2.x - p1.x); | |
| if (this.position.y >= groundY - 10) { | |
| if (this.velocity.y < this.LANDING_SPEED * 1.5 && | |
| Math.abs(this.velocity.x) < 2 && | |
| this.position.x >= 190 && | |
| this.position.x <= 310) { | |
| this.endGame(true); | |
| } else { | |
| this.endGame(false); | |
| } | |
| return true; | |
| } | |
| } | |
| } | |
| return false; | |
| } | |
| updateHUD() { | |
| document.getElementById('fuel').textContent = `Fuel: ${Math.floor(this.fuel)}%`; | |
| document.getElementById('velocity').textContent = `Velocity: ${Math.floor(this.velocity.y * 10)} m/s`; | |
| } | |
| gameLoop = () => { | |
| if (this.gameState === 'playing') { | |
| this.position.x += this.velocity.x; | |
| this.position.y += this.velocity.y; | |
| this.velocity.x *= 0.99; | |
| this.velocity.y += this.GRAVITY; | |
| this.lander.style.left = `${this.position.x}px`; | |
| this.lander.style.top = `${this.position.y}px`; | |
| this.updateHUD(); | |
| this.checkCollision(); | |
| } | |
| requestAnimationFrame(this.gameLoop); | |
| } | |
| } | |
| let game; | |
| function startGame() { | |
| game = new LunarLander(); | |
| } | |
| function restartGame() { | |
| document.getElementById('game-container').innerHTML = ` | |
| <div class="hud"> | |
| <div id="fuel">Fuel: 100%</div> | |
| <div id="velocity">Velocity: 0 m/s</div> | |
| <div id="score"></div> | |
| </div> | |
| <div class="controls"> | |
| <div>β - Thrust</div> | |
| <div>β β - Move</div> | |
| <div>Land between flags</div> | |
| </div> | |
| `; | |
| startGame(); | |
| } | |
| startGame(); | |
| </script> | |
| </body> | |
| </html> | |
| ''' | |
| def main(): | |
| st.title("Lunar Lander") | |
| # Display game | |
| st.components.v1.html(GAME_HTML, height=450) | |
| # Handle score updates | |
| st.markdown(""" | |
| <script> | |
| window.addEventListener('message', function(e) { | |
| if (e.data.type === 'score') { | |
| window.Streamlit.setComponentValue(e.data.score); | |
| } | |
| }); | |
| </script> | |
| """, unsafe_allow_html=True) | |
| # Display leaderboard | |
| if st.session_state.scores: | |
| st.subheader("Top Scores") | |
| for score in sorted(st.session_state.scores, key=lambda x: x['score'], reverse=True)[:10]: | |
| st.write(f"Score: {score['score']} - {score['timestamp'].strftime('%H:%M:%S')}") | |
| # Reset scores button | |
| if st.button("Reset Scores"): | |
| st.session_state.scores = [] | |
| st.experimental_rerun() | |
| if __name__ == "__main__": | |
| main() |