Spaces:
Running
Running
<html> | |
<head> | |
<meta charset="utf-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1"> | |
<title>Factory Game</title> | |
<script type="module" crossorigin src="https://cdn.jsdelivr.net/npm/@gradio/lite/dist/lite.js"></script> | |
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@gradio/lite/dist/lite.css" /> | |
<style> | |
html, body { | |
margin: 0; | |
padding: 0; | |
height: 100%; | |
} | |
</style> | |
</head> | |
<body> | |
<gradio-lite> | |
<gradio-file name="app.py" entrypoint> | |
import gradio as gr | |
import json | |
from dataclasses import dataclass | |
import numpy as np | |
class GameState: | |
def __init__(self): | |
self.resources = { | |
"energy": 50, | |
"minerals": 50, | |
"circuits": 0 | |
} | |
self.grid = [[None for _ in range(8)] for _ in range(8)] | |
self.buildings = { | |
"solarPanel": { | |
"cost": {"minerals": 10}, | |
"produces": "energy", | |
"color": "#FEF08A" | |
}, | |
"mineralExtractor": { | |
"cost": {"energy": 10}, | |
"produces": "minerals", | |
"color": "#D1D5DB" | |
}, | |
"circuitFactory": { | |
"cost": {"energy": 15, "minerals": 15}, | |
"produces": "circuits", | |
"color": "#BBF7D0" | |
} | |
} | |
game_state = GameState() | |
def update_game(action_type, data): | |
if action_type == "tick": | |
# Update resources based on buildings | |
for row in range(8): | |
for col in range(8): | |
building_type = game_state.grid[row][col] | |
if building_type: | |
produces = game_state.buildings[building_type]["produces"] | |
game_state.resources[produces] += 1 | |
elif action_type == "build": | |
row, col, building_type = data | |
if game_state.grid[row][col] is not None: | |
return {"error": "Cell already occupied"} | |
building = game_state.buildings[building_type] | |
# Check if can afford | |
for resource, cost in building["cost"].items(): | |
if game_state.resources[resource] < cost: | |
return {"error": "Not enough resources"} | |
# Pay costs | |
for resource, cost in building["cost"].items(): | |
game_state.resources[resource] -= cost | |
# Place building | |
game_state.grid[row][col] = building_type | |
return { | |
"resources": game_state.resources, | |
"grid": game_state.grid, | |
"buildings": game_state.buildings | |
} | |
def create_ui(): | |
html = """ | |
<div style="max-width: 800px; margin: 0 auto; padding: 1rem;"> | |
<div id="resources" style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 1rem; margin-bottom: 1rem;"></div> | |
<div id="buildings" style="display: flex; gap: 1rem; margin-bottom: 1rem;"></div> | |
<div id="message" style="text-align: center; color: #2563EB; font-weight: 500; margin: 0.5rem 0;"></div> | |
<div id="grid" style="display: grid; grid-template-columns: repeat(8, 1fr); gap: 0.25rem; background: #F3F4F6; padding: 0.5rem; border-radius: 0.5rem;"></div> | |
</div> | |
<script> | |
let selectedBuilding = null; | |
function updateUI(gameState) { | |
// Update resources | |
document.getElementById('resources').innerHTML = Object.entries(gameState.resources) | |
.map(([resource, amount]) => ` | |
<div style="padding: 0.75rem; border-radius: 0.5rem; background: ${getResourceColor(resource)}"> | |
${resource}: ${amount} | |
</div> | |
`).join(''); | |
// Update buildings | |
document.getElementById('buildings').innerHTML = Object.entries(gameState.buildings) | |
.map(([type, data]) => ` | |
<button onclick="selectBuilding('${type}')" | |
style="padding: 1rem; border-radius: 0.5rem; cursor: pointer; background: ${data.color}; | |
${type === selectedBuilding ? 'outline: 2px solid #3B82F6;' : ''}"> | |
${type}<br> | |
Cost: ${Object.entries(data.cost).map(([r, c]) => `${c} ${r}`).join(', ')} | |
</button> | |
`).join(''); | |
// Update grid | |
document.getElementById('grid').innerHTML = gameState.grid.map((row, i) => | |
row.map((cell, j) => ` | |
<button onclick="placeBuilding(${i}, ${j})" | |
style="aspect-ratio: 1; border-radius: 0.5rem; border: none; cursor: pointer; | |
background: ${cell ? gameState.buildings[cell].color : 'white'}"> | |
</button> | |
`).join('') | |
).join(''); | |
} | |
function getResourceColor(resource) { | |
switch(resource) { | |
case 'energy': return '#FEF9C3'; | |
case 'minerals': return '#F3F4F6'; | |
case 'circuits': return '#DCFCE7'; | |
default: return 'white'; | |
} | |
} | |
function selectBuilding(type) { | |
selectedBuilding = type; | |
syncGameState("tick", null); | |
} | |
function placeBuilding(row, col) { | |
if (!selectedBuilding) { | |
document.getElementById('message').textContent = 'Select a building first!'; | |
return; | |
} | |
syncGameState("build", [row, col, selectedBuilding]); | |
} | |
// Resource production tick | |
setInterval(() => syncGameState("tick", null), 1000); | |
// Initial state | |
syncGameState("tick", null); | |
</script> | |
""" | |
return gr.HTML(html) | |
def handle_action(action_type, data): | |
return json.dumps(update_game(action_type, data)) | |
with gr.Blocks() as demo: | |
ui = create_ui() | |
action = gr.State("tick") | |
data = gr.State(None) | |
demo.load(lambda: handle_action("tick", None), None, _js="(o) => { updateUI(JSON.parse(o)) }") | |
demo.load(None, None, _js=""" | |
function(arg) { | |
window.syncGameState = function(actionType, actionData) { | |
gradioApp().querySelector("gradio-app").props.action_type = actionType; | |
gradioApp().querySelector("gradio-app").props.action_data = actionData; | |
updateUI(JSON.parse(handle_action(actionType, actionData))); | |
} | |
} | |
""") | |
demo.launch() | |
</gradio-file> | |
<gradio-requirements> | |
# No additional requirements needed | |
</gradio-requirements> | |
</gradio-lite> | |
</body> | |
</html> |