Spaces:
Sleeping
Sleeping
| # NEURAL ATARI GEN-CORE v1.0 | |
| FROM python:3.10-slim | |
| WORKDIR /app | |
| # 1. System Dependencies | |
| # Fixed: libgl1-mesa-glx -> libgl1 (Debian Bookworm compatibility) | |
| RUN apt-get update && apt-get install -y \ | |
| curl \ | |
| git \ | |
| libgomp1 \ | |
| libgl1 \ | |
| libglib2.0-0 \ | |
| && rm -rf /var/lib/apt/lists/* | |
| # 2. Python Environment | |
| RUN pip install --upgrade pip | |
| RUN pip install --no-cache-dir \ | |
| torch \ | |
| torchvision \ | |
| numpy \ | |
| flask \ | |
| flask-sock \ | |
| diffusers \ | |
| transformers \ | |
| accelerate \ | |
| peft \ | |
| pillow \ | |
| safetensors \ | |
| scipy \ | |
| sentencepiece \ | |
| diskcache | |
| # 3. User Setup (Required for HF Spaces) | |
| RUN useradd -m -u 1000 user | |
| USER user | |
| ENV HOME=/home/user \ | |
| PATH=/home/user/.local/bin:$PATH | |
| # 4. The Monolithic Application (Embedded via HEREDOC) | |
| COPY --chown=user <<'HYPER_EOF' app.py | |
| import sys, os, io, base64, json, warnings, time, threading, random, re | |
| import torch | |
| import numpy as np | |
| from flask import Flask | |
| from flask_sock import Sock | |
| from PIL import Image | |
| from transformers import AutoModelForCausalLM, AutoTokenizer | |
| from diffusers import StableDiffusionPipeline, LCMScheduler | |
| import diskcache | |
| warnings.filterwarnings("ignore") | |
| # --- FRONTEND (React + Tailwind + Retro CSS) --- | |
| HTML = r""" | |
| <html> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Neural Atari 2600</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <script src="https://unpkg.com/react@18/umd/react.production.min.js"></script> | |
| <script src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script> | |
| <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script> | |
| <style> | |
| @import url('https://fonts.googleapis.com/css2?family=VT323&display=swap'); | |
| body { background: #050505; color: #eee; margin: 0; height: 100vh; display: flex; justify-content: center; align-items: center; font-family: 'VT323', monospace; overflow: hidden; } | |
| .crt-container { position: relative; width: 640px; height: 480px; background: #000; border-radius: 20px; box-shadow: 0 0 50px rgba(0,0,0,0.8), inset 0 0 20px rgba(255,255,255,0.1); border: 20px solid #1a1a1a; } | |
| .screen { width: 100%; height: 100%; position: relative; overflow: hidden; border-radius: 4px; } | |
| .scanlines { position: absolute; inset: 0; background: linear-gradient(rgba(18, 16, 16, 0) 50%, rgba(0, 0, 0, 0.25) 50%), linear-gradient(90deg, rgba(255, 0, 0, 0.06), rgba(0, 255, 0, 0.02), rgba(0, 0, 255, 0.06)); background-size: 100% 4px, 6px 100%; pointer-events: none; z-index: 50; } | |
| .glow { position: absolute; inset: 0; background: radial-gradient(circle, rgba(255,255,255,0.1) 0%, rgba(0,0,0,0.4) 100%); pointer-events: none; z-index: 40; mix-blend-mode: screen; } | |
| .flicker { animation: flicker 0.15s infinite; opacity: 0.05; position: absolute; inset: 0; background: white; pointer-events: none; z-index: 45; } | |
| @keyframes flicker { 0% { opacity: 0.02; } 50% { opacity: 0.05; } 100% { opacity: 0.02; } } | |
| canvas { image-rendering: pixelated; width: 100%; height: 100%; } | |
| .wood-panel { position: absolute; bottom: -80px; left: -20px; right: -20px; height: 80px; background: #3e2723; border-top: 4px solid #1a1a1a; display: flex; align-items: center; justify-content: center; gap: 20px; } | |
| </style> | |
| </head> | |
| <body> | |
| <div id="root"></div> | |
| <script type="text/babel"> | |
| const { useState, useEffect, useRef } = React; | |
| function App() { | |
| const [status, setStatus] = useState("BOOT"); // BOOT, INPUT, GENERATING, PLAYING, ERROR | |
| const [logs, setLogs] = useState([]); | |
| const [prompt, setPrompt] = useState(""); | |
| const [gameData, setGameData] = useState(null); | |
| const socketRef = useRef(null); | |
| const canvasRef = useRef(null); | |
| const requestRef = useRef(null); | |
| const gameStateRef = useRef({}); | |
| // --- LOGGING --- | |
| const addLog = (msg) => setLogs(prev => [...prev.slice(-6), msg]); | |
| // --- SOCKET CONNECTION --- | |
| useEffect(() => { | |
| const proto = window.location.protocol === 'https:' ? 'wss' : 'ws'; | |
| const ws = new WebSocket(`${proto}://${window.location.host}/atari`); | |
| socketRef.current = ws; | |
| ws.onopen = () => { | |
| addLog("NEURAL CORE CONNECTED"); | |
| setTimeout(() => setStatus("INPUT"), 1500); | |
| }; | |
| ws.onmessage = (e) => { | |
| const msg = JSON.parse(e.data); | |
| if (msg.type === 'log') addLog(msg.data); | |
| if (msg.type === 'game_ready') { | |
| setGameData(msg.data); | |
| setStatus("PLAYING"); | |
| } | |
| }; | |
| return () => ws.close(); | |
| }, []); | |
| // --- GAME ENGINE --- | |
| useEffect(() => { | |
| if (status === 'PLAYING' && gameData && canvasRef.current) { | |
| try { | |
| // 1. Load Sprite Sheet | |
| const img = new Image(); | |
| img.src = `data:image/png;base64,${gameData.sprites}`; | |
| img.onload = () => { | |
| // 2. Initialize Logic | |
| // We use a safe-ish function constructor to interpret the generated code | |
| // The backend sends a class body as string | |
| const GameClass = new Function('ctx', 'width', 'height', 'sprites', gameData.code + '\nreturn Game;'); | |
| const ctx = canvasRef.current.getContext('2d'); | |
| // Instantiate Game | |
| const gameInstance = new (GameClass())(ctx, 160, 192, img); | |
| if(gameInstance.init) gameInstance.init(); | |
| gameStateRef.current = { | |
| game: gameInstance, | |
| keys: { ArrowUp: false, ArrowDown: false, ArrowLeft: false, ArrowRight: false, " ": false } | |
| }; | |
| // 3. Start Loop | |
| const loop = () => { | |
| if(gameStateRef.current.game.update) { | |
| gameStateRef.current.game.update(gameStateRef.current.keys); | |
| } | |
| if(gameStateRef.current.game.draw) { | |
| // Clear Screen | |
| ctx.fillStyle = gameData.bgColor || "#000"; | |
| ctx.fillRect(0, 0, 160, 192); | |
| gameStateRef.current.game.draw(); | |
| } | |
| requestRef.current = requestAnimationFrame(loop); | |
| }; | |
| loop(); | |
| }; | |
| } catch (err) { | |
| console.error(err); | |
| addLog("ROM CORRUPTION ERROR"); | |
| setStatus("ERROR"); | |
| } | |
| } | |
| return () => cancelAnimationFrame(requestRef.current); | |
| }, [status, gameData]); | |
| // --- CONTROLS --- | |
| useEffect(() => { | |
| const handleDown = (e) => { if(gameStateRef.current.keys) gameStateRef.current.keys[e.key] = true; }; | |
| const handleUp = (e) => { if(gameStateRef.current.keys) gameStateRef.current.keys[e.key] = false; }; | |
| window.addEventListener('keydown', handleDown); | |
| window.addEventListener('keyup', handleUp); | |
| return () => { window.removeEventListener('keydown', handleDown); window.removeEventListener('keyup', handleUp); }; | |
| }, []); | |
| const generateGame = () => { | |
| if (!prompt) return; | |
| setStatus("GENERATING"); | |
| socketRef.current.send(JSON.stringify({ type: 'generate', prompt: prompt })); | |
| }; | |
| return ( | |
| <div className="crt-container"> | |
| <div className="scanlines"></div> | |
| <div className="glow"></div> | |
| <div className="flicker"></div> | |
| <div className="screen bg-black relative flex flex-col items-center justify-center p-8 text-green-400"> | |
| {/* BOOT SCREEN */} | |
| {status === 'BOOT' && ( | |
| <div className="text-left w-full animate-pulse"> | |
| <p>NEURAL BIOS v2.0</p> | |
| <p>CHECKING MEMORY... OK</p> | |
| <p>LOADING TENSOR CORES... OK</p> | |
| {logs.map((l, i) => <p key={i}>{l}</p>)} | |
| </div> | |
| )} | |
| {/* INPUT PROMPT */} | |
| {status === 'INPUT' && ( | |
| <div className="w-full max-w-md text-center"> | |
| <h1 className="text-4xl mb-8 text-white drop-shadow-[0_0_10px_rgba(255,255,255,0.8)]">NEURAL ATARI</h1> | |
| <p className="mb-2 text-green-600 uppercase">Insert Game Concept:</p> | |
| <input | |
| type="text" | |
| className="w-full bg-black border-2 border-green-600 text-green-400 p-2 font-mono outline-none text-xl uppercase mb-4 focus:border-green-400 focus:shadow-[0_0_15px_rgba(0,255,0,0.5)]" | |
| placeholder="E.G. SPACE CAT SHOOTER" | |
| value={prompt} | |
| onChange={e => setPrompt(e.target.value)} | |
| onKeyDown={e => e.key === 'Enter' && generateGame()} | |
| autoFocus | |
| /> | |
| <button | |
| onClick={generateGame} | |
| className="bg-green-700 text-black px-6 py-2 hover:bg-green-400 font-bold tracking-widest uppercase" | |
| > | |
| Generate ROM | |
| </button> | |
| </div> | |
| )} | |
| {/* GENERATING */} | |
| {status === 'GENERATING' && ( | |
| <div className="text-center space-y-4"> | |
| <div className="w-16 h-16 border-4 border-green-500 border-t-transparent rounded-full animate-spin mx-auto"></div> | |
| <h2 className="text-2xl animate-pulse">SYNTHESIZING CARTRIDGE...</h2> | |
| <div className="text-sm h-32 overflow-hidden border border-green-900 p-2 text-left bg-black/50 w-full"> | |
| {logs.map((l, i) => <div key={i}>> {l}</div>)} | |
| </div> | |
| </div> | |
| )} | |
| {/* GAMEPLAY */} | |
| {status === 'PLAYING' && ( | |
| <div className="w-full h-full relative group"> | |
| <canvas ref={canvasRef} width={160} height={192} /> | |
| <button | |
| onClick={() => setStatus("INPUT")} | |
| className="absolute top-2 right-2 opacity-0 group-hover:opacity-100 bg-red-600 text-white px-2 py-1 text-xs" | |
| > | |
| EJECT | |
| </button> | |
| </div> | |
| )} | |
| </div> | |
| <div className="wood-panel"> | |
| <div className="text-gray-400 text-sm">NEURAL SYSTEM</div> | |
| <div className="w-24 h-4 bg-black rounded-full border border-gray-700"></div> | |
| </div> | |
| </div> | |
| ); | |
| } | |
| ReactDOM.createRoot(document.getElementById('root')).render(<App />); | |
| </script> | |
| </body> | |
| </html> | |
| """ | |
| # --- BACKEND LOGIC --- | |
| class NeuralSystem: | |
| def __init__(self): | |
| self.device = "cuda" if torch.cuda.is_available() else "cpu" | |
| self.dtype = torch.float16 if self.device == "cuda" else torch.float32 | |
| print(f"⚡ Device: {self.device} | Type: {self.dtype}") | |
| # 1. Load Coder LLM | |
| print("🧠 Loading Logic Engine (Qwen)...") | |
| self.model_id = "Qwen/Qwen2.5-Coder-1.5B-Instruct" | |
| self.tokenizer = AutoTokenizer.from_pretrained(self.model_id) | |
| self.llm = AutoModelForCausalLM.from_pretrained(self.model_id, torch_dtype=self.dtype).to(self.device) | |
| # 2. Load Vision | |
| print("🎨 Loading Vision Engine (SD-LCM)...") | |
| self.pipe = StableDiffusionPipeline.from_pretrained( | |
| "runwayml/stable-diffusion-v1-5", | |
| torch_dtype=self.dtype, | |
| safety_checker=None | |
| ) | |
| if self.device == "cuda": | |
| self.pipe = self.pipe.to("cuda") | |
| self.pipe.load_lora_weights("latent-consistency/lcm-lora-sdv1-5") | |
| self.pipe.scheduler = LCMScheduler.from_config(self.pipe.scheduler.config) | |
| self.pipe.set_progress_bar_config(disable=True) | |
| print("✓ SYSTEMS ONLINE") | |
| def generate_code(self, prompt): | |
| """Generates JS Game Class""" | |
| system_prompt = """You are an Atari 2600 developer. Write a Javascript Class named 'Game'. | |
| The class MUST have: | |
| 1. constructor(ctx, width, height, spriteSheet) | |
| 2. init() - setup state | |
| 3. update(keys) - logic (keys.ArrowUp, etc) | |
| 4. draw() - rendering | |
| Constraints: | |
| - Canvas size is 160x192. | |
| - Use ctx.fillStyle, ctx.fillRect, ctx.drawImage. | |
| - The spriteSheet is an Image object. Assume it is a 2x2 grid (128x128 pixels total). | |
| - Source sprites from the sheet: ctx.drawImage(this.spriteSheet, sx, sy, sw, sh, dx, dy, dw, dh). | |
| - Keep logic simple (Pong, Space Invaders, Breakout style). | |
| - Return ONLY the javascript code for the class, no markdown.""" | |
| messages = [ | |
| {"role": "system", "content": system_prompt}, | |
| {"role": "user", "content": f"Create a game about: {prompt}"} | |
| ] | |
| text = self.tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True) | |
| inputs = self.tokenizer([text], return_tensors="pt").to(self.device) | |
| with torch.no_grad(): | |
| outputs = self.llm.generate( | |
| inputs.input_ids, | |
| max_new_tokens=1024, | |
| temperature=0.2, | |
| do_sample=True | |
| ) | |
| code = self.tokenizer.decode(outputs[0][len(inputs.input_ids[0]):], skip_special_tokens=True) | |
| # Cleanup markdown if present | |
| code = re.sub(r'```javascript|```', '', code).strip() | |
| return code | |
| def generate_sprites(self, prompt): | |
| """Generates Sprite Sheet""" | |
| # Enhance prompt for Atari style | |
| style = "pixel art sprite sheet, atari 2600 style, 8-bit, black background, minimal colors, distinct shapes, white sprites, retro" | |
| full_prompt = f"{style}, {prompt}" | |
| with torch.no_grad(): | |
| image = self.pipe( | |
| full_prompt, | |
| num_inference_steps=4, # Fast LCM | |
| guidance_scale=1.5, | |
| width=256, | |
| height=256 | |
| ).images[0] | |
| # Convert to Base64 | |
| buf = io.BytesIO() | |
| image.save(buf, format="PNG") | |
| return base64.b64encode(buf.getvalue()).decode() | |
| # Init Global System | |
| engine = NeuralSystem() | |
| app = Flask(__name__) | |
| sock = Sock(app) | |
| @sock.route('/atari') | |
| def atari_socket(ws): | |
| ws.send(json.dumps({"type": "log", "data": "SYSTEM READY."})) | |
| while True: | |
| data = ws.receive() | |
| if not data: break | |
| msg = json.loads(data) | |
| if msg['type'] == 'generate': | |
| prompt = msg['prompt'] | |
| # 1. Generate Logic | |
| ws.send(json.dumps({"type": "log", "data": "WRITING CODE..."})) | |
| try: | |
| code = engine.generate_code(prompt) | |
| ws.send(json.dumps({"type": "log", "data": "CODE COMPILED."})) | |
| # 2. Generate Assets | |
| ws.send(json.dumps({"type": "log", "data": "DREAMING SPRITES..."})) | |
| sprites = engine.generate_sprites(prompt) | |
| ws.send(json.dumps({"type": "log", "data": "ASSETS LOADED."})) | |
| # 3. Send Bundle | |
| ws.send(json.dumps({ | |
| "type": "game_ready", | |
| "data": { | |
| "code": code, | |
| "sprites": sprites, | |
| "bgColor": "#000000" | |
| } | |
| })) | |
| except Exception as e: | |
| ws.send(json.dumps({"type": "log", "data": f"ERROR: {str(e)}"})) | |
| @app.route('/') | |
| def index(): | |
| return HTML | |
| if __name__ == '__main__': | |
| app.run(host='0.0.0.0', port=7860) | |
| HYPER_EOF | |
| # 5. Runtime Configuration | |
| EXPOSE 7860 | |
| CMD ["python", "app.py"] | |