MySafeCode commited on
Commit
1e2288a
·
verified ·
1 Parent(s): 4d36f3c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +286 -351
app.py CHANGED
@@ -1,5 +1,5 @@
1
  #!/usr/bin/env python3
2
- # shader_to_pygame.py - WebGL shader PyGame streaming
3
  import os
4
  import pygame
5
  import numpy as np
@@ -7,17 +7,16 @@ import time
7
  import threading
8
  from flask import Flask, Response
9
 
10
- # Setup
11
  os.environ['SDL_VIDEODRIVER'] = 'dummy'
12
  os.environ['PYGAME_HIDE_SUPPORT_PROMPT'] = '1'
13
 
14
  PORT = int(os.getenv('PORT', 7860))
15
- WIDTH, HEIGHT = 800, 600 # Larger for shader demo
16
 
17
  app = Flask(__name__)
18
 
19
- # Mode: 0 = Shader, 1 = PyGame
20
- current_mode = 0
21
  frame_data = {
22
  "pixels": None,
23
  "frame_id": 0,
@@ -29,329 +28,103 @@ def index():
29
  return '''<!DOCTYPE html>
30
  <html>
31
  <head>
32
- <title>Shader → PyGame Demo</title>
33
  <style>
34
  body { margin: 0; padding: 20px; background: #000; color: white; font-family: monospace; text-align: center; }
35
- canvas {
36
- border: 3px solid #0af;
37
- background: black;
38
- display: block;
39
- margin: 20px auto;
40
- image-rendering: pixelated;
41
- }
42
- .mode-indicator {
43
- position: absolute;
44
- top: 20px;
45
- left: 20px;
46
- background: rgba(0, 0, 0, 0.7);
47
- padding: 10px;
48
- border-radius: 8px;
49
- font-size: 14px;
50
- }
51
- .controls {
52
- margin: 20px;
53
- }
54
- button {
55
- background: linear-gradient(45deg, #ff0080, #00ff80);
56
- color: white;
57
- border: none;
58
- padding: 12px 24px;
59
- margin: 8px;
60
- border-radius: 25px;
61
- cursor: pointer;
62
- font-family: monospace;
63
- font-weight: bold;
64
- transition: all 0.3s;
65
- }
66
- button:hover {
67
- transform: scale(1.05);
68
- box-shadow: 0 0 20px #0af;
69
- }
70
  </style>
71
  </head>
72
  <body>
73
- <div class="mode-indicator" id="modeIndicator">🎨 MODE: <span id="modeText">WEBGL SHADER</span></div>
74
-
75
- <h1 style="background: linear-gradient(45deg, #ff0080, #00ff80, #8000ff); -webkit-background-clip: text; -webkit-text-fill-color: transparent;">
76
- ⚡ SHADER → PYGAME DEMO
77
- </h1>
78
-
79
  <canvas id="canvas" width="800" height="600"></canvas>
80
 
81
- <div class="controls">
82
- <button onclick="switchToShader()">🎨 WebGL Shader</button>
83
- <button onclick="switchToPyGame()">🎮 PyGame Stream</button>
84
- <button onclick="toggleFullscreen()">📺 Fullscreen</button>
85
- <button onclick="takeScreenshot()">📸 Screenshot</button>
86
- </div>
87
-
88
  <script>
89
- console.log('🚀 Starting Shader → PyGame Demo');
90
-
91
  const canvas = document.getElementById('canvas');
92
- const gl = canvas.getContext('webgl2') || canvas.getContext('webgl');
93
- let shaderProgram = null;
94
- let mode = 0; // 0 = Shader, 1 = PyGame
95
- let timeUniform = 0;
96
- let mouseX = 0, mouseY = 0;
97
  let frameCount = 0;
98
 
99
- if (!gl) {
100
- alert('WebGL not supported! Using canvas fallback.');
101
- document.getElementById('modeText').textContent = 'CANVAS FALLBACK';
102
- } else {
103
- console.log('✅ WebGL initialized');
104
- initWebGL();
105
- }
106
-
107
- // ===== WEBGL SHADER SETUP =====
108
- function initWebGL() {
109
- // Vertex shader - simple fullscreen quad
110
- const vsSource = `
111
- attribute vec2 aPosition;
112
- varying vec2 vUv;
113
- void main() {
114
- vUv = aPosition * 0.5 + 0.5;
115
- gl_Position = vec4(aPosition, 0.0, 1.0);
116
- }
117
- `;
118
 
119
- // Fragment shader - FLASHY EFFECTS!
120
- const fsSource = `
121
- precision highp float;
122
- varying vec2 vUv;
123
- uniform float uTime;
124
- uniform vec2 uMouse;
125
- uniform int uMode;
126
-
127
- // Hash function for randomness
128
- float hash(float n) { return fract(sin(n) * 43758.5453); }
 
 
 
 
129
 
130
- // Noise function
131
- float noise(vec2 p) {
132
- vec2 ip = floor(p);
133
- vec2 u = fract(p);
134
- u = u*u*(3.0-2.0*u);
135
-
136
- float res = mix(
137
- mix(hash(ip.x+57.0*ip.y), hash(ip.x+58.0+57.0*ip.y), u.x),
138
- mix(hash(ip.x+57.0*(ip.y+1.0)), hash(ip.x+58.0+57.0*(ip.y+1.0)), u.x),
139
- u.y
140
- );
141
- return res;
142
- }
143
 
144
- // Plasma effect
145
- vec3 plasma(vec2 uv, float time) {
146
- float v = 0.0;
147
- v += sin(uv.x * 10.0 + time);
148
- v += sin(uv.y * 10.0 + time * 1.3);
149
- v += sin(uv.x * 20.0 + uv.y * 15.0 + time * 0.7);
150
- v += sin(sqrt(uv.x*uv.x + uv.y*uv.y) * 5.0 + time);
151
-
152
- vec3 color;
153
- color.r = sin(v * 3.14159);
154
- color.g = sin(v * 3.14159 + 2.094);
155
- color.b = sin(v * 3.14159 + 4.188);
156
-
157
- return color;
 
 
 
158
  }
159
 
160
- // Fractal noise
161
- float fbm(vec2 p) {
162
- float value = 0.0;
163
- float amplitude = 0.5;
164
- float frequency = 1.0;
165
-
166
- for (int i = 0; i < 6; i++) {
167
- value += amplitude * noise(p * frequency);
168
- amplitude *= 0.5;
169
- frequency *= 2.0;
170
- }
171
 
172
- return value;
 
 
 
 
 
173
  }
174
-
175
- // Main shader
176
- void main() {
177
- vec2 uv = vUv;
178
- vec2 center = vec2(0.5, 0.5);
179
- float dist = distance(uv, center);
180
-
181
- // Mouse interaction
182
- vec2 mouse = uMouse;
183
- float mouseDist = distance(uv, mouse);
184
-
185
- // Time-based animations
186
- float t = uTime * 0.5;
187
- float pulse = sin(t) * 0.5 + 0.5;
188
- float spin = t * 2.0;
189
-
190
- // Choose effect based on time
191
- int effect = int(mod(uTime / 5.0, 4.0));
192
-
193
- vec3 color = vec3(0.0);
194
-
195
- if (effect == 0) {
196
- // RAINBOW SWIRL
197
- float angle = atan(uv.y - center.y, uv.x - center.x) + spin;
198
- float radius = dist * 3.0;
199
- color = 0.5 + 0.5 * cos(angle + vec3(0, 2, 4) + radius);
200
-
201
- // Add pulsing circles
202
- float circles = sin(dist * 20.0 - t * 3.0) * 0.5 + 0.5;
203
- color += circles * 0.3;
204
-
205
- } else if (effect == 1) {
206
- // FIRE EFFECT
207
- vec2 q = vec2(fbm(uv + t * 0.5), fbm(uv + vec2(1.0)));
208
- vec2 r = vec2(fbm(uv + 4.0*q + vec2(1.7, 9.2) + 0.15*t),
209
- fbm(uv + 4.0*q + vec2(8.3, 2.8) + 0.126*t));
210
- float f = fbm(uv + 4.0*r);
211
-
212
- color = mix(
213
- vec3(1.0, 0.1, 0.0),
214
- vec3(1.0, 0.8, 0.0),
215
- clamp(f*f*4.0, 0.0, 1.0)
216
- );
217
-
218
- color = mix(
219
- color,
220
- vec3(0.0, 0.0, 0.1),
221
- clamp(length(q), 0.0, 1.0)
222
- );
223
-
224
- } else if (effect == 2) {
225
- // ELECTRIC GRID
226
- uv *= 10.0;
227
- vec2 grid = abs(fract(uv - 0.5) - 0.5) / fwidth(uv);
228
- float line = min(grid.x, grid.y);
229
-
230
- color = vec3(1.0 - smoothstep(0.0, 1.0, line));
231
- color *= vec3(0.0, 1.0, 1.0);
232
-
233
- // Animate grid
234
- uv += sin(t + uv.yx * 2.0) * 0.1;
235
- vec2 grid2 = abs(fract(uv - 0.5) - 0.5) / fwidth(uv);
236
- float line2 = min(grid2.x, grid2.y);
237
- color += vec3(1.0, 0.0, 1.0) * (1.0 - smoothstep(0.0, 1.0, line2));
238
-
239
- } else {
240
- // GALAXY/STARFIELD
241
- vec2 p = (uv - 0.5) * 2.0;
242
- p.x *= 800.0 / 600.0;
243
-
244
- // Starfield
245
- float stars = 0.0;
246
- for (int i = 0; i < 50; i++) {
247
- vec2 starPos = vec2(
248
- hash(float(i) * 1.337),
249
- hash(float(i) * 2.718)
250
- );
251
- float starSize = hash(float(i) * 3.141) * 0.002;
252
- float starBright = sin(t * 2.0 + float(i)) * 0.5 + 0.5;
253
- stars += starBright * starSize / length(p - (starPos - 0.5) * 2.0);
254
- }
255
-
256
- // Nebula clouds
257
- float clouds = fbm(p * 2.0 + t * 0.1);
258
- clouds = pow(clouds, 3.0);
259
-
260
- color = vec3(stars * 0.5) +
261
- clouds * vec3(0.8, 0.3, 1.0) +
262
- clouds * clouds * vec3(0.1, 0.5, 0.8);
263
- }
264
-
265
- // Mouse interaction glow
266
- float mouseGlow = 0.1 / (mouseDist + 0.05);
267
- color += vec3(mouseGlow * 0.5, mouseGlow, mouseGlow * 0.8);
268
-
269
- // Vignette
270
- color *= 1.0 - dist * 0.5;
271
-
272
- // Add scanlines
273
- color *= 0.9 + 0.1 * sin(uv.y * 800.0 + t * 10.0);
274
-
275
- gl_FragColor = vec4(color, 1.0);
276
- }
277
- `;
278
-
279
- // Compile shaders
280
- const vertexShader = gl.createShader(gl.VERTEX_SHADER);
281
- gl.shaderSource(vertexShader, vsSource);
282
- gl.compileShader(vertexShader);
283
-
284
- const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
285
- gl.shaderSource(fragmentShader, fsSource);
286
- gl.compileShader(fragmentShader);
287
-
288
- // Check for errors
289
- if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) {
290
- console.error('Vertex shader error:', gl.getShaderInfoLog(vertexShader));
291
- return;
292
  }
293
- if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) {
294
- console.error('Fragment shader error:', gl.getShaderInfoLog(fragmentShader));
295
- return;
296
- }
297
-
298
- // Create program
299
- shaderProgram = gl.createProgram();
300
- gl.attachShader(shaderProgram, vertexShader);
301
- gl.attachShader(shaderProgram, fragmentShader);
302
- gl.linkProgram(shaderProgram);
303
-
304
- if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
305
- console.error('Shader program error:', gl.getProgramInfoLog(shaderProgram));
306
- return;
307
- }
308
-
309
- // Set up vertices for fullscreen quad
310
- const vertices = new Float32Array([
311
- -1, -1, 1, -1, -1, 1,
312
- -1, 1, 1, -1, 1, 1
313
- ]);
314
-
315
- const vertexBuffer = gl.createBuffer();
316
- gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
317
- gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
318
-
319
- const positionAttrib = gl.getAttribLocation(shaderProgram, 'aPosition');
320
- gl.vertexAttribPointer(positionAttrib, 2, gl.FLOAT, false, 0, 0);
321
- gl.enableVertexAttribArray(positionAttrib);
322
-
323
- console.log('✅ WebGL shader program created');
324
- }
325
-
326
- // ===== RENDER LOOP =====
327
- function renderShader(time) {
328
- if (!gl || !shaderProgram) return;
329
 
330
- gl.useProgram(shaderProgram);
 
 
 
 
 
331
 
332
- // Set uniforms
333
- const timeUniform = gl.getUniformLocation(shaderProgram, 'uTime');
334
- const mouseUniform = gl.getUniformLocation(shaderProgram, 'uMouse');
335
- const modeUniform = gl.getUniformLocation(shaderProgram, 'uMode');
336
-
337
- gl.uniform1f(timeUniform, time * 0.001); // Convert to seconds
338
- gl.uniform2f(mouseUniform, mouseX / 800, 1.0 - mouseY / 600); // Flip Y
339
- gl.uniform1i(modeUniform, mode);
340
-
341
- // Draw
342
- gl.drawArrays(gl.TRIANGLES, 0, 6);
343
 
344
  frameCount++;
345
- if (frameCount % 60 === 0) {
346
- console.log(`Shader FPS: ${frameCount}`);
347
- frameCount = 0;
348
- }
349
  }
350
 
351
- function renderPyGame() {
352
- // Use canvas 2D for PyGame frames
353
- const ctx = canvas.getContext('2d');
354
-
355
  fetch('/pygame_frame')
356
  .then(r => r.arrayBuffer())
357
  .then(buffer => {
@@ -367,78 +140,59 @@ def index():
367
  }
368
 
369
  ctx.putImageData(imageData, 0, 0);
370
- });
 
 
 
 
 
 
 
371
  }
372
 
373
- function render() {
374
- if (mode === 0) {
375
- renderShader(performance.now());
 
376
  } else {
377
- renderPyGame();
378
  }
379
 
380
  requestAnimationFrame(render);
381
  }
382
 
383
  // ===== CONTROLS =====
384
- function switchToShader() {
385
- mode = 0;
386
- document.getElementById('modeText').textContent = 'WEBGL SHADER';
387
  document.getElementById('modeText').style.color = '#0af';
388
- console.log('Switched to WebGL shader');
389
  }
390
 
391
  function switchToPyGame() {
392
- mode = 1;
393
  document.getElementById('modeText').textContent = 'PYGAME STREAM';
394
  document.getElementById('modeText').style.color = '#ff3366';
395
- console.log('Switched to PyGame stream');
396
  }
397
 
398
- function toggleFullscreen() {
399
- if (!document.fullscreenElement) {
400
- canvas.requestFullscreen().catch(err => {
401
- console.error('Fullscreen error:', err);
402
- });
403
  } else {
404
- document.exitFullscreen();
405
  }
406
- }
407
-
408
- function takeScreenshot() {
409
- const link = document.createElement('a');
410
- link.download = `screenshot-${Date.now()}.png`;
411
- link.href = canvas.toDataURL();
412
- link.click();
413
- console.log('Screenshot saved');
414
- }
415
-
416
- // ===== EVENT HANDLERS =====
417
- canvas.addEventListener('mousemove', (e) => {
418
- const rect = canvas.getBoundingClientRect();
419
- mouseX = e.clientX - rect.left;
420
- mouseY = e.clientY - rect.top;
421
- });
422
-
423
- canvas.addEventListener('click', (e) => {
424
- // Create ripple effect on click
425
- console.log(`Click at ${mouseX}, ${mouseY}`);
426
- });
427
-
428
- // Start with shader
429
- switchToShader();
430
 
431
- // Start rendering
432
- render();
433
-
434
- console.log('🎮 Demo ready! Click buttons to switch modes.');
435
  </script>
436
  </body>
437
  </html>'''
438
 
439
  @app.route('/pygame_frame')
440
  def pygame_frame():
441
- """PyGame rendering endpoint"""
442
  with frame_data["lock"]:
443
  if frame_data["pixels"] is not None:
444
  return Response(
@@ -447,6 +201,187 @@ def pygame_frame():
447
  headers={'Cache-Control': 'no-cache'}
448
  )
449
 
450
- # Return black frame
451
  black = np.zeros((HEIGHT, WIDTH, 3), dtype=np.uint8).tobytes()
452
- return Response(black, mimetype='application/octet-stream')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  #!/usr/bin/env python3
2
+ # complete_fixed_demo.py - Fixed with PyGame init and animation
3
  import os
4
  import pygame
5
  import numpy as np
 
7
  import threading
8
  from flask import Flask, Response
9
 
10
+ # ===== CRITICAL FIXES =====
11
  os.environ['SDL_VIDEODRIVER'] = 'dummy'
12
  os.environ['PYGAME_HIDE_SUPPORT_PROMPT'] = '1'
13
 
14
  PORT = int(os.getenv('PORT', 7860))
15
+ WIDTH, HEIGHT = 800, 600
16
 
17
  app = Flask(__name__)
18
 
19
+ # Shared state
 
20
  frame_data = {
21
  "pixels": None,
22
  "frame_id": 0,
 
28
  return '''<!DOCTYPE html>
29
  <html>
30
  <head>
31
+ <title>Complete Demo</title>
32
  <style>
33
  body { margin: 0; padding: 20px; background: #000; color: white; font-family: monospace; text-align: center; }
34
+ canvas { border: 3px solid #0af; background: black; display: block; margin: 20px auto; }
35
+ .mode { background: #222; padding: 15px; border-radius: 8px; display: inline-block; margin: 10px; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
  </style>
37
  </head>
38
  <body>
39
+ <h1 style="color: #0af;"> Complete Demo</h1>
40
+ <div class="mode" id="mode">Mode: <span id="modeText">Loading...</span></div>
 
 
 
 
41
  <canvas id="canvas" width="800" height="600"></canvas>
42
 
 
 
 
 
 
 
 
43
  <script>
 
 
44
  const canvas = document.getElementById('canvas');
45
+ const ctx = canvas.getContext('2d');
46
+ let mode = 'pygame'; // 'canvas' or 'pygame'
47
+ let lastTime = 0;
 
 
48
  let frameCount = 0;
49
 
50
+ // ===== CANVAS EFFECTS =====
51
+ function drawCanvasEffect(currentTime) {
52
+ const delta = (currentTime - lastTime) / 1000;
53
+ lastTime = currentTime;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
 
55
+ // Clear with fade effect
56
+ ctx.fillStyle = 'rgba(0, 0, 0, 0.1)';
57
+ ctx.fillRect(0, 0, 800, 600);
58
+
59
+ // Time in seconds
60
+ const t = currentTime / 1000;
61
+
62
+ // Draw multiple rotating shapes
63
+ for (let i = 0; i < 20; i++) {
64
+ const angle = t * (1 + i * 0.2);
65
+ const radius = 50 + i * 15;
66
+ const x = 400 + Math.cos(angle) * radius;
67
+ const y = 300 + Math.sin(angle) * radius;
68
+ const size = 10 + Math.sin(t * 3 + i) * 8;
69
 
70
+ // Rainbow colors
71
+ const hue = (t * 50 + i * 18) % 360;
72
+ ctx.fillStyle = `hsl(${hue}, 100%, 60%)`;
 
 
 
 
 
 
 
 
 
 
73
 
74
+ // Different shapes
75
+ if (i % 3 === 0) {
76
+ // Circle
77
+ ctx.beginPath();
78
+ ctx.arc(x, y, size, 0, Math.PI * 2);
79
+ ctx.fill();
80
+ } else if (i % 3 === 1) {
81
+ // Square
82
+ ctx.fillRect(x - size, y - size, size * 2, size * 2);
83
+ } else {
84
+ // Triangle
85
+ ctx.beginPath();
86
+ ctx.moveTo(x, y - size);
87
+ ctx.lineTo(x - size, y + size);
88
+ ctx.lineTo(x + size, y + size);
89
+ ctx.closePath();
90
+ ctx.fill();
91
  }
92
 
93
+ // Connecting lines
94
+ if (i > 0) {
95
+ const prevAngle = t * (1 + (i - 1) * 0.2);
96
+ const prevX = 400 + Math.cos(prevAngle) * radius;
97
+ const prevY = 300 + Math.sin(prevAngle) * radius;
 
 
 
 
 
 
98
 
99
+ ctx.beginPath();
100
+ ctx.moveTo(prevX, prevY);
101
+ ctx.lineTo(x, y);
102
+ ctx.strokeStyle = `hsla(${hue}, 100%, 60%, 0.5)`;
103
+ ctx.lineWidth = 2;
104
+ ctx.stroke();
105
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
106
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
107
 
108
+ // Center pulsing circle
109
+ const pulseSize = 30 + Math.sin(t * 5) * 15;
110
+ ctx.fillStyle = `hsl(${t * 100 % 360}, 100%, 60%)`;
111
+ ctx.beginPath();
112
+ ctx.arc(400, 300, pulseSize, 0, Math.PI * 2);
113
+ ctx.fill();
114
 
115
+ // Add text
116
+ ctx.fillStyle = 'white';
117
+ ctx.font = 'bold 24px monospace';
118
+ ctx.textAlign = 'center';
119
+ ctx.fillText('CANVAS EFFECTS DEMO', 400, 50);
120
+ ctx.font = '16px monospace';
121
+ ctx.fillText(`Frame: ${frameCount} | Time: ${t.toFixed(1)}s`, 400, 80);
 
 
 
 
122
 
123
  frameCount++;
 
 
 
 
124
  }
125
 
126
+ // ===== PYGAME STREAM =====
127
+ function loadPyGameFrame() {
 
 
128
  fetch('/pygame_frame')
129
  .then(r => r.arrayBuffer())
130
  .then(buffer => {
 
140
  }
141
 
142
  ctx.putImageData(imageData, 0, 0);
143
+
144
+ // Add overlay text
145
+ ctx.fillStyle = 'rgba(255, 255, 255, 0.8)';
146
+ ctx.font = 'bold 24px monospace';
147
+ ctx.textAlign = 'center';
148
+ ctx.fillText('PYGAME STREAM', 400, 50);
149
+ })
150
+ .catch(err => console.error('PyGame error:', err));
151
  }
152
 
153
+ // ===== RENDER LOOP =====
154
+ function render(currentTime) {
155
+ if (mode === 'canvas') {
156
+ drawCanvasEffect(currentTime);
157
  } else {
158
+ loadPyGameFrame();
159
  }
160
 
161
  requestAnimationFrame(render);
162
  }
163
 
164
  // ===== CONTROLS =====
165
+ function switchToCanvas() {
166
+ mode = 'canvas';
167
+ document.getElementById('modeText').textContent = 'CANVAS EFFECTS';
168
  document.getElementById('modeText').style.color = '#0af';
 
169
  }
170
 
171
  function switchToPyGame() {
172
+ mode = 'pygame';
173
  document.getElementById('modeText').textContent = 'PYGAME STREAM';
174
  document.getElementById('modeText').style.color = '#ff3366';
 
175
  }
176
 
177
+ // Auto-switch every 5 seconds
178
+ setInterval(() => {
179
+ if (mode === 'canvas') {
180
+ switchToPyGame();
 
181
  } else {
182
+ switchToCanvas();
183
  }
184
+ }, 5000);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
185
 
186
+ // Initialize
187
+ switchToCanvas();
188
+ requestAnimationFrame(render);
 
189
  </script>
190
  </body>
191
  </html>'''
192
 
193
  @app.route('/pygame_frame')
194
  def pygame_frame():
195
+ """PyGame animation endpoint"""
196
  with frame_data["lock"]:
197
  if frame_data["pixels"] is not None:
198
  return Response(
 
201
  headers={'Cache-Control': 'no-cache'}
202
  )
203
 
204
+ # Fallback
205
  black = np.zeros((HEIGHT, WIDTH, 3), dtype=np.uint8).tobytes()
206
+ return Response(black, mimetype='application/octet-stream')
207
+
208
+ def pygame_animation():
209
+ """PyGame animation with proper initialization"""
210
+ print("🎬 Starting PyGame animation...")
211
+
212
+ try:
213
+ # ===== FIX 1: Proper PyGame initialization =====
214
+ pygame.init()
215
+
216
+ # Initialize display (CRITICAL for headless)
217
+ pygame.display.init()
218
+ pygame.display.set_mode((1, 1), pygame.HIDDEN) # Hidden window
219
+
220
+ print("✅ PyGame display initialized")
221
+
222
+ # Create surface
223
+ surface = pygame.Surface((WIDTH, HEIGHT))
224
+ print(f"✅ Surface created: {WIDTH}x{HEIGHT}")
225
+
226
+ except Exception as e:
227
+ print(f"❌ PyGame init failed: {e}")
228
+ import traceback
229
+ traceback.print_exc()
230
+ return
231
+
232
+ # ===== FIX 2: Animated objects =====
233
+ particles = []
234
+ for _ in range(100):
235
+ particles.append({
236
+ 'x': np.random.randint(0, WIDTH),
237
+ 'y': np.random.randint(0, HEIGHT),
238
+ 'dx': np.random.uniform(-3, 3),
239
+ 'dy': np.random.uniform(-3, 3),
240
+ 'size': np.random.randint(2, 10),
241
+ 'color': (
242
+ np.random.randint(50, 255),
243
+ np.random.randint(50, 255),
244
+ np.random.randint(50, 255)
245
+ ),
246
+ 'life': np.random.randint(100, 300),
247
+ 'type': np.random.choice(['circle', 'square', 'triangle'])
248
+ })
249
+
250
+ # Rotating geometric shapes
251
+ shapes = []
252
+ for i in range(8):
253
+ shapes.append({
254
+ 'angle': i * np.pi / 4,
255
+ 'radius': 100 + i * 20,
256
+ 'speed': 0.5 + i * 0.1,
257
+ 'size': 15 + i * 5,
258
+ 'color': (
259
+ (i * 32) % 256,
260
+ (i * 64) % 256,
261
+ (i * 96) % 256
262
+ )
263
+ })
264
+
265
+ print(f"✅ Created {len(particles)} particles and {len(shapes)} shapes")
266
+
267
+ frame_id = 0
268
+ start_time = time.time()
269
+
270
+ while True:
271
+ frame_start = time.time()
272
+
273
+ # ===== FIX 3: Time-based animation =====
274
+ elapsed = time.time() - start_time
275
+
276
+ # Clear with gradient
277
+ for y in range(HEIGHT):
278
+ color = int(20 + y / HEIGHT * 30)
279
+ pygame.draw.line(surface, (color, color, color + 20),
280
+ (0, y), (WIDTH, y))
281
+
282
+ # Draw rotating shapes
283
+ for shape in shapes:
284
+ shape['angle'] += shape['speed'] * 0.05
285
+
286
+ x = WIDTH // 2 + np.cos(shape['angle']) * shape['radius']
287
+ y = HEIGHT // 2 + np.sin(shape['angle']) * shape['radius']
288
+
289
+ # Draw shape
290
+ if np.random.random() < 0.3: # 30% chance of triangle
291
+ points = [
292
+ (x, y - shape['size']),
293
+ (x - shape['size'], y + shape['size']),
294
+ (x + shape['size'], y + shape['size'])
295
+ ]
296
+ pygame.draw.polygon(surface, shape['color'], points)
297
+ else:
298
+ pygame.draw.circle(surface, shape['color'],
299
+ (int(x), int(y)), shape['size'])
300
+
301
+ # Connect shapes with lines
302
+ pygame.draw.circle(surface, (255, 255, 255, 100),
303
+ (int(x), int(y)), shape['size'], 2)
304
+
305
+ # Update and draw particles
306
+ for p in particles:
307
+ p['x'] += p['dx']
308
+ p['y'] += p['dy']
309
+ p['life'] -= 1
310
+
311
+ # Bounce or respawn
312
+ if (p['x'] < 0 or p['x'] > WIDTH or
313
+ p['y'] < 0 or p['y'] > HEIGHT or
314
+ p['life'] <= 0):
315
+ p['x'] = np.random.randint(0, WIDTH)
316
+ p['y'] = np.random.randint(0, HEIGHT)
317
+ p['dx'] = np.random.uniform(-3, 3)
318
+ p['dy'] = np.random.uniform(-3, 3)
319
+ p['life'] = np.random.randint(100, 300)
320
+ p['color'] = (
321
+ np.random.randint(50, 255),
322
+ np.random.randint(50, 255),
323
+ np.random.randint(50, 255)
324
+ )
325
+
326
+ # Draw particle with trail
327
+ for i in range(3):
328
+ trail_x = p['x'] - p['dx'] * i * 0.3
329
+ trail_y = p['y'] - p['dy'] * i * 0.3
330
+ trail_size = p['size'] * (1 - i * 0.3)
331
+ trail_alpha = 200 - i * 60
332
+
333
+ pygame.draw.circle(
334
+ surface,
335
+ (*p['color'], trail_alpha),
336
+ (int(trail_x), int(trail_y)),
337
+ int(trail_size)
338
+ )
339
+
340
+ # Add frame info
341
+ font = pygame.font.Font(None, 28)
342
+ info = [
343
+ f"Frame: {frame_id}",
344
+ f"Time: {elapsed:.1f}s",
345
+ f"FPS: {int(1/(time.time()-frame_start)) if frame_id>0 else 0}",
346
+ f"Particles: {len(particles)}"
347
+ ]
348
+
349
+ for i, text in enumerate(info):
350
+ text_surface = font.render(text, True, (255, 255, 200))
351
+ surface.blit(text_surface, (10, 10 + i * 30))
352
+
353
+ # Convert to NumPy array
354
+ pixels = pygame.surfarray.pixels3d(surface)
355
+
356
+ # Update shared memory
357
+ with frame_data["lock"]:
358
+ frame_data["pixels"] = pixels.copy()
359
+ frame_data["frame_id"] = frame_id
360
+
361
+ frame_id += 1
362
+
363
+ # Log every 30 frames
364
+ if frame_id % 30 == 0:
365
+ current_fps = 1 / (time.time() - frame_start) if frame_id > 0 else 0
366
+ print(f"📊 Frame {frame_id} | FPS: {current_fps:.1f}")
367
+
368
+ # ===== FIX 4: Frame rate control =====
369
+ frame_time = time.time() - frame_start
370
+ target_time = 1/30 # 30 FPS
371
+
372
+ if frame_time < target_time:
373
+ time.sleep(target_time - frame_time)
374
+
375
+ # Start PyGame thread
376
+ threading.Thread(target=pygame_animation, daemon=True).start()
377
+
378
+ # Wait for initialization
379
+ print("⏳ Waiting for PyGame to start...")
380
+ time.sleep(3)
381
+
382
+ print("🌐 Starting Flask server...")
383
+ print(f"📡 Port: {PORT}")
384
+ print("="*60)
385
+
386
+ if __name__ == "__main__":
387
+ app.run(host='0.0.0.0', port=PORT, threaded=True, use_reloader=False)