Spaces:
Running
Running
import streamlit as st | |
from streamlit.components.v1 import html | |
import random | |
import string | |
# Define the enhanced HTML content with Three.js game | |
game_html = """ | |
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<title>Galaxxon - Enhanced Arcade Game</title> | |
<style> | |
body { margin: 0; overflow: hidden; background: #000; font-family: Arial; } | |
canvas { display: block; width: 100%; height: 100%; } | |
#ui { position: absolute; top: 10px; left: 10px; color: white; } | |
#sidebar { position: absolute; top: 10px; right: 10px; color: white; width: 200px; background: rgba(0,0,0,0.7); padding: 10px; } | |
</style> | |
</head> | |
<body> | |
<div id="ui">Score: <span id="score">0</span> | Multiplier: <span id="multiplier">1</span>x | Time: <span id="timer">0</span>s</div> | |
<div id="sidebar"> | |
<h3>High Scores</h3> | |
<div id="highScores"></div> | |
<button onclick="saveScore()">Save Score</button> | |
</div> | |
<script type="module"> | |
import * as THREE from 'https://cdn.jsdelivr.net/npm/[email protected]/build/three.module.js'; | |
let scene, camera, renderer, player, enemies = [], bullets = [], buildings = []; | |
let clock = new THREE.Clock(); | |
let moveLeft = false, moveRight = false, moveUp = false, moveDown = false, shoot = false; | |
let score = 0, multiplier = 1, gameTime = 0, lastHitTime = 0; | |
let highScores = JSON.parse(localStorage.getItem('highScores')) || []; | |
function init() { | |
scene = new THREE.Scene(); | |
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); | |
renderer = new THREE.WebGLRenderer(); | |
renderer.setSize(window.innerWidth, window.innerHeight); | |
document.body.appendChild(renderer.domElement); | |
camera.position.set(0, 20, 30); | |
camera.lookAt(0, 0, 0); | |
const playerGeometry = new THREE.BoxGeometry(1, 1, 1); | |
const playerMaterial = new THREE.MeshBasicMaterial({ color: 0x00ff00 }); | |
player = new THREE.Mesh(playerGeometry, playerMaterial); | |
player.position.y = -10; | |
scene.add(player); | |
spawnFlockingEnemies(); | |
spawnBuildings(); | |
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5); | |
scene.add(ambientLight); | |
window.addEventListener('keydown', onKeyDown); | |
window.addEventListener('keyup', onKeyUp); | |
window.addEventListener('resize', onWindowResize); | |
updateHighScoresUI(); | |
animate(); | |
} | |
function spawnFlockingEnemies() { | |
const enemyGeometry = new THREE.BoxGeometry(0.8, 0.8, 0.8); | |
const enemyMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000 }); | |
for (let i = 0; i < 10; i++) { | |
const enemy = new THREE.Mesh(enemyGeometry, enemyMaterial); | |
enemy.position.set(Math.random() * 20 - 10, Math.random() * 10 + 5, 0); | |
enemy.velocity = new THREE.Vector3(Math.random() - 0.5, Math.random() - 0.5, 0); | |
enemies.push(enemy); | |
scene.add(enemy); | |
} | |
} | |
function spawnBuildings() { | |
const primitives = [ | |
new THREE.BoxGeometry(2, 2, 2), | |
new THREE.CylinderGeometry(1, 1, 3, 8), | |
new THREE.ConeGeometry(1.5, 2, 8) | |
]; | |
const material = new THREE.MeshBasicMaterial({ color: 0x808080 }); | |
for (let i = 0; i < 5; i++) { | |
const building = new THREE.Group(); | |
const height = Math.random() * 3 + 1; | |
for (let j = 0; j < height; j++) { | |
const primitive = primitives[Math.floor(Math.random() * primitives.length)].clone(); | |
const segment = new THREE.Mesh(primitive, material); | |
segment.position.y = j * 2; | |
building.add(segment); | |
} | |
building.position.set(Math.random() * 30 - 15, -15 + height, 0); | |
buildings.push(building); | |
scene.add(building); | |
} | |
} | |
function onKeyDown(event) { | |
switch (event.code) { | |
case 'ArrowLeft': case 'KeyA': moveLeft = true; break; | |
case 'ArrowRight': case 'KeyD': moveRight = true; break; | |
case 'ArrowUp': case 'KeyW': moveUp = true; break; | |
case 'ArrowDown': case 'KeyS': moveDown = true; break; | |
case 'Space': shoot = true; break; | |
} | |
} | |
function onKeyUp(event) { | |
switch (event.code) { | |
case 'ArrowLeft': case 'KeyA': moveLeft = false; break; | |
case 'ArrowRight': case 'KeyD': moveRight = false; break; | |
case 'ArrowUp': case 'KeyW': moveUp = false; break; | |
case 'ArrowDown': case 'KeyS': moveDown = false; break; | |
case 'Space': shoot = false; break; | |
} | |
} | |
function updatePlayer(delta) { | |
const speed = 10; | |
if (moveLeft && player.position.x > -15) player.position.x -= speed * delta; | |
if (moveRight && player.position.x < 15) player.position.x += speed * delta; | |
if (moveUp && player.position.y < 0) player.position.y += speed * delta; | |
if (moveDown && player.position.y > -15) player.position.y -= speed * delta; | |
if (shoot && clock.getElapsedTime() > 0.2) { | |
shootBullet(); | |
clock = new THREE.Clock(); | |
} | |
} | |
function shootBullet() { | |
const bulletGeometry = new THREE.SphereGeometry(0.2, 8, 8); | |
const bulletMaterial = new THREE.MeshBasicMaterial({ color: 0xffff00 }); | |
const bullet = new THREE.Mesh(bulletGeometry, bulletMaterial); | |
bullet.position.copy(player.position); | |
bullet.position.y += 1; | |
bullets.push(bullet); | |
scene.add(bullet); | |
} | |
function updateBullets(delta) { | |
const bulletSpeed = 20; | |
for (let i = bullets.length - 1; i >= 0; i--) { | |
bullets[i].position.y += bulletSpeed * delta; | |
if (bullets[i].position.y > 20) { | |
scene.remove(bullets[i]); | |
bullets.splice(i, 1); | |
} else { | |
checkCollisions(bullets[i], i); | |
} | |
} | |
} | |
function checkCollisions(bullet, bulletIndex) { | |
for (let i = enemies.length - 1; i >= 0; i--) { | |
if (bullet.position.distanceTo(enemies[i].position) < 1) { | |
scene.remove(enemies[i]); | |
enemies.splice(i, 1); | |
scene.remove(bullet); | |
bullets.splice(bulletIndex, 1); | |
score += 10 * multiplier; | |
if (clock.getElapsedTime() - lastHitTime < 2) multiplier += 0.5; | |
lastHitTime = clock.getElapsedTime(); | |
updateUI(); | |
if (enemies.length < 5) spawnFlockingEnemies(); | |
break; | |
} | |
} | |
} | |
function updateFlockingEnemies(delta) { | |
const cohesion = 0.05, separation = 0.1, alignment = 0.05, speed = 2; | |
enemies.forEach(enemy => { | |
let avgPos = new THREE.Vector3(), avgVel = new THREE.Vector3(), separationForce = new THREE.Vector3(); | |
let neighbors = 0; | |
enemies.forEach(other => { | |
if (enemy !== other) { | |
const dist = enemy.position.distanceTo(other.position); | |
if (dist < 5) { | |
avgPos.add(other.position); | |
avgVel.add(other.velocity); | |
if (dist < 2) separationForce.sub(other.position.clone().sub(enemy.position).normalize().divideScalar(dist)); | |
neighbors++; | |
} | |
} | |
}); | |
if (neighbors > 0) { | |
avgPos.divideScalar(neighbors).sub(enemy.position).multiplyScalar(cohesion); | |
avgVel.divideScalar(neighbors).sub(enemy.velocity).multiplyScalar(alignment); | |
separationForce.multiplyScalar(separation); | |
enemy.velocity.add(avgPos).add(avgVel).add(separationForce).clampLength(0, speed); | |
} | |
enemy.position.add(enemy.velocity.clone().multiplyScalar(delta)); | |
if (enemy.position.y < -15) enemy.position.y = 15; | |
if (enemy.position.x < -15 || enemy.position.x > 15) enemy.velocity.x *= -1; | |
}); | |
} | |
function updateBuildings(delta) { | |
const buildingSpeed = 1; | |
buildings.forEach(building => { | |
building.position.y += buildingSpeed * delta; | |
if (building.position.y > 15) building.position.y = -15; | |
}); | |
} | |
function updateUI() { | |
document.getElementById('score').innerText = score; | |
document.getElementById('multiplier').innerText = multiplier.toFixed(1); | |
document.getElementById('timer').innerText = Math.floor(gameTime); | |
if (clock.getElapsedTime() - lastHitTime > 5) multiplier = 1; | |
} | |
function updateHighScoresUI() { | |
const scoresDiv = document.getElementById('highScores'); | |
scoresDiv.innerHTML = highScores.map(s => `${s.name}: ${s.score} (${s.time}s)`).join('<br>'); | |
} | |
window.saveScore = function() { | |
const name = prompt("Enter 3-letter name:", generateRandomName()); | |
if (name && name.length === 3) { | |
highScores.push({ name, score, time: Math.floor(gameTime) }); | |
highScores.sort((a, b) => b.score - a.score); | |
highScores = highScores.slice(0, 5); | |
localStorage.setItem('highScores', JSON.stringify(highScores)); | |
updateHighScoresUI(); | |
} | |
} | |
function generateRandomName() { | |
const letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; | |
return Array(3).fill().map(() => letters[Math.floor(Math.random() * letters.length)]).join(''); | |
} | |
function onWindowResize() { | |
camera.aspect = window.innerWidth / window.innerHeight; | |
camera.updateProjectionMatrix(); | |
renderer.setSize(window.innerWidth, window.innerHeight); | |
} | |
function animate() { | |
requestAnimationFrame(animate); | |
const delta = clock.getDelta(); | |
gameTime += delta; | |
updatePlayer(delta); | |
updateBullets(delta); | |
updateFlockingEnemies(delta); | |
updateBuildings(delta); | |
updateUI(); | |
renderer.render(scene, camera); | |
} | |
init(); | |
</script> | |
</body> | |
</html> | |
""" | |
# Streamlit app | |
st.title("Galaxxon - Enhanced Arcade Game") | |
st.write("Use WASD or Arrow Keys to move, Spacebar to shoot. Destroy red enemies for points! Chain hits for multiplier bonuses.") | |
# Render the HTML game | |
html(game_html, height=600, scrolling=False) | |
st.write("Note: The game runs in your browser. Ensure you have an internet connection for Three.js to load.") |