HAL1993 commited on
Commit
e076712
·
verified ·
1 Parent(s): 25cd4fd

Upload folder using huggingface_hub

Browse files
Files changed (1) hide show
  1. index.html +1025 -987
index.html CHANGED
@@ -2,1096 +2,1134 @@
2
  <html lang="en">
3
  <head>
4
  <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
6
- <title>Bitcoin Dash: Dino Runner</title>
7
- <link href="https://fonts.googleapis.com/css2?family=Fredoka+One&family=Press+Start+2P&display=swap" rel="stylesheet">
8
  <style>
9
- :root {
10
- --primary: #4CAF50;
11
- --accent: #FFD700;
12
- --bg-dark: #1a1a2e;
13
- --ui-bg: rgba(0, 0, 0, 0.85);
14
- --text-color: #ffffff;
15
- }
16
-
17
  * {
18
- box-sizing: border-box;
19
- user-select: none;
20
- -webkit-user-select: none;
21
- touch-action: none;
22
- }
23
-
24
- body, html {
25
  margin: 0;
26
  padding: 0;
27
- width: 100%;
28
- height: 100%;
 
 
29
  overflow: hidden;
30
- background-color: var(--bg-dark);
31
- font-family: 'Fredoka One', cursive;
32
- color: var(--text-color);
33
  }
34
-
35
- #game-container {
36
  position: relative;
37
- width: 100%;
38
- height: 100%;
39
- display: flex;
40
- justify-content: center;
41
- align-items: center;
42
  }
43
-
44
- canvas {
45
  display: block;
46
- box-shadow: 0 0 50px rgba(0, 0, 0, 0.5);
47
  }
48
-
49
- /* UI Overlays */
50
- .ui-screen {
51
  position: absolute;
52
  top: 0;
53
  left: 0;
54
  width: 100%;
55
  height: 100%;
56
- display: flex;
57
- flex-direction: column;
58
- justify-content: center;
59
- align-items: center;
60
- background: rgba(0, 0, 0, 0.6);
61
- backdrop-filter: blur(5px);
62
- z-index: 10;
63
- transition: opacity 0.3s ease;
64
- }
65
-
66
- .hidden {
67
- opacity: 0;
68
  pointer-events: none;
69
- z-index: -1;
70
  }
71
-
72
- h1 {
73
- font-size: 3rem;
74
- color: var(--primary);
75
- text-shadow: 4px 4px 0px #2E7D32;
76
- margin-bottom: 0.5rem;
77
- text-align: center;
78
- line-height: 1.2;
79
- }
80
-
81
- .btc-display {
82
- font-family: 'Press Start 2P', monospace;
83
- font-size: 1.5rem;
84
- color: var(--accent);
85
- margin: 1rem 0;
86
- text-shadow: 0 0 10px rgba(255, 215, 0, 0.5);
87
- }
88
-
89
- .instructions {
90
- font-size: 1rem;
91
- color: #ccc;
92
- margin-bottom: 2rem;
93
- text-align: center;
94
- line-height: 1.6;
95
- max-width: 600px;
96
- padding: 0 20px;
97
- }
98
-
99
- .key-badge {
100
- display: inline-block;
101
- background: #444;
102
- padding: 4px 8px;
103
- border-radius: 4px;
104
- border-bottom: 2px solid #222;
105
- color: white;
106
- font-family: sans-serif;
107
- font-size: 0.8rem;
108
- margin: 0 2px;
109
- }
110
-
111
- button {
112
- background: linear-gradient(180deg, #4CAF50 0%, #2E7D32 100%);
113
- border: none;
114
- border-radius: 50px;
115
- padding: 15px 40px;
116
- font-size: 1.5rem;
117
  color: white;
118
- font-family: 'Fredoka One', cursive;
119
- cursor: pointer;
120
- box-shadow: 0 6px 0 #1B5E20, 0 10px 10px rgba(0,0,0,0.3);
121
- transition: transform 0.1s, box-shadow 0.1s;
122
- text-transform: uppercase;
123
- }
124
-
125
- button:active {
126
- transform: translateY(4px);
127
- box-shadow: 0 2px 0 #1B5E20, 0 4px 4px rgba(0,0,0,0.3);
128
  }
129
-
130
- button:hover {
131
- filter: brightness(1.1);
 
 
132
  }
133
-
134
- /* HUD */
135
- #hud {
136
  position: absolute;
137
  top: 0;
138
  left: 0;
139
  width: 100%;
140
- padding: 15px 20px;
141
- display: flex;
142
- justify-content: space-between;
143
- align-items: flex-start;
144
- pointer-events: none;
145
- z-index: 5;
146
- }
147
-
148
- .hud-item {
149
- background: rgba(0, 0, 0, 0.5);
150
- padding: 8px 15px;
151
- border-radius: 12px;
152
- border: 1px solid rgba(255, 255, 255, 0.1);
153
  display: flex;
154
  flex-direction: column;
 
155
  align-items: center;
 
 
 
156
  }
157
-
158
- .hud-label {
159
- font-size: 0.7rem;
160
- text-transform: uppercase;
161
- color: #aaa;
162
- letter-spacing: 1px;
163
  }
164
-
165
- .hud-value {
166
- font-family: 'Press Start 2P', monospace;
167
- font-size: 1rem;
168
- color: white;
 
169
  }
170
-
171
- #pause-btn {
 
 
 
 
 
 
 
 
 
 
 
 
 
172
  pointer-events: auto;
173
- background: rgba(255, 255, 255, 0.2);
174
- border-radius: 50%;
175
- width: 40px;
176
- height: 40px;
177
- display: flex;
178
- justify-content: center;
179
- align-items: center;
180
- font-size: 1.2rem;
181
- padding: 0;
182
- box-shadow: none;
183
- border: 2px solid white;
184
  }
185
 
186
- #pause-btn:active {
187
- transform: scale(0.95);
 
188
  }
189
-
190
- /* Built with anycoder */
191
- .footer-link {
192
- position: absolute;
193
- bottom: 10px;
194
- right: 10px;
195
- font-size: 0.7rem;
196
- color: rgba(255, 255, 255, 0.4);
197
- text-decoration: none;
198
- z-index: 20;
199
  }
200
 
201
- .footer-link:hover {
202
- color: rgba(255, 255, 255, 0.8);
 
 
203
  }
204
-
205
- /* Power-up Indicators */
206
- .powerup-indicator {
207
  position: absolute;
208
- top: 80px;
209
- left: 50%;
210
- transform: translateX(-50%);
211
- display: flex;
212
- gap: 10px;
213
  }
214
 
215
- .pu-icon {
216
- width: 30px;
217
- height: 30px;
218
- background: rgba(0,0,0,0.6);
219
- border-radius: 50%;
220
- display: flex;
221
- justify-content: center;
222
- align-items: center;
223
- font-size: 14px;
224
- border: 2px solid rgba(255,255,255,0.3);
225
- opacity: 0;
226
- transition: opacity 0.3s;
227
  }
228
 
229
- .pu-icon.active {
230
- opacity: 1;
231
- animation: pulse 1s infinite;
232
- }
233
-
234
- @keyframes pulse {
235
- 0% { transform: scale(1); box-shadow: 0 0 0 0 rgba(255, 215, 0, 0.7); }
236
- 70% { transform: scale(1.1); box-shadow: 0 0 0 10px rgba(255, 215, 0, 0); }
237
- 100% { transform: scale(1); box-shadow: 0 0 0 0 rgba(255, 215, 0, 0); }
 
 
 
 
 
 
 
 
238
  }
239
-
240
  </style>
241
  </head>
242
  <body>
243
-
244
- <div id="game-container">
245
- <canvas id="gameCanvas"></canvas>
246
-
247
- <!-- HUD -->
248
- <div id="hud" class="hidden">
249
- <div class="hud-item">
250
- <span class="hud-label">Distance</span>
251
- <span class="hud-value" id="dist-val">0m</span>
252
- </div>
253
- <div id="pause-btn">❚❚</div>
254
- <div class="hud-item">
255
- <span class="hud-label">BTC Balance</span>
256
- <span class="hud-value" id="btc-val">0.00000000</span>
 
 
 
 
 
 
 
 
 
 
257
  </div>
258
- </div>
259
-
260
- <div id="powerup-container" class="powerup-indicator hidden">
261
- <div id="pu-shield" class="pu-icon" title="Shield">🛡️</div>
262
- <div id="pu-magnet" class="pu-icon" title="Magnet">🧲</div>
263
- <div id="pu-mul" class="pu-icon" title="Multiplier x2">2x</div>
264
- </div>
265
-
266
- <!-- Start Screen -->
267
- <div id="start-screen" class="ui-screen">
268
- <h1>BITCOIN DASH</h1>
269
- <div class="btc-display" id="start-btc-display">0.00000000</div>
270
- <div class="instructions">
271
- <p>RUN, DUCK, & JUMP to collect Bitcoin.</p>
272
- <p>Use <span class="key-badge">←</span> <span class="key-badge">→</span> to Switch Lanes</p>
273
- <p>Press <span class="key-badge">↑</span> or <span class="key-badge">SPACE</span> to Jump</p>
274
- <p>Press <span class="key-badge">↓</span> to Slide</p>
275
- <p style="margin-top:10px; color: var(--primary)">Avoid barriers and gaps!</p>
276
  </div>
277
- <button id="start-btn">START RUN</button>
278
  </div>
279
 
280
- <!-- Game Over Screen -->
281
- <div id="game-over-screen" class="ui-screen hidden">
282
- <h1 style="color: #ff5252; text-shadow: 4px 4px 0 #b71c1c;">CRASHED!</h1>
283
- <div class="btc-display" id="final-btc-display">0.00000000</div>
284
- <p style="margin: 10px 0 20px 0; font-size: 1.2rem;">Distance: <span id="final-dist">0</span>m</p>
285
- <button id="restart-btn">TRY AGAIN</button>
286
- </div>
287
-
288
- <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="footer-link">Built with anycoder</a>
289
- </div>
290
-
291
- <script>
292
- /**
293
- * BITCOIN DASH - AAA Game Engine
294
- * Pure HTML5 Canvas Implementation
295
- */
296
-
297
- // --- CONFIGURATION & CONSTANTS ---
298
- const CONSTANTS = {
299
- LANE_COUNT: 3,
300
- LANE_WIDTH_BASE: 100, // Will be scaled by screen width
301
- RUN_SPEED_BASE: 0.5, // World units per frame
302
- GRAVITY: 0.015,
303
- JUMP_FORCE: -0.35,
304
- SLIDE_DURATION: 40,
305
- COIN_VALUE: 0.00000001,
306
- BONUS_COIN_VALUE: 0.00000005,
307
- COLORS: {
308
- PLAYER: '#4CAF50',
309
- PLAYER_OUTLINE: '#2E7D32',
310
- COIN: '#FFD700',
311
- COIN_OUTLINE: '#F57F17',
312
- OBSTACLE: '#263238',
313
- OBSTACLE_ACCENT: '#546E7A',
314
- GROUND: '#ECEFF1',
315
- SKY: '#87CEEB'
316
- }
317
- };
318
-
319
- // --- UTILS ---
320
- const Utils = {
321
- random: (min, max) => Math.random() * (max - min) + min,
322
- randomInt: (min, max) => Math.floor(Math.random() * (max - min + 1) + min),
323
- lerp: (start, end, t) => start * (1 - t) + end * t,
324
- clamp: (val, min, max) => Math.min(Math.max(val, min), max),
325
- formatBTC: (num) => num.toFixed(8),
326
- // Draw Bitcoin Symbol
327
- drawBitcoinSymbol: (ctx, x, y, size, rotation) => {
328
- ctx.save();
329
- ctx.translate(x, y);
330
- ctx.rotate(rotation);
331
- ctx.font = `${size}px Arial`;
332
- ctx.textAlign = 'center';
333
- ctx.textBaseline = 'middle';
334
- ctx.fillStyle = CONSTANTS.COLORS.COIN;
335
- ctx.shadowColor = 'rgba(0,0,0,0.3)';
336
- ctx.shadowBlur = 5;
337
- ctx.fillText('₿', 0, 0);
338
- ctx.restore();
339
- }
340
- };
341
-
342
- // --- INPUT HANDLER ---
343
- class InputHandler {
344
- constructor() {
345
- this.keys = {
346
- left: false,
347
- right: false,
348
- up: false,
349
- down: false
350
- };
351
- this.touchStartX = 0;
352
- this.touchStartY = 0;
353
-
354
- window.addEventListener('keydown', (e) => this.handleKey(e, true));
355
- window.addEventListener('keyup', (e) => this.handleKey(e, false));
356
 
357
- // Touch handling
358
- window.addEventListener('touchstart', (e) => {
359
- this.touchStartX = e.touches[0].clientX;
360
- this.touchStartY = e.touches[0].clientY;
361
- }, {passive: false});
362
-
363
- window.addEventListener('touchend', (e) => {
364
- if (!game.isPlaying) return;
365
- const touchEndX = e.changedTouches[0].clientX;
366
- const touchEndY = e.changedTouches[0].clientY;
367
- this.handleSwipe(touchEndX, touchEndY);
368
- }, {passive: false});
369
- }
370
-
371
- handleKey(e, isDown) {
372
- switch(e.code) {
373
- case 'ArrowLeft':
374
- case 'KeyA': this.keys.left = isDown; break;
375
- case 'ArrowRight':
376
- case 'KeyD': this.keys.right = isDown; break;
377
- case 'ArrowUp':
378
- case 'Space':
379
- case 'KeyW':
380
- if (isDown && !this.keys.up) game.player.jump();
381
- this.keys.up = isDown;
382
- break;
383
- case 'ArrowDown':
384
- case 'KeyS':
385
- if (isDown && !this.keys.down) game.player.slide();
386
- this.keys.down = isDown;
387
- break;
388
- }
389
- }
390
-
391
- handleSwipe(endX, endY) {
392
- const dx = endX - this.touchStartX;
393
- const dy = endY - this.touchStartY;
394
- const absDx = Math.abs(dx);
395
- const absDy = Math.abs(dy);
396
-
397
- if (absDx > absDy) {
398
- // Horizontal
399
- if (absDx > 30) {
400
- if (dx > 0) game.player.switchLane(1);
401
- else game.player.switchLane(-1);
402
- }
403
- } else {
404
- // Vertical
405
- if (absDy > 30) {
406
- if (dy > 0) game.player.slide();
407
- else game.player.jump();
408
- }
409
- }
410
- }
411
- }
412
-
413
- // --- PARTICLE SYSTEM ---
414
- class Particle {
415
- constructor(x, y, color, speed) {
416
- this.x = x;
417
- this.y = y;
418
- this.color = color;
419
- this.size = Utils.random(2, 5);
420
- this.speedX = Utils.random(-speed, speed);
421
- this.speedY = Utils.random(-speed, speed);
422
- this.life = 1.0;
423
- this.decay = Utils.random(0.02, 0.05);
424
- }
425
-
426
- update() {
427
- this.x += this.speedX;
428
- this.y += this.speedY;
429
- this.life -= this.decay;
430
- this.size *= 0.95;
431
- }
432
-
433
- draw(ctx) {
434
- ctx.globalAlpha = this.life;
435
- ctx.fillStyle = this.color;
436
- ctx.beginPath();
437
- ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
438
- ctx.fill();
439
- ctx.globalAlpha = 1.0;
440
- }
441
- }
442
-
443
- // --- ENTITIES ---
444
-
445
- class Player {
446
- constructor(game) {
447
- this.game = game;
448
- this.width = 40;
449
- this.height = 60;
450
- this.lane = 1; // 0: Left, 1: Center, 2: Right
451
- this.targetX = 0;
452
- this.x = 0;
453
- this.y = 0;
454
- this.vy = 0;
455
- this.isJumping = false;
456
- this.isSliding = false;
457
- this.slideTimer = 0;
458
- this.color = CONSTANTS.COLORS.PLAYER;
459
 
460
- // Animation properties
461
- this.runCycle = 0;
462
- }
463
-
464
- update() {
465
- // Lane Switching Logic
466
- const laneWidth = this.game.laneWidth;
467
- const targetLX = (this.lane - 1) * laneWidth;
468
- this.targetX = Utils.lerp(this.x, targetLX, 0.2);
469
- this.x = this.targetX;
470
-
471
- // Jump Physics
472
- if (this.isJumping) {
473
- this.y += this.vy;
474
- this.vy += CONSTANTS.GRAVITY;
475
-
476
- if (this.y >= 0) {
477
- this.y = 0;
478
- this.isJumping = false;
479
- this.vy = 0;
480
- }
 
 
 
 
 
 
 
 
 
 
481
  }
482
-
483
- // Slide Logic
484
- if (this.isSliding) {
485
- this.slideTimer--;
486
- if (this.slideTimer <= 0) {
487
- this.isSliding = false;
 
 
 
 
488
  }
489
  }
490
-
491
- // Run Animation
492
- this.runCycle += 0.15 + (this.game.speed / 1000);
493
- }
494
-
495
- jump() {
496
- if (!this.isJumping && !this.isSliding) {
497
- this.isJumping = true;
498
- this.vy = CONSTANTS.JUMP_FORCE;
499
- this.game.spawnParticles(this.x + this.width/2, this.y + this.height, 5, '#fff');
500
- }
501
- }
502
-
503
- slide() {
504
- if (!this.isJumping && !this.isSliding) {
505
- this.isSliding = true;
506
- this.slideTimer = CONSTANTS.SLIDE_DURATION;
507
- this.height = 30; // Shrink height
508
- }
509
- }
510
-
511
- switchLane(dir) {
512
- const newLane = this.lane + dir;
513
- if (newLane >= 0 && newLane < CONSTANTS.LANE_COUNT) {
514
- this.lane = newLane;
515
- }
516
- }
517
-
518
- draw(ctx) {
519
- ctx.save();
520
 
521
- // Position calculation
522
- const drawX = this.x + this.width / 2;
523
- const drawY = this.game.groundY - this.height + this.y;
524
-
525
- // Squash and stretch based on state
526
- let scaleX = 1;
527
- let scaleY = 1;
 
 
 
 
 
 
 
528
 
529
- if (this.isJumping) {
530
- scaleX = 0.8;
531
- scaleY = 1.2;
532
- } else if (this.isSliding) {
533
- scaleX = 0.8;
534
- scaleY = 0.5;
535
- } else {
536
- // Run bobbing
537
- const bob = Math.sin(this.runCycle) * 2;
538
- ctx.translate(0, bob);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
539
  }
540
-
541
- ctx.translate(drawX, drawY);
542
- ctx.scale(scaleX, scaleY);
543
-
544
- // Draw Dino (Stylized)
545
- // Body
546
- ctx.fillStyle = this.color;
547
- ctx.strokeStyle = CONSTANTS.COLORS.PLAYER_OUTLINE;
548
- ctx.lineWidth = 3;
549
 
550
- // Round body
551
- ctx.beginPath();
552
- ctx.ellipse(0, 10, 20, 25, 0, 0, Math.PI * 2);
553
- ctx.fill();
554
- ctx.stroke();
555
-
556
- // Head
557
- ctx.beginPath();
558
- ctx.arc(15, -10, 18, 0, Math.PI * 2);
559
- ctx.fill();
560
- ctx.stroke();
561
-
562
- // Eyes
563
- ctx.fillStyle = 'white';
564
- ctx.beginPath();
565
- ctx.arc(20, -15, 6, 0, Math.PI * 2);
566
- ctx.fill();
567
- ctx.stroke();
568
 
569
- ctx.fillStyle = 'black';
570
- ctx.beginPath();
571
- ctx.arc(22, -15, 2.5, 0, Math.PI * 2);
572
- ctx.fill();
573
-
574
- // Headband with Bitcoin
575
- ctx.fillStyle = '#FFD700'; // Gold
576
- ctx.beginPath();
577
- ctx.ellipse(15, -10, 18, 8, 0, 0, Math.PI * 2);
578
- ctx.fill();
579
- ctx.stroke();
580
 
581
- // Bitcoin Symbol on Headband
582
- ctx.save();
583
- ctx.rotate(Math.PI / 4);
584
- ctx.fillStyle = '#000';
585
- ctx.font = 'bold 14px Arial';
586
- ctx.textAlign = 'center';
587
- ctx.textBaseline = 'middle';
588
- ctx.fillText('₿', 0, 0);
589
- ctx.restore();
590
-
591
- // Legs (Simple animation)
592
- if (!this.isSliding) {
593
- ctx.strokeStyle = this.color;
594
- ctx.lineWidth = 4;
595
- ctx.lineCap = 'round';
596
- const legOffset = Math.sin(this.runCycle) * 10;
597
-
598
- // Back Leg
599
- ctx.beginPath();
600
- ctx.moveTo(-5, 25);
601
- ctx.lineTo(-5 - legOffset, 40);
602
- ctx.stroke();
603
 
604
- // Front Leg
605
- ctx.beginPath();
606
- ctx.moveTo(5, 25);
607
- ctx.lineTo(5 + (legOffset * -1), 40);
608
- ctx.stroke();
 
 
 
 
 
 
609
  }
610
-
611
- // Tail
612
- ctx.beginPath();
613
- ctx.moveTo(-20, 10);
614
- ctx.quadraticCurveTo(-35, 5, -40, 20);
615
- ctx.stroke();
616
-
617
- ctx.restore();
618
-
619
- // Shield Effect
620
- if (this.game.playerPowerups.shield) {
621
- ctx.save();
622
- ctx.strokeStyle = 'rgba(100, 200, 255, 0.6)';
623
- ctx.lineWidth = 3;
624
- ctx.setLineDash([5, 5]);
625
- ctx.beginPath();
626
- ctx.arc(drawX, drawY + this.height/2, 45, 0, Math.PI * 2);
627
- ctx.stroke();
628
- ctx.restore();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
629
  }
630
- }
631
-
632
- getBounds() {
633
- // Return collision box
634
- let h = this.height;
635
- let y = this.y;
636
 
637
- // If sliding, hitbox is lower (ducking under bars)
638
- if (this.isSliding) {
639
- h = 30;
640
- y = this.y + 30; // Shift up
 
 
 
 
 
 
 
 
 
 
 
 
641
  }
642
 
643
- // If jumping, hitbox is smaller (don't hit low bars)
644
- if (this.isJumping) {
645
- h = 30;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
646
  }
647
-
648
- return {
649
- x: this.x + 10,
650
- y: y + 10,
651
- width: this.width - 20,
652
- height: h - 20
653
- };
654
- }
655
- }
656
-
657
- class Obstacle {
658
- constructor(game, lane, type) {
659
- this.game = game;
660
- this.lane = lane;
661
- this.x = 0;
662
- this.y = 0;
663
- this.type = type; // 'barrier', 'low', 'high', 'gap'
664
- this.passed = false;
665
 
666
- // Dimensions
667
- this.width = game.laneWidth * 0.8;
668
- this.height = type === 'gap' ? 0 : 50;
 
 
 
 
 
 
 
669
 
670
- // Positioning
671
- const laneW = game.laneWidth;
672
- this.x = (lane - 1) * laneW + (laneW - this.width) / 2;
673
- this.y = game.groundY - this.height;
674
- }
675
-
676
- update() {
677
- this.y -= this.game.speed;
678
- }
679
-
680
- draw(ctx) {
681
- if (this.type === 'gap') {
682
- // Draw hole in ground
683
- ctx.fillStyle = '#1a1a2e'; // Sky color
684
- ctx.fillRect(this.x, this.y, this.width, 20);
685
- // Edge
686
- ctx.fillStyle = '#546E7A';
687
- ctx.fillRect(this.x, this.y + 20, this.width, 5);
688
- return;
 
 
 
 
 
 
 
 
 
 
 
689
  }
690
-
691
- ctx.fillStyle = CONSTANTS.COLORS.OBSTACLE;
692
- ctx.strokeStyle = CONSTANTS.COLORS.OBSTACLE_ACCENT;
693
- ctx.lineWidth = 3;
694
-
695
- // Draw based on type
696
- if (this.type === 'low') {
697
- // Low barrier (jumpable)
698
- ctx.fillRect(this.x, this.y, this.width, 20);
699
- ctx.strokeRect(this.x, this.y, this.width, 20);
700
- // Decorative stripes
701
- ctx.fillStyle = '#37474F';
702
- ctx.fillRect(this.x + 5, this.y + 5, this.width - 10, 10);
703
- } else if (this.type === 'high') {
704
- // High barrier (cannot duck under, must jump)
705
- ctx.fillRect(this.x, this.y, this.width, this.height);
706
- ctx.strokeRect(this.x, this.y, this.width, this.height);
707
- // Warning stripes
708
- ctx.strokeStyle = '#FF5252';
709
- ctx.beginPath();
710
- for(let i=0; i<this.width; i+=20) {
711
- ctx.moveTo(this.x + i, this.y);
712
- ctx.lineTo(this.x + i + 10, this.y + this.height);
713
  }
714
- ctx.stroke();
715
- } else {
716
- // Standard barrier
717
- ctx.fillRect(this.x, this.y, this.width, this.height);
718
- ctx.strokeRect(this.x, this.y, this.width, this.height);
719
- // Gold trim
720
- ctx.fillStyle = '#B0BEC5';
721
- ctx.fillRect(this.x + 5, this.y + 5, this.width - 10, 5);
722
  }
723
- }
724
-
725
- getBounds() {
726
- return { x: this.x, y: this.y, width: this.width, height: this.height };
727
- }
728
- }
729
-
730
- class Coin {
731
- constructor(game, lane, isBonus) {
732
- this.game = game;
733
- this.lane = lane;
734
- this.isBonus = isBonus;
735
- this.x = 0;
736
- this.y = 0;
737
- this.size = isBonus ? 25 : 15;
738
- this.rotation = 0;
739
- this.collected = false;
740
 
741
- const laneW = game.laneWidth;
742
- this.x = (lane - 1) * laneW + laneW / 2;
743
- this.y = game.groundY - 80; // Float slightly
744
- }
745
-
746
- update() {
747
- this.y -= this.game.speed;
748
- this.rotation += 0.1;
 
 
 
749
 
750
- // Magnet effect
751
- if (this.game.playerPowerups.magnet && !this.collected) {
752
- const pBounds = this.game.player.getBounds();
753
- const dx = (pBounds.x + pBounds.width/2) - (this.x);
754
- const dy = (pBounds.y) - (this.y);
755
- const dist = Math.sqrt(dx*dx + dy*dy);
756
-
757
- if (dist < 150) {
758
- this.x += dx * 0.1;
759
- this.y += dy * 0.1;
760
  }
761
  }
762
- }
763
-
764
- draw(ctx) {
765
- if (this.collected) return;
766
-
767
- ctx.save();
768
- ctx.translate(this.x, this.y);
769
 
770
- // Glow
771
- ctx.shadowColor = CONSTANTS.COLORS.COIN;
772
- ctx.shadowBlur = 15;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
773
 
774
- Utils.drawBitcoinSymbol(ctx, 0, 0, this.size, this.rotation);
775
-
776
- ctx.restore();
777
- }
778
-
779
- getBounds() {
780
- return { x: this.x - this.size/2, y: this.y - this.size/2, width: this.size, height: this.size };
781
- }
782
- }
783
-
784
- class PowerUp {
785
- constructor(game, lane, type) {
786
- this.game = game;
787
- this.lane = lane;
788
- this.type = type; // 'shield', 'magnet', 'mul'
789
- this.x = 0;
790
- this.y = 0;
791
- this.width = 30;
792
- this.height = 30;
793
- this.timer = 0;
794
- this.collected = false;
795
-
796
- const laneW = game.laneWidth;
797
- this.x = (lane - 1) * laneW + laneW / 2;
798
- this.y = game.groundY - 60;
799
- }
800
-
801
- update() {
802
- this.y -= this.game.speed;
803
- this.timer++;
804
- }
805
-
806
- draw(ctx) {
807
- if (this.collected) return;
808
 
809
- ctx.save();
810
- ctx.translate(this.x, this.y);
811
- ctx.rotate(this.timer * 0.05);
 
 
812
 
813
- ctx.fillStyle = 'rgba(255, 255, 255, 0.2)';
814
- ctx.strokeStyle = '#fff';
815
- ctx.lineWidth = 2;
816
- ctx.beginPath();
817
- ctx.arc(0, 0, 15, 0, Math.PI * 2);
818
- ctx.fill();
819
- ctx.stroke();
820
-
821
- // Icon
822
- ctx.font = '16px Arial';
823
- ctx.textAlign = 'center';
824
- ctx.textBaseline = 'middle';
825
- ctx.fillStyle = '#fff';
826
- let icon = '';
827
- if(this.type === 'shield') icon = '🛡️';
828
- if(this.type === 'magnet') icon = '🧲';
829
- if(this.type === 'mul') icon = 'x2';
830
- ctx.fillText(icon, 0, 0);
831
-
832
- ctx.restore();
833
- }
834
-
835
- getBounds() {
836
- return { x: this.x - 15, y: this.y - 15, width: 30, height: 30 };
837
- }
838
- }
839
-
840
- // --- MAIN GAME ENGINE ---
841
-
842
- class Game {
843
- constructor() {
844
- this.canvas = document.getElementById('gameCanvas');
845
- this.ctx = this.canvas.getContext('2d');
846
- this.input = new InputHandler();
847
 
848
- // State
849
- this.isRunning = false;
850
- this.isPaused = false;
851
- this.lastTime = 0;
852
- this.score = 0;
853
- this.distance = 0;
854
- this.speed = CONSTANTS.RUN_SPEED_BASE;
855
- this.btcBalance = 0.00000000;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
856
 
857
- // Entities
858
- this.player = null;
859
- this.obstacles = [];
860
- this.coins = [];
861
- this.particles = [];
862
- this.powerups = [];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
863
 
864
- // Spawning
865
- this.spawnTimer = 0;
866
- this.spawnRate = 60;
867
-
868
- // Visuals
869
- this.laneWidth = 100;
870
- this.groundY = 0;
871
- this.bgOffset = 0;
872
- this.screenShake = 0;
873
-
874
- // Powerups
875
- this.playerPowerups = {
876
- shield: false,
877
- magnet: false,
878
- mul: false
879
- };
880
- this.powerupTimers = {};
881
-
882
- this.resize();
883
- window.addEventListener('resize', () => this.resize());
 
 
 
 
 
 
 
 
 
 
 
 
 
884
 
885
- // UI Bindings
886
- document.getElementById('start-btn').addEventListener('click', () => this.start());
887
- document.getElementById('restart-btn').addEventListener('click', () => this.start());
888
- document.getElementById('pause-btn').addEventListener('click', () => this.togglePause());
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
889
 
890
- // Initial Draw
891
- this.drawBackground();
892
- }
893
-
894
- resize() {
895
- this.canvas.width = window.innerWidth;
896
- this.canvas.height = window.innerHeight;
897
- this.laneWidth = this.canvas.width / 3;
898
- this.groundY = this.canvas.height * 0.75;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
899
 
900
- if (!this.isRunning) {
901
- this.drawBackground();
 
 
 
 
 
 
 
902
  }
903
- }
904
-
905
- start() {
906
- // Reset Game State
907
- this.isRunning = true;
908
- this.isPaused = false;
909
- this.score = 0;
910
- this.distance = 0;
911
- this.speed = CONSTANTS.RUN_SPEED_BASE;
912
- this.obstacles = [];
913
- this.coins = [];
914
- this.particles = [];
915
- this.powerups = [];
916
- this.btcBalance = parseFloat(document.getElementById('start-btc-display').innerText);
917
- this.spawnTimer = 0;
918
 
919
- // Reset Player
920
- this.player = new Player(this);
921
- this.playerPowerups = { shield: false, magnet: false, mul: false };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
922
 
923
- // UI
924
- document.getElementById('start-screen').classList.add('hidden');
925
- document.getElementById('game-over-screen').classList.add('hidden');
926
- document.getElementById('hud').classList.remove('hidden');
927
- document.getElementById('powerup-container').classList.remove('hidden');
928
- this.updateHUD();
929
- this.updatePowerupUI();
930
-
931
- this.lastTime = performance.now();
932
- requestAnimationFrame((t) => this.loop(t));
933
- }
934
-
935
- togglePause() {
936
- if (!this.isRunning) return;
937
- this.isPaused = !this.isPaused;
938
- document.getElementById('pause-btn').innerText = this.isPaused ? '▶' : '❚❚';
939
- if (!this.isPaused) {
940
- this.lastTime = performance.now();
941
- requestAnimationFrame((t) => this.loop(t));
942
  }
943
- }
944
-
945
- spawnParticles(x, y, count, color) {
946
- for (let i = 0; i < count; i++) {
947
- this.particles.push(new Particle(x, y, color, 5));
948
  }
949
- }
950
-
951
- spawn() {
952
- this.spawnTimer--;
953
 
954
- // Difficulty Scaling
955
- const difficultyMod = Math.min(this.distance / 5000, 1); // Cap at 2x speed
956
- const currentSpawnRate = Math.max(20, 60 - Math.floor(this.distance / 200));
957
-
958
- if (this.spawnTimer <= 0) {
959
- const lane = Utils.randomInt(0, 2);
960
- const rand = Math.random();
961
-
962
- // Spawn Obstacle
963
- if (rand < 0.4) {
964
- const type = Math.random() > 0.7 ? 'high' : (Math.random() > 0.5 ? 'low' : 'barrier');
965
- this.obstacles.push(new Obstacle(this, lane, type));
 
 
 
 
 
 
 
 
 
 
966
  }
967
- // Spawn Coins
968
- else if (rand < 0.8) {
969
- const isBonus = Math.random() > 0.8;
970
- this.coins.push(new Coin(this, lane, isBonus));
 
 
 
 
 
 
 
 
 
 
971
  }
972
- // Spawn Powerup
973
- else if (rand < 0.9) {
974
- const pTypes = ['shield', 'magnet', 'mul'];
975
- const pType = pTypes[Utils.randomInt(0, 2)];
976
- this.powerups.push(new PowerUp(this, lane, pType));
 
977
  }
978
-
979
- this.spawnTimer = currentSpawnRate;
980
  }
981
- }
982
-
983
- checkCollisions() {
984
- const pBounds = this.player.getBounds();
985
-
986
- // Obstacles
987
- for (let obs of this.obstacles) {
988
- const oBounds = obs.getBounds();
989
- if (this.rectIntersect(pBounds, oBounds)) {
990
- if (this.playerPowerups.shield) {
991
- // Destroy obstacle, keep shield
992
- this.playerPowerups.shield = false;
993
- this.spawnParticles(obs.x + obs.width/2, obs.y + obs.height/2, 10, '#87CEEB');
994
- this.obstacles = this.obstacles.filter(o => o !== obs);
995
- this.screenShake = 5;
996
- } else {
997
- this.gameOver();
998
- }
999
- }
1000
  }
1001
-
1002
- // Coins
1003
- for (let coin of this.coins) {
1004
- if (coin.collected) continue;
1005
- const cBounds = coin.getBounds();
1006
- if (this.rectIntersect(pBounds, cBounds)) {
1007
- coin.collected = true;
1008
- let val = coin.isBonus ? CONSTANTS.BONUS_COIN_VALUE : CONSTANTS.COIN_VALUE;
1009
- if (this.playerPowerups.mul) val *= 2;
1010
-
1011
- this.btcBalance += val;
1012
- this.score += 10;
1013
- this.spawnParticles(coin.x, coin.y, 5, CONSTANTS.COLORS.COIN);
1014
  }
1015
  }
1016
-
1017
- // Powerups
1018
- for (let pu of this.powerups) {
1019
- if (pu.collected) continue;
1020
- const pBounds = pu.getBounds();
1021
- if (this.rectIntersect(pBounds, pBounds)) { // Use static bounds for powerups
1022
- pu.collected = true;
1023
- this.activatePowerup(pu.type);
1024
  }
1025
  }
1026
- }
1027
-
1028
- activatePowerup(type) {
1029
- this.spawnParticles(this.player.x, this.player.y, 20, '#fff');
1030
- const duration = 600; // 10 seconds at 60fps
1031
 
1032
- if (type === 'shield') {
1033
- this.playerPowerups.shield = true;
1034
- this.powerupTimers.shield = duration;
1035
- document.getElementById('pu-shield').classList.add('active');
1036
- } else if (type === 'magnet') {
1037
- this.playerPowerups.magnet = true;
1038
- this.powerupTimers.magnet = duration;
1039
- document.getElementById('pu-magnet').classList.add('active');
1040
- } else if (type === 'mul') {
1041
- this.playerPowerups.mul = true;
1042
- this.powerupTimers.mul = duration;
1043
- document.getElementById('pu-mul').classList.add('active');
1044
  }
1045
- }
1046
-
1047
- updatePowerupUI() {
1048
- const ids = ['pu-shield', 'pu-magnet', 'pu-mul'];
1049
- ids.forEach(id => document.getElementById(id).classList.remove('active'));
1050
- }
1051
-
1052
- rectIntersect(r1, r2) {
1053
- return !(r2.x > r1.x + r1.width ||
1054
- r2.x + r2.width < r1.x ||
1055
- r2.y > r1.y + r1.height ||
1056
- r2.y + r2.height < r1.y);
1057
- }
1058
-
1059
- update(deltaTime) {
1060
- if (this.isPaused) return;
1061
-
1062
- // Speed Increase
1063
- this.speed += 0.0001;
1064
- this.distance += this.speed;
1065
- this.score += 1;
1066
-
1067
- // Update Entities
1068
- this.player.update();
1069
- this.spawn();
1070
-
1071
- this.obstacles.forEach(o => o.update());
1072
- this.obstacles = this.obstacles.filter(o => o.y > -100);
1073
-
1074
- this.coins.forEach(c => c.update());
1075
- this.coins = this.coins.filter(c => c.y > -100);
1076
-
1077
- this.powerups.forEach(p => p.update());
1078
- this.powerups = this.powerups.filter(p => p.y > -100);
1079
-
1080
- this.particles.forEach(p => p.update());
1081
- this.particles = this.particles.filter(p => p.life > 0);
1082
-
1083
- // Powerup Timers
1084
- for (let key in this.powerupTimers) {
1085
- if (this.powerupTimers[key] > 0) {
1086
- this.powerupTimers[key]--;
1087
- } else {
1088
- this.playerPowerups[key] = false;
1089
- delete this.powerupTimers[key];
1090
  }
1091
  }
1092
- this.updatePowerupUI();
1093
-
1094
- this.checkCollisions();
1095
-
1096
- // Screen Shake Decay
1097
- if (this.screenShake >
 
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>Bitcoin Dino Runner</title>
 
7
  <style>
 
 
 
 
 
 
 
 
8
  * {
 
 
 
 
 
 
 
9
  margin: 0;
10
  padding: 0;
11
+ box-sizing: border-box;
12
+ }
13
+
14
+ body {
15
  overflow: hidden;
16
+ font-family: 'Arial', sans-serif;
17
+ background: #121212;
18
+ touch-action: manipulation;
19
  }
20
+
21
+ #gameContainer {
22
  position: relative;
23
+ width: 100vw;
24
+ height: 100vh;
 
 
 
25
  }
26
+
27
+ #gameCanvas {
28
  display: block;
29
+ background: linear-gradient(to bottom, #87CEEB, #E0F7FA);
30
  }
31
+
32
+ #uiContainer {
 
33
  position: absolute;
34
  top: 0;
35
  left: 0;
36
  width: 100%;
37
  height: 100%;
 
 
 
 
 
 
 
 
 
 
 
 
38
  pointer-events: none;
 
39
  }
40
+
41
+ #hud {
42
+ position: absolute;
43
+ top: 20px;
44
+ left: 20px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
  color: white;
46
+ font-size: 18px;
47
+ text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
 
 
 
 
 
 
 
 
48
  }
49
+
50
+ #btcBalance {
51
+ font-family: 'Courier New', monospace;
52
+ font-weight: bold;
53
+ color: #FFD700;
54
  }
55
+
56
+ #startScreen, #gameOverScreen {
 
57
  position: absolute;
58
  top: 0;
59
  left: 0;
60
  width: 100%;
61
+ height: 100%;
 
 
 
 
 
 
 
 
 
 
 
 
62
  display: flex;
63
  flex-direction: column;
64
+ justify-content: center;
65
  align-items: center;
66
+ background: rgba(0, 0, 0, 0.7);
67
+ color: white;
68
+ z-index: 10;
69
  }
70
+
71
+ #gameOverScreen {
72
+ display: none;
 
 
 
73
  }
74
+
75
+ .title {
76
+ font-size: 48px;
77
+ margin-bottom: 30px;
78
+ color: #FFD700;
79
+ text-shadow: 3px 3px 6px rgba(0, 0, 0, 0.8);
80
  }
81
+
82
+ .subtitle {
83
+ font-size: 24px;
84
+ margin-bottom: 40px;
85
+ text-align: center;
86
+ }
87
+
88
+ .btn {
89
+ padding: 15px 30px;
90
+ font-size: 20px;
91
+ background: linear-gradient(to right, #FFD700, #FFA500);
92
+ border: none;
93
+ border-radius: 30px;
94
+ color: #121212;
95
+ cursor: pointer;
96
  pointer-events: auto;
97
+ transition: transform 0.2s, box-shadow 0.2s;
98
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
 
 
 
 
 
 
 
 
 
99
  }
100
 
101
+ .btn:hover {
102
+ transform: translateY(-3px);
103
+ box-shadow: 0 6px 12px rgba(0, 0, 0, 0.4);
104
  }
105
+
106
+ .btn:active {
107
+ transform: translateY(1px);
 
 
 
 
 
 
 
108
  }
109
 
110
+ #controlsInfo {
111
+ margin-top: 30px;
112
+ font-size: 16px;
113
+ opacity: 0.8;
114
  }
115
+
116
+ #header {
 
117
  position: absolute;
118
+ top: 10px;
119
+ right: 10px;
120
+ color: white;
121
+ font-size: 12px;
122
+ z-index: 20;
123
  }
124
 
125
+ #header a {
126
+ color: #FFD700;
127
+ text-decoration: none;
 
 
 
 
 
 
 
 
 
128
  }
129
 
130
+ @media (max-width: 768px) {
131
+ .title {
132
+ font-size: 36px;
133
+ }
134
+
135
+ .subtitle {
136
+ font-size: 18px;
137
+ }
138
+
139
+ .btn {
140
+ padding: 12px 24px;
141
+ font-size: 18px;
142
+ }
143
+
144
+ #hud {
145
+ font-size: 16px;
146
+ }
147
  }
 
148
  </style>
149
  </head>
150
  <body>
151
+ <div id="gameContainer">
152
+ <canvas id="gameCanvas"></canvas>
153
+ <div id="uiContainer">
154
+ <div id="hud">
155
+ <div>Distance: <span id="distance">0</span>m</div>
156
+ <div>BTC: <span id="btcBalance">0.00000000</span> ₿</div>
157
+ <div>Speed: <span id="speed">1</span>x</div>
158
+ </div>
159
+
160
+ <div id="startScreen">
161
+ <h1 class="title">Bitcoin Dino Runner</h1>
162
+ <p class="subtitle">Collect BTC while avoiding obstacles!</p>
163
+ <button class="btn" id="startBtn">Start Game</button>
164
+ <div id="controlsInfo">
165
+ <p>Controls: Arrow Keys or Swipe</p>
166
+ <p>↑ Jump | ↓ Slide | ← → Move</p>
167
+ </div>
168
+ </div>
169
+
170
+ <div id="gameOverScreen">
171
+ <h1 class="title">Game Over</h1>
172
+ <p class="subtitle">You collected <span id="finalBtc">0.00000000</span> BTC!</p>
173
+ <button class="btn" id="restartBtn">Play Again</button>
174
+ </div>
175
  </div>
176
+
177
+ <div id="header">
178
+ Built with <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank">anycoder</a>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
179
  </div>
 
180
  </div>
181
 
182
+ <script>
183
+ // Game Constants
184
+ const LANES = 3;
185
+ const LANE_WIDTH = 100;
186
+ const PLAYER_WIDTH = 60;
187
+ const PLAYER_HEIGHT = 80;
188
+ const JUMP_HEIGHT = 120;
189
+ const JUMP_DURATION = 800; // ms
190
+ const SLIDE_DURATION = 1000; // ms
191
+ const BASE_SPEED = 5;
192
+ const SPEED_INCREASE = 0.001;
193
+ const OBSTACLE_SPAWN_RATE = 120; // frames
194
+ const COIN_SPAWN_RATE = 30; // frames
195
+ const POWERUP_SPAWN_RATE = 500; // frames
196
+ const COIN_VALUE = 0.00000001;
197
+ const BIG_COIN_VALUE = 0.00000005;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
198
 
199
+ // Game Variables
200
+ let canvas, ctx;
201
+ let gameWidth, gameHeight;
202
+ let gameActive = false;
203
+ let gameOver = false;
204
+ let distance = 0;
205
+ let btcBalance = 0;
206
+ let gameSpeed = 1;
207
+ let currentLane = 1; // 0: left, 1: middle, 2: right
208
+ let playerState = 'running'; // running, jumping, sliding
209
+ let jumpStartTime = 0;
210
+ let slideStartTime = 0;
211
+ let lastObstacleSpawn = 0;
212
+ let lastCoinSpawn = 0;
213
+ let lastPowerupSpawn = 0;
214
+ let obstacles = [];
215
+ let coins = [];
216
+ let powerups = [];
217
+ let particles = [];
218
+ let backgroundOffset = 0;
219
+ let midgroundOffset = 0;
220
+ let foregroundOffset = 0;
221
+ let animationFrameId;
222
+ let lastTime = 0;
223
+ let combo = 0;
224
+ let comboTimeout = null;
225
+ let powerupActive = null;
226
+ let powerupEndTime = 0;
227
+ let shakeOffset = { x: 0, y: 0 };
228
+ let magnetRadius = 0;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
229
 
230
+ // DOM Elements
231
+ const distanceElement = document.getElementById('distance');
232
+ const btcBalanceElement = document.getElementById('btcBalance');
233
+ const speedElement = document.getElementById('speed');
234
+ const startScreen = document.getElementById('startScreen');
235
+ const gameOverScreen = document.getElementById('gameOverScreen');
236
+ const finalBtcElement = document.getElementById('finalBtc');
237
+ const startBtn = document.getElementById('startBtn');
238
+ const restartBtn = document.getElementById('restartBtn');
239
+
240
+ // Initialize game
241
+ function init() {
242
+ canvas = document.getElementById('gameCanvas');
243
+ ctx = canvas.getContext('2d');
244
+
245
+ resizeCanvas();
246
+ window.addEventListener('resize', resizeCanvas);
247
+
248
+ // Event listeners
249
+ startBtn.addEventListener('click', startGame);
250
+ restartBtn.addEventListener('click', restartGame);
251
+
252
+ // Keyboard controls
253
+ document.addEventListener('keydown', handleKeyDown);
254
+
255
+ // Touch controls
256
+ canvas.addEventListener('touchstart', handleTouchStart);
257
+ canvas.addEventListener('touchmove', handleTouchMove);
258
+
259
+ // Preload assets (in a real game, you'd want to preload images)
260
+ drawStartScreen();
261
  }
262
+
263
+ function resizeCanvas() {
264
+ gameWidth = window.innerWidth;
265
+ gameHeight = window.innerHeight;
266
+ canvas.width = gameWidth;
267
+ canvas.height = gameHeight;
268
+
269
+ // Redraw if needed
270
+ if (!gameActive) {
271
+ drawStartScreen();
272
  }
273
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
274
 
275
+ function drawStartScreen() {
276
+ // Draw animated dinosaur on start screen
277
+ ctx.clearRect(0, 0, gameWidth, gameHeight);
278
+
279
+ // Draw background
280
+ drawBackground(0);
281
+
282
+ // Draw dinosaur
283
+ const dinoX = gameWidth / 2;
284
+ const dinoY = gameHeight / 2 + 50;
285
+ const headBob = Math.sin(Date.now() / 200) * 5;
286
+
287
+ drawDino(dinoX, dinoY + headBob, 1.5, 'running');
288
+ }
289
 
290
+ function startGame() {
291
+ // Reset game state
292
+ gameActive = true;
293
+ gameOver = false;
294
+ distance = 0;
295
+ btcBalance = 0;
296
+ gameSpeed = 1;
297
+ currentLane = 1;
298
+ playerState = 'running';
299
+ obstacles = [];
300
+ coins = [];
301
+ powerups = [];
302
+ particles = [];
303
+ combo = 0;
304
+ powerupActive = null;
305
+
306
+ // Update UI
307
+ startScreen.style.display = 'none';
308
+ gameOverScreen.style.display = 'none';
309
+ updateHUD();
310
+
311
+ // Start game loop
312
+ lastTime = performance.now();
313
+ gameLoop(lastTime);
314
  }
 
 
 
 
 
 
 
 
 
315
 
316
+ function restartGame() {
317
+ gameOverScreen.style.display = 'none';
318
+ startGame();
319
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
320
 
321
+ function endGame() {
322
+ gameActive = false;
323
+ gameOver = true;
324
+ finalBtcElement.textContent = btcBalance.toFixed(8);
325
+ gameOverScreen.style.display = 'flex';
326
+ cancelAnimationFrame(animationFrameId);
327
+ }
 
 
 
 
328
 
329
+ function gameLoop(timestamp) {
330
+ if (!gameActive) return;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
331
 
332
+ const deltaTime = timestamp - lastTime;
333
+ lastTime = timestamp;
334
+
335
+ // Update game state
336
+ update(deltaTime);
337
+
338
+ // Render game
339
+ render();
340
+
341
+ // Continue loop
342
+ animationFrameId = requestAnimationFrame(gameLoop);
343
  }
344
+
345
+ function update(deltaTime) {
346
+ // Increase distance and speed
347
+ distance += gameSpeed;
348
+ gameSpeed = BASE_SPEED + (distance / 10000) * SPEED_INCREASE;
349
+
350
+ // Update player state
351
+ updatePlayer(deltaTime);
352
+
353
+ // Spawn obstacles and coins
354
+ spawnObjects();
355
+
356
+ // Update objects
357
+ updateObstacles();
358
+ updateCoins();
359
+ updatePowerups();
360
+ updateParticles();
361
+
362
+ // Check collisions
363
+ checkCollisions();
364
+
365
+ // Update powerups
366
+ updateActivePowerup(timestamp);
367
+
368
+ // Update combo
369
+ if (combo > 0 && comboTimeout && timestamp > comboTimeout) {
370
+ combo = 0;
371
+ comboTimeout = null;
372
+ }
373
+
374
+ // Update HUD
375
+ updateHUD();
376
+
377
+ // Screen shake at high speeds
378
+ if (gameSpeed > 10) {
379
+ shakeOffset.x = (Math.random() - 0.5) * 2 * (gameSpeed - 10) / 2;
380
+ shakeOffset.y = (Math.random() - 0.5) * 2 * (gameSpeed - 10) / 2;
381
+ } else {
382
+ shakeOffset.x = 0;
383
+ shakeOffset.y = 0;
384
+ }
385
  }
 
 
 
 
 
 
386
 
387
+ function updatePlayer(deltaTime) {
388
+ // Handle jump animation
389
+ if (playerState === 'jumping') {
390
+ const jumpProgress = (performance.now() - jumpStartTime) / JUMP_DURATION;
391
+ if (jumpProgress >= 1) {
392
+ playerState = 'running';
393
+ }
394
+ }
395
+
396
+ // Handle slide animation
397
+ if (playerState === 'sliding') {
398
+ const slideProgress = (performance.now() - slideStartTime) / SLIDE_DURATION;
399
+ if (slideProgress >= 1) {
400
+ playerState = 'running';
401
+ }
402
+ }
403
  }
404
 
405
+ function spawnObjects() {
406
+ // Spawn obstacles
407
+ if (distance - lastObstacleSpawn > OBSTACLE_SPAWN_RATE / gameSpeed) {
408
+ const lane = Math.floor(Math.random() * LANES);
409
+ const type = Math.random() > 0.3 ? 'barrier' : 'gap';
410
+
411
+ obstacles.push({
412
+ x: getLaneX(lane),
413
+ y: -100,
414
+ width: LANE_WIDTH - 20,
415
+ height: type === 'gap' ? 0 : 60,
416
+ type: type,
417
+ lane: lane
418
+ });
419
+
420
+ lastObstacleSpawn = distance;
421
+ }
422
+
423
+ // Spawn coins
424
+ if (distance - lastCoinSpawn > COIN_SPAWN_RATE / gameSpeed) {
425
+ const lane = Math.floor(Math.random() * LANES);
426
+ const isBig = Math.random() > 0.9;
427
+
428
+ coins.push({
429
+ x: getLaneX(lane),
430
+ y: -50,
431
+ radius: isBig ? 25 : 20,
432
+ value: isBig ? BIG_COIN_VALUE : COIN_VALUE,
433
+ isBig: isBig,
434
+ rotation: 0,
435
+ rotationSpeed: Math.random() * 0.1 + 0.05
436
+ });
437
+
438
+ lastCoinSpawn = distance;
439
+ }
440
+
441
+ // Spawn powerups
442
+ if (distance - lastPowerupSpawn > POWERUP_SPAWN_RATE / gameSpeed && !powerupActive) {
443
+ const lane = Math.floor(Math.random() * LANES);
444
+ const types = ['magnet', 'multiplier', 'shield', 'speed'];
445
+ const type = types[Math.floor(Math.random() * types.length)];
446
+
447
+ powerups.push({
448
+ x: getLaneX(lane),
449
+ y: -50,
450
+ radius: 25,
451
+ type: type,
452
+ rotation: 0
453
+ });
454
+
455
+ lastPowerupSpawn = distance;
456
+ }
457
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
458
 
459
+ function updateObstacles() {
460
+ for (let i = obstacles.length - 1; i >= 0; i--) {
461
+ obstacles[i].y += gameSpeed;
462
+
463
+ // Remove off-screen obstacles
464
+ if (obstacles[i].y > gameHeight) {
465
+ obstacles.splice(i, 1);
466
+ }
467
+ }
468
+ }
469
 
470
+ function updateCoins() {
471
+ for (let i = coins.length - 1; i >= 0; i--) {
472
+ coins[i].y += gameSpeed;
473
+ coins[i].rotation += coins[i].rotationSpeed;
474
+
475
+ // Coin magnet effect
476
+ if (powerupActive === 'magnet' && magnetRadius > 0) {
477
+ const dx = coins[i].x - getLaneX(currentLane);
478
+ const dy = coins[i].y - (gameHeight - 150);
479
+ const distance = Math.sqrt(dx * dx + dy * dy);
480
+
481
+ if (distance < magnetRadius) {
482
+ const pullStrength = (magnetRadius - distance) / magnetRadius * 10;
483
+ coins[i].x -= dx * 0.05 * pullStrength;
484
+ coins[i].y -= dy * 0.05 * pullStrength;
485
+ }
486
+ }
487
+
488
+ // Remove off-screen coins
489
+ if (coins[i].y > gameHeight + 50) {
490
+ coins.splice(i, 1);
491
+ }
492
+ }
493
+
494
+ // Update magnet radius
495
+ if (powerupActive === 'magnet') {
496
+ magnetRadius = 200 + Math.sin(Date.now() / 200) * 20;
497
+ } else {
498
+ magnetRadius = 0;
499
+ }
500
  }
501
+
502
+ function updatePowerups() {
503
+ for (let i = powerups.length - 1; i >= 0; i--) {
504
+ powerups[i].y += gameSpeed;
505
+ powerups[i].rotation += 0.05;
506
+
507
+ // Remove off-screen powerups
508
+ if (powerups[i].y > gameHeight + 50) {
509
+ powerups.splice(i, 1);
510
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
511
  }
 
 
 
 
 
 
 
 
512
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
513
 
514
+ function updateParticles() {
515
+ for (let i = particles.length - 1; i >= 0; i--) {
516
+ particles[i].x += particles[i].vx;
517
+ particles[i].y += particles[i].vy;
518
+ particles[i].life--;
519
+
520
+ if (particles[i].life <= 0) {
521
+ particles.splice(i, 1);
522
+ }
523
+ }
524
+ }
525
 
526
+ function updateActivePowerup(timestamp) {
527
+ if (powerupActive && timestamp > powerupEndTime) {
528
+ powerupActive = null;
529
+ powerupEndTime = 0;
 
 
 
 
 
 
530
  }
531
  }
 
 
 
 
 
 
 
532
 
533
+ function checkCollisions() {
534
+ const playerX = getLaneX(currentLane);
535
+ const playerY = gameHeight - 150;
536
+ const playerHeight = playerState === 'sliding' ? PLAYER_HEIGHT / 2 : PLAYER_HEIGHT;
537
+ const playerBottom = playerY + playerHeight;
538
+
539
+ // Check coin collisions
540
+ for (let i = coins.length - 1; i >= 0; i--) {
541
+ const dx = coins[i].x - playerX;
542
+ const dy = coins[i].y - playerY;
543
+ const distance = Math.sqrt(dx * dx + dy * dy);
544
+
545
+ if (distance < coins[i].radius + PLAYER_WIDTH / 2) {
546
+ // Collect coin
547
+ let value = coins[i].value;
548
+
549
+ // Apply multiplier if active
550
+ if (powerupActive === 'multiplier') {
551
+ value *= 2;
552
+ }
553
+
554
+ btcBalance += value;
555
+ coins.splice(i, 1);
556
+
557
+ // Add combo
558
+ combo++;
559
+ if (comboTimeout) clearTimeout(comboTimeout);
560
+ comboTimeout = timestamp + 2000; // 2 second combo window
561
+
562
+ // Create particles
563
+ createParticles(coins[i].x, coins[i].y, coins[i].isBig ? 'gold' : 'yellow', 10);
564
+ }
565
+ }
566
+
567
+ // Check powerup collisions
568
+ for (let i = powerups.length - 1; i >= 0; i--) {
569
+ const dx = powerups[i].x - playerX;
570
+ const dy = powerups[i].y - playerY;
571
+ const distance = Math.sqrt(dx * dx + dy * dy);
572
+
573
+ if (distance < powerups[i].radius + PLAYER_WIDTH / 2) {
574
+ // Activate powerup
575
+ powerupActive = powerups[i].type;
576
+ powerupEndTime = performance.now() + 10000; // 10 seconds
577
+ powerups.splice(i, 1);
578
+
579
+ // Create particles
580
+ createParticles(powerups[i].x, powerups[i].y, 'blue', 15);
581
+ }
582
+ }
583
+
584
+ // Check obstacle collisions
585
+ if (powerupActive === 'shield') return; // Shield protects from collisions
586
+
587
+ for (let i = 0; i < obstacles.length; i++) {
588
+ const obstacle = obstacles[i];
589
+
590
+ // Check if in same lane
591
+ if (obstacle.lane !== currentLane) continue;
592
+
593
+ // Check vertical position
594
+ if (obstacle.y + obstacle.height < playerY) continue;
595
+ if (obstacle.y > playerBottom) continue;
596
+
597
+ // For gaps, check if player is jumping
598
+ if (obstacle.type === 'gap' && playerState !== 'jumping') {
599
+ endGame();
600
+ return;
601
+ }
602
+
603
+ // For barriers, check if player is sliding
604
+ if (obstacle.type === 'barrier') {
605
+ if (playerState !== 'sliding' || obstacle.y > playerY + PLAYER_HEIGHT / 2) {
606
+ endGame();
607
+ return;
608
+ }
609
+ }
610
+ }
611
+ }
612
 
613
+ function createParticles(x, y, color, count) {
614
+ for (let i = 0; i < count; i++) {
615
+ particles.push({
616
+ x: x,
617
+ y: y,
618
+ vx: (Math.random() - 0.5) * 4,
619
+ vy: (Math.random() - 0.5) * 4,
620
+ radius: Math.random() * 3 + 2,
621
+ color: color,
622
+ life: Math.random() * 30 + 30
623
+ });
624
+ }
625
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
626
 
627
+ function updateHUD() {
628
+ distanceElement.textContent = Math.floor(distance / 10);
629
+ btcBalanceElement.textContent = btcBalance.toFixed(8);
630
+ speedElement.textContent = gameSpeed.toFixed(2);
631
+ }
632
 
633
+ function render() {
634
+ // Clear canvas with shake offset
635
+ ctx.save();
636
+ ctx.translate(shakeOffset.x, shakeOffset.y);
637
+ ctx.clearRect(0, 0, gameWidth, gameHeight);
638
+
639
+ // Draw background layers with parallax
640
+ drawBackground(backgroundOffset);
641
+
642
+ // Draw game objects
643
+ drawObstacles();
644
+ drawPowerups();
645
+ drawCoins();
646
+ drawParticles();
647
+
648
+ // Draw player
649
+ const playerX = getLaneX(currentLane);
650
+ const playerY = gameHeight - 150;
651
+ drawDino(playerX, playerY, 1, playerState);
652
+
653
+ // Draw powerup indicator
654
+ if (powerupActive) {
655
+ drawPowerupIndicator();
656
+ }
657
+
658
+ // Draw combo
659
+ if (combo > 1) {
660
+ drawCombo();
661
+ }
662
+
663
+ ctx.restore();
664
+ }
 
 
665
 
666
+ function drawBackground(offset) {
667
+ // Sky gradient
668
+ const skyGradient = ctx.createLinearGradient(0, 0, 0, gameHeight);
669
+ skyGradient.addColorStop(0, '#87CEEB');
670
+ skyGradient.addColorStop(1, '#E0F7FA');
671
+ ctx.fillStyle = skyGradient;
672
+ ctx.fillRect(0, 0, gameWidth, gameHeight);
673
+
674
+ // Distant mountains
675
+ ctx.fillStyle = '#5D4037';
676
+ for (let i = 0; i < 5; i++) {
677
+ const x = (i * 400 - offset * 0.2) % (gameWidth + 400) - 200;
678
+ ctx.beginPath();
679
+ ctx.moveTo(x, gameHeight - 200);
680
+ ctx.lineTo(x + 200, gameHeight - 300);
681
+ ctx.lineTo(x + 400, gameHeight - 200);
682
+ ctx.fill();
683
+ }
684
+
685
+ // Ground
686
+ ctx.fillStyle = '#8BC34A';
687
+ ctx.fillRect(0, gameHeight - 100, gameWidth, 100);
688
+
689
+ // Lane markers
690
+ ctx.strokeStyle = '#FFFFFF';
691
+ ctx.lineWidth = 2;
692
+ for (let lane = 1; lane < LANES; lane++) {
693
+ const x = getLaneX(lane) - LANE_WIDTH / 2;
694
+ ctx.setLineDash([20, 20]);
695
+ ctx.beginPath();
696
+ ctx.moveTo(x, gameHeight - 100);
697
+ ctx.lineTo(x, gameHeight);
698
+ ctx.stroke();
699
+ }
700
+ ctx.setLineDash([]);
701
+
702
+ // Update background offsets
703
+ backgroundOffset += gameSpeed * 0.2;
704
+ midgroundOffset += gameSpeed * 0.5;
705
+ foregroundOffset += gameSpeed;
706
+ }
707
 
708
+ function drawDino(x, y, scale, state) {
709
+ ctx.save();
710
+ ctx.translate(x, y);
711
+ ctx.scale(scale, scale);
712
+
713
+ // Body color
714
+ ctx.fillStyle = '#4CAF50';
715
+
716
+ // Calculate animation offsets
717
+ let bodyYOffset = 0;
718
+ let headBob = 0;
719
+ let legOffset = 0;
720
+
721
+ if (state === 'running') {
722
+ headBob = Math.sin(Date.now() / 200) * 5;
723
+ legOffset = Math.sin(Date.now() / 100) * 10;
724
+ } else if (state === 'jumping') {
725
+ const jumpProgress = (performance.now() - jumpStartTime) / JUMP_DURATION;
726
+ const jumpHeight = JUMP_HEIGHT * Math.sin(jumpProgress * Math.PI);
727
+ bodyYOffset = -jumpHeight;
728
+ } else if (state === 'sliding') {
729
+ bodyYOffset = PLAYER_HEIGHT / 4;
730
+ }
731
+
732
+ // Body
733
+ ctx.beginPath();
734
+ ctx.ellipse(0, bodyYOffset + 20, 30, 40, 0, 0, Math.PI * 2);
735
+ ctx.fill();
736
+
737
+ // Head
738
+ ctx.beginPath();
739
+ ctx.ellipse(0, bodyYOffset - 30 + headBob, 25, 20, 0, 0, Math.PI * 2);
740
+ ctx.fill();
741
+
742
+ // Eyes
743
+ ctx.fillStyle = 'white';
744
+ ctx.beginPath();
745
+ ctx.arc(-10, bodyYOffset - 35 + headBob, 5, 0, Math.PI * 2);
746
+ ctx.arc(10, bodyYOffset - 35 + headBob, 5, 0, Math.PI * 2);
747
+ ctx.fill();
748
+
749
+ ctx.fillStyle = 'black';
750
+ ctx.beginPath();
751
+ ctx.arc(-10, bodyYOffset - 35 + headBob, 2, 0, Math.PI * 2);
752
+ ctx.arc(10, bodyYOffset - 35 + headBob, 2, 0, Math.PI * 2);
753
+ ctx.fill();
754
+
755
+ // Headband
756
+ ctx.strokeStyle = 'red';
757
+ ctx.lineWidth = 3;
758
+ ctx.beginPath();
759
+ ctx.arc(0, bodyYOffset - 30 + headBob, 20, 0, Math.PI * 2);
760
+ ctx.stroke();
761
+
762
+ // Bitcoin symbol on headband
763
+ ctx.fillStyle = 'gold';
764
+ ctx.font = 'bold 16px Arial';
765
+ ctx.textAlign = 'center';
766
+ ctx.textBaseline = 'middle';
767
+ ctx.fillText('₿', 0, bodyYOffset - 30 + headBob);
768
+
769
+ // Legs
770
+ ctx.fillStyle = '#4CAF50';
771
+ if (state === 'sliding') {
772
+ // Sliding pose
773
+ ctx.beginPath();
774
+ ctx.ellipse(-20, bodyYOffset + 50, 10, 5, Math.PI/4, 0, Math.PI * 2);
775
+ ctx.ellipse(20, bodyYOffset + 50, 10, 5, -Math.PI/4, 0, Math.PI * 2);
776
+ ctx.fill();
777
+ } else {
778
+ // Running/jumping pose
779
+ ctx.beginPath();
780
+ ctx.ellipse(-15 + legOffset, bodyYOffset + 50, 10, 5, 0, 0, Math.PI * 2);
781
+ ctx.ellipse(15 - legOffset, bodyYOffset + 50, 10, 5, 0, 0, Math.PI * 2);
782
+ ctx.fill();
783
+ }
784
+
785
+ // Tail
786
+ ctx.beginPath();
787
+ ctx.moveTo(30, bodyYOffset + 10);
788
+ ctx.quadraticCurveTo(50, bodyYOffset, 60, bodyYOffset + 20);
789
+ ctx.lineWidth = 8;
790
+ ctx.strokeStyle = '#4CAF50';
791
+ ctx.stroke();
792
+
793
+ ctx.restore();
794
+ }
795
 
796
+ function drawObstacles() {
797
+ obstacles.forEach(obstacle => {
798
+ if (obstacle.type === 'barrier') {
799
+ // Draw barrier
800
+ ctx.fillStyle = '#795548';
801
+ ctx.fillRect(
802
+ obstacle.x - obstacle.width / 2,
803
+ obstacle.y,
804
+ obstacle.width,
805
+ obstacle.height
806
+ );
807
+
808
+ // Add some details
809
+ ctx.fillStyle = '#5D4037';
810
+ ctx.fillRect(
811
+ obstacle.x - obstacle.width / 2 + 5,
812
+ obstacle.y + 5,
813
+ obstacle.width - 10,
814
+ obstacle.height - 10
815
+ );
816
+ } else if (obstacle.type === 'gap') {
817
+ // Draw gap indicators
818
+ ctx.strokeStyle = '#FF5722';
819
+ ctx.lineWidth = 3;
820
+ ctx.setLineDash([10, 5]);
821
+ ctx.beginPath();
822
+ ctx.moveTo(obstacle.x - LANE_WIDTH / 2 + 10, obstacle.y + 20);
823
+ ctx.lineTo(obstacle.x + LANE_WIDTH / 2 - 10, obstacle.y + 20);
824
+ ctx.stroke();
825
+ ctx.setLineDash([]);
826
+ }
827
+ });
828
+ }
829
 
830
+ function drawCoins() {
831
+ coins.forEach(coin => {
832
+ ctx.save();
833
+ ctx.translate(coin.x, coin.y);
834
+ ctx.rotate(coin.rotation);
835
+
836
+ // Coin gradient
837
+ const gradient = ctx.createRadialGradient(0, 0, 0, 0, 0, coin.radius);
838
+ gradient.addColorStop(0, coin.isBig ? '#FFD700' : '#FFEB3B');
839
+ gradient.addColorStop(0.7, coin.isBig ? '#FFC107' : '#FFD600');
840
+ gradient.addColorStop(1, coin.isBig ? '#FFA000' : '#FFC107');
841
+
842
+ ctx.fillStyle = gradient;
843
+ ctx.beginPath();
844
+ ctx.arc(0, 0, coin.radius, 0, Math.PI * 2);
845
+ ctx.fill();
846
+
847
+ // Bitcoin symbol
848
+ ctx.fillStyle = 'rgba(0, 0, 0, 0.3)';
849
+ ctx.font = `bold ${coin.radius}px Arial`;
850
+ ctx.textAlign = 'center';
851
+ ctx.textBaseline = 'middle';
852
+ ctx.fillText('₿', 0, 0);
853
+
854
+ // Glow effect
855
+ if (Math.sin(Date.now() / 200) > 0.8) {
856
+ ctx.shadowColor = 'gold';
857
+ ctx.shadowBlur = 15;
858
+ ctx.beginPath();
859
+ ctx.arc(0, 0, coin.radius, 0, Math.PI * 2);
860
+ ctx.fill();
861
+ }
862
+
863
+ ctx.restore();
864
+ });
865
+ }
866
 
867
+ function drawPowerups() {
868
+ powerups.forEach(powerup => {
869
+ ctx.save();
870
+ ctx.translate(powerup.x, powerup.y);
871
+ ctx.rotate(powerup.rotation);
872
+
873
+ // Draw different powerups
874
+ switch (powerup.type) {
875
+ case 'magnet':
876
+ // Magnet powerup
877
+ ctx.fillStyle = '#2196F3';
878
+ ctx.beginPath();
879
+ ctx.arc(0, 0, powerup.radius, 0, Math.PI * 2);
880
+ ctx.fill();
881
+
882
+ ctx.fillStyle = 'white';
883
+ ctx.font = 'bold 20px Arial';
884
+ ctx.textAlign = 'center';
885
+ ctx.textBaseline = 'middle';
886
+ ctx.fillText('M', 0, 0);
887
+ break;
888
+
889
+ case 'multiplier':
890
+ // Multiplier powerup
891
+ ctx.fillStyle = '#FF9800';
892
+ ctx.beginPath();
893
+ ctx.arc(0, 0, powerup.radius, 0, Math.PI * 2);
894
+ ctx.fill();
895
+
896
+ ctx.fillStyle = 'white';
897
+ ctx.font = 'bold 20px Arial';
898
+ ctx.textAlign = 'center';
899
+ ctx.textBaseline = 'middle';
900
+ ctx.fillText('2×', 0, 0);
901
+ break;
902
+
903
+ case 'shield':
904
+ // Shield powerup
905
+ ctx.fillStyle = '#4CAF50';
906
+ ctx.beginPath();
907
+ ctx.arc(0, 0, powerup.radius, 0, Math.PI * 2);
908
+ ctx.fill();
909
+
910
+ ctx.fillStyle = 'white';
911
+ ctx.font = 'bold 20px Arial';
912
+ ctx.textAlign = 'center';
913
+ ctx.textBaseline = 'middle';
914
+ ctx.fillText('S', 0, 0);
915
+ break;
916
+
917
+ case 'speed':
918
+ // Speed powerup
919
+ ctx.fillStyle = '#F44336';
920
+ ctx.beginPath();
921
+ ctx.arc(0, 0, powerup.radius, 0, Math.PI * 2);
922
+ ctx.fill();
923
+
924
+ ctx.fillStyle = 'white';
925
+ ctx.font = 'bold 20px Arial';
926
+ ctx.textAlign = 'center';
927
+ ctx.textBaseline = 'middle';
928
+ ctx.fillText('⚡', 0, 0);
929
+ break;
930
+ }
931
+
932
+ // Glow effect
933
+ ctx.shadowColor = 'rgba(255, 255, 255, 0.7)';
934
+ ctx.shadowBlur = 10;
935
+ ctx.beginPath();
936
+ ctx.arc(0, 0, powerup.radius, 0, Math.PI * 2);
937
+ ctx.fill();
938
+
939
+ ctx.restore();
940
+ });
941
+ }
942
 
943
+ function drawParticles() {
944
+ particles.forEach(particle => {
945
+ ctx.globalAlpha = particle.life / 60;
946
+ ctx.fillStyle = particle.color;
947
+ ctx.beginPath();
948
+ ctx.arc(particle.x, particle.y, particle.radius, 0, Math.PI * 2);
949
+ ctx.fill();
950
+ });
951
+ ctx.globalAlpha = 1;
952
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
953
 
954
+ function drawPowerupIndicator() {
955
+ const x = gameWidth - 50;
956
+ const y = 50;
957
+ const radius = 20;
958
+ const timeLeft = (powerupEndTime - performance.now()) / 1000;
959
+ const angle = (timeLeft / 10) * Math.PI * 2;
960
+
961
+ // Draw background circle
962
+ ctx.fillStyle = 'rgba(0, 0, 0, 0.5)';
963
+ ctx.beginPath();
964
+ ctx.arc(x, y, radius + 5, 0, Math.PI * 2);
965
+ ctx.fill();
966
+
967
+ // Draw timer arc
968
+ ctx.strokeStyle = 'white';
969
+ ctx.lineWidth = 3;
970
+ ctx.beginPath();
971
+ ctx.arc(x, y, radius + 2, -Math.PI/2, -Math.PI/2 + angle);
972
+ ctx.stroke();
973
+
974
+ // Draw powerup icon
975
+ switch (powerupActive) {
976
+ case 'magnet':
977
+ ctx.fillStyle = '#2196F3';
978
+ ctx.beginPath();
979
+ ctx.arc(x, y, radius, 0, Math.PI * 2);
980
+ ctx.fill();
981
+
982
+ ctx.fillStyle = 'white';
983
+ ctx.font = 'bold 16px Arial';
984
+ ctx.textAlign = 'center';
985
+ ctx.textBaseline = 'middle';
986
+ ctx.fillText('M', x, y);
987
+ break;
988
+
989
+ case 'multiplier':
990
+ ctx.fillStyle = '#FF9800';
991
+ ctx.beginPath();
992
+ ctx.arc(x, y, radius, 0, Math.PI * 2);
993
+ ctx.fill();
994
+
995
+ ctx.fillStyle = 'white';
996
+ ctx.font = 'bold 16px Arial';
997
+ ctx.textAlign = 'center';
998
+ ctx.textBaseline = 'middle';
999
+ ctx.fillText('2×', x, y);
1000
+ break;
1001
+
1002
+ case 'shield':
1003
+ ctx.fillStyle = '#4CAF50';
1004
+ ctx.beginPath();
1005
+ ctx.arc(x, y, radius, 0, Math.PI * 2);
1006
+ ctx.fill();
1007
+
1008
+ ctx.fillStyle = 'white';
1009
+ ctx.font = 'bold 16px Arial';
1010
+ ctx.textAlign = 'center';
1011
+ ctx.textBaseline = 'middle';
1012
+ ctx.fillText('S', x, y);
1013
+ break;
1014
+
1015
+ case 'speed':
1016
+ ctx.fillStyle = '#F44336';
1017
+ ctx.beginPath();
1018
+ ctx.arc(x, y, radius, 0, Math.PI * 2);
1019
+ ctx.fill();
1020
+
1021
+ ctx.fillStyle = 'white';
1022
+ ctx.font = 'bold 16px Arial';
1023
+ ctx.textAlign = 'center';
1024
+ ctx.textBaseline = 'middle';
1025
+ ctx.fillText('⚡', x, y);
1026
+ break;
1027
+ }
1028
+ }
1029
 
1030
+ function drawCombo() {
1031
+ const x = gameWidth / 2;
1032
+ const y = 100;
1033
+ const scale = 1 + combo * 0.05;
1034
+
1035
+ ctx.save();
1036
+ ctx.translate(x, y);
1037
+ ctx.scale(scale, scale);
1038
+
1039
+ ctx.fillStyle = 'rgba(255, 215, 0, 0.8)';
1040
+ ctx.font = 'bold 24px Arial';
1041
+ ctx.textAlign = 'center';
1042
+ ctx.textBaseline = 'middle';
1043
+ ctx.fillText(`${combo}× COMBO!`, 0, 0);
1044
+
1045
+ ctx.restore();
 
 
 
1046
  }
1047
+
1048
+ function getLaneX(lane) {
1049
+ const centerX = gameWidth / 2;
1050
+ const laneOffset = (lane - 1) * LANE_WIDTH;
1051
+ return centerX + laneOffset;
1052
  }
 
 
 
 
1053
 
1054
+ function handleKeyDown(e) {
1055
+ if (!gameActive) return;
1056
+
1057
+ switch (e.key) {
1058
+ case 'ArrowLeft':
1059
+ moveLeft();
1060
+ break;
1061
+ case 'ArrowRight':
1062
+ moveRight();
1063
+ break;
1064
+ case 'ArrowUp':
1065
+ jump();
1066
+ break;
1067
+ case 'ArrowDown':
1068
+ slide();
1069
+ break;
1070
+ case ' ':
1071
+ jump();
1072
+ break;
1073
+ case 'Escape':
1074
+ // Pause game
1075
+ break;
1076
  }
1077
+ }
1078
+
1079
+ function handleTouchStart(e) {
1080
+ if (!gameActive) return;
1081
+ e.preventDefault();
1082
+
1083
+ const touchX = e.touches[0].clientX;
1084
+ const touchY = e.touches[0].clientY;
1085
+
1086
+ // Check if touch is in left or right half of screen
1087
+ if (touchX < gameWidth / 2) {
1088
+ moveLeft();
1089
+ } else {
1090
+ moveRight();
1091
  }
1092
+
1093
+ // Jump if touch is in upper half, slide if in lower half
1094
+ if (touchY < gameHeight / 2) {
1095
+ jump();
1096
+ } else {
1097
+ slide();
1098
  }
 
 
1099
  }
1100
+
1101
+ function handleTouchMove(e) {
1102
+ e.preventDefault();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1103
  }
1104
+
1105
+ function moveLeft() {
1106
+ if (playerState === 'running' || playerState === 'sliding') {
1107
+ currentLane = Math.max(0, currentLane - 1);
 
 
 
 
 
 
 
 
 
1108
  }
1109
  }
1110
+
1111
+ function moveRight() {
1112
+ if (playerState === 'running' || playerState === 'sliding') {
1113
+ currentLane = Math.min(LANES - 1, currentLane + 1);
 
 
 
 
1114
  }
1115
  }
 
 
 
 
 
1116
 
1117
+ function jump() {
1118
+ if (playerState === 'running') {
1119
+ playerState = 'jumping';
1120
+ jumpStartTime = performance.now();
1121
+ }
 
 
 
 
 
 
 
1122
  }
1123
+
1124
+ function slide() {
1125
+ if (playerState === 'running') {
1126
+ playerState = 'sliding';
1127
+ slideStartTime = performance.now();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1128
  }
1129
  }
1130
+
1131
+ // Start the game
1132
+ window.onload = init;
1133
+ </script>
1134
+ </body>
1135
+ </html>