Spaces:
Sleeping
Sleeping
| #!/usr/bin/env python3 | |
| # complete_fixed_demo.py - Fixed with PyGame init and animation | |
| import os | |
| import pygame | |
| import numpy as np | |
| import time | |
| import threading | |
| from flask import Flask, Response | |
| # ===== CRITICAL FIXES ===== | |
| os.environ['SDL_VIDEODRIVER'] = 'dummy' | |
| os.environ['PYGAME_HIDE_SUPPORT_PROMPT'] = '1' | |
| PORT = int(os.getenv('PORT', 7860)) | |
| WIDTH, HEIGHT = 800, 600 | |
| app = Flask(__name__) | |
| # Shared state | |
| frame_data = { | |
| "pixels": None, | |
| "frame_id": 0, | |
| "lock": threading.Lock() | |
| } | |
| def index(): | |
| return '''<!DOCTYPE html> | |
| <html> | |
| <head> | |
| <title>Complete Demo</title> | |
| <style> | |
| body { margin: 0; padding: 20px; background: #000; color: white; font-family: monospace; text-align: center; } | |
| canvas { border: 3px solid #0af; background: black; display: block; margin: 20px auto; } | |
| .mode { background: #222; padding: 15px; border-radius: 8px; display: inline-block; margin: 10px; } | |
| </style> | |
| </head> | |
| <body> | |
| <h1 style="color: #0af;">⚡ Complete Demo</h1> | |
| <div class="mode" id="mode">Mode: <span id="modeText">Loading...</span></div> | |
| <canvas id="canvas" width="800" height="600"></canvas> | |
| <script> | |
| const canvas = document.getElementById('canvas'); | |
| const ctx = canvas.getContext('2d'); | |
| let mode = 'pygame'; // 'canvas' or 'pygame' | |
| let lastTime = 0; | |
| let frameCount = 0; | |
| // ===== CANVAS EFFECTS ===== | |
| function drawCanvasEffect(currentTime) { | |
| const delta = (currentTime - lastTime) / 1000; | |
| lastTime = currentTime; | |
| // Clear with fade effect | |
| ctx.fillStyle = 'rgba(0, 0, 0, 0.1)'; | |
| ctx.fillRect(0, 0, 800, 600); | |
| // Time in seconds | |
| const t = currentTime / 1000; | |
| // Draw multiple rotating shapes | |
| for (let i = 0; i < 20; i++) { | |
| const angle = t * (1 + i * 0.2); | |
| const radius = 50 + i * 15; | |
| const x = 400 + Math.cos(angle) * radius; | |
| const y = 300 + Math.sin(angle) * radius; | |
| const size = 10 + Math.sin(t * 3 + i) * 8; | |
| // Rainbow colors | |
| const hue = (t * 50 + i * 18) % 360; | |
| ctx.fillStyle = `hsl(${hue}, 100%, 60%)`; | |
| // Different shapes | |
| if (i % 3 === 0) { | |
| // Circle | |
| ctx.beginPath(); | |
| ctx.arc(x, y, size, 0, Math.PI * 2); | |
| ctx.fill(); | |
| } else if (i % 3 === 1) { | |
| // Square | |
| ctx.fillRect(x - size, y - size, size * 2, size * 2); | |
| } else { | |
| // Triangle | |
| ctx.beginPath(); | |
| ctx.moveTo(x, y - size); | |
| ctx.lineTo(x - size, y + size); | |
| ctx.lineTo(x + size, y + size); | |
| ctx.closePath(); | |
| ctx.fill(); | |
| } | |
| // Connecting lines | |
| if (i > 0) { | |
| const prevAngle = t * (1 + (i - 1) * 0.2); | |
| const prevX = 400 + Math.cos(prevAngle) * radius; | |
| const prevY = 300 + Math.sin(prevAngle) * radius; | |
| ctx.beginPath(); | |
| ctx.moveTo(prevX, prevY); | |
| ctx.lineTo(x, y); | |
| ctx.strokeStyle = `hsla(${hue}, 100%, 60%, 0.5)`; | |
| ctx.lineWidth = 2; | |
| ctx.stroke(); | |
| } | |
| } | |
| // Center pulsing circle | |
| const pulseSize = 30 + Math.sin(t * 5) * 15; | |
| ctx.fillStyle = `hsl(${t * 100 % 360}, 100%, 60%)`; | |
| ctx.beginPath(); | |
| ctx.arc(400, 300, pulseSize, 0, Math.PI * 2); | |
| ctx.fill(); | |
| // Add text | |
| ctx.fillStyle = 'white'; | |
| ctx.font = 'bold 24px monospace'; | |
| ctx.textAlign = 'center'; | |
| ctx.fillText('CANVAS EFFECTS DEMO', 400, 50); | |
| ctx.font = '16px monospace'; | |
| ctx.fillText(`Frame: ${frameCount} | Time: ${t.toFixed(1)}s`, 400, 80); | |
| frameCount++; | |
| } | |
| // ===== PYGAME STREAM ===== | |
| function loadPyGameFrame() { | |
| fetch('/pygame_frame') | |
| .then(r => r.arrayBuffer()) | |
| .then(buffer => { | |
| const imageData = new ImageData(800, 600); | |
| const rgba = imageData.data; | |
| const rgb = new Uint8Array(buffer); | |
| for (let i = 0, j = 0; i < rgba.length; i += 4, j += 3) { | |
| rgba[i] = rgb[j]; | |
| rgba[i + 1] = rgb[j + 1]; | |
| rgba[i + 2] = rgb[j + 2]; | |
| rgba[i + 3] = 255; | |
| } | |
| ctx.putImageData(imageData, 0, 0); | |
| // Add overlay text | |
| ctx.fillStyle = 'rgba(255, 255, 255, 0.8)'; | |
| ctx.font = 'bold 24px monospace'; | |
| ctx.textAlign = 'center'; | |
| ctx.fillText('PYGAME STREAM', 400, 50); | |
| }) | |
| .catch(err => console.error('PyGame error:', err)); | |
| } | |
| // ===== RENDER LOOP ===== | |
| function render(currentTime) { | |
| if (mode === 'canvas') { | |
| drawCanvasEffect(currentTime); | |
| } else { | |
| loadPyGameFrame(); | |
| } | |
| requestAnimationFrame(render); | |
| } | |
| // ===== CONTROLS ===== | |
| function switchToCanvas() { | |
| mode = 'canvas'; | |
| document.getElementById('modeText').textContent = 'CANVAS EFFECTS'; | |
| document.getElementById('modeText').style.color = '#0af'; | |
| } | |
| function switchToPyGame() { | |
| mode = 'pygame'; | |
| document.getElementById('modeText').textContent = 'PYGAME STREAM'; | |
| document.getElementById('modeText').style.color = '#ff3366'; | |
| } | |
| // Auto-switch every 5 seconds | |
| setInterval(() => { | |
| if (mode === 'canvas') { | |
| switchToPyGame(); | |
| } else { | |
| switchToCanvas(); | |
| } | |
| }, 5000); | |
| // Initialize | |
| switchToCanvas(); | |
| requestAnimationFrame(render); | |
| </script> | |
| </body> | |
| </html>''' | |
| def pygame_frame(): | |
| """PyGame animation endpoint""" | |
| with frame_data["lock"]: | |
| if frame_data["pixels"] is not None: | |
| return Response( | |
| frame_data["pixels"].tobytes(), | |
| mimetype='application/octet-stream', | |
| headers={'Cache-Control': 'no-cache'} | |
| ) | |
| # Fallback | |
| black = np.zeros((HEIGHT, WIDTH, 3), dtype=np.uint8).tobytes() | |
| return Response(black, mimetype='application/octet-stream') | |
| def pygame_animation(): | |
| """PyGame animation with proper initialization""" | |
| print("🎬 Starting PyGame animation...") | |
| try: | |
| # ===== FIX 1: Proper PyGame initialization ===== | |
| pygame.init() | |
| # Initialize display (CRITICAL for headless) | |
| pygame.display.init() | |
| pygame.display.set_mode((1, 1), pygame.HIDDEN) # Hidden window | |
| print("✅ PyGame display initialized") | |
| # Create surface | |
| surface = pygame.Surface((WIDTH, HEIGHT)) | |
| print(f"✅ Surface created: {WIDTH}x{HEIGHT}") | |
| except Exception as e: | |
| print(f"❌ PyGame init failed: {e}") | |
| import traceback | |
| traceback.print_exc() | |
| return | |
| # ===== FIX 2: Animated objects ===== | |
| particles = [] | |
| for _ in range(100): | |
| particles.append({ | |
| 'x': np.random.randint(0, WIDTH), | |
| 'y': np.random.randint(0, HEIGHT), | |
| 'dx': np.random.uniform(-3, 3), | |
| 'dy': np.random.uniform(-3, 3), | |
| 'size': np.random.randint(2, 10), | |
| 'color': ( | |
| np.random.randint(50, 255), | |
| np.random.randint(50, 255), | |
| np.random.randint(50, 255) | |
| ), | |
| 'life': np.random.randint(100, 300), | |
| 'type': np.random.choice(['circle', 'square', 'triangle']) | |
| }) | |
| # Rotating geometric shapes | |
| shapes = [] | |
| for i in range(8): | |
| shapes.append({ | |
| 'angle': i * np.pi / 4, | |
| 'radius': 100 + i * 20, | |
| 'speed': 0.5 + i * 0.1, | |
| 'size': 15 + i * 5, | |
| 'color': ( | |
| (i * 32) % 256, | |
| (i * 64) % 256, | |
| (i * 96) % 256 | |
| ) | |
| }) | |
| print(f"✅ Created {len(particles)} particles and {len(shapes)} shapes") | |
| frame_id = 0 | |
| start_time = time.time() | |
| while True: | |
| frame_start = time.time() | |
| # ===== FIX 3: Time-based animation ===== | |
| elapsed = time.time() - start_time | |
| # Clear with gradient | |
| for y in range(HEIGHT): | |
| color = int(20 + y / HEIGHT * 30) | |
| pygame.draw.line(surface, (color, color, color + 20), | |
| (0, y), (WIDTH, y)) | |
| # Draw rotating shapes | |
| for shape in shapes: | |
| shape['angle'] += shape['speed'] * 0.05 | |
| x = WIDTH // 2 + np.cos(shape['angle']) * shape['radius'] | |
| y = HEIGHT // 2 + np.sin(shape['angle']) * shape['radius'] | |
| # Draw shape | |
| if np.random.random() < 0.3: # 30% chance of triangle | |
| points = [ | |
| (x, y - shape['size']), | |
| (x - shape['size'], y + shape['size']), | |
| (x + shape['size'], y + shape['size']) | |
| ] | |
| pygame.draw.polygon(surface, shape['color'], points) | |
| else: | |
| pygame.draw.circle(surface, shape['color'], | |
| (int(x), int(y)), shape['size']) | |
| # Connect shapes with lines | |
| pygame.draw.circle(surface, (255, 255, 255, 100), | |
| (int(x), int(y)), shape['size'], 2) | |
| # Update and draw particles | |
| for p in particles: | |
| p['x'] += p['dx'] | |
| p['y'] += p['dy'] | |
| p['life'] -= 1 | |
| # Bounce or respawn | |
| if (p['x'] < 0 or p['x'] > WIDTH or | |
| p['y'] < 0 or p['y'] > HEIGHT or | |
| p['life'] <= 0): | |
| p['x'] = np.random.randint(0, WIDTH) | |
| p['y'] = np.random.randint(0, HEIGHT) | |
| p['dx'] = np.random.uniform(-3, 3) | |
| p['dy'] = np.random.uniform(-3, 3) | |
| p['life'] = np.random.randint(100, 300) | |
| p['color'] = ( | |
| np.random.randint(50, 255), | |
| np.random.randint(50, 255), | |
| np.random.randint(50, 255) | |
| ) | |
| # Draw particle with trail | |
| for i in range(3): | |
| trail_x = p['x'] - p['dx'] * i * 0.3 | |
| trail_y = p['y'] - p['dy'] * i * 0.3 | |
| trail_size = p['size'] * (1 - i * 0.3) | |
| trail_alpha = 200 - i * 60 | |
| pygame.draw.circle( | |
| surface, | |
| (*p['color'], trail_alpha), | |
| (int(trail_x), int(trail_y)), | |
| int(trail_size) | |
| ) | |
| # Add frame info | |
| font = pygame.font.Font(None, 28) | |
| info = [ | |
| f"Frame: {frame_id}", | |
| f"Time: {elapsed:.1f}s", | |
| f"FPS: {int(1/(time.time()-frame_start)) if frame_id>0 else 0}", | |
| f"Particles: {len(particles)}" | |
| ] | |
| for i, text in enumerate(info): | |
| text_surface = font.render(text, True, (255, 255, 200)) | |
| surface.blit(text_surface, (10, 10 + i * 30)) | |
| # Convert to NumPy array | |
| pixels = pygame.surfarray.pixels3d(surface) | |
| # Update shared memory | |
| with frame_data["lock"]: | |
| frame_data["pixels"] = pixels.copy() | |
| frame_data["frame_id"] = frame_id | |
| frame_id += 1 | |
| # Log every 30 frames | |
| if frame_id % 30 == 0: | |
| current_fps = 1 / (time.time() - frame_start) if frame_id > 0 else 0 | |
| print(f"📊 Frame {frame_id} | FPS: {current_fps:.1f}") | |
| # ===== FIX 4: Frame rate control ===== | |
| frame_time = time.time() - frame_start | |
| target_time = 1/30 # 30 FPS | |
| if frame_time < target_time: | |
| time.sleep(target_time - frame_time) | |
| # Start PyGame thread | |
| threading.Thread(target=pygame_animation, daemon=True).start() | |
| # Wait for initialization | |
| print("⏳ Waiting for PyGame to start...") | |
| time.sleep(3) | |
| print("🌐 Starting Flask server...") | |
| print(f"📡 Port: {PORT}") | |
| print("="*60) | |
| if __name__ == "__main__": | |
| app.run(host='0.0.0.0', port=PORT, threaded=True, use_reloader=False) |