import pygame import numpy as np from flask import Flask, Response, render_template_string from flask_sock import Sock import time import os import cv2 import threading import json # Initialize Pygame headlessly os.environ['SDL_VIDEODRIVER'] = 'dummy' pygame.init() try: pygame.mixer.init(frequency=44100, size=-16, channels=2) print("✅ Audio mixer initialized") except Exception as e: print(f"⚠️ Audio mixer not available: {e}") app = Flask(__name__) sock = Sock(app) class ShaderRenderer: def __init__(self, width=640, height=480): self.width = width self.height = height self.mouse_x = width // 2 self.mouse_y = height // 2 self.start_time = time.time() self.surface = pygame.Surface((width, height)) self.frame_count = 0 self.last_frame_time = time.time() self.fps = 0 self.button_clicked = False self.sound_source = 'none' def set_mouse(self, x, y): self.mouse_x = max(0, min(self.width, x)) self.mouse_y = max(0, min(self.height, y)) def handle_click(self, x, y): button_rect = pygame.Rect(self.width-200, 120, 180, 40) if button_rect.collidepoint(x, y): self.button_clicked = not self.button_clicked return True return False def render_frame(self): t = time.time() - self.start_time # Calculate FPS self.frame_count += 1 if time.time() - self.last_frame_time > 1.0: self.fps = self.frame_count self.frame_count = 0 self.last_frame_time = time.time() # Clear self.surface.fill((20, 20, 30)) font = pygame.font.Font(None, 24) # Draw TOP marker pygame.draw.rect(self.surface, (255, 100, 100), (10, 10, 100, 30)) text = font.render("TOP", True, (255, 255, 255)) self.surface.blit(text, (20, 15)) # Draw BOTTOM marker pygame.draw.rect(self.surface, (100, 255, 100), (10, self.height-40, 100, 30)) text = font.render("BOTTOM", True, (0, 0, 0)) self.surface.blit(text, (20, self.height-35)) # Draw CLOCK current_time = time.time() seconds = int(current_time) % 60 hundredths = int((current_time * 100) % 100) time_str = f"{seconds:02d}.{hundredths:02d}s" clock_text = font.render(time_str, True, (0, 255, 255)) self.surface.blit(clock_text, (self.width-150, 40)) # Draw BUTTON button_rect = pygame.Rect(self.width-200, 120, 180, 40) mouse_over = button_rect.collidepoint(self.mouse_x, self.mouse_y) if self.button_clicked: button_color = (0, 200, 0) elif mouse_over: button_color = (100, 100, 200) else: button_color = (80, 80, 80) pygame.draw.rect(self.surface, button_color, button_rect) pygame.draw.rect(self.surface, (200, 200, 200), button_rect, 2) btn_text = "✅ CLICKED!" if self.button_clicked else "🔘 CLICK ME" text_surf = font.render(btn_text, True, (255, 255, 255)) text_rect = text_surf.get_rect(center=button_rect.center) self.surface.blit(text_surf, text_rect) # Draw circle circle_size = 30 + int(20 * np.sin(t * 2)) if self.sound_source == 'pygame': color = (100, 255, 100) elif self.sound_source == 'browser': color = (100, 100, 255) else: color = (255, 100, 100) pygame.draw.circle(self.surface, color, (self.mouse_x, self.mouse_y), circle_size) # Draw grid for x in range(0, self.width, 50): alpha = int(40 + 20 * np.sin(x * 0.1 + t)) pygame.draw.line(self.surface, (alpha, alpha, 50), (x, 0), (x, self.height)) for y in range(0, self.height, 50): alpha = int(40 + 20 * np.cos(y * 0.1 + t)) pygame.draw.line(self.surface, (alpha, alpha, 50), (0, y), (self.width, y)) # FPS counter fps_text = font.render(f"FPS: {self.fps}", True, (255, 255, 0)) self.surface.blit(fps_text, (self.width-150, self.height-60)) return pygame.image.tostring(self.surface, 'RGB') def get_frame_jpeg(self, quality=70): frame = self.render_frame() img = np.frombuffer(frame, dtype=np.uint8).reshape((self.height, self.width, 3)) img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR) _, jpeg = cv2.imencode('.jpg', img, [cv2.IMWRITE_JPEG_QUALITY, quality]) return jpeg.tobytes() renderer = ShaderRenderer() # WebSocket for all interactions @sock.route('/ws') def websocket(ws): """Single WebSocket connection for all interaction""" while True: try: message = ws.receive() if not message: continue data = json.loads(message) if data['type'] == 'mouse': renderer.set_mouse(data['x'], data['y']) elif data['type'] == 'click': renderer.handle_click(data['x'], data['y']) # Broadcast button state to all clients? Optional elif data['type'] == 'sound': renderer.sound_source = data['source'] except: break # MJPEG stream (one long connection, zero API calls) @app.route('/video.mjpeg') def video_mjpeg(): def generate(): while True: frame = renderer.get_frame_jpeg() yield (b'--frame\r\n' b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n') time.sleep(1/30) return Response( generate(), mimetype='multipart/x-mixed-replace; boundary=frame' ) @app.route('/') def index(): return render_template_string(''' 🎮 Pygame + WebSocket

🎮 Pygame + WebSocket (Zero API)

X: 320, Y: 240

🔊 Sound Source

Connection: 🟢 Connected
API Calls: 0

⚡ Zero API polling • All interaction via WebSocket • MJPEG stream

''') @app.route('/static/sound.mp3') def serve_sound(): if os.path.exists('sound.mp3'): with open('sound.mp3', 'rb') as f: return Response(f.read(), mimetype='audio/mpeg') return 'Sound not found', 404 if __name__ == '__main__': port = int(os.environ.get('PORT', 7860)) app.run(host='0.0.0.0', port=port, debug=False, threaded=True)