kollabor 0.4.9__py3-none-any.whl → 0.4.15__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (192) hide show
  1. agents/__init__.py +2 -0
  2. agents/coder/__init__.py +0 -0
  3. agents/coder/agent.json +4 -0
  4. agents/coder/api-integration.md +2150 -0
  5. agents/coder/cli-pretty.md +765 -0
  6. agents/coder/code-review.md +1092 -0
  7. agents/coder/database-design.md +1525 -0
  8. agents/coder/debugging.md +1102 -0
  9. agents/coder/dependency-management.md +1397 -0
  10. agents/coder/git-workflow.md +1099 -0
  11. agents/coder/refactoring.md +1454 -0
  12. agents/coder/security-hardening.md +1732 -0
  13. agents/coder/system_prompt.md +1448 -0
  14. agents/coder/tdd.md +1367 -0
  15. agents/creative-writer/__init__.py +0 -0
  16. agents/creative-writer/agent.json +4 -0
  17. agents/creative-writer/character-development.md +1852 -0
  18. agents/creative-writer/dialogue-craft.md +1122 -0
  19. agents/creative-writer/plot-structure.md +1073 -0
  20. agents/creative-writer/revision-editing.md +1484 -0
  21. agents/creative-writer/system_prompt.md +690 -0
  22. agents/creative-writer/worldbuilding.md +2049 -0
  23. agents/data-analyst/__init__.py +30 -0
  24. agents/data-analyst/agent.json +4 -0
  25. agents/data-analyst/data-visualization.md +992 -0
  26. agents/data-analyst/exploratory-data-analysis.md +1110 -0
  27. agents/data-analyst/pandas-data-manipulation.md +1081 -0
  28. agents/data-analyst/sql-query-optimization.md +881 -0
  29. agents/data-analyst/statistical-analysis.md +1118 -0
  30. agents/data-analyst/system_prompt.md +928 -0
  31. agents/default/__init__.py +0 -0
  32. agents/default/agent.json +4 -0
  33. agents/default/dead-code.md +794 -0
  34. agents/default/explore-agent-system.md +585 -0
  35. agents/default/system_prompt.md +1448 -0
  36. agents/kollabor/__init__.py +0 -0
  37. agents/kollabor/analyze-plugin-lifecycle.md +175 -0
  38. agents/kollabor/analyze-terminal-rendering.md +388 -0
  39. agents/kollabor/code-review.md +1092 -0
  40. agents/kollabor/debug-mcp-integration.md +521 -0
  41. agents/kollabor/debug-plugin-hooks.md +547 -0
  42. agents/kollabor/debugging.md +1102 -0
  43. agents/kollabor/dependency-management.md +1397 -0
  44. agents/kollabor/git-workflow.md +1099 -0
  45. agents/kollabor/inspect-llm-conversation.md +148 -0
  46. agents/kollabor/monitor-event-bus.md +558 -0
  47. agents/kollabor/profile-performance.md +576 -0
  48. agents/kollabor/refactoring.md +1454 -0
  49. agents/kollabor/system_prompt copy.md +1448 -0
  50. agents/kollabor/system_prompt.md +757 -0
  51. agents/kollabor/trace-command-execution.md +178 -0
  52. agents/kollabor/validate-config.md +879 -0
  53. agents/research/__init__.py +0 -0
  54. agents/research/agent.json +4 -0
  55. agents/research/architecture-mapping.md +1099 -0
  56. agents/research/codebase-analysis.md +1077 -0
  57. agents/research/dependency-audit.md +1027 -0
  58. agents/research/performance-profiling.md +1047 -0
  59. agents/research/security-review.md +1359 -0
  60. agents/research/system_prompt.md +492 -0
  61. agents/technical-writer/__init__.py +0 -0
  62. agents/technical-writer/agent.json +4 -0
  63. agents/technical-writer/api-documentation.md +2328 -0
  64. agents/technical-writer/changelog-management.md +1181 -0
  65. agents/technical-writer/readme-writing.md +1360 -0
  66. agents/technical-writer/style-guide.md +1410 -0
  67. agents/technical-writer/system_prompt.md +653 -0
  68. agents/technical-writer/tutorial-creation.md +1448 -0
  69. core/__init__.py +0 -2
  70. core/application.py +343 -88
  71. core/cli.py +229 -10
  72. core/commands/menu_renderer.py +463 -59
  73. core/commands/registry.py +14 -9
  74. core/commands/system_commands.py +2461 -14
  75. core/config/loader.py +151 -37
  76. core/config/service.py +18 -6
  77. core/events/bus.py +29 -9
  78. core/events/executor.py +205 -75
  79. core/events/models.py +27 -8
  80. core/fullscreen/command_integration.py +20 -24
  81. core/fullscreen/components/__init__.py +10 -1
  82. core/fullscreen/components/matrix_components.py +1 -2
  83. core/fullscreen/components/space_shooter_components.py +654 -0
  84. core/fullscreen/plugin.py +5 -0
  85. core/fullscreen/renderer.py +52 -13
  86. core/fullscreen/session.py +52 -15
  87. core/io/__init__.py +29 -5
  88. core/io/buffer_manager.py +6 -1
  89. core/io/config_status_view.py +7 -29
  90. core/io/core_status_views.py +267 -347
  91. core/io/input/__init__.py +25 -0
  92. core/io/input/command_mode_handler.py +711 -0
  93. core/io/input/display_controller.py +128 -0
  94. core/io/input/hook_registrar.py +286 -0
  95. core/io/input/input_loop_manager.py +421 -0
  96. core/io/input/key_press_handler.py +502 -0
  97. core/io/input/modal_controller.py +1011 -0
  98. core/io/input/paste_processor.py +339 -0
  99. core/io/input/status_modal_renderer.py +184 -0
  100. core/io/input_errors.py +5 -1
  101. core/io/input_handler.py +211 -2452
  102. core/io/key_parser.py +7 -0
  103. core/io/layout.py +15 -3
  104. core/io/message_coordinator.py +111 -2
  105. core/io/message_renderer.py +129 -4
  106. core/io/status_renderer.py +147 -607
  107. core/io/terminal_renderer.py +97 -51
  108. core/io/terminal_state.py +21 -4
  109. core/io/visual_effects.py +816 -165
  110. core/llm/agent_manager.py +1063 -0
  111. core/llm/api_adapters/__init__.py +44 -0
  112. core/llm/api_adapters/anthropic_adapter.py +432 -0
  113. core/llm/api_adapters/base.py +241 -0
  114. core/llm/api_adapters/openai_adapter.py +326 -0
  115. core/llm/api_communication_service.py +167 -113
  116. core/llm/conversation_logger.py +322 -16
  117. core/llm/conversation_manager.py +556 -30
  118. core/llm/file_operations_executor.py +84 -32
  119. core/llm/llm_service.py +934 -103
  120. core/llm/mcp_integration.py +541 -57
  121. core/llm/message_display_service.py +135 -18
  122. core/llm/plugin_sdk.py +1 -2
  123. core/llm/profile_manager.py +1183 -0
  124. core/llm/response_parser.py +274 -56
  125. core/llm/response_processor.py +16 -3
  126. core/llm/tool_executor.py +6 -1
  127. core/logging/__init__.py +2 -0
  128. core/logging/setup.py +34 -6
  129. core/models/resume.py +54 -0
  130. core/plugins/__init__.py +4 -2
  131. core/plugins/base.py +127 -0
  132. core/plugins/collector.py +23 -161
  133. core/plugins/discovery.py +37 -3
  134. core/plugins/factory.py +6 -12
  135. core/plugins/registry.py +5 -17
  136. core/ui/config_widgets.py +128 -28
  137. core/ui/live_modal_renderer.py +2 -1
  138. core/ui/modal_actions.py +5 -0
  139. core/ui/modal_overlay_renderer.py +0 -60
  140. core/ui/modal_renderer.py +268 -7
  141. core/ui/modal_state_manager.py +29 -4
  142. core/ui/widgets/base_widget.py +7 -0
  143. core/updates/__init__.py +10 -0
  144. core/updates/version_check_service.py +348 -0
  145. core/updates/version_comparator.py +103 -0
  146. core/utils/config_utils.py +685 -526
  147. core/utils/plugin_utils.py +1 -1
  148. core/utils/session_naming.py +111 -0
  149. fonts/LICENSE +21 -0
  150. fonts/README.md +46 -0
  151. fonts/SymbolsNerdFont-Regular.ttf +0 -0
  152. fonts/SymbolsNerdFontMono-Regular.ttf +0 -0
  153. fonts/__init__.py +44 -0
  154. {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/METADATA +54 -4
  155. kollabor-0.4.15.dist-info/RECORD +228 -0
  156. {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/top_level.txt +2 -0
  157. plugins/agent_orchestrator/__init__.py +39 -0
  158. plugins/agent_orchestrator/activity_monitor.py +181 -0
  159. plugins/agent_orchestrator/file_attacher.py +77 -0
  160. plugins/agent_orchestrator/message_injector.py +135 -0
  161. plugins/agent_orchestrator/models.py +48 -0
  162. plugins/agent_orchestrator/orchestrator.py +403 -0
  163. plugins/agent_orchestrator/plugin.py +976 -0
  164. plugins/agent_orchestrator/xml_parser.py +191 -0
  165. plugins/agent_orchestrator_plugin.py +9 -0
  166. plugins/enhanced_input/box_styles.py +1 -0
  167. plugins/enhanced_input/color_engine.py +19 -4
  168. plugins/enhanced_input/config.py +2 -2
  169. plugins/enhanced_input_plugin.py +61 -11
  170. plugins/fullscreen/__init__.py +6 -2
  171. plugins/fullscreen/example_plugin.py +1035 -222
  172. plugins/fullscreen/setup_wizard_plugin.py +592 -0
  173. plugins/fullscreen/space_shooter_plugin.py +131 -0
  174. plugins/hook_monitoring_plugin.py +436 -78
  175. plugins/query_enhancer_plugin.py +66 -30
  176. plugins/resume_conversation_plugin.py +1494 -0
  177. plugins/save_conversation_plugin.py +98 -32
  178. plugins/system_commands_plugin.py +70 -56
  179. plugins/tmux_plugin.py +154 -78
  180. plugins/workflow_enforcement_plugin.py +94 -92
  181. system_prompt/default.md +952 -886
  182. core/io/input_mode_manager.py +0 -402
  183. core/io/modal_interaction_handler.py +0 -315
  184. core/io/raw_input_processor.py +0 -946
  185. core/storage/__init__.py +0 -5
  186. core/storage/state_manager.py +0 -84
  187. core/ui/widget_integration.py +0 -222
  188. core/utils/key_reader.py +0 -171
  189. kollabor-0.4.9.dist-info/RECORD +0 -128
  190. {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/WHEEL +0 -0
  191. {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/entry_points.txt +0 -0
  192. {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,654 @@
1
+ """Space shooter components for the full-screen framework.
2
+
3
+ Retro 80s arcade-style vertical space shooter demo with ships flying upward through starfield.
4
+ Classic Galaga-style vertical scrolling.
5
+ """
6
+
7
+ import random
8
+ from typing import List
9
+ from ...io.visual_effects import ColorPalette
10
+
11
+
12
+ class Star:
13
+ """A single star in the background starfield."""
14
+
15
+ def __init__(self, x: int, y: int, width: int, height: int):
16
+ """Initialize star.
17
+
18
+ Args:
19
+ x: X position
20
+ y: Y position
21
+ width: Terminal width (for wrapping)
22
+ height: Terminal height (for wrapping)
23
+ """
24
+ self.x = x
25
+ self.y = float(y)
26
+ self.width = width
27
+ self.height = height
28
+ # Different star speeds create parallax effect
29
+ self.layer = random.choice([1, 2, 3]) # 1=far, 3=close
30
+ self.speed = self.layer * 12.0 # Faster layers = closer stars
31
+ self.char = random.choice(['.', '.', '.', '*', '+', '·', '°'])
32
+ self.next_update = 0
33
+
34
+ def update(self, time: float) -> bool:
35
+ """Update star position (moves downward - ships flying up).
36
+
37
+ Args:
38
+ time: Current time
39
+
40
+ Returns:
41
+ True always (stars wrap around)
42
+ """
43
+ if time < self.next_update:
44
+ return True
45
+
46
+ self.next_update = time + (1.0 / self.speed)
47
+
48
+ # Stars move downward (ships flying upward)
49
+ self.y += 1
50
+
51
+ # Wrap around at bottom
52
+ if self.y >= self.height:
53
+ self.y = 0
54
+ self.x = random.randint(0, self.width - 1)
55
+
56
+ return True
57
+
58
+ def render(self, renderer):
59
+ """Render star.
60
+
61
+ Args:
62
+ renderer: FullScreenRenderer instance
63
+ """
64
+ # Dimmer stars are further away
65
+ if self.layer == 1:
66
+ color = ColorPalette.DIM_GREY
67
+ elif self.layer == 2:
68
+ color = ColorPalette.GREY
69
+ else:
70
+ color = ColorPalette.BRIGHT_WHITE
71
+
72
+ renderer.write_at(self.x, int(self.y), self.char, color)
73
+
74
+
75
+ class Ship:
76
+ """A player ship with banking animations for vertical flight."""
77
+
78
+ # Ship sprites - pointing upward
79
+ SPRITES = {
80
+ 'viper': {
81
+ 'straight': [
82
+ " ▲ ",
83
+ " ▐█▌ ",
84
+ " ▟███▙ ",
85
+ " █▀ ▀█ ",
86
+ ],
87
+ 'bank_right': [
88
+ " ▲ ",
89
+ " ▐█▌ ",
90
+ " ▟███▙▄",
91
+ " █▀ ▀█ ",
92
+ ],
93
+ 'bank_left': [
94
+ " ▲ ",
95
+ " ▐█▌ ",
96
+ "▄▟███▙ ",
97
+ " █▀ ▀█ ",
98
+ ],
99
+ },
100
+ 'falcon': {
101
+ 'straight': [
102
+ " █ ",
103
+ " ▟█▙ ",
104
+ " ▟███▙ ",
105
+ " █▀███▀█ ",
106
+ ],
107
+ 'bank_right': [
108
+ " █ ",
109
+ " ▟█▙ ",
110
+ " ▟███▙▄",
111
+ " █▀███▀█",
112
+ ],
113
+ 'bank_left': [
114
+ " █ ",
115
+ " ▟█▙ ",
116
+ "▄▟███▙ ",
117
+ "█▀███▀█ ",
118
+ ],
119
+ },
120
+ 'interceptor': {
121
+ 'straight': [
122
+ " █ ",
123
+ " ▟█▙ ",
124
+ " █████ ",
125
+ " █▀ █ ▀█ ",
126
+ ],
127
+ 'bank_right': [
128
+ " █ ",
129
+ " ▟█▙ ",
130
+ " █████▄",
131
+ " █▀ █ ▀█",
132
+ ],
133
+ 'bank_left': [
134
+ " █ ",
135
+ " ▟█▙ ",
136
+ "▄█████ ",
137
+ "█▀ █ ▀█ ",
138
+ ],
139
+ },
140
+ }
141
+
142
+ def __init__(self, ship_type: str, start_x: int, start_y: int, width: int, height: int):
143
+ """Initialize ship.
144
+
145
+ Args:
146
+ ship_type: Type of ship ('viper', 'falcon', 'interceptor')
147
+ start_x: Starting X position
148
+ start_y: Starting Y position
149
+ width: Terminal width
150
+ height: Terminal height
151
+ """
152
+ self.ship_type = ship_type
153
+ self.x = float(start_x)
154
+ self.y = float(start_y)
155
+ self.width = width
156
+ self.height = height
157
+ self.state = 'straight'
158
+ self.target_x = start_x
159
+ self.next_update = 0
160
+ self.maneuver_timer = 0
161
+ self.maneuver_duration = 0
162
+
163
+ # Engine exhaust animation
164
+ self.exhaust_frame = 0
165
+ self.exhaust_chars = ['▒', '▓', '█', '▓']
166
+
167
+ def start_maneuver(self, time: float):
168
+ """Start a random maneuver (dodge left or right).
169
+
170
+ Args:
171
+ time: Current time
172
+ """
173
+ maneuver = random.choice(['dodge_left', 'dodge_right', 'straight', 'straight'])
174
+
175
+ if maneuver == 'dodge_left':
176
+ self.target_x = max(2, self.x - random.randint(5, 15))
177
+ self.state = 'bank_left'
178
+ self.maneuver_duration = 1.2
179
+ elif maneuver == 'dodge_right':
180
+ self.target_x = min(self.width - 12, self.x + random.randint(5, 15))
181
+ self.state = 'bank_right'
182
+ self.maneuver_duration = 1.2
183
+ else:
184
+ self.state = 'straight'
185
+ self.maneuver_duration = 0.8
186
+
187
+ self.maneuver_timer = time + self.maneuver_duration
188
+
189
+ def update(self, time: float) -> bool:
190
+ """Update ship position and state.
191
+
192
+ Args:
193
+ time: Current time
194
+
195
+ Returns:
196
+ True to continue
197
+ """
198
+ if time < self.next_update:
199
+ return True
200
+
201
+ self.next_update = time + (1.0 / 30.0) # 30 FPS for smooth movement
202
+
203
+ # Move horizontally towards target
204
+ if abs(self.x - self.target_x) > 0.5:
205
+ if self.x < self.target_x:
206
+ self.x += 0.4
207
+ else:
208
+ self.x -= 0.4
209
+ else:
210
+ # Reached target, straighten out
211
+ if self.state != 'straight' and time > self.maneuver_timer:
212
+ self.state = 'straight'
213
+
214
+ # Check if maneuver is done
215
+ if time > self.maneuver_timer:
216
+ # Random chance to start new maneuver
217
+ if random.random() < 0.025:
218
+ self.start_maneuver(time)
219
+
220
+ # Update exhaust animation
221
+ self.exhaust_frame = (self.exhaust_frame + 1) % len(self.exhaust_chars)
222
+
223
+ return True
224
+
225
+ def render(self, renderer):
226
+ """Render ship.
227
+
228
+ Args:
229
+ renderer: FullScreenRenderer instance
230
+ """
231
+ sprites = self.SPRITES.get(self.ship_type, self.SPRITES['viper'])
232
+ sprite = sprites.get(self.state, sprites['straight'])
233
+
234
+ x = int(self.x)
235
+ y = int(self.y)
236
+
237
+ # Ship colors
238
+ ship_color = ColorPalette.BRIGHT_CYAN
239
+
240
+ for row_idx, row in enumerate(sprite):
241
+ for col_idx, char in enumerate(row):
242
+ if char != ' ':
243
+ px = x + col_idx
244
+ py = y + row_idx
245
+ if 0 <= px < self.width and 0 <= py < self.height:
246
+ renderer.write_at(px, py, char, ship_color)
247
+
248
+ # Render engine exhaust below ship (we're flying up)
249
+ exhaust_positions = [(4, 4), (4, 5)] # Two exhaust points below ship
250
+ for ex_offset, ey_offset in exhaust_positions:
251
+ exhaust_x = x + ex_offset
252
+ exhaust_y = y + ey_offset
253
+ if 0 <= exhaust_x < self.width and 0 <= exhaust_y < self.height:
254
+ exhaust_char = self.exhaust_chars[self.exhaust_frame]
255
+ renderer.write_at(exhaust_x, exhaust_y, exhaust_char, ColorPalette.YELLOW)
256
+
257
+
258
+ class Enemy:
259
+ """An enemy ship (invader or boss) coming from the top."""
260
+
261
+ # Enemy sprites - pointing downward (coming at player)
262
+ SPRITES = {
263
+ 'invader_f': [
264
+ " ▀ ▀ ",
265
+ "▄▀▀▄▀▀▄",
266
+ "▐█▄▄▄█▌",
267
+ " ▀▄▄▄▀ ",
268
+ ],
269
+ 'boss_galaga': [
270
+ " ▄▀▀▄▀▀▄ ",
271
+ " █▄███▄█ ",
272
+ " ▐█████▌ ",
273
+ " ▀███▀ ",
274
+ ],
275
+ }
276
+
277
+ def __init__(self, enemy_type: str, x: int, y: int, width: int, height: int):
278
+ """Initialize enemy.
279
+
280
+ Args:
281
+ enemy_type: Type of enemy ('invader_f', 'boss_galaga')
282
+ x: X position
283
+ y: Y position (starts negative, above screen)
284
+ width: Terminal width
285
+ height: Terminal height
286
+ """
287
+ self.enemy_type = enemy_type
288
+ self.x = float(x)
289
+ self.y = float(y)
290
+ self.width = width
291
+ self.height = height
292
+ self.speed = random.uniform(6.0, 12.0)
293
+ self.next_update = 0
294
+ self.wobble = 0
295
+ self.wobble_dir = 1
296
+
297
+ def update(self, time: float) -> bool:
298
+ """Update enemy position (moving downward).
299
+
300
+ Args:
301
+ time: Current time
302
+
303
+ Returns:
304
+ True if enemy is still on screen
305
+ """
306
+ if time < self.next_update:
307
+ return True
308
+
309
+ self.next_update = time + (1.0 / self.speed)
310
+
311
+ # Move downward (towards player at bottom)
312
+ self.y += 1
313
+
314
+ # Wobble left and right
315
+ self.wobble += self.wobble_dir * 0.4
316
+ if abs(self.wobble) > 3:
317
+ self.wobble_dir *= -1
318
+
319
+ # Off screen at bottom
320
+ if self.y > self.height + 5:
321
+ return False
322
+
323
+ return True
324
+
325
+ def render(self, renderer):
326
+ """Render enemy.
327
+
328
+ Args:
329
+ renderer: FullScreenRenderer instance
330
+ """
331
+ sprite = self.SPRITES.get(self.enemy_type, self.SPRITES['invader_f'])
332
+
333
+ x = int(self.x + self.wobble)
334
+ y = int(self.y)
335
+
336
+ # Enemy colors
337
+ if self.enemy_type == 'boss_galaga':
338
+ color = ColorPalette.BRIGHT_YELLOW
339
+ else:
340
+ color = ColorPalette.BRIGHT_RED
341
+
342
+ for row_idx, row in enumerate(sprite):
343
+ for col_idx, char in enumerate(row):
344
+ if char != ' ':
345
+ px = x + col_idx
346
+ py = y + row_idx
347
+ if 0 <= px < self.width and 0 <= py < self.height:
348
+ renderer.write_at(px, py, char, color)
349
+
350
+
351
+ class Laser:
352
+ """A laser projectile firing upward."""
353
+
354
+ def __init__(self, x: int, y: int, height: int):
355
+ """Initialize laser.
356
+
357
+ Args:
358
+ x: X position
359
+ y: Y position
360
+ height: Terminal height
361
+ """
362
+ self.x = x
363
+ self.y = float(y)
364
+ self.height = height
365
+ self.speed = 50.0
366
+ self.next_update = 0
367
+ self.chars = ['│', '║', '┃', '║']
368
+ self.frame = 0
369
+
370
+ def update(self, time: float) -> bool:
371
+ """Update laser position (moving upward).
372
+
373
+ Args:
374
+ time: Current time
375
+
376
+ Returns:
377
+ True if laser is still on screen
378
+ """
379
+ if time < self.next_update:
380
+ return True
381
+
382
+ self.next_update = time + (1.0 / self.speed)
383
+ self.y -= 1 # Move upward
384
+ self.frame = (self.frame + 1) % len(self.chars)
385
+
386
+ return self.y >= 0
387
+
388
+ def render(self, renderer):
389
+ """Render laser.
390
+
391
+ Args:
392
+ renderer: FullScreenRenderer instance
393
+ """
394
+ y = int(self.y)
395
+ char = self.chars[self.frame]
396
+
397
+ # Draw laser trail (vertical)
398
+ for i in range(3):
399
+ py = y + i # Trail below the head
400
+ if 0 <= py < self.height:
401
+ if i == 0:
402
+ color = ColorPalette.BRIGHT_WHITE
403
+ elif i == 1:
404
+ color = ColorPalette.BRIGHT_CYAN
405
+ else:
406
+ color = ColorPalette.CYAN
407
+ renderer.write_at(self.x, py, char, color)
408
+
409
+
410
+ class Explosion:
411
+ """An explosion effect."""
412
+
413
+ FRAMES = [
414
+ ['*'],
415
+ [' * ', '*+*', ' * '],
416
+ [' * ', ' *** ', '**+**', ' *** ', ' * '],
417
+ [' . . ', '. + .', ' . . '],
418
+ [' . ', ' . . ', ' . '],
419
+ ['. .', ' . ', '. .'],
420
+ ]
421
+
422
+ def __init__(self, x: int, y: int):
423
+ """Initialize explosion.
424
+
425
+ Args:
426
+ x: X position
427
+ y: Y position
428
+ """
429
+ self.x = x
430
+ self.y = y
431
+ self.frame = 0
432
+ self.next_update = 0
433
+ self.speed = 12.0
434
+
435
+ def update(self, time: float) -> bool:
436
+ """Update explosion animation.
437
+
438
+ Args:
439
+ time: Current time
440
+
441
+ Returns:
442
+ True if explosion is still animating
443
+ """
444
+ if time < self.next_update:
445
+ return True
446
+
447
+ self.next_update = time + (1.0 / self.speed)
448
+ self.frame += 1
449
+
450
+ return self.frame < len(self.FRAMES)
451
+
452
+ def render(self, renderer):
453
+ """Render explosion.
454
+
455
+ Args:
456
+ renderer: FullScreenRenderer instance
457
+ """
458
+ if self.frame >= len(self.FRAMES):
459
+ return
460
+
461
+ frame = self.FRAMES[self.frame]
462
+ colors = [ColorPalette.BRIGHT_WHITE, ColorPalette.BRIGHT_YELLOW,
463
+ ColorPalette.YELLOW, ColorPalette.RED, ColorPalette.DIM_RED, ColorPalette.DIM_GREY]
464
+ color = colors[min(self.frame, len(colors) - 1)]
465
+
466
+ for row_idx, row in enumerate(frame):
467
+ for col_idx, char in enumerate(row):
468
+ if char != ' ':
469
+ px = self.x + col_idx - len(row) // 2
470
+ py = self.y + row_idx - len(frame) // 2
471
+ renderer.write_at(px, py, char, color)
472
+
473
+
474
+ class SpaceShooterRenderer:
475
+ """Renders the complete vertical space shooter demo."""
476
+
477
+ def __init__(self, terminal_width: int, terminal_height: int):
478
+ """Initialize space shooter renderer.
479
+
480
+ Args:
481
+ terminal_width: Terminal width in columns
482
+ terminal_height: Terminal height in rows
483
+ """
484
+ self.terminal_width = terminal_width
485
+ self.terminal_height = terminal_height
486
+ self.stars: List[Star] = []
487
+ self.ships: List[Ship] = []
488
+ self.enemies: List[Enemy] = []
489
+ self.lasers: List[Laser] = []
490
+ self.explosions: List[Explosion] = []
491
+ self.start_time = 0
492
+ self.last_enemy_spawn = 0
493
+ self.last_laser_time = 0
494
+ self.score = 0
495
+
496
+ self._create_starfield()
497
+ self._create_ships()
498
+
499
+ def _create_starfield(self):
500
+ """Create initial starfield."""
501
+ self.stars = []
502
+ num_stars = (self.terminal_width * self.terminal_height) // 25
503
+
504
+ for _ in range(num_stars):
505
+ x = random.randint(0, self.terminal_width - 1)
506
+ y = random.randint(0, self.terminal_height - 1)
507
+ self.stars.append(Star(x, y, self.terminal_width, self.terminal_height))
508
+
509
+ def _create_ships(self):
510
+ """Create the three player ships at the bottom."""
511
+ self.ships = []
512
+ ship_types = ['viper', 'falcon', 'interceptor']
513
+
514
+ # Ships positioned at bottom, spread horizontally
515
+ ship_y = self.terminal_height - 8 # Near bottom
516
+
517
+ for i, ship_type in enumerate(ship_types):
518
+ # Spread ships across the width
519
+ x = (self.terminal_width // 4) * (i + 1) - 5
520
+ ship = Ship(ship_type, x, ship_y, self.terminal_width, self.terminal_height)
521
+ self.ships.append(ship)
522
+
523
+ def update(self, current_time: float):
524
+ """Update all game objects.
525
+
526
+ Args:
527
+ current_time: Current time for animation
528
+ """
529
+ # Update stars
530
+ for star in self.stars:
531
+ star.update(current_time)
532
+
533
+ # Update ships
534
+ for ship in self.ships:
535
+ ship.update(current_time)
536
+
537
+ # Update enemies
538
+ active_enemies = []
539
+ for enemy in self.enemies:
540
+ if enemy.update(current_time):
541
+ active_enemies.append(enemy)
542
+ self.enemies = active_enemies
543
+
544
+ # Update lasers
545
+ active_lasers = []
546
+ for laser in self.lasers:
547
+ if laser.update(current_time):
548
+ active_lasers.append(laser)
549
+ self.lasers = active_lasers
550
+
551
+ # Update explosions
552
+ active_explosions = []
553
+ for explosion in self.explosions:
554
+ if explosion.update(current_time):
555
+ active_explosions.append(explosion)
556
+ self.explosions = active_explosions
557
+
558
+ # Spawn enemies from top
559
+ if current_time - self.last_enemy_spawn > 2.0 and len(self.enemies) < 8:
560
+ if random.random() < 0.04:
561
+ enemy_type = random.choice(['invader_f', 'invader_f', 'boss_galaga'])
562
+ x = random.randint(5, self.terminal_width - 15)
563
+ enemy = Enemy(enemy_type, x, -5, # Start above screen
564
+ self.terminal_width, self.terminal_height)
565
+ self.enemies.append(enemy)
566
+ self.last_enemy_spawn = current_time
567
+
568
+ # Ships fire lasers upward
569
+ if current_time - self.last_laser_time > 0.3:
570
+ for ship in self.ships:
571
+ if random.random() < 0.05:
572
+ # Fire from top center of ship
573
+ laser = Laser(int(ship.x) + 4, int(ship.y) - 1,
574
+ self.terminal_height)
575
+ self.lasers.append(laser)
576
+ self.last_laser_time = current_time
577
+
578
+ # Check for laser-enemy collisions
579
+ for laser in self.lasers[:]:
580
+ for enemy in self.enemies[:]:
581
+ if (abs(laser.x - (enemy.x + 4)) < 4 and
582
+ abs(laser.y - enemy.y) < 4):
583
+ # Explosion!
584
+ self.explosions.append(Explosion(int(enemy.x) + 4, int(enemy.y) + 2))
585
+ if laser in self.lasers:
586
+ self.lasers.remove(laser)
587
+ if enemy in self.enemies:
588
+ self.enemies.remove(enemy)
589
+ self.score += 100 if enemy.enemy_type == 'invader_f' else 250
590
+ break
591
+
592
+ def render(self, renderer):
593
+ """Render all game objects.
594
+
595
+ Args:
596
+ renderer: FullScreenRenderer instance
597
+ """
598
+ # Clear screen
599
+ renderer.clear_screen()
600
+
601
+ # Render stars (background)
602
+ for star in self.stars:
603
+ star.render(renderer)
604
+
605
+ # Render enemies (from top)
606
+ for enemy in self.enemies:
607
+ enemy.render(renderer)
608
+
609
+ # Render lasers
610
+ for laser in self.lasers:
611
+ laser.render(renderer)
612
+
613
+ # Render ships (at bottom)
614
+ for ship in self.ships:
615
+ ship.render(renderer)
616
+
617
+ # Render explosions
618
+ for explosion in self.explosions:
619
+ explosion.render(renderer)
620
+
621
+ # Render HUD
622
+ self._render_hud(renderer)
623
+
624
+ def _render_hud(self, renderer):
625
+ """Render heads-up display.
626
+
627
+ Args:
628
+ renderer: FullScreenRenderer instance
629
+ """
630
+ # Title
631
+ title = "SPACE SQUADRON"
632
+ renderer.write_at(self.terminal_width // 2 - len(title) // 2, 0,
633
+ title, ColorPalette.BRIGHT_CYAN)
634
+
635
+ # Score
636
+ score_text = f"SCORE: {self.score:06d}"
637
+ renderer.write_at(2, 0, score_text, ColorPalette.BRIGHT_GREEN)
638
+
639
+ # Instructions
640
+ instructions = "Press Q or ESC to exit"
641
+ renderer.write_at(self.terminal_width // 2 - len(instructions) // 2,
642
+ self.terminal_height - 1, instructions, ColorPalette.DIM_GREY)
643
+
644
+ def reset(self):
645
+ """Reset the space shooter demo."""
646
+ self._create_starfield()
647
+ self._create_ships()
648
+ self.enemies = []
649
+ self.lasers = []
650
+ self.explosions = []
651
+ self.start_time = 0
652
+ self.last_enemy_spawn = 0
653
+ self.last_laser_time = 0
654
+ self.score = 0
core/fullscreen/plugin.py CHANGED
@@ -48,6 +48,11 @@ class FullScreenPlugin(ABC):
48
48
  self.frame_count = 0
49
49
  self.last_frame_time = 0.0
50
50
 
51
+ # Frame rate control (can be overridden by subclasses)
52
+ # Static plugins (forms, menus) should use 15-20 fps
53
+ # Animated plugins (matrix, effects) should use 60 fps
54
+ self.target_fps = 60.0
55
+
51
56
  logger.info(f"Initialized full-screen plugin: {metadata.name}")
52
57
 
53
58
  @property