Spaces:
Sleeping
Sleeping
import streamlit as st | |
# Set wide layout | |
st.set_page_config(layout="wide", page_title="Galaxian 3D Evolution", initial_sidebar_state="expanded") | |
# Sidebar instructions | |
st.sidebar.markdown("## Galaxian 3D Evolution Controls") | |
st.sidebar.markdown(""" | |
- **Controls:** | |
- **WASD:** Move camera (forward, left, back, right) | |
- **QE:** Rotate camera up/down | |
- **Mouse:** Orbit camera (click and drag) | |
- **Arrow Keys:** Direct snake (Up, Down, Left, Right) | |
- **R:** Reset game | |
- **Features:** | |
- 3D snake navigates a space grid. | |
- Eat alien food (👾) to grow and trigger L-system growth. | |
- Creatures exchange quine messages (hover to see). | |
- Avoid walls and self-collision. | |
Enjoy the 3D Galaxian experience! | |
""") | |
# Three.js HTML/JavaScript code | |
html_code = r""" | |
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset="UTF-8"> | |
<title>Galaxian 3D Evolution</title> | |
<style> | |
body { margin: 0; padding: 0; overflow: hidden; background: black; } | |
canvas { display: block; } | |
</style> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r134/three.min.js"></script> | |
<script src="https://cdn.jsdelivr.net/npm/[email protected]/examples/js/controls/OrbitControls.js"></script> | |
</head> | |
<body> | |
<script> | |
let scene, camera, renderer, controls, snake, food, creatures = [], messages = []; | |
const gridSize = 20; | |
const worldSize = 400; | |
function init() { | |
// Scene setup | |
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); | |
// Orbit controls | |
controls = new THREE.OrbitControls(camera, renderer.domElement); | |
controls.enableDamping = true; | |
controls.dampingFactor = 0.05; | |
// Lighting | |
const ambientLight = new THREE.AmbientLight(0x404040); | |
scene.add(ambientLight); | |
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.5); | |
directionalLight.position.set(1, 1, 1); | |
scene.add(directionalLight); | |
// Starfield | |
const starsGeometry = new THREE.BufferGeometry(); | |
const starsMaterial = new THREE.PointsMaterial({ color: 0xffffff, size: 2 }); | |
const starPositions = new Float32Array(1000 * 3); | |
for (let i = 0; i < 1000; i++) { | |
starPositions[i * 3] = (Math.random() - 0.5) * worldSize; | |
starPositions[i * 3 + 1] = (Math.random() - 0.5) * worldSize; | |
starPositions[i * 3 + 2] = (Math.random() - 0.5) * worldSize; | |
} | |
starsGeometry.setAttribute('position', new THREE.BufferAttribute(starPositions, 3)); | |
const stars = new THREE.Points(starsGeometry, starsMaterial); | |
scene.add(stars); | |
// Initialize snake | |
snake = []; | |
const snakeMaterial = new THREE.MeshPhongMaterial({ color: 0x00ff00 }); | |
for (let i = 0; i < 3; i++) { | |
const segment = new THREE.Mesh(new THREE.SphereGeometry(gridSize / 2, 32, 32), snakeMaterial); | |
segment.position.set(-i * gridSize, 0, 0); | |
snake.push(segment); | |
scene.add(segment); | |
} | |
snake.direction = new THREE.Vector3(gridSize, 0, 0); | |
// Food (alien) | |
placeFood(); | |
// L-system creature | |
createLSysCreature(); | |
// Camera position | |
camera.position.set(0, 100, 200); | |
camera.lookAt(0, 0, 0); | |
animate(); | |
} | |
function placeFood() { | |
if (food) scene.remove(food); | |
const foodGeometry = new THREE.DodecahedronGeometry(gridSize / 2); | |
const foodMaterial = new THREE.MeshPhongMaterial({ color: 0xff00ff }); | |
food = new THREE.Mesh(foodGeometry, foodMaterial); | |
food.position.set( | |
(Math.floor(Math.random() * (worldSize / gridSize)) - worldSize / (2 * gridSize)) * gridSize, | |
0, | |
(Math.floor(Math.random() * (worldSize / gridSize)) - worldSize / (2 * gridSize)) * gridSize | |
); | |
scene.add(food); | |
} | |
function createLSysCreature() { | |
const lSys = { | |
axiom: "F", | |
rules: { "F": "F[+F]F[-F]F" }, | |
angle: 25, | |
length: gridSize, | |
iterations: 3 | |
}; | |
let turtleString = lSys.axiom; | |
for (let i = 0; i < lSys.iterations; i++) { | |
turtleString = applyLSysRules(turtleString, lSys.rules); | |
} | |
const creatureMaterial = new THREE.MeshPhongMaterial({ color: 0x0000ff }); | |
const creature = new THREE.Group(); | |
let stack = []; | |
let pos = new THREE.Vector3(worldSize / 4, 0, worldSize / 4); | |
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(2, 2, lSys.length, 16), creatureMaterial); | |
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; | |
} | |
} | |
scene.add(creature); | |
creatures.push(creature); | |
sendQuineMessage(creature); | |
} | |
function applyLSysRules(str, rules) { | |
return str.split("").map(char => rules[char] || char).join(""); | |
} | |
function sendQuineMessage(sender) { | |
const quine = "function q(){console.log('Message from creature: '+q.toString())}q()"; | |
const messageGeometry = new THREE.TextGeometry("Quine Msg", { | |
font: new THREE.FontLoader().parse({}), // Placeholder: requires font file | |
size: 10, | |
height: 2 | |
}); | |
const messageMaterial = new THREE.MeshBasicMaterial({ color: 0xffff00 }); | |
const message = new THREE.Mesh(messageGeometry, messageMaterial); | |
message.position.copy(sender.position).add(new THREE.Vector3(0, 50, 0)); | |
scene.add(message); | |
messages.push({ mesh: message, ttl: 100 }); | |
setTimeout(() => creatures.forEach(c => c !== sender && listenToMessage(c, quine)), 1000); | |
} | |
function listenToMessage(creature, msg) { | |
const response = new THREE.Mesh( | |
new THREE.SphereGeometry(5, 32, 32), | |
new THREE.MeshBasicMaterial({ color: 0xff0000 }) | |
); | |
response.position.copy(creature.position).add(new THREE.Vector3(0, 30, 0)); | |
scene.add(response); | |
setTimeout(() => scene.remove(response), 2000); | |
} | |
function updateSnake() { | |
const head = snake[0]; | |
const newHead = head.clone(); | |
newHead.position.add(snake.direction); | |
if (Math.abs(newHead.position.x) > worldSize / 2 || Math.abs(newHead.position.z) > worldSize / 2) { | |
resetGame(); | |
return; | |
} | |
for (let i = 1; i < snake.length; i++) { | |
if (newHead.position.distanceTo(snake[i].position) < gridSize) { | |
resetGame(); | |
return; | |
} | |
} | |
snake.unshift(newHead); | |
scene.add(newHead); | |
if (newHead.position.distanceTo(food.position) < gridSize) { | |
placeFood(); | |
createLSysCreature(); | |
} else { | |
scene.remove(snake.pop()); | |
} | |
} | |
function resetGame() { | |
snake.forEach(seg => scene.remove(seg)); | |
snake = []; | |
const snakeMaterial = new THREE.MeshPhongMaterial({ color: 0x00ff00 }); | |
for (let i = 0; i < 3; i++) { | |
const segment = new THREE.Mesh(new THREE.SphereGeometry(gridSize / 2, 32, 32), snakeMaterial); | |
segment.position.set(-i * gridSize, 0, 0); | |
snake.push(segment); | |
scene.add(segment); | |
} | |
snake.direction = new THREE.Vector3(gridSize, 0, 0); | |
placeFood(); | |
} | |
let moveCounter = 0; | |
const moveDelay = 10; | |
function animate() { | |
requestAnimationFrame(animate); | |
controls.update(); | |
moveCounter++; | |
if (moveCounter >= moveDelay) { | |
updateSnake(); | |
moveCounter = 0; | |
} | |
messages = messages.filter(m => { | |
m.ttl--; | |
if (m.ttl <= 0) scene.remove(m.mesh); | |
return m.ttl > 0; | |
}); | |
renderer.render(scene, camera); | |
} | |
document.addEventListener("keydown", (e) => { | |
switch (e.key) { | |
case "ArrowUp": snake.direction.set(0, 0, -gridSize); break; | |
case "ArrowDown": snake.direction.set(0, 0, gridSize); break; | |
case "ArrowLeft": snake.direction.set(-gridSize, 0, 0); break; | |
case "ArrowRight": snake.direction.set(gridSize, 0, 0); break; | |
case "r": resetGame(); break; | |
} | |
}); | |
window.addEventListener("resize", () => { | |
camera.aspect = window.innerWidth / window.innerHeight; | |
camera.updateProjectionMatrix(); | |
renderer.setSize(window.innerWidth, window.innerHeight); | |
}); | |
init(); | |
</script> | |
</body> | |
</html> | |
""" | |
st.components.v1.html(html_code, height=700, scrolling=False) |