awacke1 commited on
Commit
13f01f5
·
verified ·
1 Parent(s): 25a8c8d

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +620 -18
index.html CHANGED
@@ -1,19 +1,621 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  </html>
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Asteroids</title>
7
+ <link rel="preconnect" href="https://fonts.googleapis.com">
8
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
9
+ <link href="https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap" rel="stylesheet">
10
+ <style>
11
+ body {
12
+ margin: 0;
13
+ padding: 0;
14
+ background-color: #000;
15
+ color: #fff;
16
+ font-family: 'Press Start 2P', cursive;
17
+ display: flex;
18
+ flex-direction: column;
19
+ align-items: center;
20
+ justify-content: center;
21
+ height: 100vh;
22
+ overflow: hidden;
23
+ }
24
+
25
+ #game-container {
26
+ position: relative;
27
+ width: 100%;
28
+ max-width: 800px;
29
+ aspect-ratio: 4 / 3;
30
+ border: 2px solid #fff;
31
+ box-shadow: 0 0 20px #fff;
32
+ }
33
+
34
+ canvas {
35
+ display: block;
36
+ background-color: #000;
37
+ width: 100%;
38
+ height: 100%;
39
+ }
40
+
41
+ #score-container {
42
+ position: absolute;
43
+ top: 10px;
44
+ left: 0;
45
+ width: 100%;
46
+ display: flex;
47
+ justify-content: space-between;
48
+ padding: 0 20px;
49
+ box-sizing: border-box;
50
+ font-size: 16px;
51
+ text-shadow: 0 0 5px #fff;
52
+ }
53
+
54
+ #message-overlay {
55
+ position: absolute;
56
+ top: 50%;
57
+ left: 50%;
58
+ transform: translate(-50%, -50%);
59
+ text-align: center;
60
+ font-size: 24px;
61
+ display: none; /* Hidden by default */
62
+ }
63
+
64
+ #message-overlay p {
65
+ margin: 0;
66
+ padding: 10px;
67
+ }
68
+ </style>
69
+ </head>
70
+ <body>
71
+
72
+ <div id="game-container">
73
+ <div id="score-container">
74
+ <span id="score">SCORE: 0</span>
75
+ <span id="high-score">HIGH: 0</span>
76
+ </div>
77
+ <canvas id="gameCanvas"></canvas>
78
+ <div id="message-overlay">
79
+ <p id="message-title">ASTEROIDS</p>
80
+ <p id="message-subtitle" style="font-size: 14px;">PRESS ENTER TO START</p>
81
+ </div>
82
+ </div>
83
+
84
+ <script>
85
+ // --- DOM ELEMENTS ---
86
+ const canvas = document.getElementById('gameCanvas');
87
+ const ctx = canvas.getContext('2d');
88
+ const scoreEl = document.getElementById('score');
89
+ const highScoreEl = document.getElementById('high-score');
90
+ const messageOverlay = document.getElementById('message-overlay');
91
+ const messageTitle = document.getElementById('message-title');
92
+ const messageSubtitle = document.getElementById('message-subtitle');
93
+
94
+ // --- GAME CONSTANTS ---
95
+ const SHIP_SIZE = 15;
96
+ const SHIP_THRUST = 0.1;
97
+ const SHIP_TURN_SPEED = 0.1; // radians
98
+ const FRICTION = 0.99;
99
+ const BULLET_SPEED = 5;
100
+ const BULLET_MAX = 10;
101
+ const ASTEROID_NUM = 3;
102
+ const ASTEROID_SPEED = 1;
103
+ const ASTEROID_SIZE_LARGE = 50;
104
+ const ASTEROID_SIZE_MEDIUM = 25;
105
+ const ASTEROID_SIZE_SMALL = 12;
106
+ const ASTEROID_VERTICES = 10;
107
+ const ASTEROID_JAG = 0.4; // Jaggedness of the asteroids
108
+ const SAUCER_SPEED = 2;
109
+ const SAUCER_SIZE = 15;
110
+ const SAUCER_FIRE_RATE = 0.03; // ~ every second at 30fps
111
+ const SAUCER_SPAWN_TIME = 15000; // 15 seconds
112
+
113
+ // --- GAME STATE ---
114
+ let ship;
115
+ let asteroids = [];
116
+ let bullets = [];
117
+ let saucer = null;
118
+ let score = 0;
119
+ let highScore = localStorage.getItem('asteroidsHighScore') || 0;
120
+ let lives = 3;
121
+ let isPlaying = false;
122
+ let keys = {};
123
+ let saucerTimer;
124
+
125
+ // --- UTILITY FUNCTIONS ---
126
+ const degToRad = (deg) => deg * Math.PI / 180;
127
+ const radToDeg = (rad) => rad * 180 / Math.PI;
128
+
129
+ function resizeCanvas() {
130
+ const container = document.getElementById('game-container');
131
+ const { width, height } = container.getBoundingClientRect();
132
+ canvas.width = width;
133
+ canvas.height = height;
134
+ }
135
+
136
+ // --- CLASSES ---
137
+
138
+ class Ship {
139
+ constructor() {
140
+ this.x = canvas.width / 2;
141
+ this.y = canvas.height / 2;
142
+ this.radius = SHIP_SIZE / 2;
143
+ this.angle = degToRad(270); // Pointing up
144
+ this.vel = { x: 0, y: 0 };
145
+ this.isThrusting = false;
146
+ this.canShoot = true;
147
+ this.isInvincible = true;
148
+ this.invincibilityTime = 3000; // 3 seconds
149
+ setTimeout(() => this.isInvincible = false, this.invincibilityTime);
150
+ }
151
+
152
+ draw() {
153
+ ctx.strokeStyle = this.isInvincible ? 'grey' : 'white';
154
+ ctx.lineWidth = SHIP_SIZE / 10;
155
+ ctx.beginPath();
156
+ // Nose of the ship
157
+ ctx.moveTo(
158
+ this.x + this.radius * Math.cos(this.angle),
159
+ this.y + this.radius * Math.sin(this.angle)
160
+ );
161
+ // Left wing
162
+ ctx.lineTo(
163
+ this.x - this.radius * (Math.cos(this.angle) + Math.sin(this.angle)),
164
+ this.y - this.radius * (Math.sin(this.angle) - Math.cos(this.angle))
165
+ );
166
+ // Right wing
167
+ ctx.lineTo(
168
+ this.x - this.radius * (Math.cos(this.angle) - Math.sin(this.angle)),
169
+ this.y - this.radius * (Math.sin(this.angle) + Math.cos(this.angle))
170
+ );
171
+ ctx.closePath();
172
+ ctx.stroke();
173
+
174
+ // Draw thrust flame
175
+ if (this.isThrusting) {
176
+ ctx.fillStyle = "red";
177
+ ctx.strokeStyle = "yellow";
178
+ ctx.lineWidth = SHIP_SIZE / 15;
179
+ ctx.beginPath();
180
+ // Flame point
181
+ ctx.moveTo(
182
+ this.x - this.radius * (1.5 * Math.cos(this.angle) - 0.5 * Math.sin(this.angle)),
183
+ this.y - this.radius * (1.5 * Math.sin(this.angle) + 0.5 * Math.cos(this.angle))
184
+ );
185
+ // Flame base center
186
+ ctx.lineTo(
187
+ this.x - this.radius * 2.5 * Math.cos(this.angle),
188
+ this.y - this.radius * 2.5 * Math.sin(this.angle)
189
+ );
190
+ // Flame point
191
+ ctx.lineTo(
192
+ this.x - this.radius * (1.5 * Math.cos(this.angle) + 0.5 * Math.sin(this.angle)),
193
+ this.y - this.radius * (1.5 * Math.sin(this.angle) - 0.5 * Math.cos(this.angle))
194
+ );
195
+ ctx.closePath();
196
+ ctx.fill();
197
+ ctx.stroke();
198
+ }
199
+ }
200
+
201
+ update() {
202
+ // Rotate ship
203
+ if (keys['a'] || keys['A']) {
204
+ this.angle -= SHIP_TURN_SPEED;
205
+ }
206
+ if (keys['d'] || keys['D']) {
207
+ this.angle += SHIP_TURN_SPEED;
208
+ }
209
+
210
+ // Thrust
211
+ this.isThrusting = (keys['w'] || keys['W'] || keys['e'] || keys['E']);
212
+ if (this.isThrusting) {
213
+ this.vel.x += SHIP_THRUST * Math.cos(this.angle);
214
+ this.vel.y += SHIP_THRUST * Math.sin(this.angle);
215
+ }
216
+
217
+ // Apply friction
218
+ this.vel.x *= FRICTION;
219
+ this.vel.y *= FRICTION;
220
+
221
+ // Move ship
222
+ this.x += this.vel.x;
223
+ this.y += this.vel.y;
224
+
225
+ // Handle screen wrapping
226
+ this.handleScreenWrap();
227
+ this.draw();
228
+ }
229
+
230
+ shoot() {
231
+ if (this.canShoot && bullets.length < BULLET_MAX) {
232
+ const bullet = new Bullet(
233
+ this.x + this.radius * Math.cos(this.angle),
234
+ this.y + this.radius * Math.sin(this.angle),
235
+ this.angle
236
+ );
237
+ bullets.push(bullet);
238
+ this.canShoot = false;
239
+ setTimeout(() => this.canShoot = true, 250); // Cooldown
240
+ }
241
+ }
242
+
243
+ handleScreenWrap() {
244
+ if (this.x < 0 - this.radius) this.x = canvas.width + this.radius;
245
+ if (this.x > canvas.width + this.radius) this.x = 0 - this.radius;
246
+ if (this.y < 0 - this.radius) this.y = canvas.height + this.radius;
247
+ if (this.y > canvas.height + this.radius) this.y = 0 - this.radius;
248
+ }
249
+
250
+ destroy() {
251
+ if (this.isInvincible) return;
252
+ lives--;
253
+ if (lives > 0) {
254
+ ship = new Ship();
255
+ } else {
256
+ gameOver();
257
+ }
258
+ }
259
+ }
260
+
261
+ class Bullet {
262
+ constructor(x, y, angle) {
263
+ this.x = x;
264
+ this.y = y;
265
+ this.vel = {
266
+ x: BULLET_SPEED * Math.cos(angle),
267
+ y: BULLET_SPEED * Math.sin(angle)
268
+ };
269
+ this.radius = 2;
270
+ this.lifespan = 80; // frames
271
+ }
272
+
273
+ draw() {
274
+ ctx.fillStyle = 'white';
275
+ ctx.beginPath();
276
+ ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
277
+ ctx.fill();
278
+ }
279
+
280
+ update() {
281
+ this.x += this.vel.x;
282
+ this.y += this.vel.y;
283
+ this.lifespan--;
284
+ this.draw();
285
+ }
286
+ }
287
+
288
+ class Asteroid {
289
+ constructor(x, y, radius) {
290
+ this.x = x || Math.random() * canvas.width;
291
+ this.y = y || Math.random() * canvas.height;
292
+ this.radius = radius || ASTEROID_SIZE_LARGE;
293
+ this.vel = {
294
+ x: (Math.random() * ASTEROID_SPEED * 2 - ASTEROID_SPEED),
295
+ y: (Math.random() * ASTEROID_SPEED * 2 - ASTEROID_SPEED)
296
+ };
297
+ this.angle = 0;
298
+ this.angleVel = (Math.random() - 0.5) * 0.02;
299
+
300
+ // Create a jagged shape
301
+ this.vertices = [];
302
+ for (let i = 0; i < ASTEROID_VERTICES; i++) {
303
+ this.vertices.push(Math.random() * ASTEROID_JAG * 2 + 1 - ASTEROID_JAG);
304
+ }
305
+ }
306
+
307
+ draw() {
308
+ ctx.strokeStyle = 'white';
309
+ ctx.lineWidth = 2;
310
+ ctx.beginPath();
311
+ let vertAngle = ((Math.PI * 2) / ASTEROID_VERTICES);
312
+ ctx.moveTo(
313
+ this.x + this.radius * this.vertices[0] * Math.cos(this.angle),
314
+ this.y + this.radius * this.vertices[0] * Math.sin(this.angle)
315
+ );
316
+ for (let i = 1; i < ASTEROID_VERTICES; i++) {
317
+ ctx.lineTo(
318
+ this.x + this.radius * this.vertices[i] * Math.cos(this.angle + i * vertAngle),
319
+ this.y + this.radius * this.vertices[i] * Math.sin(this.angle + i * vertAngle)
320
+ );
321
+ }
322
+ ctx.closePath();
323
+ ctx.stroke();
324
+ }
325
+
326
+ update() {
327
+ this.x += this.vel.x;
328
+ this.y += this.vel.y;
329
+ this.angle += this.angleVel;
330
+
331
+ // Handle screen wrapping
332
+ if (this.x < 0 - this.radius) this.x = canvas.width + this.radius;
333
+ if (this.x > canvas.width + this.radius) this.x = 0 - this.radius;
334
+ if (this.y < 0 - this.radius) this.y = canvas.height + this.radius;
335
+ if (this.y > canvas.height + this.radius) this.y = 0 - this.radius;
336
+
337
+ this.draw();
338
+ }
339
+
340
+ breakup() {
341
+ if (this.radius === ASTEROID_SIZE_LARGE) {
342
+ asteroids.push(new Asteroid(this.x, this.y, ASTEROID_SIZE_MEDIUM));
343
+ asteroids.push(new Asteroid(this.x, this.y, ASTEROID_SIZE_MEDIUM));
344
+ updateScore(20);
345
+ } else if (this.radius === ASTEROID_SIZE_MEDIUM) {
346
+ asteroids.push(new Asteroid(this.x, this.y, ASTEROID_SIZE_SMALL));
347
+ asteroids.push(new Asteroid(this.x, this.y, ASTEROID_SIZE_SMALL));
348
+ updateScore(50);
349
+ } else {
350
+ updateScore(100);
351
+ }
352
+ }
353
+ }
354
+
355
+ class Saucer {
356
+ constructor() {
357
+ this.x = Math.random() > 0.5 ? 0 - SAUCER_SIZE : canvas.width + SAUCER_SIZE;
358
+ this.y = Math.random() * canvas.height;
359
+ this.radius = SAUCER_SIZE;
360
+ this.vel = {
361
+ x: this.x < 0 ? SAUCER_SPEED : -SAUCER_SPEED,
362
+ y: 0
363
+ };
364
+ this.bullets = [];
365
+ }
366
+
367
+ draw() {
368
+ ctx.strokeStyle = 'white';
369
+ ctx.lineWidth = 2;
370
+ ctx.beginPath();
371
+ ctx.moveTo(this.x - this.radius, this.y);
372
+ ctx.lineTo(this.x + this.radius, this.y);
373
+ ctx.moveTo(this.x - this.radius / 2, this.y - this.radius / 2);
374
+ ctx.lineTo(this.x + this.radius / 2, this.y - this.radius / 2);
375
+ ctx.moveTo(this.x - this.radius, this.y);
376
+ ctx.quadraticCurveTo(this.x, this.y - this.radius, this.x + this.radius, this.y);
377
+ ctx.closePath();
378
+ ctx.stroke();
379
+ }
380
+
381
+ update() {
382
+ this.x += this.vel.x;
383
+ this.y += this.vel.y;
384
+
385
+ // Saucer shoots at player
386
+ if (Math.random() < SAUCER_FIRE_RATE && ship) {
387
+ const angleToShip = Math.atan2(ship.y - this.y, ship.x - this.x);
388
+ const bullet = new Bullet(this.x, this.y, angleToShip);
389
+ this.bullets.push(bullet);
390
+ }
391
+
392
+ // Update saucer bullets
393
+ for (let i = this.bullets.length - 1; i >= 0; i--) {
394
+ this.bullets[i].update();
395
+ if (this.bullets[i].lifespan <= 0) {
396
+ this.bullets.splice(i, 1);
397
+ }
398
+ }
399
+
400
+ this.draw();
401
+ }
402
+ }
403
+
404
+ // --- GAME LOGIC ---
405
+
406
+ function init() {
407
+ resizeCanvas();
408
+ highScoreEl.textContent = `HIGH: ${highScore}`;
409
+ showMessage("ASTEROIDS", "PRESS ENTER TO START");
410
+ }
411
+
412
+ function startGame() {
413
+ isPlaying = true;
414
+ score = 0;
415
+ lives = 3;
416
+ updateScore(0);
417
+ messageOverlay.style.display = 'none';
418
+
419
+ ship = new Ship();
420
+
421
+ // Create initial asteroids
422
+ asteroids = [];
423
+ for (let i = 0; i < ASTEROID_NUM; i++) {
424
+ asteroids.push(new Asteroid());
425
+ }
426
+
427
+ // Start saucer timer
428
+ clearTimeout(saucerTimer);
429
+ saucerTimer = setTimeout(spawnSaucer, SAUCER_SPAWN_TIME);
430
+
431
+ gameLoop();
432
+ }
433
+
434
+ function gameOver() {
435
+ isPlaying = false;
436
+ ship = null;
437
+ if (score > highScore) {
438
+ highScore = score;
439
+ localStorage.setItem('asteroidsHighScore', highScore);
440
+ highScoreEl.textContent = `HIGH: ${highScore}`;
441
+ }
442
+ clearTimeout(saucerTimer);
443
+ saucer = null;
444
+ showMessage("GAME OVER", "PRESS ENTER TO RESTART");
445
+ }
446
+
447
+ function showMessage(title, subtitle) {
448
+ messageTitle.textContent = title;
449
+ messageSubtitle.textContent = subtitle;
450
+ messageOverlay.style.display = 'block';
451
+ }
452
+
453
+ function spawnSaucer() {
454
+ if (isPlaying) {
455
+ saucer = new Saucer();
456
+ clearTimeout(saucerTimer);
457
+ saucerTimer = setTimeout(spawnSaucer, SAUCER_SPAWN_TIME);
458
+ }
459
+ }
460
+
461
+ function updateScore(points) {
462
+ score += points;
463
+ scoreEl.textContent = `SCORE: ${score}`;
464
+ }
465
+
466
+ function checkCollisions() {
467
+ // Ship with asteroids
468
+ if (ship) {
469
+ for (let i = asteroids.length - 1; i >= 0; i--) {
470
+ const ast = asteroids[i];
471
+ if (isColliding(ship, ast)) {
472
+ ship.destroy();
473
+ ast.breakup();
474
+ asteroids.splice(i, 1);
475
+ break; // Prevent multiple collisions in one frame
476
+ }
477
+ }
478
+ }
479
+
480
+ // Bullets with asteroids
481
+ for (let i = bullets.length - 1; i >= 0; i--) {
482
+ const bullet = bullets[i];
483
+ for (let j = asteroids.length - 1; j >= 0; j--) {
484
+ const ast = asteroids[j];
485
+ if (isColliding(bullet, ast)) {
486
+ ast.breakup();
487
+ asteroids.splice(j, 1);
488
+ bullets.splice(i, 1);
489
+ break; // Bullet can only hit one asteroid
490
+ }
491
+ }
492
+ }
493
+
494
+ // Saucer logic
495
+ if (saucer) {
496
+ // Ship bullets with saucer
497
+ for (let i = bullets.length - 1; i >= 0; i--) {
498
+ if (isColliding(bullets[i], saucer)) {
499
+ updateScore(200);
500
+ saucer = null;
501
+ bullets.splice(i, 1);
502
+ break;
503
+ }
504
+ }
505
+
506
+ if (saucer) {
507
+ // Ship with saucer
508
+ if (ship && isColliding(ship, saucer)) {
509
+ ship.destroy();
510
+ saucer = null;
511
+ }
512
+ // Saucer bullets with ship
513
+ else if (ship) {
514
+ for (let i = saucer.bullets.length - 1; i >= 0; i--) {
515
+ if(isColliding(ship, saucer.bullets[i])) {
516
+ ship.destroy();
517
+ saucer.bullets.splice(i, 1);
518
+ break;
519
+ }
520
+ }
521
+ }
522
+ }
523
+ }
524
+ }
525
+
526
+ function isColliding(obj1, obj2) {
527
+ const dist = Math.sqrt(Math.pow(obj1.x - obj2.x, 2) + Math.pow(obj1.y - obj2.y, 2));
528
+ return dist < obj1.radius + obj2.radius;
529
+ }
530
+
531
+ function drawLives() {
532
+ let startX = canvas.width - 60;
533
+ for (let i = 0; i < lives; i++) {
534
+ ctx.save();
535
+ ctx.translate(startX - i * (SHIP_SIZE + 5), 30);
536
+ ctx.rotate(degToRad(-90));
537
+ ctx.strokeStyle = 'white';
538
+ ctx.lineWidth = 1;
539
+ ctx.beginPath();
540
+ ctx.moveTo(0, -SHIP_SIZE / 2);
541
+ ctx.lineTo(SHIP_SIZE / 2, SHIP_SIZE / 2);
542
+ ctx.lineTo(-SHIP_SIZE / 2, SHIP_SIZE / 2);
543
+ ctx.closePath();
544
+ ctx.stroke();
545
+ ctx.restore();
546
+ }
547
+ }
548
+
549
+ function gameLoop() {
550
+ if (!isPlaying) return;
551
+
552
+ // Clear canvas
553
+ ctx.fillStyle = 'black';
554
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
555
+
556
+ // Update and draw ship
557
+ if (ship) {
558
+ ship.update();
559
+ }
560
+
561
+ // Update and draw asteroids
562
+ for (let i = 0; i < asteroids.length; i++) {
563
+ asteroids[i].update();
564
+ }
565
+
566
+ // Update and draw bullets
567
+ for (let i = bullets.length - 1; i >= 0; i--) {
568
+ bullets[i].update();
569
+ if (bullets[i].lifespan <= 0) {
570
+ bullets.splice(i, 1);
571
+ }
572
+ }
573
+
574
+ // Update and draw saucer
575
+ if (saucer) {
576
+ saucer.update();
577
+ // Remove saucer if it goes off-screen
578
+ if (saucer.x < 0 - saucer.radius || saucer.x > canvas.width + saucer.radius) {
579
+ saucer = null;
580
+ }
581
+ }
582
+
583
+ // Check for collisions
584
+ checkCollisions();
585
+
586
+ // Draw lives
587
+ drawLives();
588
+
589
+ // Check for level clear
590
+ if (asteroids.length === 0) {
591
+ lives++;
592
+ startGame();
593
+ }
594
+
595
+ requestAnimationFrame(gameLoop);
596
+ }
597
+
598
+ // --- EVENT LISTENERS ---
599
+ window.addEventListener('keydown', (e) => {
600
+ if (e.key === 'Enter' && !isPlaying) {
601
+ startGame();
602
+ }
603
+ keys[e.key] = true;
604
+ if (e.key === ' ' && ship) { // Spacebar for shooting
605
+ e.preventDefault();
606
+ ship.shoot();
607
+ }
608
+ });
609
+
610
+ window.addEventListener('keyup', (e) => {
611
+ keys[e.key] = false;
612
+ });
613
+
614
+ window.addEventListener('resize', resizeCanvas);
615
+
616
+ // --- INITIALIZE ---
617
+ init();
618
+
619
+ </script>
620
+ </body>
621
  </html>