awacke1's picture
Update app.py
948c597 verified
raw
history blame
14.2 kB
import streamlit as st
from streamlit.components.v1 import html
# Set Streamlit to wide mode
st.set_page_config(layout="wide", page_title="Galaxian Snake 3D")
# Define the enhanced HTML content with Three.js
game_html = """
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Galaxian Snake 3D</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; }
.message { position: absolute; color: cyan; font-size: 16px; z-index: 1; }
</style>
</head>
<body>
<div id="ui">Score: <span id="score">0</span> | Time: <span id="timer">0</span>s</div>
<div id="lives">Lives: <span id="livesCount">3</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, snake, foodItems = [], lSysCreatures = [], messages = [];
let clock = new THREE.Clock();
let moveDir = new THREE.Vector3(1, 0, 0), moveSpeed = 0.2;
let score = 0, gameTime = 0, lives = 3;
let highScores = JSON.parse(localStorage.getItem('highScores')) || [];
let gameOver = 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, 20, 30);
camera.lookAt(0, 0, 0);
// Initialize 3D snake
snake = [];
const snakeMaterial = new THREE.MeshPhongMaterial({ color: 0x00ff00, shininess: 100 });
for (let i = 0; i < 3; i++) {
const segment = new THREE.Mesh(new THREE.SphereGeometry(0.5, 16, 16), snakeMaterial);
segment.position.set(-i * 1.2, 0, 0);
snake.push(segment);
scene.add(segment);
}
spawnFood();
spawnLSysCreatures();
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);
// Starfield
const starsGeometry = new THREE.BufferGeometry();
const starsMaterial = new THREE.PointsMaterial({ color: 0xffffff, size: 0.1 });
const starPositions = new Float32Array(1000 * 3);
for (let i = 0; i < 1000; i++) {
starPositions[i * 3] = (Math.random() - 0.5) * 100;
starPositions[i * 3 + 1] = (Math.random() - 0.5) * 100;
starPositions[i * 3 + 2] = (Math.random() - 0.5) * 100;
}
starsGeometry.setAttribute('position', new THREE.BufferAttribute(starPositions, 3));
scene.add(new THREE.Points(starsGeometry, starsMaterial));
window.addEventListener('keydown', onKeyDown);
window.addEventListener('resize', onWindowResize);
updateHighScoresUI();
animate();
}
function spawnFood() {
const foodGeometry = new THREE.DodecahedronGeometry(0.5);
const foodMaterial = new THREE.MeshPhongMaterial({ color: 0xff00ff });
for (let i = 0; i < 5; i++) {
const food = new THREE.Mesh(foodGeometry, foodMaterial);
food.position.set((Math.random() - 0.5) * 40, 0, (Math.random() - 0.5) * 40);
foodItems.push(food);
scene.add(food);
}
}
function spawnLSysCreatures() {
const lSys = {
axiom: "F",
rules: { "F": "F[+F]F[-F]F" },
angle: 25,
length: 2,
iterations: 3
};
const material = new THREE.MeshPhongMaterial({ color: 0x0000ff });
for (let i = 0; i < 3; i++) {
let turtleString = lSys.axiom;
for (let j = 0; j < lSys.iterations; j++) {
turtleString = turtleString.split('').map(c => lSys.rules[c] || c).join('');
}
const creature = new THREE.Group();
let stack = [], pos = new THREE.Vector3((Math.random() - 0.5) * 40, 0, (Math.random() - 0.5) * 40);
let dir = new THREE.Vector3(0, lSys.length, 0);
for (let char of turtleString) {
if (char === 'F') {
const segment = new THREE.Mesh(new THREE.CylinderGeometry(0.1, 0.1, lSys.length, 8), material);
segment.position.copy(pos).add(dir.clone().multiplyScalar(0.5));
segment.quaternion.setFromUnitVectors(new THREE.Vector3(0, 1, 0), dir.clone().normalize());
creature.add(segment);
pos.add(dir);
} else if (char === '+') {
dir.applyAxisAngle(new THREE.Vector3(0, 0, 1), lSys.angle * Math.PI / 180);
} else if (char === '-') {
dir.applyAxisAngle(new THREE.Vector3(0, 0, 1), -lSys.angle * Math.PI / 180);
} else if (char === '[') {
stack.push({ pos: pos.clone(), dir: dir.clone() });
} else if (char === ']') {
const state = stack.pop();
pos = state.pos;
dir = state.dir;
}
}
creature.position.set(pos.x, 0, pos.z);
lSysCreatures.push(creature);
scene.add(creature);
sendQuineMessage(creature);
}
}
function sendQuineMessage(sender) {
const quine = "function q(){alert('I am alive! '+q.toString())}q()";
const messageDiv = document.createElement('div');
messageDiv.className = 'message';
messageDiv.innerText = 'Quine Msg';
messageDiv.style.left = `${(sender.position.x + 20) * window.innerWidth / 40}px`;
messageDiv.style.top = `${(20 - sender.position.z) * window.innerHeight / 40}px`;
document.body.appendChild(messageDiv);
messages.push({ div: messageDiv, ttl: 3 });
setTimeout(() => lSysCreatures.forEach(c => c !== sender && listenToMessage(c)), 1000);
}
function listenToMessage(creature) {
const response = new THREE.Mesh(
new THREE.SphereGeometry(0.3, 16, 16),
new THREE.MeshBasicMaterial({ color: 0xff0000 })
);
response.position.copy(creature.position).add(new THREE.Vector3(0, 5, 0));
scene.add(response);
setTimeout(() => scene.remove(response), 2000);
}
function onKeyDown(event) {
if (gameOver && event.code === 'KeyR') {
resetGame();
return;
}
if (gameOver) return;
switch (event.code) {
case 'ArrowLeft': case 'KeyA': moveDir.set(-1, 0, 0); break;
case 'ArrowRight': case 'KeyD': moveDir.set(1, 0, 0); break;
case 'ArrowUp': case 'KeyW': moveDir.set(0, 0, -1); break;
case 'ArrowDown': case 'KeyS': moveDir.set(0, 0, 1); break;
}
}
function updateSnake(delta) {
if (gameOver) return;
const head = snake[0];
const newHead = new THREE.Mesh(head.geometry, head.material);
newHead.position.copy(head.position).add(moveDir.clone().multiplyScalar(moveSpeed));
// Boundary check
if (Math.abs(newHead.position.x) > 20 || Math.abs(newHead.position.z) > 20) {
loseLife();
return;
}
// Self-collision check
for (let i = 1; i < snake.length; i++) {
if (newHead.position.distanceTo(snake[i].position) < 0.9) {
loseLife();
return;
}
}
snake.unshift(newHead);
scene.add(newHead);
// Food collision
for (let i = foodItems.length - 1; i >= 0; i--) {
if (newHead.position.distanceTo(foodItems[i].position) < 1) {
scene.remove(foodItems[i]);
foodItems.splice(i, 1);
score += 10;
spawnFood(); // Add new food
spawnLSysCreatures(); // Add new creature
break;
} else {
snake.pop(); // Remove tail if no food eaten
scene.remove(snake[snake.length - 1]);
}
}
// Creature collision
for (let creature of lSysCreatures) {
if (newHead.position.distanceTo(creature.position) < 2) {
loseLife();
return;
}
}
}
function loseLife() {
lives--;
updateUI();
if (lives <= 0) {
gameOver = true;
alert("Game Over! Final Score: " + score);
saveScore();
} else {
explodeSnake();
}
}
function explodeSnake() {
const particleGeometry = new THREE.SphereGeometry(0.1, 8, 8);
const particleMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000 });
for (let i = 0; i < 20; i++) {
const particle = new THREE.Mesh(particleGeometry, particleMaterial);
particle.position.copy(snake[0].position);
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);
}
resetSnake();
}
function resetSnake() {
snake.forEach(seg => scene.remove(seg));
snake = [];
const snakeMaterial = new THREE.MeshPhongMaterial({ color: 0x00ff00, shininess: 100 });
for (let i = 0; i < 3; i++) {
const segment = new THREE.Mesh(new THREE.SphereGeometry(0.5, 16, 16), snakeMaterial);
segment.position.set(-i * 1.2, 0, 0);
snake.push(segment);
scene.add(segment);
}
moveDir.set(1, 0, 0);
}
function resetGame() {
resetSnake();
foodItems.forEach(f => scene.remove(f));
lSysCreatures.forEach(c => scene.remove(c));
foodItems = [];
lSysCreatures = [];
spawnFood();
spawnLSysCreatures();
score = 0;
lives = 3;
gameOver = false;
updateUI();
}
function updateUI() {
document.getElementById('score').innerText = score;
document.getElementById('timer').innerText = Math.floor(gameTime);
document.getElementById('livesCount').innerText = lives;
}
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;
updateSnake(delta);
messages = messages.filter(m => {
m.ttl -= delta;
if (m.ttl <= 0) document.body.removeChild(m.div);
return m.ttl > 0;
});
renderer.render(scene, camera);
}
init();
</script>
</body>
</html>
"""
# Streamlit app with sidebar
with st.sidebar:
st.title("Galaxian Snake 3D")
st.write("**Controls:**")
st.write("- WASD or Arrow Keys to move")
st.write("- R to reset after game over")
st.write("**Objective:**")
st.write("- Eat alien food (pink dodecahedrons) to grow")
st.write("- Avoid L-system creatures (blue structures)")
st.write("- Watch creatures exchange quine messages")
# Render the HTML game
html(game_html, height=800, width=2000, scrolling=False)
st.write("Note: Requires internet for Three.js to load.")