sqlshell 0.4.4__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.
- sqlshell/__init__.py +84 -0
- sqlshell/__main__.py +4926 -0
- sqlshell/ai_autocomplete.py +392 -0
- sqlshell/ai_settings_dialog.py +337 -0
- sqlshell/context_suggester.py +768 -0
- sqlshell/create_test_data.py +152 -0
- sqlshell/data/create_test_data.py +137 -0
- sqlshell/db/__init__.py +6 -0
- sqlshell/db/database_manager.py +1318 -0
- sqlshell/db/export_manager.py +188 -0
- sqlshell/editor.py +1166 -0
- sqlshell/editor_integration.py +127 -0
- sqlshell/execution_handler.py +421 -0
- sqlshell/menus.py +262 -0
- sqlshell/notification_manager.py +370 -0
- sqlshell/query_tab.py +904 -0
- sqlshell/resources/__init__.py +1 -0
- sqlshell/resources/icon.png +0 -0
- sqlshell/resources/logo_large.png +0 -0
- sqlshell/resources/logo_medium.png +0 -0
- sqlshell/resources/logo_small.png +0 -0
- sqlshell/resources/splash_screen.gif +0 -0
- sqlshell/space_invaders.py +501 -0
- sqlshell/splash_screen.py +405 -0
- sqlshell/sqlshell/__init__.py +5 -0
- sqlshell/sqlshell/create_test_data.py +118 -0
- sqlshell/sqlshell/create_test_databases.py +96 -0
- sqlshell/sqlshell_demo.png +0 -0
- sqlshell/styles.py +257 -0
- sqlshell/suggester_integration.py +330 -0
- sqlshell/syntax_highlighter.py +124 -0
- sqlshell/table_list.py +996 -0
- sqlshell/ui/__init__.py +6 -0
- sqlshell/ui/bar_chart_delegate.py +49 -0
- sqlshell/ui/filter_header.py +469 -0
- sqlshell/utils/__init__.py +16 -0
- sqlshell/utils/profile_cn2.py +1661 -0
- sqlshell/utils/profile_column.py +2635 -0
- sqlshell/utils/profile_distributions.py +616 -0
- sqlshell/utils/profile_entropy.py +347 -0
- sqlshell/utils/profile_foreign_keys.py +779 -0
- sqlshell/utils/profile_keys.py +2834 -0
- sqlshell/utils/profile_ohe.py +934 -0
- sqlshell/utils/profile_ohe_advanced.py +754 -0
- sqlshell/utils/profile_ohe_comparison.py +237 -0
- sqlshell/utils/profile_prediction.py +926 -0
- sqlshell/utils/profile_similarity.py +876 -0
- sqlshell/utils/search_in_df.py +90 -0
- sqlshell/widgets.py +400 -0
- sqlshell-0.4.4.dist-info/METADATA +441 -0
- sqlshell-0.4.4.dist-info/RECORD +54 -0
- sqlshell-0.4.4.dist-info/WHEEL +5 -0
- sqlshell-0.4.4.dist-info/entry_points.txt +2 -0
- sqlshell-0.4.4.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# SQLShell resources package initialization
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,501 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Space Invaders mini-game for SQLShell About menu.
|
|
3
|
+
A classic arcade game implemented in PyQt6.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import random
|
|
7
|
+
from PyQt6.QtWidgets import QWidget, QVBoxLayout, QLabel, QPushButton, QDialog
|
|
8
|
+
from PyQt6.QtCore import Qt, QTimer, QRectF, pyqtSignal
|
|
9
|
+
from PyQt6.QtGui import QPainter, QColor, QFont, QBrush, QPen, QKeyEvent
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class SpaceInvadersGame(QWidget):
|
|
13
|
+
"""The main Space Invaders game widget."""
|
|
14
|
+
|
|
15
|
+
game_over_signal = pyqtSignal(int) # Signal emitted with final score
|
|
16
|
+
|
|
17
|
+
def __init__(self, parent=None):
|
|
18
|
+
super().__init__(parent)
|
|
19
|
+
self.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
|
|
20
|
+
self.setMinimumSize(500, 600)
|
|
21
|
+
|
|
22
|
+
# Game state
|
|
23
|
+
self.game_running = False
|
|
24
|
+
self.game_over = False
|
|
25
|
+
self.score = 0
|
|
26
|
+
self.lives = 3
|
|
27
|
+
self.level = 1
|
|
28
|
+
|
|
29
|
+
# Player
|
|
30
|
+
self.player_x = 250
|
|
31
|
+
self.player_width = 50
|
|
32
|
+
self.player_height = 20
|
|
33
|
+
self.player_speed = 8
|
|
34
|
+
|
|
35
|
+
# Bullets
|
|
36
|
+
self.bullets = []
|
|
37
|
+
self.bullet_speed = 10
|
|
38
|
+
self.can_shoot = True
|
|
39
|
+
self.shoot_cooldown = 250 # ms
|
|
40
|
+
|
|
41
|
+
# Invaders
|
|
42
|
+
self.invaders = []
|
|
43
|
+
self.invader_direction = 1
|
|
44
|
+
self.invader_speed = 2
|
|
45
|
+
self.invader_drop = 20
|
|
46
|
+
self.invader_width = 35
|
|
47
|
+
self.invader_height = 25
|
|
48
|
+
|
|
49
|
+
# Invader bullets
|
|
50
|
+
self.invader_bullets = []
|
|
51
|
+
self.invader_bullet_speed = 5
|
|
52
|
+
|
|
53
|
+
# Stars background
|
|
54
|
+
self.stars = [(random.randint(0, 500), random.randint(0, 600)) for _ in range(100)]
|
|
55
|
+
|
|
56
|
+
# Colors - retro arcade style
|
|
57
|
+
self.bg_color = QColor(5, 5, 20)
|
|
58
|
+
self.player_color = QColor(0, 255, 100)
|
|
59
|
+
self.bullet_color = QColor(255, 255, 0)
|
|
60
|
+
self.invader_colors = [
|
|
61
|
+
QColor(255, 50, 50), # Red
|
|
62
|
+
QColor(255, 150, 50), # Orange
|
|
63
|
+
QColor(255, 255, 50), # Yellow
|
|
64
|
+
QColor(50, 255, 255), # Cyan
|
|
65
|
+
]
|
|
66
|
+
self.star_color = QColor(255, 255, 255, 100)
|
|
67
|
+
|
|
68
|
+
# Timers
|
|
69
|
+
self.game_timer = QTimer(self)
|
|
70
|
+
self.game_timer.timeout.connect(self.game_loop)
|
|
71
|
+
|
|
72
|
+
self.shoot_timer = QTimer(self)
|
|
73
|
+
self.shoot_timer.timeout.connect(self.enable_shooting)
|
|
74
|
+
self.shoot_timer.setSingleShot(True)
|
|
75
|
+
|
|
76
|
+
self.invader_shoot_timer = QTimer(self)
|
|
77
|
+
self.invader_shoot_timer.timeout.connect(self.invader_shoot)
|
|
78
|
+
|
|
79
|
+
# Key states
|
|
80
|
+
self.keys_pressed = set()
|
|
81
|
+
|
|
82
|
+
# Initialize game
|
|
83
|
+
self.init_invaders()
|
|
84
|
+
|
|
85
|
+
def init_invaders(self):
|
|
86
|
+
"""Initialize the invader grid."""
|
|
87
|
+
self.invaders = []
|
|
88
|
+
rows = min(4, 2 + self.level // 2)
|
|
89
|
+
cols = min(10, 6 + self.level // 2)
|
|
90
|
+
|
|
91
|
+
start_x = (500 - cols * 45) // 2
|
|
92
|
+
start_y = 60
|
|
93
|
+
|
|
94
|
+
for row in range(rows):
|
|
95
|
+
for col in range(cols):
|
|
96
|
+
x = start_x + col * 45
|
|
97
|
+
y = start_y + row * 35
|
|
98
|
+
color_idx = row % len(self.invader_colors)
|
|
99
|
+
self.invaders.append({
|
|
100
|
+
'x': x,
|
|
101
|
+
'y': y,
|
|
102
|
+
'color': self.invader_colors[color_idx],
|
|
103
|
+
'alive': True
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
def start_game(self):
|
|
107
|
+
"""Start the game."""
|
|
108
|
+
self.game_running = True
|
|
109
|
+
self.game_over = False
|
|
110
|
+
self.score = 0
|
|
111
|
+
self.lives = 3
|
|
112
|
+
self.level = 1
|
|
113
|
+
self.player_x = 225
|
|
114
|
+
self.bullets = []
|
|
115
|
+
self.invader_bullets = []
|
|
116
|
+
self.invader_direction = 1
|
|
117
|
+
self.invader_speed = 2
|
|
118
|
+
self.init_invaders()
|
|
119
|
+
|
|
120
|
+
self.game_timer.start(16) # ~60 FPS
|
|
121
|
+
self.invader_shoot_timer.start(1500)
|
|
122
|
+
self.setFocus()
|
|
123
|
+
|
|
124
|
+
def stop_game(self):
|
|
125
|
+
"""Stop the game."""
|
|
126
|
+
self.game_running = False
|
|
127
|
+
self.game_timer.stop()
|
|
128
|
+
self.invader_shoot_timer.stop()
|
|
129
|
+
|
|
130
|
+
def game_loop(self):
|
|
131
|
+
"""Main game loop."""
|
|
132
|
+
if not self.game_running:
|
|
133
|
+
return
|
|
134
|
+
|
|
135
|
+
self.handle_input()
|
|
136
|
+
self.update_bullets()
|
|
137
|
+
self.update_invaders()
|
|
138
|
+
self.update_invader_bullets()
|
|
139
|
+
self.check_collisions()
|
|
140
|
+
self.check_level_complete()
|
|
141
|
+
|
|
142
|
+
self.update()
|
|
143
|
+
|
|
144
|
+
def handle_input(self):
|
|
145
|
+
"""Handle player input."""
|
|
146
|
+
if Qt.Key.Key_Left in self.keys_pressed or Qt.Key.Key_A in self.keys_pressed:
|
|
147
|
+
self.player_x = max(0, self.player_x - self.player_speed)
|
|
148
|
+
if Qt.Key.Key_Right in self.keys_pressed or Qt.Key.Key_D in self.keys_pressed:
|
|
149
|
+
self.player_x = min(500 - self.player_width, self.player_x + self.player_speed)
|
|
150
|
+
if Qt.Key.Key_Space in self.keys_pressed and self.can_shoot:
|
|
151
|
+
self.shoot()
|
|
152
|
+
|
|
153
|
+
def shoot(self):
|
|
154
|
+
"""Fire a bullet."""
|
|
155
|
+
if not self.can_shoot:
|
|
156
|
+
return
|
|
157
|
+
|
|
158
|
+
bullet_x = self.player_x + self.player_width // 2 - 2
|
|
159
|
+
bullet_y = 560
|
|
160
|
+
self.bullets.append({'x': bullet_x, 'y': bullet_y})
|
|
161
|
+
|
|
162
|
+
self.can_shoot = False
|
|
163
|
+
self.shoot_timer.start(self.shoot_cooldown)
|
|
164
|
+
|
|
165
|
+
def enable_shooting(self):
|
|
166
|
+
"""Re-enable shooting after cooldown."""
|
|
167
|
+
self.can_shoot = True
|
|
168
|
+
|
|
169
|
+
def invader_shoot(self):
|
|
170
|
+
"""Random invader shoots."""
|
|
171
|
+
if not self.game_running:
|
|
172
|
+
return
|
|
173
|
+
|
|
174
|
+
alive_invaders = [inv for inv in self.invaders if inv['alive']]
|
|
175
|
+
if alive_invaders:
|
|
176
|
+
shooter = random.choice(alive_invaders)
|
|
177
|
+
self.invader_bullets.append({
|
|
178
|
+
'x': shooter['x'] + self.invader_width // 2,
|
|
179
|
+
'y': shooter['y'] + self.invader_height
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
def update_bullets(self):
|
|
183
|
+
"""Update player bullet positions."""
|
|
184
|
+
for bullet in self.bullets[:]:
|
|
185
|
+
bullet['y'] -= self.bullet_speed
|
|
186
|
+
if bullet['y'] < 0:
|
|
187
|
+
self.bullets.remove(bullet)
|
|
188
|
+
|
|
189
|
+
def update_invader_bullets(self):
|
|
190
|
+
"""Update invader bullet positions."""
|
|
191
|
+
for bullet in self.invader_bullets[:]:
|
|
192
|
+
bullet['y'] += self.invader_bullet_speed
|
|
193
|
+
if bullet['y'] > 600:
|
|
194
|
+
self.invader_bullets.remove(bullet)
|
|
195
|
+
|
|
196
|
+
def update_invaders(self):
|
|
197
|
+
"""Update invader positions."""
|
|
198
|
+
alive_invaders = [inv for inv in self.invaders if inv['alive']]
|
|
199
|
+
if not alive_invaders:
|
|
200
|
+
return
|
|
201
|
+
|
|
202
|
+
# Check if any invader hits the edge
|
|
203
|
+
hit_edge = False
|
|
204
|
+
for inv in alive_invaders:
|
|
205
|
+
if self.invader_direction > 0 and inv['x'] + self.invader_width >= 490:
|
|
206
|
+
hit_edge = True
|
|
207
|
+
break
|
|
208
|
+
elif self.invader_direction < 0 and inv['x'] <= 10:
|
|
209
|
+
hit_edge = True
|
|
210
|
+
break
|
|
211
|
+
|
|
212
|
+
if hit_edge:
|
|
213
|
+
self.invader_direction *= -1
|
|
214
|
+
for inv in alive_invaders:
|
|
215
|
+
inv['y'] += self.invader_drop
|
|
216
|
+
else:
|
|
217
|
+
for inv in alive_invaders:
|
|
218
|
+
inv['x'] += self.invader_speed * self.invader_direction
|
|
219
|
+
|
|
220
|
+
def check_collisions(self):
|
|
221
|
+
"""Check for collisions."""
|
|
222
|
+
# Player bullets vs invaders
|
|
223
|
+
for bullet in self.bullets[:]:
|
|
224
|
+
bullet_rect = QRectF(bullet['x'], bullet['y'], 4, 10)
|
|
225
|
+
for inv in self.invaders:
|
|
226
|
+
if not inv['alive']:
|
|
227
|
+
continue
|
|
228
|
+
inv_rect = QRectF(inv['x'], inv['y'], self.invader_width, self.invader_height)
|
|
229
|
+
if bullet_rect.intersects(inv_rect):
|
|
230
|
+
inv['alive'] = False
|
|
231
|
+
if bullet in self.bullets:
|
|
232
|
+
self.bullets.remove(bullet)
|
|
233
|
+
self.score += 10 * self.level
|
|
234
|
+
break
|
|
235
|
+
|
|
236
|
+
# Invader bullets vs player
|
|
237
|
+
player_rect = QRectF(self.player_x, 560, self.player_width, self.player_height)
|
|
238
|
+
for bullet in self.invader_bullets[:]:
|
|
239
|
+
bullet_rect = QRectF(bullet['x'], bullet['y'], 4, 10)
|
|
240
|
+
if bullet_rect.intersects(player_rect):
|
|
241
|
+
self.invader_bullets.remove(bullet)
|
|
242
|
+
self.lives -= 1
|
|
243
|
+
if self.lives <= 0:
|
|
244
|
+
self.end_game()
|
|
245
|
+
|
|
246
|
+
# Check if invaders reached the player
|
|
247
|
+
for inv in self.invaders:
|
|
248
|
+
if inv['alive'] and inv['y'] + self.invader_height >= 540:
|
|
249
|
+
self.end_game()
|
|
250
|
+
break
|
|
251
|
+
|
|
252
|
+
def check_level_complete(self):
|
|
253
|
+
"""Check if all invaders are destroyed."""
|
|
254
|
+
alive_invaders = [inv for inv in self.invaders if inv['alive']]
|
|
255
|
+
if not alive_invaders:
|
|
256
|
+
self.level += 1
|
|
257
|
+
self.invader_speed = min(6, 2 + self.level * 0.5)
|
|
258
|
+
self.init_invaders()
|
|
259
|
+
self.invader_bullets = []
|
|
260
|
+
# Bonus points for completing level
|
|
261
|
+
self.score += 100 * self.level
|
|
262
|
+
|
|
263
|
+
def end_game(self):
|
|
264
|
+
"""End the game."""
|
|
265
|
+
self.game_over = True
|
|
266
|
+
self.stop_game()
|
|
267
|
+
self.game_over_signal.emit(self.score)
|
|
268
|
+
self.update()
|
|
269
|
+
|
|
270
|
+
def paintEvent(self, event):
|
|
271
|
+
"""Render the game."""
|
|
272
|
+
painter = QPainter(self)
|
|
273
|
+
painter.setRenderHint(QPainter.RenderHint.Antialiasing)
|
|
274
|
+
|
|
275
|
+
# Background
|
|
276
|
+
painter.fillRect(self.rect(), self.bg_color)
|
|
277
|
+
|
|
278
|
+
# Stars
|
|
279
|
+
painter.setPen(QPen(self.star_color))
|
|
280
|
+
for star in self.stars:
|
|
281
|
+
painter.drawPoint(star[0], star[1])
|
|
282
|
+
|
|
283
|
+
if not self.game_running and not self.game_over:
|
|
284
|
+
self.draw_start_screen(painter)
|
|
285
|
+
return
|
|
286
|
+
|
|
287
|
+
# Draw player
|
|
288
|
+
painter.setBrush(QBrush(self.player_color))
|
|
289
|
+
painter.setPen(Qt.PenStyle.NoPen)
|
|
290
|
+
# Ship body
|
|
291
|
+
painter.drawRect(int(self.player_x), 565, self.player_width, 15)
|
|
292
|
+
# Ship cockpit
|
|
293
|
+
painter.drawRect(int(self.player_x) + 20, 555, 10, 10)
|
|
294
|
+
|
|
295
|
+
# Draw player bullets
|
|
296
|
+
painter.setBrush(QBrush(self.bullet_color))
|
|
297
|
+
for bullet in self.bullets:
|
|
298
|
+
painter.drawRect(int(bullet['x']), int(bullet['y']), 4, 10)
|
|
299
|
+
|
|
300
|
+
# Draw invaders
|
|
301
|
+
for inv in self.invaders:
|
|
302
|
+
if not inv['alive']:
|
|
303
|
+
continue
|
|
304
|
+
painter.setBrush(QBrush(inv['color']))
|
|
305
|
+
# Invader body
|
|
306
|
+
self.draw_invader(painter, int(inv['x']), int(inv['y']))
|
|
307
|
+
|
|
308
|
+
# Draw invader bullets
|
|
309
|
+
painter.setBrush(QBrush(QColor(255, 100, 100)))
|
|
310
|
+
for bullet in self.invader_bullets:
|
|
311
|
+
painter.drawRect(int(bullet['x']), int(bullet['y']), 4, 10)
|
|
312
|
+
|
|
313
|
+
# Draw UI
|
|
314
|
+
self.draw_ui(painter)
|
|
315
|
+
|
|
316
|
+
if self.game_over:
|
|
317
|
+
self.draw_game_over(painter)
|
|
318
|
+
|
|
319
|
+
def draw_invader(self, painter, x, y):
|
|
320
|
+
"""Draw a pixelated invader."""
|
|
321
|
+
# Main body
|
|
322
|
+
painter.drawRect(x + 5, y + 5, 25, 15)
|
|
323
|
+
# Antennae
|
|
324
|
+
painter.drawRect(x + 2, y, 6, 8)
|
|
325
|
+
painter.drawRect(x + 27, y, 6, 8)
|
|
326
|
+
# Eyes
|
|
327
|
+
painter.setBrush(QBrush(QColor(0, 0, 0)))
|
|
328
|
+
painter.drawRect(x + 10, y + 8, 5, 5)
|
|
329
|
+
painter.drawRect(x + 20, y + 8, 5, 5)
|
|
330
|
+
# Restore color
|
|
331
|
+
painter.setBrush(QBrush(self.invader_colors[0]))
|
|
332
|
+
|
|
333
|
+
def draw_ui(self, painter):
|
|
334
|
+
"""Draw score and lives."""
|
|
335
|
+
painter.setPen(QPen(QColor(255, 255, 255)))
|
|
336
|
+
font = QFont('Courier', 14, QFont.Weight.Bold)
|
|
337
|
+
painter.setFont(font)
|
|
338
|
+
|
|
339
|
+
# Score
|
|
340
|
+
painter.drawText(10, 25, f'SCORE: {self.score}')
|
|
341
|
+
|
|
342
|
+
# Level
|
|
343
|
+
painter.drawText(200, 25, f'LEVEL: {self.level}')
|
|
344
|
+
|
|
345
|
+
# Lives
|
|
346
|
+
painter.drawText(380, 25, f'LIVES: {"♥" * self.lives}')
|
|
347
|
+
|
|
348
|
+
def draw_start_screen(self, painter):
|
|
349
|
+
"""Draw the start screen."""
|
|
350
|
+
painter.setPen(QPen(QColor(0, 255, 100)))
|
|
351
|
+
|
|
352
|
+
# Title
|
|
353
|
+
title_font = QFont('Courier', 32, QFont.Weight.Bold)
|
|
354
|
+
painter.setFont(title_font)
|
|
355
|
+
painter.drawText(self.rect(), Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignTop,
|
|
356
|
+
'\n\nSPACE INVADERS')
|
|
357
|
+
|
|
358
|
+
# SQLShell edition
|
|
359
|
+
sub_font = QFont('Courier', 14)
|
|
360
|
+
painter.setFont(sub_font)
|
|
361
|
+
painter.setPen(QPen(QColor(100, 200, 255)))
|
|
362
|
+
painter.drawText(self.rect(), Qt.AlignmentFlag.AlignHCenter,
|
|
363
|
+
'\n\n\n\n\n\nSQLShell Edition')
|
|
364
|
+
|
|
365
|
+
# Instructions
|
|
366
|
+
painter.setPen(QPen(QColor(255, 255, 255)))
|
|
367
|
+
inst_font = QFont('Courier', 12)
|
|
368
|
+
painter.setFont(inst_font)
|
|
369
|
+
|
|
370
|
+
instructions = [
|
|
371
|
+
'',
|
|
372
|
+
'',
|
|
373
|
+
'← → or A D: Move',
|
|
374
|
+
'SPACE: Shoot',
|
|
375
|
+
'',
|
|
376
|
+
'Press SPACE or click START to play!'
|
|
377
|
+
]
|
|
378
|
+
|
|
379
|
+
y = 300
|
|
380
|
+
for line in instructions:
|
|
381
|
+
painter.drawText(self.rect().adjusted(0, y, 0, 0),
|
|
382
|
+
Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignTop, line)
|
|
383
|
+
y += 25
|
|
384
|
+
|
|
385
|
+
# Draw sample invaders
|
|
386
|
+
painter.setBrush(QBrush(self.invader_colors[0]))
|
|
387
|
+
self.draw_invader(painter, 150, 200)
|
|
388
|
+
painter.setBrush(QBrush(self.invader_colors[1]))
|
|
389
|
+
self.draw_invader(painter, 220, 200)
|
|
390
|
+
painter.setBrush(QBrush(self.invader_colors[2]))
|
|
391
|
+
self.draw_invader(painter, 290, 200)
|
|
392
|
+
|
|
393
|
+
def draw_game_over(self, painter):
|
|
394
|
+
"""Draw game over screen."""
|
|
395
|
+
# Darken background
|
|
396
|
+
painter.fillRect(self.rect(), QColor(0, 0, 0, 150))
|
|
397
|
+
|
|
398
|
+
painter.setPen(QPen(QColor(255, 50, 50)))
|
|
399
|
+
font = QFont('Courier', 36, QFont.Weight.Bold)
|
|
400
|
+
painter.setFont(font)
|
|
401
|
+
painter.drawText(self.rect(), Qt.AlignmentFlag.AlignCenter, 'GAME OVER')
|
|
402
|
+
|
|
403
|
+
painter.setPen(QPen(QColor(255, 255, 255)))
|
|
404
|
+
score_font = QFont('Courier', 18)
|
|
405
|
+
painter.setFont(score_font)
|
|
406
|
+
painter.drawText(self.rect().adjusted(0, 60, 0, 0),
|
|
407
|
+
Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignCenter,
|
|
408
|
+
f'Final Score: {self.score}')
|
|
409
|
+
|
|
410
|
+
painter.setPen(QPen(QColor(100, 255, 100)))
|
|
411
|
+
painter.drawText(self.rect().adjusted(0, 120, 0, 0),
|
|
412
|
+
Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignCenter,
|
|
413
|
+
'Press SPACE to play again')
|
|
414
|
+
|
|
415
|
+
def keyPressEvent(self, event: QKeyEvent):
|
|
416
|
+
"""Handle key press."""
|
|
417
|
+
self.keys_pressed.add(event.key())
|
|
418
|
+
|
|
419
|
+
if event.key() == Qt.Key.Key_Space:
|
|
420
|
+
if not self.game_running:
|
|
421
|
+
self.start_game()
|
|
422
|
+
|
|
423
|
+
event.accept()
|
|
424
|
+
|
|
425
|
+
def keyReleaseEvent(self, event: QKeyEvent):
|
|
426
|
+
"""Handle key release."""
|
|
427
|
+
self.keys_pressed.discard(event.key())
|
|
428
|
+
event.accept()
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
class SpaceInvadersDialog(QDialog):
|
|
432
|
+
"""Dialog wrapper for the Space Invaders game."""
|
|
433
|
+
|
|
434
|
+
def __init__(self, parent=None):
|
|
435
|
+
super().__init__(parent)
|
|
436
|
+
self.setWindowTitle('🎮 Space Invaders - SQLShell Edition')
|
|
437
|
+
self.setMinimumSize(520, 700)
|
|
438
|
+
self.setModal(True)
|
|
439
|
+
|
|
440
|
+
# Dark theme
|
|
441
|
+
self.setStyleSheet("""
|
|
442
|
+
QDialog {
|
|
443
|
+
background-color: #0a0a1a;
|
|
444
|
+
}
|
|
445
|
+
QPushButton {
|
|
446
|
+
background-color: #1a3a1a;
|
|
447
|
+
color: #00ff64;
|
|
448
|
+
border: 2px solid #00ff64;
|
|
449
|
+
padding: 10px 20px;
|
|
450
|
+
font-family: 'Courier New', monospace;
|
|
451
|
+
font-size: 14px;
|
|
452
|
+
font-weight: bold;
|
|
453
|
+
}
|
|
454
|
+
QPushButton:hover {
|
|
455
|
+
background-color: #2a5a2a;
|
|
456
|
+
}
|
|
457
|
+
QPushButton:pressed {
|
|
458
|
+
background-color: #0a2a0a;
|
|
459
|
+
}
|
|
460
|
+
QLabel {
|
|
461
|
+
color: #00ff64;
|
|
462
|
+
font-family: 'Courier New', monospace;
|
|
463
|
+
}
|
|
464
|
+
""")
|
|
465
|
+
|
|
466
|
+
layout = QVBoxLayout(self)
|
|
467
|
+
layout.setContentsMargins(10, 10, 10, 10)
|
|
468
|
+
|
|
469
|
+
# Game widget
|
|
470
|
+
self.game = SpaceInvadersGame(self)
|
|
471
|
+
layout.addWidget(self.game)
|
|
472
|
+
|
|
473
|
+
# Start button
|
|
474
|
+
self.start_btn = QPushButton('▶ START GAME')
|
|
475
|
+
self.start_btn.clicked.connect(self.start_game)
|
|
476
|
+
layout.addWidget(self.start_btn)
|
|
477
|
+
|
|
478
|
+
# Close button
|
|
479
|
+
self.close_btn = QPushButton('✕ CLOSE')
|
|
480
|
+
self.close_btn.clicked.connect(self.close)
|
|
481
|
+
layout.addWidget(self.close_btn)
|
|
482
|
+
|
|
483
|
+
def start_game(self):
|
|
484
|
+
"""Start the game."""
|
|
485
|
+
self.game.start_game()
|
|
486
|
+
self.start_btn.setText('▶ RESTART')
|
|
487
|
+
|
|
488
|
+
def keyPressEvent(self, event: QKeyEvent):
|
|
489
|
+
"""Forward key events to game."""
|
|
490
|
+
self.game.keyPressEvent(event)
|
|
491
|
+
|
|
492
|
+
def keyReleaseEvent(self, event: QKeyEvent):
|
|
493
|
+
"""Forward key events to game."""
|
|
494
|
+
self.game.keyReleaseEvent(event)
|
|
495
|
+
|
|
496
|
+
|
|
497
|
+
def show_space_invaders(parent=None):
|
|
498
|
+
"""Show the Space Invaders game dialog."""
|
|
499
|
+
dialog = SpaceInvadersDialog(parent)
|
|
500
|
+
dialog.exec()
|
|
501
|
+
|