ZaxxoGalaxian / app.py
awacke1's picture
Update app.py
8f8e846 verified
import streamlit as st
from streamlit.components.v1 import html
import random
import string
# Set Streamlit to wide mode
st.set_page_config(layout="wide")
# 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>
html, body { margin: 0; padding: 0; overflow: hidden; background: #000; font-family: Arial; height: 100%; width: 100%; }
canvas { display: block; width: 100vw !important; height: 100vh !important; }
#ui { position: absolute; top: 10px; left: 10px; color: white; z-index: 1; }
#sidebar { position: absolute; top: 10px; right: 10px; color: white; width: 200px; background: rgba(0,0,0,0.7); padding: 10px; z-index: 1; }
#lives { position: absolute; top: 40px; left: 10px; color: white; z-index: 1; }
.bonus { position: absolute; color: yellow; font-size: 20px; z-index: 1; }
</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="lives">Lives: <span id="livesCount">5</span></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, lives = 5, buildingStreak = 0;
let highScores = JSON.parse(localStorage.getItem('highScores')) || [];
let exploding = false;
function init() {
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
camera.position.set(0, 5, 15);
camera.lookAt(0, 0, -50);
const playerGeometry = new THREE.BoxGeometry(1, 1, 1);
const playerMaterial = new THREE.MeshPhongMaterial({ color: 0x00ff00, shininess: 100 });
player = new THREE.Mesh(playerGeometry, playerMaterial);
player.position.set(0, 0, 0);
scene.add(player);
spawnBuildings();
spawnEnemyFormations();
const ambientLight = new THREE.AmbientLight(0x404040, 0.5);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(5, 10, 5);
scene.add(directionalLight);
window.addEventListener('keydown', onKeyDown);
window.addEventListener('keyup', onKeyUp);
window.addEventListener('resize', onWindowResize);
updateHighScoresUI();
animate();
}
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.MeshPhongMaterial({ color: 0x808080, shininess: 50 });
for (let i = 0; i < 10; i++) {
const building = new THREE.Group();
const height = Math.random() * 5 + 2;
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 - height;
building.add(segment);
}
building.position.set(Math.random() * 40 - 20, height / 2, -50 - Math.random() * 50);
building.spawnTimer = 0;
buildings.push(building);
scene.add(building);
}
}
function spawnEnemyFormations() {
const enemyGeometry = new THREE.BoxGeometry(0.8, 0.8, 0.8);
const enemyMaterial = new THREE.MeshPhongMaterial({ color: 0xff0000, shininess: 50 });
const formations = [
{ pattern: [[-2, 5], [0, 5], [2, 5], [-1, 6], [1, 6]], center: new THREE.Vector3(0, 5.5, -30) },
{ pattern: [[-3, 4], [-1, 4], [1, 4], [3, 4], [0, 5]], center: new THREE.Vector3(10, 4.5, -40) },
{ pattern: [[-2, 6], [0, 6], [2, 6], [-1, 7], [1, 7]], center: new THREE.Vector3(-10, 6.5, -35) }
];
formations.forEach(formation => {
formation.pattern.forEach(pos => {
const enemy = new THREE.Mesh(enemyGeometry, enemyMaterial);
enemy.position.set(formation.center.x + pos[0], formation.center.y + pos[1] - formation.center.y, formation.center.z);
enemy.velocity = new THREE.Vector3();
enemy.formationCenter = formation.center;
enemy.shootTimer = Math.random() * 5;
enemies.push(enemy);
scene.add(enemy);
});
});
}
function spawnEnemyFromPosition(position) {
const enemyGeometry = new THREE.BoxGeometry(0.8, 0.8, 0.8);
const enemyMaterial = new THREE.MeshPhongMaterial({ color: 0xff0000, shininess: 50 });
const enemy = new THREE.Mesh(enemyGeometry, enemyMaterial);
enemy.position.copy(position);
enemy.velocity = new THREE.Vector3(Math.random() - 0.5, Math.random() - 0.5, 1).normalize();
enemy.shootTimer = Math.random() * 5;
enemies.push(enemy);
scene.add(enemy);
}
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) {
if (exploding) return;
const speed = 10;
if (moveLeft && player.position.x > -20) player.position.x -= speed * delta;
if (moveRight && player.position.x < 20) player.position.x += speed * delta;
if (moveUp && player.position.y < 10) player.position.y += speed * delta;
if (moveDown && player.position.y > -5) 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.MeshPhongMaterial({ color: 0xffff00, shininess: 100 });
const bullet = new THREE.Mesh(bulletGeometry, bulletMaterial);
bullet.position.copy(player.position);
bullet.position.z -= 1;
bullet.velocity = new THREE.Vector3(0, 0, -1);
bullet.bounces = 0;
bullet.timer = 5;
bullet.isPlayerBullet = true;
bullets.push(bullet);
scene.add(bullet);
}
function shootEnemyBullet(enemy) {
const bulletGeometry = new THREE.SphereGeometry(0.2, 8, 8);
const bulletMaterial = new THREE.MeshPhongMaterial({ color: 0xff00ff, shininess: 100 });
const bullet = new THREE.Mesh(bulletGeometry, bulletMaterial);
bullet.position.copy(enemy.position);
bullet.velocity = new THREE.Vector3(0, 0, 1);
bullet.bounces = 0;
bullet.timer = 5;
bullet.isPlayerBullet = false;
bullets.push(bullet);
scene.add(bullet);
}
function updateBullets(delta) {
const bulletSpeed = 20;
for (let i = bullets.length - 1; i >= 0; i--) {
bullets[i].position.add(bullets[i].velocity.clone().multiplyScalar(bulletSpeed * delta));
bullets[i].timer -= delta;
if (bullets[i].position.z < -100 || bullets[i].position.z > 10 || bullets[i].timer <= 0) {
scene.remove(bullets[i]);
bullets.splice(i, 1);
continue;
}
if (bullets[i].position.x < -20 || bullets[i].position.x > 20) {
bullets[i].velocity.x *= -1;
bullets[i].bounces++;
}
if (bullets[i].position.y < -5 || bullets[i].position.y > 10) {
bullets[i].velocity.y *= -1;
bullets[i].bounces++;
}
if (bullets[i].bounces > 3) {
scene.remove(bullets[i]);
bullets.splice(i, 1);
continue;
}
for (let j = buildings.length - 1; j >= 0; j--) {
if (buildings[j].children.some(child => child.visible && bullets[i].position.distanceTo(buildings[j].position) < 3)) {
bullets[i].velocity.z *= -1;
bullets[i].bounces++;
break;
}
}
checkBulletCollisions(bullets[i], i);
}
}
function checkBulletCollisions(bullet, bulletIndex) {
for (let i = enemies.length - 1; i >= 0; i--) {
if (bullet.position.distanceTo(enemies[i].position) < 1 && bullet.isPlayerBullet) {
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();
return;
}
}
if (!bullet.isPlayerBullet && bullet.position.distanceTo(player.position) < 1 && !exploding) {
explodePlayer();
scene.remove(bullet);
bullets.splice(bulletIndex, 1);
return;
}
for (let j = bullets.length - 1; j >= 0; j--) {
if (j !== bulletIndex && bullet.position.distanceTo(bullets[j].position) < 0.4) {
scene.remove(bullet);
scene.remove(bullets[j]);
bullets.splice(Math.max(bulletIndex, j), 1);
bullets.splice(Math.min(bulletIndex, j), 1);
break;
}
}
}
function updateFlockingEnemies(delta) {
const speed = 3;
enemies.forEach(enemy => {
if (enemy.formationCenter) {
const centerDir = enemy.formationCenter.clone().sub(enemy.position).normalize();
enemy.velocity.lerp(centerDir, delta * 0.5);
enemy.position.add(enemy.velocity.clone().multiplyScalar(delta * speed));
} else {
enemy.position.add(enemy.velocity.clone().multiplyScalar(delta * speed));
if (enemy.position.z > 10) {
scene.remove(enemy);
enemies.splice(enemies.indexOf(enemy), 1);
return;
}
}
enemy.shootTimer -= delta;
if (enemy.shootTimer <= 0) {
shootEnemyBullet(enemy);
enemy.shootTimer = Math.random() * 5 + 2;
}
});
if (enemies.length < 10) spawnEnemyFormations();
}
function updateBuildings(delta) {
const buildingSpeed = 5;
for (let i = buildings.length - 1; i >= 0; i--) {
const building = buildings[i];
building.position.z += buildingSpeed * delta;
if (building.position.z > 20) {
building.position.z = -50 - Math.random() * 50;
building.position.x = Math.random() * 40 - 20;
building.children.forEach(child => child.visible = true);
}
const distToPlayer = building.position.distanceTo(player.position);
if (distToPlayer < 10 && building.spawnTimer <= 0) {
spawnEnemyFromBuilding(building);
building.spawnTimer = 2;
}
building.spawnTimer -= delta;
if (!exploding && distToPlayer < 3 && building.children.some(child => child.visible)) {
explodePlayer();
destroyBuilding(building, i);
}
}
}
function spawnEnemyFromBuilding(building) {
const topPos = building.position.clone();
topPos.y += building.children.length * 2;
spawnEnemyFromPosition(topPos);
}
function explodePlayer() {
exploding = true;
lives--;
updateUI();
const particleGeometry = new THREE.SphereGeometry(0.1, 8, 8);
const particleMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000 });
for (let i = 0; i < 30; i++) {
const particle = new THREE.Mesh(particleGeometry, particleMaterial);
particle.position.copy(player.position);
particle.velocity = new THREE.Vector3(Math.random() - 0.5, Math.random() - 0.5, Math.random() - 0.5).multiplyScalar(5);
scene.add(particle);
setTimeout(() => scene.remove(particle), 1000);
}
player.visible = false;
setTimeout(() => {
player.visible = true;
exploding = false;
player.position.set(0, 0, 0);
if (lives === 1) alert("Last life remaining!");
if (lives <= 0) {
alert("Game Over! Final Score: " + score);
saveScore();
lives = 5;
score = 0;
multiplier = 1;
buildingStreak = 0;
updateUI();
}
}, 1000);
}
function destroyBuilding(building, index) {
const explosionPos = building.position.clone();
const particleGeometry = new THREE.SphereGeometry(0.2, 8, 8);
const particleMaterial = new THREE.MeshBasicMaterial({ color: 0xff8800 });
for (let i = 0; i < 20; i++) {
const particle = new THREE.Mesh(particleGeometry, particleMaterial);
particle.position.copy(explosionPos);
particle.velocity = new THREE.Vector3(Math.random() - 0.5, Math.random() - 0.5, Math.random() - 0.5).multiplyScalar(3);
scene.add(particle);
setTimeout(() => scene.remove(particle), 1000);
}
building.children.forEach((segment, idx) => {
setTimeout(() => segment.visible = false, idx * 100);
});
buildingStreak++;
const bonus = 50 * buildingStreak;
score += bonus;
showBonus(explosionPos, bonus);
if (Math.random() < 0.5) {
for (let i = 0; i < 2; i++) {
setTimeout(() => spawnEnemyFromPosition(explosionPos), Math.random() * 500);
}
}
}
function showBonus(position, points) {
const bonusDiv = document.createElement('div');
bonusDiv.className = 'bonus';
bonusDiv.innerText = `+${points}`;
bonusDiv.style.left = `${(position.x + 20) * window.innerWidth / 40}px`;
bonusDiv.style.top = `${(10 - position.y) * window.innerHeight / 15}px`;
document.body.appendChild(bonusDiv);
setTimeout(() => document.body.removeChild(bonusDiv), 1000);
}
function updateUI() {
document.getElementById('score').innerText = score;
document.getElementById('multiplier').innerText = multiplier.toFixed(1);
document.getElementById('timer').innerText = Math.floor(gameTime);
document.getElementById('livesCount').innerText = lives;
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 with sidebar for title and instructions
with st.sidebar:
st.title("Galaxxon - Enhanced Arcade Game")
st.write("**Controls:**")
st.write("- Use WASD or Arrow Keys to move")
st.write("- Spacebar to shoot")
st.write("**Objective:**")
st.write("- Crash into buildings for bonus points")
st.write("- Destroy enemies and avoid their bullets")
# Render the HTML game full-screen
html(game_html, height=1000, width=2000, scrolling=False)
st.write("Note: The game runs in your browser. Ensure you have an internet connection for Three.js to load.")