|
import streamlit as st |
|
from streamlit.components.v1 import html |
|
|
|
|
|
p5js_code = """ |
|
let gridSize = 40; |
|
let grid = []; |
|
let buildings = []; |
|
let train = { x: 0, y: 0, dir: 1 }; |
|
let monsterMode = false; |
|
let currentMonster = 'Godzilla'; |
|
let monsterX, monsterY; |
|
let angle = PI / 6; // 3D perspective angle |
|
|
|
function setup() { |
|
createCanvas(800, 600); |
|
for (let i = 0; i < width / gridSize; i++) { |
|
grid[i] = []; |
|
for (let j = 0; j < height / gridSize; j++) { |
|
grid[i][j] = 'empty'; |
|
} |
|
} |
|
for (let i = 0; i < width / gridSize; i++) { |
|
grid[i][5] = 'track'; |
|
} |
|
train.x = 0; |
|
train.y = 5 * gridSize; |
|
monsterX = width / 2; |
|
monsterY = height / 2; |
|
} |
|
|
|
function draw() { |
|
background(220); |
|
drawGrid(); |
|
drawBuildings(); |
|
drawTrain(); |
|
if (monsterMode) drawMonster(); |
|
} |
|
|
|
function drawGrid() { |
|
for (let i = 0; i < grid.length; i++) { |
|
for (let j = 0; j < grid[i].length; j++) { |
|
let x = i * gridSize; |
|
let y = j * gridSize; |
|
let z = j * gridSize * sin(angle); // 3D depth effect |
|
fill(grid[i][j] === 'track' ? 100 : 150, 200, 150); |
|
stroke(0); |
|
beginShape(); |
|
vertex(x, y - z); |
|
vertex(x + gridSize, y - z); |
|
vertex(x + gridSize * cos(angle), y + gridSize * sin(angle) - z); |
|
vertex(x + gridSize * (1 - cos(angle)), y + gridSize * sin(angle) - z); |
|
endShape(CLOSE); |
|
} |
|
} |
|
} |
|
|
|
function drawBuildings() { |
|
buildings.forEach(b => { |
|
let x = b.x * gridSize; |
|
let y = b.y * gridSize; |
|
let z = b.y * gridSize * sin(angle); |
|
fill(b.color); |
|
noStroke(); |
|
beginShape(); |
|
if (b.type === 'Residential') { // House with chimney |
|
vertex(x + 10, y + 30 - z); vertex(x + 20, y + 10 - z); vertex(x + 30, y + 30 - z); |
|
vertex(x + 25, y + 30 - z); vertex(x + 25, y + 35 - z); vertex(x + 15, y + 35 - z); |
|
vertex(x + 15, y + 30 - z); vertex(x + 17, y + 25 - z); vertex(x + 23, y + 25 - z); |
|
} else if (b.type === 'Commercial') { // Skyscraper with windows |
|
vertex(x + 10, y + 35 - z); vertex(x + 15, y + 15 - z); vertex(x + 25, y + 15 - z); |
|
vertex(x + 30, y + 35 - z); vertex(x + 27, y + 35 - z); vertex(x + 27, y + 20 - z); |
|
vertex(x + 13, y + 20 - z); vertex(x + 13, y + 35 - z); |
|
} else if (b.type === 'Industrial') { // Factory with smokestack |
|
vertex(x + 5, y + 35 - z); vertex(x + 15, y + 20 - z); vertex(x + 25, y + 20 - z); |
|
vertex(x + 35, y + 35 - z); vertex(x + 30, y + 35 - z); vertex(x + 30, y + 15 - z); |
|
vertex(x + 33, y + 15 - z); vertex(x + 33, y + 35 - z); |
|
} else if (b.type === 'School') { // School with flagpole |
|
vertex(x + 5, y + 35 - z); vertex(x + 10, y + 20 - z); vertex(x + 30, y + 20 - z); |
|
vertex(x + 35, y + 35 - z); vertex(x + 25, y + 35 - z); vertex(x + 25, y + 10 - z); |
|
vertex(x + 27, y + 10 - z); vertex(x + 27, y + 35 - z); |
|
} else if (b.type === 'PowerPlant') { // Power plant with turbines |
|
vertex(x + 5, y + 35 - z); vertex(x + 15, y + 15 - z); vertex(x + 25, y + 15 - z); |
|
vertex(x + 35, y + 35 - z); vertex(x + 30, y + 35 - z); vertex(x + 30, y + 25 - z); |
|
vertex(x + 20, y + 25 - z); vertex(x + 20, y + 35 - z); |
|
} |
|
endShape(CLOSE); |
|
}); |
|
} |
|
|
|
function drawTrain() { |
|
let tx = train.x; |
|
let ty = train.y; |
|
let tz = train.y * sin(angle); |
|
fill(150, 50, 50); |
|
noStroke(); |
|
beginShape(); |
|
vertex(tx + 10, ty + 10 - tz); vertex(tx + 30, ty + 10 - tz); vertex(tx + 35, ty + 20 - tz); |
|
vertex(tx + 30, ty + 30 - tz); vertex(tx + 10, ty + 30 - tz); vertex(tx + 5, ty + 20 - tz); |
|
vertex(tx + 15, ty + 20 - tz); vertex(tx + 15, ty + 15 - tz); vertex(tx + 25, ty + 15 - tz); |
|
vertex(tx + 25, ty + 20 - tz); |
|
endShape(CLOSE); |
|
train.x += train.dir * 2; |
|
if (train.x > width || train.x < 0) train.dir *= -1; |
|
} |
|
|
|
function drawMonster() { |
|
let mz = monsterY * sin(angle); |
|
fill(255, 0, 0); |
|
noStroke(); |
|
beginShape(); |
|
if (currentMonster === 'Godzilla') { // Detailed Godzilla with spines |
|
vertex(monsterX, monsterY + 40 - mz); vertex(monsterX + 10, monsterY + 20 - mz); |
|
vertex(monsterX + 20, monsterY - mz); vertex(monsterX + 30, monsterY + 20 - mz); |
|
vertex(monsterX + 40, monsterY + 40 - mz); vertex(monsterX + 35, monsterY + 50 - mz); |
|
vertex(monsterX + 25, monsterY + 60 - mz); vertex(monsterX + 15, monsterY + 50 - mz); |
|
vertex(monsterX + 20, monsterY + 40 - mz); vertex(monsterX + 25, monsterY + 30 - mz); |
|
} else if (currentMonster === 'GiantRobot') { // Detailed Robot with joints |
|
vertex(monsterX, monsterY + 40 - mz); vertex(monsterX + 10, monsterY + 20 - mz); |
|
vertex(monsterX + 15, monsterY - mz); vertex(monsterX + 25, monsterY - mz); |
|
vertex(monsterX + 30, monsterY + 20 - mz); vertex(monsterX + 40, monsterY + 40 - mz); |
|
vertex(monsterX + 35, monsterY + 50 - mz); vertex(monsterX + 20, monsterY + 60 - mz); |
|
vertex(monsterX + 5, monsterY + 50 - mz); vertex(monsterX + 15, monsterY + 30 - mz); |
|
} |
|
endShape(CLOSE); |
|
monsterX += random(-5, 5); |
|
monsterY += random(-5, 5); |
|
monsterX = constrain(monsterX, 0, width); |
|
monsterY = constrain(monsterY, 0, height); |
|
} |
|
|
|
function mousePressed() { |
|
let i = floor(mouseX / gridSize); |
|
let j = floor(mouseY / gridSize); |
|
if (grid[i][j] === 'empty' && !monsterMode) { |
|
let types = ['Residential', 'Commercial', 'Industrial', 'School', 'PowerPlant']; |
|
let colors = [[0, 200, 0], [0, 0, 200], [200, 200, 0], [200, 0, 200], [100, 100, 100]]; |
|
let idx = floor(random(5)); |
|
buildings.push({ x: i, y: j, type: types[idx], color: colors[idx] }); |
|
grid[i][j] = 'building'; |
|
} |
|
} |
|
|
|
function keyPressed() { |
|
if (key === 'm' || key === 'M') monsterMode = !monsterMode; |
|
if (key === 'g' || key === 'G') currentMonster = 'Godzilla'; |
|
if (key === 'r' || key === 'R') currentMonster = 'GiantRobot'; |
|
if (key === 't' || key === 'T') train.dir *= -1; |
|
} |
|
|
|
window.toggleMonsterMode = function() { monsterMode = !monsterMode; }; |
|
window.setMonster = function(monster) { currentMonster = monster; }; |
|
window.reverseTrain = function() { train.dir *= -1; }; |
|
""" |
|
|
|
|
|
html_content = f""" |
|
<!DOCTYPE html> |
|
<html> |
|
<head> |
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.2/p5.min.js"></script> |
|
<style> |
|
body {{ font-family: Arial, sans-serif; margin: 0; padding: 20px; background: #f0f0f0; }} |
|
#controls {{ margin-bottom: 20px; }} |
|
button {{ padding: 10px 20px; margin: 5px; background: #4CAF50; color: white; border: none; border-radius: 5px; cursor: pointer; }} |
|
button:hover {{ background: #45a049; }} |
|
select {{ padding: 10px; margin: 5px; border-radius: 5px; }} |
|
</style> |
|
</head> |
|
<body> |
|
<div id="controls"> |
|
<button onclick="toggleMonster()">Toggle Monster Mode (M)</button> |
|
<select onchange="setMonster(this.value)"> |
|
<option value="Godzilla">Godzilla (G)</option> |
|
<option value="GiantRobot">Giant Robot (R)</option> |
|
</select> |
|
<button onclick="reverseTrain()">Reverse Train (T)</button> |
|
</div> |
|
<div id="sketch-holder"></div> |
|
|
|
<script> |
|
{p5js_code} |
|
|
|
function toggleMonster() {{ window.toggleMonsterMode(); }} |
|
function setMonster(monster) {{ window.setMonster(monster); }} |
|
function reverseTrain() {{ window.reverseTrain(); }} |
|
</script> |
|
</body> |
|
</html> |
|
""" |
|
|
|
|
|
st.title("SimCity 2000 with Monster Mode") |
|
st.write("Click to place buildings. Use keys: M (monster), G (Godzilla), R (Robot), T (train reverse).") |
|
|
|
|
|
with st.sidebar: |
|
st.header("Controls") |
|
|
|
|
|
st.subheader("π‘ Buildings") |
|
st.write("Click on the grid to randomly place buildings (Residential, Commercial, Industrial, School, Power Plant).") |
|
|
|
|
|
st.subheader("πΎ Monsters") |
|
if st.button("Toggle Monster Mode (M)", key="monster_toggle"): |
|
st.markdown('<script>toggleMonster()</script>', unsafe_allow_html=True) |
|
monster_choice = st.selectbox("Choose Monster", ["Godzilla (G)", "Giant Robot (R)"], index=0) |
|
if monster_choice.startswith("Godzilla"): |
|
st.markdown('<script>setMonster("Godzilla")</script>', unsafe_allow_html=True) |
|
else: |
|
st.markdown('<script>setMonster("GiantRobot")</script>', unsafe_allow_html=True) |
|
|
|
|
|
st.subheader("π Train") |
|
if st.button("Reverse Train (T)", key="train_reverse"): |
|
st.markdown('<script>reverseTrain()</script>', unsafe_allow_html=True) |
|
|
|
html(html_content, height=700) |