#!/usr/bin/env python3 # bouncing_balls_pygame_flask.py - PyGame renders, Flask displays import pygame import numpy as np import time import threading import os from flask import Flask, Response # ===== 1. SETUP ===== os.environ['SDL_VIDEODRIVER'] = 'dummy' os.environ['PYGAME_HIDE_SUPPORT_PROMPT'] = '1' PORT = int(os.getenv('PORT', 7860)) WIDTH, HEIGHT = 400, 300 print(f"🚀 Starting PyGame + Flask on port {PORT}") # ===== 2. SHARED MEMORY ===== # Create initial black frame initial_frame = np.zeros((HEIGHT, WIDTH, 3), dtype=np.uint8) shared_state = { "pixels": initial_frame, # NumPy array (300, 400, 3) "frame_count": 0, "streaming": True, "lock": threading.Lock() } # ===== 3. FLASK APP ===== app = Flask(__name__) HTML = ''' PyGame Bouncing Balls

🎱 PyGame Bouncing Balls

Frames: 0
Status: Loading...
''' @app.route('/') def index(): return HTML @app.route('/stats') def stats(): with shared_state['lock']: return {'frame_count': shared_state['frame_count']} @app.route('/frame') def get_frame(): """Return raw RGB pixel data""" with shared_state['lock']: # Convert NumPy array to raw bytes pixel_bytes = shared_state['pixels'].tobytes() return Response( pixel_bytes, mimetype='application/octet-stream', headers={'Cache-Control': 'no-cache'} ) # ===== 4. PYGAME RENDER THREAD ===== def pygame_render(): """PyGame renders bouncing balls""" print("🎬 Starting PyGame render thread...") try: pygame.init() print("✅ PyGame initialized") except Exception as e: print(f"❌ PyGame init failed: {e}") return # Create PyGame surface surface = pygame.Surface((WIDTH, HEIGHT)) # Create 3 bouncing balls balls = [ {'x': 100, 'y': 150, 'dx': 3, 'dy': 2, 'radius': 20, 'color': (255, 50, 50)}, {'x': 200, 'y': 100, 'dx': -2, 'dy': 3, 'radius': 15, 'color': (50, 255, 50)}, {'x': 300, 'y': 200, 'dx': 4, 'dy': -1, 'radius': 25, 'color': (50, 50, 255)}, ] print("✅ Created 3 bouncing balls") while shared_state['streaming']: # Clear screen surface.fill((20, 20, 40)) # Update and draw each ball for ball in balls: # Update position ball['x'] += ball['dx'] ball['y'] += ball['dy'] # Bounce off walls if ball['x'] - ball['radius'] < 0 or ball['x'] + ball['radius'] > WIDTH: ball['dx'] = -ball['dx'] if ball['y'] - ball['radius'] < 0 or ball['y'] + ball['radius'] > HEIGHT: ball['dy'] = -ball['dy'] # Draw ball pygame.draw.circle( surface, ball['color'], (int(ball['x']), int(ball['y'])), ball['radius'] ) # Draw outline pygame.draw.circle( surface, (255, 255, 255), (int(ball['x']), int(ball['y'])), ball['radius'], 2 ) # Add frame counter font = pygame.font.Font(None, 24) with shared_state['lock']: frame_num = shared_state['frame_count'] text = font.render(f"Frame: {frame_num}", True, (255, 255, 255)) surface.blit(text, (10, 10)) # Convert surface to NumPy array pixel_array = pygame.surfarray.pixels3d(surface) # Update shared memory with shared_state['lock']: shared_state['pixels'] = pixel_array.copy() shared_state['frame_count'] += 1 # Control frame rate (30 FPS) time.sleep(1/30) pygame.quit() print("🛑 PyGame render stopped") # ===== 5. MAIN ===== def main(): print("="*60) print("🎯 PyGame + Flask Bouncing Balls") print("="*60) print(f"📡 Port: {PORT}") print(f"🎨 Resolution: {WIDTH}x{HEIGHT}") print("="*60) # Start PyGame render thread render_thread = threading.Thread(target=pygame_render, daemon=True) render_thread.start() # Wait a moment time.sleep(1) # Start Flask app.run(host='0.0.0.0', port=PORT, threaded=True, use_reloader=False) if __name__ == "__main__": main()