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.
- agents/__init__.py +2 -0
- agents/coder/__init__.py +0 -0
- agents/coder/agent.json +4 -0
- agents/coder/api-integration.md +2150 -0
- agents/coder/cli-pretty.md +765 -0
- agents/coder/code-review.md +1092 -0
- agents/coder/database-design.md +1525 -0
- agents/coder/debugging.md +1102 -0
- agents/coder/dependency-management.md +1397 -0
- agents/coder/git-workflow.md +1099 -0
- agents/coder/refactoring.md +1454 -0
- agents/coder/security-hardening.md +1732 -0
- agents/coder/system_prompt.md +1448 -0
- agents/coder/tdd.md +1367 -0
- agents/creative-writer/__init__.py +0 -0
- agents/creative-writer/agent.json +4 -0
- agents/creative-writer/character-development.md +1852 -0
- agents/creative-writer/dialogue-craft.md +1122 -0
- agents/creative-writer/plot-structure.md +1073 -0
- agents/creative-writer/revision-editing.md +1484 -0
- agents/creative-writer/system_prompt.md +690 -0
- agents/creative-writer/worldbuilding.md +2049 -0
- agents/data-analyst/__init__.py +30 -0
- agents/data-analyst/agent.json +4 -0
- agents/data-analyst/data-visualization.md +992 -0
- agents/data-analyst/exploratory-data-analysis.md +1110 -0
- agents/data-analyst/pandas-data-manipulation.md +1081 -0
- agents/data-analyst/sql-query-optimization.md +881 -0
- agents/data-analyst/statistical-analysis.md +1118 -0
- agents/data-analyst/system_prompt.md +928 -0
- agents/default/__init__.py +0 -0
- agents/default/agent.json +4 -0
- agents/default/dead-code.md +794 -0
- agents/default/explore-agent-system.md +585 -0
- agents/default/system_prompt.md +1448 -0
- agents/kollabor/__init__.py +0 -0
- agents/kollabor/analyze-plugin-lifecycle.md +175 -0
- agents/kollabor/analyze-terminal-rendering.md +388 -0
- agents/kollabor/code-review.md +1092 -0
- agents/kollabor/debug-mcp-integration.md +521 -0
- agents/kollabor/debug-plugin-hooks.md +547 -0
- agents/kollabor/debugging.md +1102 -0
- agents/kollabor/dependency-management.md +1397 -0
- agents/kollabor/git-workflow.md +1099 -0
- agents/kollabor/inspect-llm-conversation.md +148 -0
- agents/kollabor/monitor-event-bus.md +558 -0
- agents/kollabor/profile-performance.md +576 -0
- agents/kollabor/refactoring.md +1454 -0
- agents/kollabor/system_prompt copy.md +1448 -0
- agents/kollabor/system_prompt.md +757 -0
- agents/kollabor/trace-command-execution.md +178 -0
- agents/kollabor/validate-config.md +879 -0
- agents/research/__init__.py +0 -0
- agents/research/agent.json +4 -0
- agents/research/architecture-mapping.md +1099 -0
- agents/research/codebase-analysis.md +1077 -0
- agents/research/dependency-audit.md +1027 -0
- agents/research/performance-profiling.md +1047 -0
- agents/research/security-review.md +1359 -0
- agents/research/system_prompt.md +492 -0
- agents/technical-writer/__init__.py +0 -0
- agents/technical-writer/agent.json +4 -0
- agents/technical-writer/api-documentation.md +2328 -0
- agents/technical-writer/changelog-management.md +1181 -0
- agents/technical-writer/readme-writing.md +1360 -0
- agents/technical-writer/style-guide.md +1410 -0
- agents/technical-writer/system_prompt.md +653 -0
- agents/technical-writer/tutorial-creation.md +1448 -0
- core/__init__.py +0 -2
- core/application.py +343 -88
- core/cli.py +229 -10
- core/commands/menu_renderer.py +463 -59
- core/commands/registry.py +14 -9
- core/commands/system_commands.py +2461 -14
- core/config/loader.py +151 -37
- core/config/service.py +18 -6
- core/events/bus.py +29 -9
- core/events/executor.py +205 -75
- core/events/models.py +27 -8
- core/fullscreen/command_integration.py +20 -24
- core/fullscreen/components/__init__.py +10 -1
- core/fullscreen/components/matrix_components.py +1 -2
- core/fullscreen/components/space_shooter_components.py +654 -0
- core/fullscreen/plugin.py +5 -0
- core/fullscreen/renderer.py +52 -13
- core/fullscreen/session.py +52 -15
- core/io/__init__.py +29 -5
- core/io/buffer_manager.py +6 -1
- core/io/config_status_view.py +7 -29
- core/io/core_status_views.py +267 -347
- core/io/input/__init__.py +25 -0
- core/io/input/command_mode_handler.py +711 -0
- core/io/input/display_controller.py +128 -0
- core/io/input/hook_registrar.py +286 -0
- core/io/input/input_loop_manager.py +421 -0
- core/io/input/key_press_handler.py +502 -0
- core/io/input/modal_controller.py +1011 -0
- core/io/input/paste_processor.py +339 -0
- core/io/input/status_modal_renderer.py +184 -0
- core/io/input_errors.py +5 -1
- core/io/input_handler.py +211 -2452
- core/io/key_parser.py +7 -0
- core/io/layout.py +15 -3
- core/io/message_coordinator.py +111 -2
- core/io/message_renderer.py +129 -4
- core/io/status_renderer.py +147 -607
- core/io/terminal_renderer.py +97 -51
- core/io/terminal_state.py +21 -4
- core/io/visual_effects.py +816 -165
- core/llm/agent_manager.py +1063 -0
- core/llm/api_adapters/__init__.py +44 -0
- core/llm/api_adapters/anthropic_adapter.py +432 -0
- core/llm/api_adapters/base.py +241 -0
- core/llm/api_adapters/openai_adapter.py +326 -0
- core/llm/api_communication_service.py +167 -113
- core/llm/conversation_logger.py +322 -16
- core/llm/conversation_manager.py +556 -30
- core/llm/file_operations_executor.py +84 -32
- core/llm/llm_service.py +934 -103
- core/llm/mcp_integration.py +541 -57
- core/llm/message_display_service.py +135 -18
- core/llm/plugin_sdk.py +1 -2
- core/llm/profile_manager.py +1183 -0
- core/llm/response_parser.py +274 -56
- core/llm/response_processor.py +16 -3
- core/llm/tool_executor.py +6 -1
- core/logging/__init__.py +2 -0
- core/logging/setup.py +34 -6
- core/models/resume.py +54 -0
- core/plugins/__init__.py +4 -2
- core/plugins/base.py +127 -0
- core/plugins/collector.py +23 -161
- core/plugins/discovery.py +37 -3
- core/plugins/factory.py +6 -12
- core/plugins/registry.py +5 -17
- core/ui/config_widgets.py +128 -28
- core/ui/live_modal_renderer.py +2 -1
- core/ui/modal_actions.py +5 -0
- core/ui/modal_overlay_renderer.py +0 -60
- core/ui/modal_renderer.py +268 -7
- core/ui/modal_state_manager.py +29 -4
- core/ui/widgets/base_widget.py +7 -0
- core/updates/__init__.py +10 -0
- core/updates/version_check_service.py +348 -0
- core/updates/version_comparator.py +103 -0
- core/utils/config_utils.py +685 -526
- core/utils/plugin_utils.py +1 -1
- core/utils/session_naming.py +111 -0
- fonts/LICENSE +21 -0
- fonts/README.md +46 -0
- fonts/SymbolsNerdFont-Regular.ttf +0 -0
- fonts/SymbolsNerdFontMono-Regular.ttf +0 -0
- fonts/__init__.py +44 -0
- {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/METADATA +54 -4
- kollabor-0.4.15.dist-info/RECORD +228 -0
- {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/top_level.txt +2 -0
- plugins/agent_orchestrator/__init__.py +39 -0
- plugins/agent_orchestrator/activity_monitor.py +181 -0
- plugins/agent_orchestrator/file_attacher.py +77 -0
- plugins/agent_orchestrator/message_injector.py +135 -0
- plugins/agent_orchestrator/models.py +48 -0
- plugins/agent_orchestrator/orchestrator.py +403 -0
- plugins/agent_orchestrator/plugin.py +976 -0
- plugins/agent_orchestrator/xml_parser.py +191 -0
- plugins/agent_orchestrator_plugin.py +9 -0
- plugins/enhanced_input/box_styles.py +1 -0
- plugins/enhanced_input/color_engine.py +19 -4
- plugins/enhanced_input/config.py +2 -2
- plugins/enhanced_input_plugin.py +61 -11
- plugins/fullscreen/__init__.py +6 -2
- plugins/fullscreen/example_plugin.py +1035 -222
- plugins/fullscreen/setup_wizard_plugin.py +592 -0
- plugins/fullscreen/space_shooter_plugin.py +131 -0
- plugins/hook_monitoring_plugin.py +436 -78
- plugins/query_enhancer_plugin.py +66 -30
- plugins/resume_conversation_plugin.py +1494 -0
- plugins/save_conversation_plugin.py +98 -32
- plugins/system_commands_plugin.py +70 -56
- plugins/tmux_plugin.py +154 -78
- plugins/workflow_enforcement_plugin.py +94 -92
- system_prompt/default.md +952 -886
- core/io/input_mode_manager.py +0 -402
- core/io/modal_interaction_handler.py +0 -315
- core/io/raw_input_processor.py +0 -946
- core/storage/__init__.py +0 -5
- core/storage/state_manager.py +0 -84
- core/ui/widget_integration.py +0 -222
- core/utils/key_reader.py +0 -171
- kollabor-0.4.9.dist-info/RECORD +0 -128
- {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/WHEEL +0 -0
- {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/entry_points.txt +0 -0
- {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
|