Mousco commited on
Commit
74a5ae4
·
verified ·
1 Parent(s): 989e024

Upload folder using huggingface_hub

Browse files
Files changed (1) hide show
  1. index.html +924 -764
index.html CHANGED
@@ -1,792 +1,952 @@
1
  <!DOCTYPE html>
2
  <html lang="fr">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Aviator Aléatoire - Jeu Web</title>
7
- <!-- Importation de FontAwesome pour les icônes -->
8
- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
9
- <!-- Police Google Fonts -->
10
- <link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700;900&family=Roboto:wght@300;400;700&display=swap" rel="stylesheet">
11
-
12
- <style>
13
- :root {
14
- --bg-dark: #0f1923;
15
- --bg-panel: #1b2733;
16
- --primary: #e50914; /* Rouge crash */
17
- --accent: #4ade80; /* Vert gain */
18
- --text-main: #ffffff;
19
- --text-muted: #8b9bb4;
20
- --grid-line: rgba(255, 255, 255, 0.05);
21
- --border-radius: 12px;
22
- --glow: 0 0 15px rgba(74, 222, 128, 0.3);
23
- }
24
-
25
- * {
26
- box-sizing: border-box;
27
- margin: 0;
28
- padding: 0;
29
- user-select: none; /* Empêche la sélection de texte pendant le jeu */
30
- }
31
-
32
- body {
33
- font-family: 'Roboto', sans-serif;
34
- background-color: var(--bg-dark);
35
- color: var(--text-main);
36
- height: 100vh;
37
- display: flex;
38
- flex-direction: column;
39
- overflow: hidden;
40
- }
41
-
42
- /* --- Header --- */
43
- header {
44
- height: 60px;
45
- background-color: var(--bg-panel);
46
- display: flex;
47
- align-items: center;
48
- justify-content: space-between;
49
- padding: 0 20px;
50
- border-bottom: 1px solid rgba(255,255,255,0.1);
51
- z-index: 10;
52
- }
53
-
54
- .logo {
55
- font-family: 'Orbitron', sans-serif;
56
- font-size: 1.5rem;
57
- font-weight: 900;
58
- color: var(--text-main);
59
- display: flex;
60
- align-items: center;
61
- gap: 10px;
62
- }
63
-
64
- .logo i {
65
- color: var(--primary);
66
- }
67
-
68
- .built-with {
69
- font-size: 0.85rem;
70
- color: var(--text-muted);
71
- text-decoration: none;
72
- transition: color 0.3s;
73
- }
74
-
75
- .built-with:hover {
76
- color: var(--accent);
77
- }
78
-
79
- /* --- Main Layout --- */
80
- main {
81
- flex: 1;
82
- display: grid;
83
- grid-template-columns: 1fr 350px;
84
- gap: 20px;
85
- padding: 20px;
86
- height: calc(100vh - 60px);
87
- }
88
-
89
- /* --- Game Area (Canvas) --- */
90
- #game-container {
91
- background-color: var(--bg-panel);
92
- border-radius: var(--border-radius);
93
- position: relative;
94
- overflow: hidden;
95
- display: flex;
96
- flex-direction: column;
97
- box-shadow: 0 4px 20px rgba(0,0,0,0.3);
98
- }
99
-
100
- #history-bar {
101
- height: 40px;
102
- display: flex;
103
- align-items: center;
104
- gap: 8px;
105
- padding: 0 15px;
106
- background: rgba(0,0,0,0.2);
107
- overflow-x: auto;
108
- }
109
-
110
- /* Scrollbar customisée */
111
- #history-bar::-webkit-scrollbar { height: 4px; }
112
- #history-bar::-webkit-scrollbar-thumb { background: #333; border-radius: 2px; }
113
-
114
- .history-badge {
115
- padding: 2px 8px;
116
- border-radius: 4px;
117
- font-size: 0.8rem;
118
- font-weight: bold;
119
- min-width: 50px;
120
- text-align: center;
121
- }
122
- .badge-low { background: rgba(229, 9, 20, 0.2); color: var(--primary); }
123
- .badge-high { background: rgba(74, 222, 128, 0.2); color: var(--accent); }
124
-
125
- #canvas-wrapper {
126
- flex: 1;
127
- position: relative;
128
- width: 100%;
129
- height: 100%;
130
- }
131
-
132
- canvas {
133
- display: block;
134
- width: 100%;
135
- height: 100%;
136
- }
137
-
138
- #multiplier-overlay {
139
- position: absolute;
140
- top: 50%;
141
- left: 50%;
142
- transform: translate(-50%, -50%);
143
- font-family: 'Orbitron', sans-serif;
144
- font-size: 5rem;
145
- font-weight: 700;
146
- color: rgba(255,255,255,0.1);
147
- pointer-events: none;
148
- transition: color 0.2s;
149
- }
150
-
151
- /* --- Sidebar Controls --- */
152
- #sidebar {
153
- background-color: var(--bg-panel);
154
- border-radius: var(--border-radius);
155
- padding: 25px;
156
- display: flex;
157
- flex-direction: column;
158
- gap: 20px;
159
- box-shadow: 0 4px 20px rgba(0,0,0,0.3);
160
- }
161
-
162
- .balance-card {
163
- background: rgba(255,255,255,0.05);
164
- padding: 15px;
165
- border-radius: 8px;
166
- text-align: center;
167
- }
168
-
169
- .balance-label {
170
- font-size: 0.9rem;
171
- color: var(--text-muted);
172
- margin-bottom: 5px;
173
- }
174
-
175
- .balance-amount {
176
- font-size: 1.8rem;
177
- font-weight: bold;
178
- color: var(--text-main);
179
- }
180
-
181
- .control-group {
182
- display: flex;
183
- flex-direction: column;
184
- gap: 10px;
185
- }
186
-
187
- label {
188
- font-size: 0.9rem;
189
- color: var(--text-muted);
190
- }
191
-
192
- .input-wrapper {
193
- position: relative;
194
- display: flex;
195
- align-items: center;
196
- }
197
-
198
- .currency-symbol {
199
- position: absolute;
200
- left: 15px;
201
- color: var(--text-muted);
202
- }
203
-
204
- input[type="number"] {
205
- width: 100%;
206
- background: rgba(0,0,0,0.3);
207
- border: 1px solid rgba(255,255,255,0.1);
208
- padding: 12px 12px 12px 35px;
209
- border-radius: 8px;
210
- color: white;
211
- font-size: 1.1rem;
212
- font-family: 'Roboto', sans-serif;
213
- outline: none;
214
- transition: border-color 0.3s;
215
- }
216
-
217
- input[type="number"]:focus {
218
- border-color: var(--accent);
219
- }
220
-
221
- .quick-amounts {
222
- display: flex;
223
- gap: 10px;
224
- }
225
-
226
- .btn-quick {
227
- background: rgba(255,255,255,0.1);
228
- border: none;
229
- color: var(--text-muted);
230
- padding: 5px 10px;
231
- border-radius: 4px;
232
- cursor: pointer;
233
- font-size: 0.8rem;
234
- transition: all 0.2s;
235
- }
236
-
237
- .btn-quick:hover {
238
- background: rgba(255,255,255,0.2);
239
- color: white;
240
- }
241
-
242
- #action-btn {
243
- width: 100%;
244
- padding: 18px;
245
- border: none;
246
- border-radius: 8px;
247
- font-size: 1.2rem;
248
- font-weight: bold;
249
- text-transform: uppercase;
250
- cursor: pointer;
251
- transition: transform 0.1s, box-shadow 0.3s;
252
- font-family: 'Orbitron', sans-serif;
253
- }
254
-
255
- .btn-bet {
256
- background: linear-gradient(135deg, #e50914 0%, #b20710 100%);
257
- color: white;
258
- box-shadow: 0 4px 15px rgba(229, 9, 20, 0.4);
259
- }
260
-
261
- .btn-cashout {
262
- background: linear-gradient(135deg, #4ade80 0%, #22c55e 100%);
263
- color: #064e3b;
264
- box-shadow: 0 4px 15px rgba(74, 222, 128, 0.4);
265
- }
266
-
267
- .btn-disabled {
268
- background: #333;
269
- color: #777;
270
- cursor: not-allowed;
271
- box-shadow: none;
272
- }
273
-
274
- #action-btn:active:not(.btn-disabled) {
275
- transform: scale(0.98);
276
- }
277
 
278
- .status-msg {
279
- text-align: center;
280
- font-size: 0.9rem;
281
- min-height: 1.2em;
282
- }
283
-
284
- /* --- Footer/Stats --- */
285
- .stats-row {
286
- margin-top: auto;
287
- display: flex;
288
- justify-content: space-between;
289
- padding-top: 20px;
290
- border-top: 1px solid rgba(255,255,255,0.1);
291
- font-size: 0.9rem;
292
- color: var(--text-muted);
293
- }
294
-
295
- /* --- Toast Notifications --- */
296
- #toast-container {
297
- position: fixed;
298
- bottom: 20px;
299
- right: 20px;
300
- display: flex;
301
- flex-direction: column;
302
- gap: 10px;
303
- z-index: 100;
304
- }
305
-
306
- .toast {
307
- background: var(--bg-panel);
308
- border-left: 4px solid var(--accent);
309
- padding: 15px 20px;
310
- border-radius: 4px;
311
- box-shadow: 0 5px 15px rgba(0,0,0,0.5);
312
- display: flex;
313
- align-items: center;
314
- gap: 10px;
315
- animation: slideIn 0.3s ease-out forwards;
316
- min-width: 250px;
317
- }
318
-
319
- .toast.error { border-left-color: var(--primary); }
320
- .toast i { font-size: 1.2rem; }
321
-
322
- @keyframes slideIn {
323
- from { transform: translateX(100%); opacity: 0; }
324
- to { transform: translateX(0); opacity: 1; }
325
- }
326
-
327
- @keyframes fadeOut {
328
- to { opacity: 0; transform: translateX(20px); }
329
- }
330
-
331
- /* --- Responsive Design --- */
332
- @media (max-width: 900px) {
333
- main {
334
- grid-template-columns: 1fr;
335
- grid-template-rows: 1fr auto;
336
- height: auto;
337
- overflow-y: auto;
338
- }
339
-
340
- #game-container {
341
- height: 50vh;
342
- }
343
-
344
- #sidebar {
345
- height: auto;
346
- }
347
- }
348
- </style>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
349
  </head>
 
350
  <body>
351
 
352
- <header>
353
- <div class="logo">
354
- <i class="fa-solid fa-plane-up"></i>
355
- <span>AVIATOR</span>
356
- </div>
357
- <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="built-with">
358
- Built with anycoder <i class="fa-solid fa-external-link-alt"></i>
359
- </a>
360
- </header>
361
-
362
- <main>
363
- <!-- Zone de Jeu -->
364
- <section id="game-container">
365
- <div id="history-bar">
366
- <!-- Historique rempli par JS -->
367
- </div>
368
- <div id="canvas-wrapper">
369
- <canvas id="gameCanvas"></canvas>
370
- <div id="multiplier-overlay">1.00x</div>
 
 
 
 
 
371
  </div>
372
- </section>
373
-
374
- <!-- Contrôles -->
375
- <section id="sidebar">
376
- <div class="balance-card">
377
- <div class="balance-label">Solde Disponible</div>
378
- <div class="balance-amount">$<span id="balance">1000.00</span></div>
379
  </div>
380
-
381
- <div class="control-group">
382
- <label for="bet-input">Montant du pari</label>
383
- <div class="input-wrapper">
384
- <span class="currency-symbol">$</span>
385
- <input type="number" id="bet-input" value="10.00" min="1.00" step="1.00">
386
- </div>
387
- <div class="quick-amounts">
388
- <button class="btn-quick" onclick="setBet(10)">10</button>
389
- <button class="btn-quick" onclick="setBet(50)">50</button>
390
- <button class="btn-quick" onclick="setBet(100)">100</button>
391
- <button class="btn-quick" onclick="setBet(maxBet())">Max</button>
392
- </div>
393
  </div>
394
-
395
- <div id="status-display" class="status-msg">Prêt pour le décollage</div>
396
-
397
- <button id="action-btn" class="btn-bet">MISER</button>
398
-
399
- <div class="stats-row">
400
- <span>Dernier gain: <span id="last-win" style="color:white">$0.00</span></span>
401
- <span>Meilleur: <span id="best-crash" style="color:white">-</span></span>
402
  </div>
403
- </section>
404
- </main>
405
-
406
- <div id="toast-container"></div>
407
-
408
- <script>
409
- // --- Configuration du Jeu ---
410
- const config = {
411
- maxSpeed: 0.05, // Vitesse d'augmentation du multiplicateur par frame
412
- crashProbability: 0.05 // Facteur pour l'algorithme de crash
413
- };
414
-
415
- // --- État du Jeu ---
416
- const state = {
417
- balance: 1000.00,
418
- currentBet: 0,
419
- isBetPlaced: false,
420
- gameStatus: 'IDLE', // IDLE, FLYING, CRASHED
421
- multiplier: 1.00,
422
- crashPoint: 0,
423
- history: [],
424
- startTime: 0,
425
- animationId: null
426
- };
427
-
428
- // --- Éléments DOM ---
429
- const canvas = document.getElementById('gameCanvas');
430
- const ctx = canvas.getContext('2d');
431
- const multiplierEl = document.getElementById('multiplier-overlay');
432
- const balanceEl = document.getElementById('balance');
433
- const betInput = document.getElementById('bet-input');
434
- const actionBtn = document.getElementById('action-btn');
435
- const statusEl = document.getElementById('status-display');
436
- const historyContainer = document.getElementById('history-bar');
437
- const lastWinEl = document.getElementById('last-win');
438
- const bestCrashEl = document.getElementById('best-crash');
439
-
440
- // --- Redimensionnement du Canvas ---
441
- let width, height;
442
- function resizeCanvas() {
443
- const wrapper = document.getElementById('canvas-wrapper');
444
- width = wrapper.clientWidth;
445
- height = wrapper.clientHeight;
446
- canvas.width = width;
447
- canvas.height = height;
448
- }
449
- window.addEventListener('resize', resizeCanvas);
450
- resizeCanvas();
451
-
452
- // --- Utilitaires ---
453
- function formatMoney(amount) {
454
- return amount.toFixed(2);
455
- }
456
-
457
- function showToast(message, type = 'success') {
458
- const container = document.getElementById('toast-container');
459
- const toast = document.createElement('div');
460
- toast.className = `toast ${type}`;
461
-
462
- const icon = type === 'success' ? 'fa-check-circle' : 'fa-exclamation-circle';
463
- toast.innerHTML = `<i class="fa-solid ${icon}"></i> <span>${message}</span>`;
464
-
465
- container.appendChild(toast);
466
-
467
- // Auto remove
468
- setTimeout(() => {
469
- toast.style.animation = 'fadeOut 0.3s ease-out forwards';
470
- toast.addEventListener('animationend', () => toast.remove());
471
- }, 3000);
472
- }
473
-
474
- function setBet(amount) {
475
- if (state.gameStatus !== 'IDLE') return;
476
- betInput.value = amount;
477
- }
478
 
479
- function maxBet() {
480
- return state.balance;
481
- }
482
 
483
- // --- Logique RNG (Génération Aléatoire) ---
484
- // Utilise une distribution inverse pour favoriser les petits nombres mais permettre des crashs élevés
485
- function generateCrashPoint() {
486
- // Formule standard: E = 100 / (1 - random)
487
- // Nous ajustons pour que le minimum soit souvent 1.00x
488
- const r = Math.random();
489
- // House edge simulée (3%)
490
- const crashPoint = (0.97 / (1 - r));
491
-
492
- // Arrondir à 2 décimales, minimum 1.00
493
- let finalCrash = Math.floor(crashPoint * 100) / 100;
494
- if (finalCrash < 1.00) finalCrash = 1.00;
495
-
496
- return finalCrash;
497
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
498
 
499
- // --- Boucle de Jeu et Rendu ---
500
- function startGame() {
501
- // Validation
502
- const betAmount = parseFloat(betInput.value);
503
- if (isNaN(betAmount) || betAmount <= 0) {
504
- showToast("Veuillez entrer un montant valide.", "error");
505
- return;
506
- }
507
- if (betAmount > state.balance) {
508
- showToast("Fonds insuffisants.", "error");
509
- return;
510
- }
511
-
512
- // Mise à jour état
513
- state.balance -= betAmount;
514
- state.currentBet = betAmount;
515
- state.isBetPlaced = true;
516
- state.gameStatus = 'FLYING';
517
- state.crashPoint = generateCrashPoint();
518
- state.startTime = Date.now();
519
- state.multiplier = 1.00;
520
-
521
- updateUI();
522
-
523
- // Démarrage animation
524
- if (state.animationId) cancelAnimationFrame(state.animationId);
525
- gameLoop();
526
- }
527
 
528
- function cashOut() {
529
- if (!state.isBetPlaced || state.gameStatus !== 'FLYING') return;
530
-
531
- const winAmount = state.currentBet * state.multiplier;
532
- state.balance += winAmount;
533
- state.isBetPlaced = false; // Plus de pari en cours
534
-
535
- lastWinEl.textContent = `$${formatMoney(winAmount)}`;
536
- showToast(`Gagné! $${formatMoney(winAmount)} à ${state.multiplier.toFixed(2)}x`, "success");
537
-
538
- updateUI();
539
-
540
- // Désactiver le bouton cashout visuellement
541
- actionBtn.className = 'btn-disabled';
542
- actionBtn.textContent = 'EN ATTENTE...';
543
  }
544
 
545
- function endGame() {
546
- state.gameStatus = 'CRASHED';
547
- state.isBetPlaced = false;
548
- state.multiplier = state.crashPoint;
549
-
550
- // Mise à jour historique
551
- addToHistory(state.crashPoint);
552
-
553
- // Message si le joueur avait misé et n'a pas cashout
554
- const betAmount = parseFloat(betInput.value);
555
- if (state.currentBet > 0 && state.currentBet === betAmount) {
556
- // Logique simple : si on crash et qu'on a encore un bet actif en mémoire (simplifié ici)
557
- // Dans ce code simple, si on n'a pas cashout, on a perdu.
558
- showToast(`L'avion a parti à ${state.crashPoint}x`, "error");
559
- }
560
-
561
- updateUI();
562
-
563
- // Redémarrage automatique après 5 secondes
564
- statusEl.textContent = "Prochain décollage dans 5s...";
565
- setTimeout(() => {
566
- resetGame();
567
- }, 5000);
568
  }
569
 
570
- function resetGame() {
571
- state.gameStatus = 'IDLE';
572
- state.multiplier = 1.00;
573
- state.crashPoint = 0;
574
- updateUI();
575
- }
576
 
577
- function addToHistory(crash) {
578
- state.history.unshift(crash);
579
- if (state.history.length > 20) state.history.pop();
580
-
581
- const badge = document.createElement('div');
582
- badge.className = `history-badge ${crash >= 2.0 ? 'badge-high' : 'badge-low'}`;
583
- badge.textContent = `${crash.toFixed(2)}x`;
584
-
585
- historyContainer.prepend(badge);
586
- if (historyContainer.children.length > 20) {
587
- historyContainer.lastChild.remove();
588
- }
589
-
590
- // Meilleur crash
591
- const currentBest = parseFloat(bestCrashEl.textContent) || 0;
592
- if (crash > currentBest) {
593
- bestCrashEl.textContent = `${crash.toFixed(2)}x`;
594
- }
595
- }
596
 
597
- function updateUI() {
598
- balanceEl.textContent = formatMoney(state.balance);
599
- multiplierEl.textContent = state.multiplier.toFixed(2) + 'x';
600
-
601
- if (state.gameStatus === 'IDLE') {
602
- actionBtn.className = 'btn-bet';
603
- actionBtn.textContent = 'MISER';
604
- actionBtn.disabled = false;
605
- actionBtn.style.cursor = 'pointer';
606
- statusEl.textContent = "Veuillez placer votre pari";
607
- multiplierEl.style.color = "rgba(255,255,255,0.1)";
608
- } else if (state.gameStatus === 'FLYING') {
609
- if (state.isBetPlaced) {
610
- actionBtn.className = 'btn-cashout';
611
- actionBtn.textContent = `RETIRER ($${formatMoney(state.currentBet * state.multiplier)})`;
612
- actionBtn.onclick = cashOut;
613
- multiplierEl.style.color = "var(--accent)";
614
- statusEl.textContent = "En vol...";
615
- } else {
616
- // En vol mais sans pari (tour suivant)
617
- actionBtn.className = 'btn-disabled';
618
- actionBtn.textContent = 'EN VOL';
619
- multiplierEl.style.color = "var(--text-main)";
620
- statusEl.textContent = "Trop tard pour miser!";
621
- }
622
- } else if (state.gameStatus === 'CRASHED') {
623
- actionBtn.className = 'btn-disabled';
624
- actionBtn.textContent = 'CRASHED';
625
- multiplierEl.style.color = "var(--primary)";
626
- statusEl.textContent = `Envoyé à ${state.crashPoint.toFixed(2)}x`;
627
- }
628
- }
629
 
630
- // --- Rendu Graphique (Canvas) ---
631
- function drawGrid() {
632
- ctx.clearRect(0, 0, width, height);
633
-
634
- ctx.strokeStyle = 'rgba(255, 255, 255, 0.05)';
635
- ctx.lineWidth = 1;
636
-
637
- // Lignes verticales
638
- for (let x = 0; x < width; x += 50) {
639
- ctx.beginPath();
640
- ctx.moveTo(x, 0);
641
- ctx.lineTo(x, height);
642
- ctx.stroke();
643
- }
644
-
645
- // Lignes horizontales
646
- for (let y = 0; y < height; y += 50) {
647
- ctx.beginPath();
648
- ctx.moveTo(0, y);
649
- ctx.lineTo(width, y);
650
- ctx.stroke();
651
- }
652
- }
653
 
654
- function gameLoop() {
655
- if (state.gameStatus !== 'FLYING') return;
656
-
657
- // Calcul du multiplicateur basé sur le temps
658
- // Croissance exponentielle simple
659
- const now = Date.now();
660
- const elapsed = (now - state.startTime) / 1000;
661
-
662
- // Formule de croissance: 1 + (elapsed^2) * speed pour une accélération progressive
663
- state.multiplier = 1 + (elapsed * elapsed * 0.1) + (elapsed * 0.1);
664
-
665
- // Vérification Crash
666
- if (state.multiplier >= state.crashPoint) {
667
- state.multiplier = state.crashPoint; // Clamp pour affichage exact
668
- drawGrid();
669
- drawCurve(state.crashPoint, true);
670
- endGame();
671
- return;
672
- }
673
-
674
- // Mise à jour UI Bouton (montant potentiel)
675
- if (state.isBetPlaced) {
676
- actionBtn.textContent = `RETIRER ($${formatMoney(state.currentBet * state.multiplier)})`;
677
- }
678
- multiplierEl.textContent = state.multiplier.toFixed(2) + 'x';
679
-
680
- // Dessin
681
- drawGrid();
682
- drawCurve(state.multiplier, false);
683
-
684
- state.animationId = requestAnimationFrame(gameLoop);
685
- }
686
 
687
- function drawCurve(currentMult, isCrashed) {
688
- ctx.beginPath();
689
-
690
- // Définition de l'échelle
691
- // X représente le temps, Y le multiplicateur
692
- // On veut que la courbe reste visible à l'écran
693
-
694
- // Echelle Y dynamique : plus le multiplicateur est haut, plus on dézoome
695
- const scaleY = height / Math.max(2, currentMult * 1.5);
696
- const scaleX = width / 10; // 10 secondes de largeur virtuelle par défaut
697
-
698
- // Dessiner la courbe rouge (trajectoire passée)
699
- ctx.moveTo(0, height - (1 * scaleY)); // Départ à 1x en bas à gauche
700
-
701
- // Simulation de la courbe quadratique
702
- // On dessine une courbe de Bézier simple pour l'effet visuel
703
- const endX = (Math.sqrt(currentMult - 1) * 200) + 50; // Mapping arbitraire pour l'effet visuel
704
- const endY = height - (currentMult * scaleY);
705
-
706
- ctx.quadraticCurveTo(endX / 2, height, endX, endY);
707
-
708
- ctx.lineWidth = 5;
709
- ctx.strokeStyle = isCrashed ? '#e50914' : '#e50914';
710
- ctx.stroke();
711
-
712
- // Ombre/Glow
713
- ctx.shadowBlur = 10;
714
- ctx.shadowColor = '#e50914';
715
- ctx.stroke();
716
- ctx.shadowBlur = 0;
717
-
718
- // Dessiner l'avion (ou le point de crash)
719
- if (!isCrashed) {
720
- ctx.save();
721
- ctx.translate(endX, endY);
722
- // Rotation basée sur la pente
723
- ctx.rotate(-Math.PI / 4);
724
-
725
- // Dessin de l'icône avion simplifiée
726
- ctx.fillStyle = '#ffffff';
727
- ctx.beginPath();
728
- ctx.moveTo(10, 0);
729
- ctx.lineTo(-10, 7);
730
- ctx.lineTo(-10, -7);
731
- ctx.fill();
732
-
733
- // Flamme
734
- ctx.fillStyle = '#f59e0b';
735
- ctx.beginPath();
736
- ctx.moveTo(-10, 0);
737
- ctx.lineTo(-18, 0);
738
- ctx.stroke();
739
-
740
- ctx.restore();
741
- } else {
742
- // Dessiner le point de crash
743
- ctx.fillStyle = '#e50914';
744
- ctx.beginPath();
745
- ctx.arc(endX, endY, 6, 0, Math.PI * 2);
746
- ctx.fill();
747
-
748
- // Croix
749
- ctx.strokeStyle = '#fff';
750
- ctx.lineWidth = 2;
751
- ctx.beginPath();
752
- ctx.moveTo(endX - 10, endY - 10);
753
- ctx.lineTo(endX + 10, endY + 10);
754
- ctx.moveTo(endX + 10, endY - 10);
755
- ctx.lineTo(endX - 10, endY + 10);
756
- ctx.stroke();
757
- }
758
- }
759
 
760
- // --- Event Listeners ---
761
- actionBtn.addEventListener('click', () => {
762
- if (state.gameStatus === 'IDLE') {
763
- startGame();
764
- } else if (state.gameStatus === 'FLYING' && state.isBetPlaced) {
765
- cashOut();
766
- }
767
- });
768
-
769
- // Initialisation
770
- resizeCanvas();
771
- drawGrid();
772
 
773
- // Animation d'attente (grille qui scrolle légèrement)
774
- let bgOffset = 0;
775
- function idleAnimation() {
776
- if (state.gameStatus === 'IDLE') {
777
- bgOffset = (bgOffset + 0.5) % 50;
778
- drawGrid();
779
-
780
- // Effet visuel "Ready"
781
- ctx.fillStyle = "rgba(255, 255, 255, 0.1)";
782
- ctx.font = "20px Orbitron";
783
- ctx.textAlign = "center";
784
- ctx.fillText("EN ATTENTE DE DÉCOLLAGE", width/2, height/2);
785
- }
786
- requestAnimationFrame(idleAnimation);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
787
  }
788
- idleAnimation();
789
-
790
- </script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
791
  </body>
 
792
  </html>
 
1
  <!DOCTYPE html>
2
  <html lang="fr">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
 
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>Naruto Shippuden - Duel Ninja</title>
8
+ <!-- Importation de FontAwesome pour les icônes -->
9
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
10
+ <!-- Police Google Fonts -->
11
+ <link href="https://fonts.googleapis.com/css2?family=Bangers&family=Roboto:wght@400;700&display=swap" rel="stylesheet">
12
+
13
+ <style>
14
+ :root {
15
+ --naruto-orange: #ff5e00;
16
+ --naruto-yellow: #ffcc00;
17
+ --sasuke-blue: #2e3192;
18
+ --sasuke-purple: #662d91;
19
+ --bg-dark: #1a1a1a;
20
+ --ui-panel: rgba(0, 0, 0, 0.8);
21
+ --text-main: #ffffff;
22
+ }
23
+
24
+ * {
25
+ box-sizing: border-box;
26
+ margin: 0;
27
+ padding: 0;
28
+ user-select: none;
29
+ }
30
+
31
+ body {
32
+ font-family: 'Roboto', sans-serif;
33
+ background-color: #050505;
34
+ color: var(--text-main);
35
+ height: 100vh;
36
+ display: flex;
37
+ flex-direction: column;
38
+ overflow: hidden;
39
+ background-image: radial-gradient(circle at center, #2a2a2a 0%, #000000 100%);
40
+ }
41
+
42
+ /* --- Header --- */
43
+ header {
44
+ height: 60px;
45
+ background: rgba(0, 0, 0, 0.5);
46
+ display: flex;
47
+ align-items: center;
48
+ justify-content: space-between;
49
+ padding: 0 20px;
50
+ border-bottom: 1px solid rgba(255, 255, 255, 0.1);
51
+ z-index: 10;
52
+ backdrop-filter: blur(5px);
53
+ }
54
+
55
+ .logo {
56
+ font-family: 'Bangers', cursive;
57
+ font-size: 1.8rem;
58
+ letter-spacing: 2px;
59
+ color: var(--naruto-orange);
60
+ text-shadow: 2px 2px 0px #fff;
61
+ display: flex;
62
+ align-items: center;
63
+ gap: 10px;
64
+ }
65
+
66
+ .logo i {
67
+ color: var(--sasuke-blue);
68
+ }
69
+
70
+ .built-with {
71
+ font-size: 0.85rem;
72
+ color: #888;
73
+ text-decoration: none;
74
+ transition: color 0.3s;
75
+ }
76
+
77
+ .built-with:hover {
78
+ color: white;
79
+ }
80
+
81
+ /* --- Main Game Area --- */
82
+ main {
83
+ flex: 1;
84
+ position: relative;
85
+ display: flex;
86
+ justify-content: center;
87
+ align-items: center;
88
+ }
89
+
90
+ #game-wrapper {
91
+ position: relative;
92
+ width: 100%;
93
+ height: 100%;
94
+ max-width: 1200px;
95
+ max-height: 800px;
96
+ background: #222;
97
+ box-shadow: 0 0 50px rgba(0, 0, 0, 0.5);
98
+ overflow: hidden;
99
+ border: 2px solid #333;
100
+ }
101
+
102
+ canvas {
103
+ display: block;
104
+ width: 100%;
105
+ height: 100%;
106
+ }
107
+
108
+ /* --- UI Overlay (HUD) --- */
109
+ #ui-layer {
110
+ position: absolute;
111
+ top: 0;
112
+ left: 0;
113
+ width: 100%;
114
+ height: 100%;
115
+ pointer-events: none;
116
+ padding: 20px;
117
+ display: flex;
118
+ flex-direction: column;
119
+ justify-content: space-between;
120
+ }
121
+
122
+ /* Health Bars */
123
+ .hud-top {
124
+ display: flex;
125
+ justify-content: space-between;
126
+ align-items: flex-start;
127
+ width: 100%;
128
+ }
129
+
130
+ .player-status {
131
+ width: 45%;
132
+ display: flex;
133
+ flex-direction: column;
134
+ gap: 5px;
135
+ }
136
+
137
+ .p1-status { align-items: flex-start; }
138
+ .p2-status { align-items: flex-end; }
139
+
140
+ .name-tag {
141
+ font-family: 'Bangers', cursive;
142
+ font-size: 1.5rem;
143
+ text-transform: uppercase;
144
+ margin-bottom: 5px;
145
+ }
146
+
147
+ .p1-name { color: var(--naruto-yellow); }
148
+ .p2-name { color: #aaddff; }
149
+
150
+ .bar-container {
151
+ width: 100%;
152
+ height: 25px;
153
+ background: #333;
154
+ border: 2px solid #555;
155
+ position: relative;
156
+ transform: skewX(-20deg);
157
+ }
158
+
159
+ .hp-bar {
160
+ height: 100%;
161
+ background: linear-gradient(90deg, #ff3333, #ff8888);
162
+ width: 100%;
163
+ transition: width 0.2s cubic-bezier(0.4, 0, 0.2, 1);
164
+ }
165
+
166
+ .p2-hp { background: linear-gradient(90deg, #8888ff, #3333ff); float: right; }
167
+
168
+ .chakra-bar-container {
169
+ width: 80%;
170
+ height: 10px;
171
+ background: #222;
172
+ border: 1px solid #444;
173
+ margin-top: 5px;
174
+ position: relative;
175
+ transform: skewX(-20deg);
176
+ }
177
+
178
+ .chakra-bar {
179
+ height: 100%;
180
+ width: 0%;
181
+ background: linear-gradient(90deg, #00ffff, #0088ff);
182
+ transition: width 0.1s linear;
183
+ }
184
+
185
+ .p2-chakra { background: linear-gradient(90deg, #aa00ff, #ff00ff); float: right; }
186
+
187
+ /* Timer */
188
+ .timer-box {
189
+ background: rgba(0,0,0,0.7);
190
+ padding: 10px 20px;
191
+ border: 2px solid #555;
192
+ border-radius: 5px;
193
+ text-align: center;
194
+ }
195
+
196
+ .timer {
197
+ font-family: 'Bangers', cursive;
198
+ font-size: 2.5rem;
199
+ color: #fff;
200
+ }
201
+
202
+ /* Controls Hint */
203
+ .controls-hint {
204
+ position: absolute;
205
+ bottom: 20px;
206
+ left: 50%;
207
+ transform: translateX(-50%);
208
+ display: flex;
209
+ gap: 15px;
210
+ background: rgba(0, 0, 0, 0.7);
211
+ padding: 10px 20px;
212
+ border-radius: 30px;
213
+ border: 1px solid rgba(255,255,255,0.1);
214
+ }
215
+
216
+ .key-group {
217
+ display: flex;
218
+ align-items: center;
219
+ gap: 8px;
220
+ font-size: 0.8rem;
221
+ color: #aaa;
222
+ }
223
+
224
+ .key-icon {
225
+ width: 30px;
226
+ height: 30px;
227
+ display: flex;
228
+ align-items: center;
229
+ justify-content: center;
230
+ border-radius: 50%;
231
+ font-weight: bold;
232
+ color: #fff;
233
+ font-size: 0.9rem;
234
+ box-shadow: 0 2px 5px rgba(0,0,0,0.5);
235
+ }
236
+
237
+ .k-x { background: #e74c3c; } /* Jump */
238
+ .k-square { background: #3498db; } /* Light */
239
+ .k-triangle { background: #2ecc71; } /* Heavy */
240
+ .k-circle { background: #f1c40f; color: #333; } /* Jutsu */
241
+
242
+ /* Screens (Start / Game Over) */
243
+ .overlay-screen {
244
+ position: absolute;
245
+ top: 0;
246
+ left: 0;
247
+ width: 100%;
248
+ height: 100%;
249
+ background: rgba(0, 0, 0, 0.85);
250
+ display: flex;
251
+ flex-direction: column;
252
+ justify-content: center;
253
+ align-items: center;
254
+ z-index: 20;
255
+ backdrop-filter: blur(8px);
256
+ transition: opacity 0.3s;
257
+ }
258
+
259
+ .hidden { opacity: 0; pointer-events: none; }
260
+
261
+ .game-title {
262
+ font-family: 'Bangers', cursive;
263
+ font-size: 4rem;
264
+ color: transparent;
265
+ -webkit-text-stroke: 2px #fff;
266
+ margin-bottom: 20px;
267
+ text-transform: uppercase;
268
+ letter-spacing: 5px;
269
+ }
270
+
271
+ .game-title span { color: var(--naruto-orange); -webkit-text-stroke: 0; }
272
+
273
+ .btn-start {
274
+ padding: 15px 40px;
275
+ font-size: 1.5rem;
276
+ font-family: 'Bangers', cursive;
277
+ background: var(--naruto-orange);
278
+ color: white;
279
+ border: none;
280
+ cursor: pointer;
281
+ transform: skewX(-10deg);
282
+ transition: transform 0.2s, background 0.2s;
283
+ box-shadow: 0 0 20px rgba(255, 94, 0, 0.5);
284
+ }
285
+
286
+ .btn-start:hover {
287
+ transform: skewX(-10deg) scale(1.1);
288
+ background: #fff;
289
+ color: var(--naruto-orange);
290
+ }
291
+
292
+ /* Toast Messages */
293
+ #toast-container {
294
+ position: absolute;
295
+ top: 20%;
296
+ left: 50%;
297
+ transform: translateX(-50%);
298
+ display: flex;
299
+ flex-direction: column;
300
+ align-items: center;
301
+ gap: 10px;
302
+ pointer-events: none;
303
+ }
304
+
305
+ .combo-text {
306
+ font-family: 'Bangers', cursive;
307
+ font-size: 2rem;
308
+ color: #fff;
309
+ text-shadow: 0 0 10px var(--naruto-orange);
310
+ animation: popUp 0.8s ease-out forwards;
311
+ }
312
+
313
+ @keyframes popUp {
314
+ 0% { transform: scale(0.5); opacity: 0; }
315
+ 50% { transform: scale(1.2); opacity: 1; }
316
+ 100% { transform: scale(1.0) translateY(-20px); opacity: 0; }
317
+ }
318
+
319
+ /* Responsive */
320
+ @media (max-width: 768px) {
321
+ .controls-hint { display: none; } /* Hide hints on mobile */
322
+ .game-title { font-size: 2.5rem; }
323
+ }
324
+
325
+ </style>
326
  </head>
327
+
328
  <body>
329
 
330
+ <header>
331
+ <div class="logo">
332
+ <i class="fa-solid fa-bolt"></i>
333
+ <span>SHIPPUDEN <span>DUEL</span></span>
334
+ </div>
335
+ <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="built-with">
336
+ Built with anycoder <i class="fa-solid fa-external-link-alt"></i>
337
+ </a>
338
+ </header>
339
+
340
+ <main>
341
+ <div id="game-wrapper">
342
+ <canvas id="gameCanvas"></canvas>
343
+
344
+ <!-- UI Layer -->
345
+ <div id="ui-layer">
346
+
347
+ <!-- HUD -->
348
+ <div class="hud-top">
349
+ <!-- Player 1 (Naruto) -->
350
+ <div class="player-status p1-status">
351
+ <div class="name-tag p1-name">Naruto</div>
352
+ <div class="bar-container">
353
+ <div class="hp-bar" id="p1-hp" style="width: 100%;"></div>
354
  </div>
355
+ <div class="chakra-bar-container">
356
+ <div class="chakra-bar" id="p1-chakra"></div>
 
 
 
 
 
357
  </div>
358
+ </div>
359
+
360
+ <!-- Timer -->
361
+ <div class="timer-box">
362
+ <div class="timer" id="game-timer">60</div>
363
+ </div>
364
+
365
+ <!-- Player 2 (Sasuke) -->
366
+ <div class="player-status p2-status">
367
+ <div class="name-tag p2-name">Sasuke (AI)</div>
368
+ <div class="bar-container">
369
+ <div class="hp-bar p2-hp" id="p2-hp" style="width: 100%;"></div>
 
370
  </div>
371
+ <div class="chakra-bar-container">
372
+ <div class="chakra-bar p2-chakra" id="p2-chakra"></div>
 
 
 
 
 
 
373
  </div>
374
+ </div>
375
+ </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
376
 
377
+ <!-- Toasts -->
378
+ <div id="toast-container"></div>
 
379
 
380
+ <!-- Controls Hint -->
381
+ <div class="controls-hint">
382
+ <div class="key-group"><div class="key-icon k-x">X</div> Saut</div>
383
+ <div class="key-group"><div class="key-icon k-square">■</div> Frappe</div>
384
+ <div class="key-group"><div class="key-icon k-triangle">▲</div> Lourd</div>
385
+ <div class="key-group"><div class="key-icon k-circle">●</div> Jutsu</div>
386
+ </div>
387
+ </div>
388
+
389
+ <!-- Start Screen -->
390
+ <div id="start-screen" class="overlay-screen">
391
+ <h1 class="game-title">Ninja <span>Duel</span></h1>
392
+ <p style="margin-bottom: 30px; color: #ccc;">Utilisez Z, E, R et Espace</p>
393
+ <button class="btn-start" onclick="startGame()">COMBATTRE</button>
394
+ </div>
395
+
396
+ <!-- Game Over Screen -->
397
+ <div id="game-over-screen" class="overlay-screen hidden">
398
+ <h1 class="game-title" id="winner-text">VICTOIRE</h1>
399
+ <button class="btn-start" onclick="resetGame()">REJOUER</button>
400
+ </div>
401
+
402
+ </div>
403
+ </main>
404
+
405
+ <script>
406
+ // --- Configuration ---
407
+ const canvas = document.getElementById('gameCanvas');
408
+ const ctx = canvas.getContext('2d');
409
+
410
+ // Game Constants
411
+ const GRAVITY = 0.8;
412
+ const GROUND_Y = 550; // Ajusté dynamiquement selon la hauteur
413
+ const FRICTION = 0.8;
414
+
415
+ // Inputs Mapping
416
+ // X = Space (Jump)
417
+ // Square = Z (Light Hit)
418
+ // Triangle = E (Heavy Hit)
419
+ // Circle = R (Jutsu)
420
+ const KEYS = {
421
+ LEFT: 'ArrowLeft',
422
+ RIGHT: 'ArrowRight',
423
+ JUMP: ' ',
424
+ LIGHT: 'z', // Carré
425
+ HEAVY: 'e', // Triangle
426
+ JUTSU: 'r' // Rond
427
+ };
428
+
429
+ // --- Game State ---
430
+ let gameState = 'MENU'; // MENU, PLAYING, GAMEOVER
431
+ let animationId;
432
+ let lastTime = 0;
433
+ let timer = 60;
434
+ let timerInterval;
435
+ let cameraShake = 0;
436
+
437
+ // --- Classes ---
438
+
439
+ class Sprite {
440
+ constructor({ position, velocity, color, isPlayer, name }) {
441
+ this.position = position;
442
+ this.velocity = velocity;
443
+ this.width = 50;
444
+ this.height = 100;
445
+ this.color = color;
446
+ this.isPlayer = isPlayer;
447
+ this.name = name;
448
+
449
+ // Stats
450
+ this.hp = 100;
451
+ this.maxHp = 100;
452
+ this.chakra = 0;
453
+ this.maxChakra = 100;
454
+ this.isDead = false;
455
+
456
+ // Combat
457
+ this.isAttacking = false;
458
+ this.isBlocking = false;
459
+ this.attackCooldown = 0;
460
+ this.facingRight = isPlayer; // P1 regarde droite, P2 regarde gauche
461
+ this.hitbox = { x: 0, y: 0, width: 0, height: 0 };
462
+ }
463
+
464
+ draw() {
465
+ ctx.save();
466
+
467
+ // Flip if facing left
468
+ if (!this.facingRight) {
469
+ ctx.translate(this.position.x + this.width, this.position.y);
470
+ ctx.scale(-1, 1);
471
+ ctx.translate(-(this.position.x + this.width), -this.position.y);
472
+ }
473
+
474
+ // Body (Jumpsuit)
475
+ ctx.fillStyle = this.color;
476
+ ctx.fillRect(this.position.x, this.position.y, this.width, this.height);
477
+
478
+ // Head Band
479
+ ctx.fillStyle = '#333';
480
+ ctx.fillRect(this.position.x - 5, this.position.y + 10, this.width + 10, 10);
481
+ ctx.fillStyle = '#ccc'; // Metal plate
482
+ ctx.fillRect(this.position.x + 5, this.position.y + 12, 30, 6);
483
+
484
+ // Eyes (Simple)
485
+ ctx.fillStyle = this.isPlayer ? '#00ccff' : '#6600cc';
486
+ ctx.fillRect(this.position.x + 30, this.position.y + 25, 8, 5);
487
+
488
+ // Spiky Hair (Procedural)
489
+ ctx.fillStyle = this.isPlayer ? '#ffcc00' : '#000000';
490
+ ctx.beginPath();
491
+ ctx.moveTo(this.position.x - 5, this.position.y + 10);
492
+ ctx.lineTo(this.position.x + 10, this.position.y - 15);
493
+ ctx.lineTo(this.position.x + 25, this.position.y + 5);
494
+ ctx.lineTo(this.position.x + 40, this.position.y - 20);
495
+ ctx.lineTo(this.position.x + 55, this.position.y + 10);
496
+ ctx.fill();
497
+
498
+ // Attack Hitbox (Debug visual - optional, usually invisible)
499
+ if (this.isAttacking) {
500
+ ctx.fillStyle = 'rgba(255, 255, 255, 0.5)';
501
+ ctx.fillRect(this.hitbox.x, this.hitbox.y, this.hitbox.width, this.hitbox.height);
502
+ }
503
+
504
+ ctx.restore();
505
+ }
506
+
507
+ update() {
508
+ this.draw();
509
+
510
+ if (!this.isDead) {
511
+ this.position.x += this.velocity.x;
512
+ this.position.y += this.velocity.y;
513
 
514
+ // Gravity
515
+ if (this.position.y + this.height + this.velocity.y >= canvas.height - 50) {
516
+ this.velocity.y = 0;
517
+ this.position.y = canvas.height - 50 - this.height;
518
+ } else {
519
+ this.velocity.y += GRAVITY;
520
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
521
 
522
+ // Boundaries
523
+ if (this.position.x < 0) this.position.x = 0;
524
+ if (this.position.x + this.width > canvas.width) this.position.x = canvas.width - this.width;
 
 
 
 
 
 
 
 
 
 
 
 
525
  }
526
 
527
+ // Face opponent
528
+ if (this.isPlayer) {
529
+ this.facingRight = this.position.x < enemy.position.x;
530
+ } else {
531
+ this.facingRight = this.position.x > player.position.x;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
532
  }
533
 
534
+ // Cooldowns
535
+ if (this.attackCooldown > 0) this.attackCooldown--;
536
+
537
+ // Passive Chakra Regen
538
+ if (this.chakra < this.maxChakra) this.chakra += 0.05;
539
+ }
540
 
541
+ attack(type) {
542
+ if (this.attackCooldown > 0 || this.isDead) return;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
543
 
544
+ this.isAttacking = true;
545
+
546
+ // Define Hitbox
547
+ const reach = type === 'heavy' ? 80 : 60;
548
+ const damage = type === 'heavy' ? 15 : 5;
549
+ const cooldown = type === 'heavy' ? 40 : 20;
550
+
551
+ this.hitbox = {
552
+ x: this.facingRight ? this.position.x + this.width : this.position.x - reach,
553
+ y: this.position.y + 20,
554
+ width: reach,
555
+ height: 40
556
+ };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
557
 
558
+ this.attackCooldown = cooldown;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
559
 
560
+ setTimeout(() => {
561
+ this.isAttacking = false;
562
+ }, 100);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
563
 
564
+ return damage;
565
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
566
 
567
+ jutsu() {
568
+ if (this.chakra < 50 || this.attackCooldown > 0 || this.isDead) return false;
569
+
570
+ this.chakra -= 50;
571
+ this.attackCooldown = 60; // Long cooldown
572
+
573
+ // Create Projectile
574
+ const direction = this.facingRight ? 1 : -1;
575
+ const startX = this.facingRight ? this.position.x + this.width : this.position.x - 40;
576
+ const startY = this.position.y + 40;
 
 
577
 
578
+ const type = this.isPlayer ? 'rasengan' : 'chidori';
579
+
580
+ projectiles.push(new Projectile({
581
+ position: { x: startX, y: startY },
582
+ velocity: { x: 10 * direction, y: 0 },
583
+ owner: this,
584
+ type: type
585
+ }));
586
+
587
+ showFloatingText(type === 'rasengan' ? "RASENGAN!" : "CHIDORI!", this.position.x, this.position.y - 20, this.isPlayer ? '#00ffff' : '#aa00ff');
588
+ return true;
589
+ }
590
+
591
+ takeHit(damage) {
592
+ this.hp -= damage;
593
+ if (this.hp < 0) this.hp = 0;
594
+
595
+ // Knockback
596
+ const dir = this.isPlayer ? -1 : 1;
597
+ this.velocity.x = 10 * dir;
598
+ this.velocity.y = -5;
599
+
600
+ if (this.hp <= 0) {
601
+ this.isDead = true;
602
+ endGame(!this.isPlayer); // Opponent wins
603
+ }
604
+ }
605
+ }
606
+
607
+ class Projectile {
608
+ constructor({ position, velocity, owner, type }) {
609
+ this.position = position;
610
+ this.velocity = velocity;
611
+ this.owner = owner;
612
+ this.type = type;
613
+ this.radius = 25;
614
+ this.active = true;
615
+ }
616
+
617
+ draw() {
618
+ ctx.beginPath();
619
+ ctx.arc(this.position.x, this.position.y, this.radius, 0, Math.PI * 2);
620
+
621
+ if (this.type === 'rasengan') {
622
+ ctx.fillStyle = '#00ccff';
623
+ ctx.shadowBlur = 20;
624
+ ctx.shadowColor = '#00ffff';
625
+ } else {
626
+ ctx.fillStyle = '#aa00ff';
627
+ ctx.shadowBlur = 20;
628
+ ctx.shadowColor = '#ff00ff';
629
  }
630
+
631
+ ctx.fill();
632
+ ctx.shadowBlur = 0;
633
+ ctx.closePath();
634
+ }
635
+
636
+ update() {
637
+ this.draw();
638
+ this.position.x += this.velocity.x;
639
+
640
+ // Remove if off screen
641
+ if (this.position.x < 0 || this.position.x > canvas.width) {
642
+ this.active = false;
643
+ }
644
+ }
645
+ }
646
+
647
+ class Particle {
648
+ constructor({ position, velocity, color }) {
649
+ this.position = position;
650
+ this.velocity = velocity;
651
+ this.color = color;
652
+ this.alpha = 1;
653
+ }
654
+
655
+ draw() {
656
+ ctx.save();
657
+ ctx.globalAlpha = this.alpha;
658
+ ctx.fillStyle = this.color;
659
+ ctx.fillRect(this.position.x, this.position.y, 4, 4);
660
+ ctx.restore();
661
+ }
662
+
663
+ update() {
664
+ this.draw();
665
+ this.position.x += this.velocity.x;
666
+ this.position.y += this.velocity.y;
667
+ this.alpha -= 0.02;
668
+ }
669
+ }
670
+
671
+ // --- Instances ---
672
+ let player;
673
+ let enemy;
674
+ let projectiles = [];
675
+ let particles = [];
676
+
677
+ // --- Input Handling ---
678
+ const keys = {
679
+ ArrowRight: { pressed: false },
680
+ ArrowLeft: { pressed: false },
681
+ ArrowUp: { pressed: false }
682
+ };
683
+
684
+ window.addEventListener('keydown', (e) => {
685
+ if (gameState !== 'PLAYING') return;
686
+
687
+ switch (e.key.toLowerCase()) {
688
+ case KEYS.RIGHT: keys.ArrowRight.pressed = true; break;
689
+ case KEYS.LEFT: keys.ArrowLeft.pressed = true; break;
690
+ case KEYS.JUMP:
691
+ if (player.velocity.y === 0) player.velocity.y = -20;
692
+ break;
693
+ case KEYS.LIGHT: // Square
694
+ player.attack('light');
695
+ checkCollision(player, enemy, 5);
696
+ break;
697
+ case KEYS.HEAVY: // Triangle
698
+ player.attack('heavy');
699
+ checkCollision(player, enemy, 15);
700
+ break;
701
+ case KEYS.JUTSU: // Circle
702
+ player.jutsu();
703
+ break;
704
+ }
705
+ });
706
+
707
+ window.addEventListener('keyup', (e) => {
708
+ switch (e.key.toLowerCase()) {
709
+ case KEYS.RIGHT: keys.ArrowRight.pressed = false; break;
710
+ case KEYS.LEFT: keys.ArrowLeft.pressed = false; break;
711
+ }
712
+ });
713
+
714
+ // --- Game Logic ---
715
+
716
+ function initGame() {
717
+ resizeCanvas();
718
+
719
+ player = new Sprite({
720
+ position: { x: 100, y: 0 },
721
+ velocity: { x: 0, y: 0 },
722
+ color: '#ff5e00', // Naruto Orange
723
+ isPlayer: true,
724
+ name: 'Naruto'
725
+ });
726
+
727
+ enemy = new Sprite({
728
+ position: { x: canvas.width - 150, y: 0 },
729
+ velocity: { x: 0, y: 0 },
730
+ color: '#2e3192', // Sasuke Blue
731
+ isPlayer: false,
732
+ name: 'Sasuke'
733
+ });
734
+
735
+ projectiles = [];
736
+ particles = [];
737
+ timer = 60;
738
+ cameraShake = 0;
739
+
740
+ document.getElementById('p1-hp').style.width = '100%';
741
+ document.getElementById('p2-hp').style.width = '100%';
742
+ document.getElementById('game-timer').textContent = timer;
743
+ }
744
+
745
+ function checkCollision(attacker, receiver, damage) {
746
+ if (
747
+ attacker.hitbox.x < receiver.position.x + receiver.width &&
748
+ attacker.hitbox.x + attacker.hitbox.width > receiver.position.x &&
749
+ attacker.hitbox.y < receiver.position.y + receiver.height &&
750
+ attacker.hitbox.y + attacker.hitbox.height > receiver.position.y
751
+ ) {
752
+ receiver.takeHit(damage);
753
+ createParticles(receiver.position.x + receiver.width/2, receiver.position.y + receiver.height/2, 10, '#fff');
754
+ startShake(5);
755
+ updateUI();
756
+ }
757
+ }
758
+
759
+ function createParticles(x, y, count, color) {
760
+ for (let i = 0; i < count; i++) {
761
+ particles.push(new Particle({
762
+ position: { x, y },
763
+ velocity: {
764
+ x: (Math.random() - 0.5) * 10,
765
+ y: (Math.random() - 0.5) * 10
766
+ },
767
+ color: color
768
+ }));
769
+ }
770
+ }
771
+
772
+ function startShake(intensity) {
773
+ cameraShake = intensity;
774
+ }
775
+
776
+ function updateUI() {
777
+ document.getElementById('p1-hp').style.width = player.hp + '%';
778
+ document.getElementById('p2-hp').style.width = enemy.hp + '%';
779
+ document.getElementById('p1-chakra').style.width = player.chakra + '%';
780
+ document.getElementById('p2-chakra').style.width = enemy.chakra + '%';
781
+ }
782
+
783
+ function showFloatingText(text, x, y, color) {
784
+ const toast = document.createElement('div');
785
+ toast.className = 'combo-text';
786
+ toast.textContent = text;
787
+ toast.style.color = color;
788
+ toast.style.left = x + 'px';
789
+ toast.style.top = y + 'px';
790
+ document.getElementById('toast-container').appendChild(toast);
791
+ setTimeout(() => toast.remove(), 1000);
792
+ }
793
+
794
+ // --- AI Logic (Simple) ---
795
+ function updateAI() {
796
+ if (enemy.isDead || gameState !== 'PLAYING') return;
797
+
798
+ const distance = Math.abs(player.position.x - enemy.position.x);
799
+ const action = Math.random();
800
+
801
+ // Movement
802
+ if (distance > 80) {
803
+ enemy.velocity.x = player.position.x < enemy.position.x ? -3 : 3;
804
+ } else {
805
+ enemy.velocity.x = 0;
806
+ }
807
+
808
+ // Jump randomly or if attacked
809
+ if (action < 0.01 && enemy.velocity.y === 0) enemy.velocity.y = -15;
810
+
811
+ // Attack
812
+ if (distance < 100) {
813
+ if (action < 0.05) {
814
+ enemy.attack('light');
815
+ checkCollision(enemy, player, 5);
816
+ } else if (action < 0.02) {
817
+ enemy.attack('heavy');
818
+ checkCollision(enemy, player, 15);
819
+ }
820
+ }
821
+
822
+ // Jutsu if far and chakra full
823
+ if (distance > 200 && enemy.chakra >= 50 && action < 0.02) {
824
+ enemy.jutsu();
825
+ }
826
+ }
827
+
828
+ // --- Main Loop ---
829
+ function animate() {
830
+ animationId = requestAnimationFrame(animate);
831
+
832
+ // Clear Screen
833
+ ctx.fillStyle = '#1a1a1a';
834
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
835
+
836
+ // Draw Background (Simple Floor)
837
+ ctx.fillStyle = '#111';
838
+ ctx.fillRect(0, canvas.height - 50, canvas.width, 50);
839
+
840
+ // Grid effect on floor
841
+ ctx.strokeStyle = '#333';
842
+ ctx.beginPath();
843
+ ctx.moveTo(0, canvas.height - 50);
844
+ ctx.lineTo(canvas.width, canvas.height - 50);
845
+ ctx.stroke();
846
+
847
+ // Camera Shake
848
+ if (cameraShake > 0) {
849
+ ctx.save();
850
+ const dx = (Math.random() - 0.5) * cameraShake;
851
+ const dy = (Math.random() - 0.5) * cameraShake;
852
+ ctx.translate(dx, dy);
853
+ cameraShake *= 0.9;
854
+ if (cameraShake < 0.5) cameraShake = 0;
855
+ }
856
+
857
+ // Player Movement
858
+ player.velocity.x = 0;
859
+ if (keys.ArrowRight.pressed) player.velocity.x = 5;
860
+ else if (keys.ArrowLeft.pressed) player.velocity.x = -5;
861
+
862
+ // Update Entities
863
+ player.update();
864
+ enemy.update();
865
+ updateAI();
866
+
867
+ // Projectiles
868
+ projectiles.forEach((proj, index) => {
869
+ proj.update();
870
+
871
+ // Hit detection
872
+ const target = proj.owner === player ? enemy : player;
873
+ if (
874
+ proj.position.x < target.position.x + target.width &&
875
+ proj.position.x + proj.radius > target.position.x &&
876
+ proj.position.y < target.position.y + target.height &&
877
+ proj.position.y + proj.radius > target.position.y
878
+ ) {
879
+ target.takeHit(35);
880
+ proj.active = false;
881
+ createParticles(proj.position.x, proj.position.y, 15, proj.type === 'rasengan' ? '#00ffff' : '#ff00ff');
882
+ startShake(15);
883
+ updateUI();
884
+ }
885
+
886
+ if (!proj.active) projectiles.splice(index, 1);
887
+ });
888
+
889
+ // Particles
890
+ particles.forEach((part, index) => {
891
+ if (part.alpha <= 0) particles.splice(index, 1);
892
+ else part.update();
893
+ });
894
+
895
+ // Restore shake context
896
+ if (cameraShake > 0 || ctx.getTransform().e !== 0) ctx.restore();
897
+ }
898
+
899
+ // --- Game Flow Control ---
900
+
901
+ function startGame() {
902
+ document.getElementById('start-screen').classList.add('hidden');
903
+ document.getElementById('game-over-screen').classList.add('hidden');
904
+ gameState = 'PLAYING';
905
+ initGame();
906
+
907
+ clearInterval(timerInterval);
908
+ timerInterval = setInterval(() => {
909
+ if (gameState === 'PLAYING') {
910
+ timer--;
911
+ document.getElementById('game-timer').textContent = timer;
912
+ if (timer <= 0) {
913
+ endGame(player.hp > enemy.hp); // Who has more HP wins
914
+ }
915
+ }
916
+ }, 1000);
917
+ }
918
+
919
+ function endGame(playerWins) {
920
+ gameState = 'GAMEOVER';
921
+ clearInterval(timerInterval);
922
+
923
+ const winnerText = document.getElementById('winner-text');
924
+ if (playerWins) {
925
+ winnerText.innerHTML = '<span style="color:#ffcc00">NARUTO</span> GAGNE!';
926
+ } else {
927
+ winnerText.innerHTML = '<span style="color:#aaddff">SASUKE</span> GAGNE!';
928
+ }
929
+
930
+ document.getElementById('game-over-screen').classList.remove('hidden');
931
+ }
932
+
933
+ function resetGame() {
934
+ startGame();
935
+ }
936
+
937
+ // --- Utils ---
938
+ function resizeCanvas() {
939
+ const wrapper = document.getElementById('game-wrapper');
940
+ canvas.width = wrapper.clientWidth;
941
+ canvas.height = wrapper.clientHeight;
942
+ }
943
+ window.addEventListener('resize', resizeCanvas);
944
+
945
+ // Start Loop
946
+ resizeCanvas();
947
+ animate();
948
+
949
+ </script>
950
  </body>
951
+
952
  </html>