Sebastiankay commited on
Commit
44f8732
1 Parent(s): 41c3a47

Update index.html

Browse files

adding initial index.html

Files changed (1) hide show
  1. index.html +709 -12
index.html CHANGED
@@ -1,19 +1,716 @@
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>
3
  <head>
4
+ <title>Bubble Shooter</title>
5
+ <meta charset="UTF-8" />
6
+ <meta name="description" content="One-minute creation by AI Coding Autonomous Agent MOUSE-I" />
7
+ <meta name="keywords" content="AI Coding, Bubble Shooter, MOUSE-I, Sebastian Kay, Browser game" />
8
+ <meta name="author" content="Sebastian Kay" />
9
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
10
+ <link href="https://fonts.googleapis.com/css2?family=Tiny5&display=swap" rel="stylesheet" />
11
+ <style>
12
+ * {
13
+ margin: 0;
14
+ padding: 0;
15
+ box-sizing: border-box;
16
+ font-family: "Tiny5", cursive;
17
+ }
18
+
19
+ body {
20
+ background-color: #1a1a1a;
21
+ display: flex;
22
+ flex-direction: column;
23
+ align-items: center;
24
+ min-height: 100vh;
25
+ color: #fff;
26
+ }
27
+
28
+ .game-container {
29
+ position: relative;
30
+ margin: auto 0;
31
+ background: #2d2d2d;
32
+ padding: 20px;
33
+ border-radius: 10px;
34
+ box-shadow: 0 0 20px rgba(0, 0, 0, 0.3);
35
+ }
36
+
37
+ .info-panel {
38
+ display: flex;
39
+ justify-content: space-between;
40
+ align-items: center;
41
+ margin-bottom: 10px;
42
+ padding: 10px;
43
+ background: rgba(0, 0, 0, 0.2);
44
+ border-radius: 5px;
45
+ font-size: 14px;
46
+ line-height: 1.5;
47
+ }
48
+
49
+ canvas {
50
+ border: 2px solid #444;
51
+ border-radius: 5px;
52
+ }
53
+
54
+ .controls {
55
+ position: absolute;
56
+ margin-top: 26px;
57
+ left: 0;
58
+ right: 0;
59
+ text-align: center;
60
+ font-size: 0.9rem;
61
+ color: rgba(136, 136, 136, 0.2);
62
+ }
63
+
64
+ button {
65
+ background: #444;
66
+ color: #fff;
67
+ border: none;
68
+ padding: 10px 20px;
69
+ font-size: 12px;
70
+ cursor: pointer;
71
+ margin: 5px;
72
+ border-radius: 5px;
73
+ font-family: "Tiny5", cursive;
74
+ }
75
+
76
+ button:hover {
77
+ background: #555;
78
+ }
79
+
80
+ div.game-over,
81
+ .start-screen {
82
+ position: absolute;
83
+ top: 50%;
84
+ left: 50%;
85
+ transform: translate(-50%, -50%);
86
+ text-align: center;
87
+ display: none;
88
+ z-index: 20;
89
+ h2 {
90
+ --h-bg-color: #b9fecd;
91
+ background: var(--h-bg-color);
92
+ border: 5px solid var(--h-bg-color);
93
+ transform: rotate(-1deg);
94
+ font-size: 2.4rem;
95
+ color: rgba(0, 0, 0, 0.5);
96
+ border-radius: 5px;
97
+ }
98
+ h2.game-over {
99
+ --h-bg-color: #ffb3ba !important;
100
+ background: var(--h-bg-color);
101
+ border: 5px solid var(--h-bg-color);
102
+ transform: rotate(-1deg);
103
+ font-size: 2.4rem;
104
+ color: rgba(0, 0, 0, 0.5);
105
+ border-radius: 5px;
106
+ }
107
+ }
108
+
109
+ .pause-screen {
110
+ inset: 0;
111
+ backdrop-filter: blur(2px);
112
+ background: rgba(0, 0, 0, 0.5);
113
+ position: absolute;
114
+ display: none;
115
+ z-index: 20;
116
+ .inner {
117
+ position: absolute;
118
+ top: 50%;
119
+ left: 50%;
120
+ transform: translate(-50%, -50%);
121
+ text-align: center;
122
+ background: rgba(0, 0, 0, 0.7);
123
+ padding: 40px;
124
+ border-radius: 10px;
125
+ }
126
+ }
127
+
128
+ .score {
129
+ position: absolute;
130
+ top: 20px;
131
+ right: 20px;
132
+ text-align: right;
133
+ }
134
+ </style>
135
  </head>
136
  <body>
137
+ <div class="start-screen">
138
+ <h2>BUBBLE SHOOTER</h2>
139
+ <button id="startButton">START GAME</button>
 
 
 
 
140
  </div>
141
+ <div class="game-over">
142
+ <h2 class="game-over">GAME OVER</h2>
143
+ <button id="restartButton">PLAY AGAIN</button>
144
+ </div>
145
+ <div class="pause-screen">
146
+ <div class="inner">
147
+ <h2>PAUSED</h2>
148
+ <button id="resumeButton">RESUME</button>
149
+ </div>
150
+ </div>
151
+ <div class="game-container">
152
+ <div class="info-panel">
153
+ <div>LEVEL: <span id="level">1</span></div>
154
+ <div>SCORE: <span id="score">0</span></div>
155
+ <div>HIGH: <span id="highScore">0</span></div>
156
+ <button id="pauseButton" class="btn">ǁ (P)</button>
157
+ </div>
158
+ <canvas width="400" height="600" id="game"></canvas>
159
+ <div class="controls">
160
+ <p>← → : AIM | ↑ : SHOOT | P : PAUSE</p>
161
+ <p>OR USE MOUSE TO AIM AND CLICK TO SHOOT</p>
162
+ </div>
163
+ </div>
164
+
165
+ <script>
166
+ const canvas = document.getElementById("game")
167
+ const context = canvas.getContext("2d")
168
+ const grid = 39
169
+ const body = document.querySelector("body")
170
+ const buttonsAll = document.getElementsByTagName("button")
171
+ const startButton = document.getElementById("startButton")
172
+ const restartButton = document.getElementById("restartButton")
173
+ const pauseButton = document.getElementById("pauseButton")
174
+ const resumeButton = document.getElementById("resumeButton")
175
+
176
+ const levelValue = document.getElementById("level")
177
+ const scoreValue = document.getElementById("score")
178
+ const highScoreValue = document.getElementById("highScore")
179
+
180
+ const startScreen = document.querySelector(".start-screen")
181
+ const gameoverScreen = document.querySelector("div.game-over")
182
+
183
+ let addedLevel = 0
184
+ let isOddRow = false
185
+ let addRowInterval
186
+
187
+ function initGame() {
188
+ gameState = {
189
+ isGameOver: false,
190
+ isPaused: false,
191
+ isPlaying: true,
192
+ score: 0,
193
+ level: 1,
194
+ }
195
+
196
+ let addRowInterval
197
+ updateUI()
198
+ }
199
+
200
+ // each even row is 10 bubbles long and each odd row is 9 bubbles long.
201
+ const level1 = Array.from({ length: 4 }, () => Array.from({ length: 10 }, () => Math.floor(Math.random() * 4)))
202
+
203
+ // create a mapping between color short code (R, G, B, Y) and color name
204
+ const colorMap = {
205
+ 0: "#FFB3BA",
206
+ 1: "#BAFFC9",
207
+ 2: "#BAE1FF",
208
+ 3: "#FFFFBA",
209
+ }
210
+ const colors = Object.values(colorMap)
211
+
212
+ // use a 1px gap between each bubble
213
+ const bubbleGap = 0.8
214
+
215
+ // the size of the outer walls for the game
216
+ const wallSize = 0
217
+ const bubbles = []
218
+ let particles = []
219
+
220
+ // helper function to convert deg to radians
221
+ function degToRad(deg) {
222
+ return (deg * Math.PI) / 180
223
+ }
224
+
225
+ // rotate a point by an angle
226
+ function rotatePoint(x, y, angle) {
227
+ let sin = Math.sin(angle)
228
+ let cos = Math.cos(angle)
229
+
230
+ return {
231
+ x: x * cos - y * sin,
232
+ y: x * sin + y * cos,
233
+ }
234
+ }
235
+
236
+ // get a random integer between the range of [min,max]
237
+ function getRandomInt(min, max) {
238
+ min = Math.ceil(min)
239
+ max = Math.floor(max)
240
+
241
+ return Math.floor(Math.random() * (max - min + 1)) + min
242
+ }
243
+
244
+ // get the distance between two points
245
+ function getDistance(obj1, obj2) {
246
+ const distX = obj1.x - obj2.x
247
+ const distY = obj1.y - obj2.y
248
+ return Math.sqrt(distX * distX + distY * distY)
249
+ }
250
+
251
+ // check for collision between two circles
252
+ function collides(obj1, obj2) {
253
+ return getDistance(obj1, obj2) < obj1.radius + obj2.radius
254
+ }
255
+
256
+ // find the closest bubbles that collide with the object
257
+ function getClosestBubble(obj, activeState = false) {
258
+ const closestBubbles = bubbles.filter((bubble) => bubble.active == activeState && collides(obj, bubble))
259
+
260
+ if (!closestBubbles.length) {
261
+ return
262
+ }
263
+
264
+ return (
265
+ closestBubbles
266
+ // turn the array of bubbles into an array of distances
267
+ .map((bubble) => {
268
+ return {
269
+ distance: getDistance(obj, bubble),
270
+ bubble,
271
+ }
272
+ })
273
+ .sort((a, b) => a.distance - b.distance)[0].bubble
274
+ )
275
+ }
276
+
277
+ // create the bubble grid bubble. passing a color will create
278
+ // an active bubble
279
+ function createBubble(x, y, color, isOddRow = false) {
280
+ const row = Math.floor(y / grid)
281
+ const col = Math.floor(x / grid)
282
+ let startX
283
+ if (row % 2 === 0) {
284
+ if (isOddRow) startX = 0.5 * grid
285
+ else startX = 0
286
+ } else startX = 0.5 * grid
287
+ const center = grid / 2
288
+
289
+ bubbles.push({
290
+ x: wallSize + (grid + bubbleGap) * col + startX + center,
291
+ y: wallSize + (grid + bubbleGap - 4) * row + center,
292
+
293
+ radius: grid / 2,
294
+ color: color,
295
+ active: color ? true : false,
296
+ })
297
+ }
298
+ // MARK: UPDATE UI
299
+ function updateUI() {
300
+ scoreValue.textContent = gameState.score
301
+ highScoreValue.textContent = localStorage.getItem("highScore") || 0
302
+ if (scoreValue.textContent > highScoreValue.textContent) {
303
+ localStorage.setItem("highScore", scoreValue.textContent)
304
+ }
305
+ if (gameState.level < Math.floor(gameState.score / 2000 + 1)) {
306
+ console.log("NEW LEVEL")
307
+ gameState.level = Math.floor(gameState.score / 2000 + 1)
308
+ levelValue.textContent = gameState.level
309
+ addRowInterval = setInterval(addNewRow, (30 - gameState.level) * 1000)
310
+ }
311
+ }
312
+
313
+ // get all bubbles that touch the passed in bubble
314
+ function getNeighbors(bubble) {
315
+ const neighbors = []
316
+
317
+ // check each of the 6 directions by "moving" the bubble by a full
318
+ // grid in each of the 6 directions (60 degree intervals)
319
+ const dirs = [
320
+ // right
321
+ rotatePoint(grid, 0, 0), // up-right
322
+ rotatePoint(grid, 0, degToRad(60)), // up-left
323
+ rotatePoint(grid, 0, degToRad(120)), // left
324
+ rotatePoint(grid, 0, degToRad(180)), // down-left
325
+ rotatePoint(grid, 0, degToRad(240)), // down-right
326
+ rotatePoint(grid, 0, degToRad(300)),
327
+ ]
328
+
329
+ for (let i = 0; i < dirs.length; i++) {
330
+ const dir = dirs[i]
331
+
332
+ const newBubble = {
333
+ x: bubble.x + dir.x,
334
+ y: bubble.y + dir.y,
335
+ radius: bubble.radius,
336
+ }
337
+ const neighbor = getClosestBubble(newBubble, true)
338
+ if (neighbor && neighbor !== bubble && !neighbors.includes(neighbor)) {
339
+ neighbors.push(neighbor)
340
+ }
341
+ }
342
+
343
+ return neighbors
344
+ }
345
+
346
+ // remove bubbles that create a match of 3 colors
347
+ function removeMatch(targetBubble) {
348
+ const matches = [targetBubble]
349
+
350
+ bubbles.forEach((bubble) => (bubble.processed = false))
351
+ targetBubble.processed = true
352
+
353
+ // loop over the neighbors of matching colors for more matches
354
+ let neighbors = getNeighbors(targetBubble)
355
+ for (let i = 0; i < neighbors.length; i++) {
356
+ let neighbor = neighbors[i]
357
+
358
+ if (!neighbor.processed) {
359
+ neighbor.processed = true
360
+
361
+ if (neighbor.color === targetBubble.color) {
362
+ matches.push(neighbor)
363
+ neighbors = neighbors.concat(getNeighbors(neighbor))
364
+ }
365
+ }
366
+ }
367
+
368
+ // MARK: MATCHES
369
+ if (matches.length >= 3) {
370
+ console.log("Matches found: " + matches.length)
371
+ const scoreMultiplier = matches.length * 100 + matches.length * 10
372
+ gameState.score += scoreMultiplier
373
+ console.log(gameState.score)
374
+ matches.forEach((bubble) => {
375
+ bubble.active = false
376
+ })
377
+
378
+ updateUI()
379
+ }
380
+ }
381
+
382
+ // make any floating bubbles (bubbles that don't have a bubble chain
383
+ // that touch the ceiling) drop down the screen
384
+ function dropFloatingBubbles() {
385
+ const activeBubbles = bubbles.filter((bubble) => bubble.active)
386
+ activeBubbles.forEach((bubble) => (bubble.processed = false))
387
+
388
+ // start at the bubbles that touch the ceiling
389
+ let neighbors = activeBubbles.filter((bubble) => bubble.y - grid <= wallSize)
390
+
391
+ // process all bubbles that form a chain with the ceiling bubbles
392
+ for (let i = 0; i < neighbors.length; i++) {
393
+ let neighbor = neighbors[i]
394
+
395
+ if (!neighbor.processed) {
396
+ neighbor.processed = true
397
+ neighbors = neighbors.concat(getNeighbors(neighbor))
398
+ }
399
+ }
400
+
401
+ // any bubble that is not processed doesn't touch the ceiling
402
+ activeBubbles
403
+ .filter((bubble) => !bubble.processed)
404
+ .forEach((bubble) => {
405
+ bubble.active = false
406
+ // create a particle bubble that falls down the screen
407
+ particles.push({
408
+ x: bubble.x,
409
+ y: bubble.y,
410
+ color: bubble.color,
411
+ radius: bubble.radius,
412
+ active: true,
413
+ })
414
+ })
415
+ }
416
+
417
+ // fill the grid with inactive bubbles
418
+ for (let row = 0; row < 10; row++) {
419
+ for (let col = 0; col < (row % 2 === 0 ? 10 : 9); col++) {
420
+ const color = level1[row]?.[col]
421
+ createBubble(col * grid, row * grid, colorMap[color])
422
+ }
423
+ }
424
+
425
+ function addNewRow() {
426
+ console.log("Added new row")
427
+ console.log(Date.now())
428
+ // move all bubbles one row down
429
+ bubbles.forEach((bubble) => (bubble.y += grid))
430
+
431
+ const level = addedLevel++
432
+ const offset = level % 2 === 0 ? grid : 0
433
+ isOddRow = level % 2 === 0
434
+ console.log("Is odd row 1: " + isOddRow)
435
+ // create a new row at the top
436
+ for (let col = 0; col < (level % 2 === 0 ? 9 : 10); col++) {
437
+ const color = colors[Math.floor(Math.random() * colors.length)]
438
+ createBubble(col * grid, 0, color, isOddRow)
439
+ }
440
+ }
441
+
442
+ const curBubblePos = {
443
+ // place the current bubble horizontally in the middle of the screen
444
+ x: canvas.width / 2,
445
+ y: canvas.height - 40,
446
+ }
447
+ const curBubble = {
448
+ x: curBubblePos.x,
449
+ y: curBubblePos.y,
450
+ color: colors[getRandomInt(0, colors.length - 1)],
451
+ radius: grid / 2, // a circles radius is half the width (diameter)
452
+
453
+ // how fast the bubble should go in either the x or y direction
454
+ speed: 16,
455
+
456
+ // bubble velocity
457
+ dx: 0,
458
+ dy: 0,
459
+ }
460
+
461
+ // angle (in radians) of the shooting arrow
462
+ let shootDeg = 0
463
+ const minDeg = degToRad(-80)
464
+ const maxDeg = degToRad(80)
465
+ let shootDir = 0
466
+
467
+ const nextBubblePos = {
468
+ x: canvas.width - 40,
469
+ y: canvas.height - 40,
470
+ }
471
+
472
+ let nextBubble = {
473
+ x: nextBubblePos.x,
474
+ y: nextBubblePos.y,
475
+ color: colors[getRandomInt(0, colors.length - 1)],
476
+ radius: grid / 2,
477
+ }
478
+
479
+ // reset the bubble to shoot to the bottom of the screen
480
+ function getNewBubble() {
481
+ curBubble.x = curBubblePos.x
482
+ curBubble.y = curBubblePos.y
483
+ curBubble.dx = curBubble.dy = 0
484
+
485
+ // Use nextBubble's color for current bubble
486
+ curBubble.color = nextBubble.color
487
+
488
+ // Generate a new nextBubble color
489
+ nextBubble.color = colors[getRandomInt(0, colors.length - 1)]
490
+ }
491
+
492
+ // handle collision between the current bubble and another bubble
493
+ function handleCollision(bubble) {
494
+ bubble.color = curBubble.color
495
+ bubble.active = true
496
+ getNewBubble()
497
+ removeMatch(bubble)
498
+ dropFloatingBubbles()
499
+ }
500
+
501
+ // MARK: GAME LOOP START
502
+ function loop() {
503
+ requestAnimationFrame(loop)
504
+ if (gameState.isPaused || gameState.isGameOver) return
505
+
506
+ context.clearRect(0, 0, canvas.width, canvas.height)
507
+
508
+ // move the shooting arrow
509
+ shootDeg = shootDeg + degToRad(2) * shootDir
510
+
511
+ // prevent shooting arrow from going below/above min/max
512
+ if (shootDeg < minDeg) {
513
+ shootDeg = minDeg
514
+ } else if (shootDeg > maxDeg) {
515
+ shootDeg = maxDeg
516
+ }
517
+
518
+ // move current bubble by it's velocity
519
+ curBubble.x += curBubble.dx
520
+ curBubble.y += curBubble.dy
521
+
522
+ // prevent bubble from going through walls by changing its velocity
523
+ if (curBubble.x - grid / 2 < wallSize) {
524
+ curBubble.x = wallSize + grid / 2
525
+ curBubble.dx *= -1
526
+ } else if (curBubble.x + grid / 2 > canvas.width - wallSize) {
527
+ curBubble.x = canvas.width - wallSize - grid / 2
528
+ curBubble.dx *= -1
529
+ }
530
+
531
+ // check to see if bubble collides with the top wall
532
+ if (curBubble.y - grid / 2 < wallSize) {
533
+ // make the closest inactive bubble active
534
+ const closestBubble = getClosestBubble(curBubble)
535
+ handleCollision(closestBubble)
536
+ }
537
+
538
+ // check to see if bubble collides with another bubble
539
+ for (let i = 0; i < bubbles.length; i++) {
540
+ const bubble = bubbles[i]
541
+
542
+ if (bubble.active && collides(curBubble, bubble)) {
543
+ const closestBubble = getClosestBubble(curBubble)
544
+ // MARK: GAME-OVER
545
+ if (!closestBubble) {
546
+ //window.alert("Game Over")
547
+ //window.location.reload()
548
+ gameState.isGameOver = true
549
+ clearInterval(addRowInterval)
550
+ const highScore = localStorage.getItem("highScore") || 0
551
+ if (gameState.score > highScore) {
552
+ localStorage.setItem("highScore", gameState.score)
553
+ }
554
+ document.querySelector("div.game-over").style.display = "block"
555
+ return
556
+ }
557
+
558
+ if (closestBubble) {
559
+ handleCollision(closestBubble)
560
+ }
561
+ }
562
+ }
563
+
564
+ // move bubble particles
565
+ particles.forEach((particle) => {
566
+ particle.y += 8
567
+ })
568
+
569
+ // remove particles that went off the screen
570
+ particles = particles.filter((particles) => particles.y < canvas.height - grid / 2)
571
+
572
+ // draw walls
573
+ context.fillStyle = "lightgrey"
574
+ context.fillRect(0, 0, canvas.width, wallSize)
575
+ context.fillRect(0, 0, wallSize, canvas.height)
576
+ context.fillRect(canvas.width - wallSize, 0, wallSize, canvas.height)
577
+ context.beginPath()
578
+ bottomBorderImage = new Image()
579
+ bottomBorderImage.src =
580
+ "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAZAAAABGCAYAAADir8JKAAAACXBIWXMAAAsSAAALEgHS3X78AAAEGElEQVR4nO3dzVHbQBjG8Yc0ALnyHuwOcCqIU0HoICrBHYR0QAe4BKcCnAridIAPew6ugBy0zGSYsZFWH+t39f/N+Cjp2QsPq5VWFy8vLwKmyMwWkpaSFpLmkj6/c8gvSU+SdpK2IYTdgPGAs3dBgWBKzOxW0uvvsuPpDpI2kjYhhE3XbIA3FAiKZ2ZXklaSKkmzgS6zl7SWdB9CeB7oGsBZoUBQNDO7U10eXWcbTR1Ul8jdSNcDsqFAUCQzW6qeEQw143jPXlIVQthmuj4wuA+5AwB9M7N7SY/KVx6K136MWYAiMQNBMcxsrnpR+yZzlLf+SLoNITzlDgL0iQJBEeIjuVuNt9bR1kHSkkd/URJuYcE9B+Uh1dm2MStQBAoErjkpj1eUCIrCLSy4Fdc8dvJRHv87SFqwJgLvmIHApfhy4Eb+ykOqM/PmOtyjQODVnc7vaas2bnjEF95xCwvuxJcEH3Pn6MkXXjaEV8xA4NE6d4AerXMHAFJRIHAl7m2V8w3zvs3imAB3uIUFN+LC+ZN8LpyfcpA0ZxdfeMMMBJ5UKq88pHpMVe4QQFsUCDxZ5Q4woJLHhkJRIHAhfkmwpLWPt2ZxjIAbFAi8mMIf1ymMEQWhQODFFP64TmGMKAgFgrMXNx8scfH8rUs2WoQnFAg8WOYOMKJl7gBAUxQIPJjSf+VTGiuco0DgwTx3gBHNcwcAmqJA4MHn3AFGNKWxwjkKBACQ5OL6+prNsADguMlsud/2UwnMQAAASSgQAEASCgQAkIQCAQAkoUAA4LSr3AFG1GqsFAgAnDal3QFajZUCAYDTKJAjKBAAOI0COYICAYDTZmY2zx1iaHGMrb76SYEAwPuWuQOMYNn2AAoEAN43ha9Fth4je2EBQDMfQwjPuUMMwcyuJP1texwzEABopsodYEBVykEUCAA0s8odYEBJY6NAAKCZmZlVuUP0LY6p1dNXr1gDAYDm9pIWpayFxLWPnRILhBkIADQ3U1m3slZKLA+JGQgApPgUQtjlDtGFmS0k/e5yDmYgANDeOneAHqy7noACAYD2bsxsnTtEqpj9put5KBAASPPN41NZMfO3Ps5FgQBAugdPJRKzPvR1PgoEALpxUSJ9l4dEgQBAH866RIYoD4kCAYC+PJzjwnrM1Ht5SLwHAgB9+yOpyv2eSHzPY60enrY6hgIBgGH8kHQ/9rYncXuSlaTvQ1+LW1gAMIzvknZjro3Ea+00QnlIzEAAYAx7SfeS1n3PSOKMo1LHfa1SUCAAMK6fkjaStiGEp5QTmNlc9TfMbyV97StYWxQIAOSzV33L6fV3bHZyJWnx32/UmcYxFAgAIAmL6ACAJBQIACAJBQIASEKBAACSUCAAgCQUCAAgCQUCAEhCgQAAklAgAIAk/wDMhdGTLpCVPwAAAABJRU5ErkJggg=="
581
+ context.drawImage(bottomBorderImage, 0, canvas.height - 70)
582
+ context.closePath()
583
+
584
+ // Draw next bubble
585
+ context.fillStyle = nextBubble.color
586
+ context.beginPath()
587
+ context.arc(nextBubble.x, nextBubble.y, nextBubble.radius, 0, 2 * Math.PI)
588
+ context.fill()
589
+
590
+ // draw bubbles and particles
591
+ bubbles.concat(particles).forEach((bubble) => {
592
+ if (!bubble.active) return
593
+ context.fillStyle = bubble.color
594
+
595
+ // draw a circle
596
+ context.beginPath()
597
+ context.arc(bubble.x, bubble.y, bubble.radius, 0, 2 * Math.PI)
598
+ context.fill()
599
+ })
600
+
601
+ // draw fire arrow. since we're rotating the canvas we need to save
602
+ // the state and restore it when we're done
603
+ context.save()
604
+ context.translate(curBubblePos.x, curBubblePos.y)
605
+ context.rotate(shootDeg)
606
+ context.translate(0, -10)
607
+
608
+ // draw arrow ↑
609
+ shooterImage = new Image()
610
+ shooterImage.src = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAB2CAYAAAD1J9I6AAAACXBIWXMAAAsSAAALEgHS3X78AAAAjElEQVRIie3QsQ3DIBRF0QvKFFB4JbMJo2STMFIo/hzfhRMJWQlBSvtuywOhE9ydnPMO3IGNsw5UM2shpbQDDz5XQkrpOdy81uPkEGCLk0MA4utD3+oRqJNBjWbWgHJ5qQPFzFpw959/+G9wA0Qt6neiBkS9PhC1qMdEDYh6fSBqUY+JGhD1+kDUJ/UByN4RqOiQZ/8AAAAASUVORK5CYII="
611
+ context.drawImage(shooterImage, 0, -90)
612
+
613
+ context.restore()
614
+
615
+ // draw current bubble
616
+ context.fillStyle = curBubble.color
617
+ context.beginPath()
618
+ context.arc(curBubble.x, curBubble.y, curBubble.radius, 0, 2 * Math.PI)
619
+ context.fill()
620
+ }
621
+ // MARK: GAME LOOP ENDE
622
+
623
+ // MARK: EVENT HANDLER START
624
+
625
+ // Mousemove event for aiming
626
+ document.addEventListener("mousemove", (e) => {
627
+ const rect = canvas.getBoundingClientRect()
628
+ const centerX = rect.left + canvas.width / 2
629
+ const centerY = rect.top + canvas.height
630
+ const mouseX = e.clientX - centerX
631
+ const mouseY = e.clientY - centerY
632
+ shootDeg = Math.atan2(mouseX, -mouseY) // Calculate angle relative to center
633
+
634
+ // Constrain angle within -80 to 80 degrees
635
+ const maxAngleRad = degToRad(180)
636
+ const minAngleRad = degToRad(-180)
637
+ shootDeg = Math.max(minAngleRad, Math.min(maxAngleRad, shootDeg))
638
+ })
639
+
640
+ // Click event for shooting
641
+ canvas.addEventListener("click", (e) => {
642
+ if (curBubble.dx === 0 && curBubble.dy === 0 && !gameState.isPaused && !gameState.isGameOver) {
643
+ // Only shoot if bubble isn't moving & game is active
644
+ curBubble.dx = Math.sin(shootDeg) * curBubble.speed
645
+ curBubble.dy = -Math.cos(shootDeg) * curBubble.speed
646
+ }
647
+ })
648
+
649
+ function pauseGame() {
650
+ gameState.isPaused = !gameState.isPaused
651
+ document.querySelector(".pause-screen").style.display = gameState.isPaused ? "block" : "none"
652
+ canvas.style.cursor = gameState.isPaused ? "initial" : "none"
653
+
654
+ // Clear velocity when paused to prevent movement
655
+ if (gameState.isPaused) {
656
+ curBubble.dx = 0
657
+ curBubble.dy = 0
658
+ }
659
+ }
660
+
661
+ document.addEventListener("keydown", (e) => {
662
+ switch (e.key) {
663
+ case "ArrowLeft":
664
+ if (gameState.isPaused) break
665
+ if (gameState.isPlaying) gameState.shooterAngle = Math.max(gameState.shooterAngle - 0.1, -Math.PI / 3)
666
+ break
667
+ case "ArrowRight":
668
+ if (gameState.isPaused) break
669
+ if (gameState.isPlaying) gameState.shooterAngle = Math.min(gameState.shooterAngle + 0.1, Math.PI / 3)
670
+ break
671
+ case "ArrowUp":
672
+ if (gameState.isPaused) break
673
+ if (gameState.isPlaying && curBubble.dx === 0 && curBubble.dy === 0) {
674
+ curBubble.dx = Math.sin(shootDeg) * curBubble.speed
675
+ curBubble.dy = -Math.cos(shootDeg) * curBubble.speed
676
+ }
677
+ break
678
+ case "p":
679
+ case "Escape":
680
+ pauseGame()
681
+ break
682
+ }
683
+ })
684
+
685
+ document.addEventListener("keyup", (e) => {
686
+ if ((e.code === "ArrowLeft" && shootDir === -1) || (e.code === "ArrowRight" && shootDir === 1)) {
687
+ shootDir = 0
688
+ }
689
+ })
690
+
691
+ startButton.addEventListener("click", () => {
692
+ document.querySelector("div.start-screen").style.display = "none"
693
+ canvas.style.cursor = "none"
694
+ initGame()
695
+ requestAnimationFrame(loop)
696
+ })
697
+
698
+ document.getElementById("pauseButton").addEventListener("click", () => {
699
+ if (!gameState.isPlaying || gameState.isPaused) return
700
+ pauseGame()
701
+ })
702
+
703
+ resumeButton.addEventListener("click", () => {
704
+ pauseGame()
705
+ })
706
+
707
+ restartButton.addEventListener("click", () => {
708
+ window.location.reload()
709
+ })
710
+
711
+ // MARK: EVENT HANDLER ENDE
712
+
713
+ startScreen.style.display = "block"
714
+ </script>
715
  </body>
716
  </html>