InfernalDread commited on
Commit
f3d2a89
·
verified ·
1 Parent(s): 61ded34

Upload folder using huggingface_hub

Browse files
Files changed (1) hide show
  1. index.html +1101 -19
index.html CHANGED
@@ -1,19 +1,1101 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>3D Pac-Man</title>
7
+ <script type="importmap">
8
+ {
9
+ "imports": {
10
+ "three": "https://unpkg.com/three@0.160.0/build/three.module.js",
11
+ "three/addons/": "https://unpkg.com/three@0.160.0/examples/jsm/"
12
+ }
13
+ }
14
+ </script>
15
+ <link href="https://fonts.googleapis.com/css2?family=Press+Start+2P&family=Orbitron:wght@400;700;900&display=swap" rel="stylesheet">
16
+ <style>
17
+ :root {
18
+ --bg: #0a0a12;
19
+ --fg: #ffffff;
20
+ --accent: #ffcc00;
21
+ --ghost-red: #ff0000;
22
+ --ghost-pink: #ffb8ff;
23
+ --ghost-cyan: #00ffff;
24
+ --ghost-orange: #ffb852;
25
+ --wall: #1a1aff;
26
+ --pellet: #ffcc00;
27
+ --power: #ff00ff;
28
+ }
29
+
30
+ * {
31
+ margin: 0;
32
+ padding: 0;
33
+ box-sizing: border-box;
34
+ }
35
+
36
+ body {
37
+ font-family: 'Orbitron', sans-serif;
38
+ background: var(--bg);
39
+ color: var(--fg);
40
+ overflow: hidden;
41
+ min-height: 100vh;
42
+ }
43
+
44
+ #game-container {
45
+ position: relative;
46
+ width: 100vw;
47
+ height: 100vh;
48
+ }
49
+
50
+ canvas {
51
+ display: block;
52
+ }
53
+
54
+ .ui-overlay {
55
+ position: absolute;
56
+ top: 0;
57
+ left: 0;
58
+ width: 100%;
59
+ pointer-events: none;
60
+ z-index: 100;
61
+ }
62
+
63
+ .header {
64
+ display: flex;
65
+ justify-content: space-between;
66
+ align-items: center;
67
+ padding: 20px 30px;
68
+ background: linear-gradient(180deg, rgba(0,0,0,0.8) 0%, transparent 100%);
69
+ }
70
+
71
+ .logo {
72
+ font-family: 'Press Start 2P', cursive;
73
+ font-size: clamp(14px, 3vw, 24px);
74
+ color: var(--accent);
75
+ text-shadow:
76
+ 0 0 10px var(--accent),
77
+ 0 0 20px var(--accent),
78
+ 0 0 40px var(--accent);
79
+ letter-spacing: 2px;
80
+ }
81
+
82
+ .logo span {
83
+ color: #ff3333;
84
+ text-shadow:
85
+ 0 0 10px #ff3333,
86
+ 0 0 20px #ff3333;
87
+ }
88
+
89
+ .built-with {
90
+ font-size: 10px;
91
+ color: #666;
92
+ text-decoration: none;
93
+ transition: color 0.3s;
94
+ pointer-events: auto;
95
+ }
96
+
97
+ .built-with:hover {
98
+ color: var(--accent);
99
+ }
100
+
101
+ .stats {
102
+ display: flex;
103
+ gap: 30px;
104
+ }
105
+
106
+ .stat {
107
+ text-align: center;
108
+ }
109
+
110
+ .stat-label {
111
+ font-size: 10px;
112
+ color: #888;
113
+ text-transform: uppercase;
114
+ letter-spacing: 2px;
115
+ margin-bottom: 5px;
116
+ }
117
+
118
+ .stat-value {
119
+ font-family: 'Press Start 2P', cursive;
120
+ font-size: clamp(16px, 2.5vw, 24px);
121
+ color: var(--accent);
122
+ text-shadow: 0 0 10px var(--accent);
123
+ }
124
+
125
+ .lives-container {
126
+ display: flex;
127
+ gap: 8px;
128
+ }
129
+
130
+ .life {
131
+ width: 24px;
132
+ height: 24px;
133
+ background: var(--accent);
134
+ border-radius: 50%;
135
+ box-shadow: 0 0 10px var(--accent);
136
+ position: relative;
137
+ }
138
+
139
+ .life::before {
140
+ content: '';
141
+ position: absolute;
142
+ right: 0;
143
+ top: 50%;
144
+ transform: translateY(-50%);
145
+ width: 0;
146
+ height: 0;
147
+ border-left: 8px solid var(--bg);
148
+ border-top: 6px solid transparent;
149
+ border-bottom: 6px solid transparent;
150
+ }
151
+
152
+ .screen-overlay {
153
+ position: absolute;
154
+ top: 0;
155
+ left: 0;
156
+ width: 100%;
157
+ height: 100%;
158
+ display: flex;
159
+ flex-direction: column;
160
+ justify-content: center;
161
+ align-items: center;
162
+ background: rgba(0,0,0,0.9);
163
+ z-index: 200;
164
+ pointer-events: auto;
165
+ transition: opacity 0.5s;
166
+ }
167
+
168
+ .screen-overlay.hidden {
169
+ opacity: 0;
170
+ pointer-events: none;
171
+ }
172
+
173
+ .title {
174
+ font-family: 'Press Start 2P', cursive;
175
+ font-size: clamp(28px, 6vw, 64px);
176
+ color: var(--accent);
177
+ text-shadow:
178
+ 0 0 20px var(--accent),
179
+ 0 0 40px var(--accent),
180
+ 0 0 60px var(--accent);
181
+ margin-bottom: 20px;
182
+ animation: pulse 2s infinite;
183
+ }
184
+
185
+ @keyframes pulse {
186
+ 0%, 100% { opacity: 1; }
187
+ 50% { opacity: 0.8; }
188
+ }
189
+
190
+ .subtitle {
191
+ font-size: clamp(12px, 2vw, 18px);
192
+ color: #888;
193
+ margin-bottom: 40px;
194
+ text-align: center;
195
+ padding: 0 20px;
196
+ }
197
+
198
+ .start-btn {
199
+ font-family: 'Press Start 2P', cursive;
200
+ font-size: clamp(12px, 2vw, 18px);
201
+ padding: 20px 40px;
202
+ background: transparent;
203
+ border: 3px solid var(--accent);
204
+ color: var(--accent);
205
+ cursor: pointer;
206
+ transition: all 0.3s;
207
+ text-transform: uppercase;
208
+ letter-spacing: 2px;
209
+ }
210
+
211
+ .start-btn:hover {
212
+ background: var(--accent);
213
+ color: var(--bg);
214
+ box-shadow: 0 0 30px var(--accent);
215
+ transform: scale(1.05);
216
+ }
217
+
218
+ .controls-info {
219
+ margin-top: 40px;
220
+ text-align: center;
221
+ }
222
+
223
+ .controls-info h3 {
224
+ font-size: 14px;
225
+ color: #666;
226
+ margin-bottom: 15px;
227
+ }
228
+
229
+ .keys {
230
+ display: flex;
231
+ gap: 10px;
232
+ justify-content: center;
233
+ flex-wrap: wrap;
234
+ }
235
+
236
+ .key {
237
+ width: 40px;
238
+ height: 40px;
239
+ background: #222;
240
+ border: 2px solid #444;
241
+ border-radius: 8px;
242
+ display: flex;
243
+ align-items: center;
244
+ justify-content: center;
245
+ font-size: 12px;
246
+ color: #888;
247
+ }
248
+
249
+ .game-over-text {
250
+ font-family: 'Press Start 2P', cursive;
251
+ font-size: clamp(20px, 4vw, 40px);
252
+ color: #ff3333;
253
+ text-shadow: 0 0 20px #ff3333;
254
+ margin-bottom: 20px;
255
+ }
256
+
257
+ .win-text {
258
+ font-family: 'Press Start 2P', cursive;
259
+ font-size: clamp(20px, 4vw, 40px);
260
+ color: #00ff00;
261
+ text-shadow: 0 0 20px #00ff00;
262
+ margin-bottom: 20px;
263
+ }
264
+
265
+ .final-score {
266
+ font-size: 18px;
267
+ color: var(--accent);
268
+ margin-bottom: 30px;
269
+ }
270
+
271
+ .mobile-controls {
272
+ position: absolute;
273
+ bottom: 20px;
274
+ left: 50%;
275
+ transform: translateX(-50%);
276
+ display: none;
277
+ z-index: 150;
278
+ pointer-events: auto;
279
+ }
280
+
281
+ @media (max-width: 768px) {
282
+ .mobile-controls {
283
+ display: grid;
284
+ grid-template-columns: repeat(3, 60px);
285
+ grid-template-rows: repeat(3, 60px);
286
+ gap: 5px;
287
+ }
288
+ }
289
+
290
+ .mobile-btn {
291
+ width: 60px;
292
+ height: 60px;
293
+ background: rgba(255, 204, 0, 0.2);
294
+ border: 2px solid var(--accent);
295
+ border-radius: 12px;
296
+ display: flex;
297
+ align-items: center;
298
+ justify-content: center;
299
+ color: var(--accent);
300
+ font-size: 24px;
301
+ cursor: pointer;
302
+ transition: all 0.2s;
303
+ user-select: none;
304
+ -webkit-tap-highlight-color: transparent;
305
+ }
306
+
307
+ .mobile-btn:active {
308
+ background: var(--accent);
309
+ color: var(--bg);
310
+ }
311
+
312
+ .mobile-btn.up { grid-column: 2; grid-row: 1; }
313
+ .mobile-btn.left { grid-column: 1; grid-row: 2; }
314
+ .mobile-btn.right { grid-column: 3; grid-row: 2; }
315
+ .mobile-btn.down { grid-column: 2; grid-row: 3; }
316
+
317
+ .power-indicator {
318
+ position: absolute;
319
+ top: 50%;
320
+ left: 50%;
321
+ transform: translate(-50%, -50%);
322
+ font-family: 'Press Start 2P', cursive;
323
+ font-size: clamp(16px, 3vw, 28px);
324
+ color: var(--power);
325
+ text-shadow: 0 0 20px var(--power);
326
+ opacity: 0;
327
+ transition: opacity 0.3s;
328
+ pointer-events: none;
329
+ z-index: 150;
330
+ }
331
+
332
+ .power-indicator.active {
333
+ opacity: 1;
334
+ animation: flash 0.3s infinite;
335
+ }
336
+
337
+ @keyframes flash {
338
+ 0%, 100% { opacity: 1; }
339
+ 50% { opacity: 0.5; }
340
+ }
341
+
342
+ .level-indicator {
343
+ position: absolute;
344
+ top: 50%;
345
+ left: 50%;
346
+ transform: translate(-50%, -50%);
347
+ font-family: 'Press Start 2P', cursive;
348
+ font-size: clamp(24px, 5vw, 48px);
349
+ color: var(--accent);
350
+ text-shadow: 0 0 30px var(--accent);
351
+ opacity: 0;
352
+ pointer-events: none;
353
+ z-index: 150;
354
+ }
355
+
356
+ .level-indicator.active {
357
+ animation: levelUp 2s forwards;
358
+ }
359
+
360
+ @keyframes levelUp {
361
+ 0% { opacity: 0; transform: translate(-50%, -50%) scale(0.5); }
362
+ 20% { opacity: 1; transform: translate(-50%, -50%) scale(1.2); }
363
+ 80% { opacity: 1; transform: translate(-50%, -50%) scale(1); }
364
+ 100% { opacity: 0; transform: translate(-50%, -50%) scale(1); }
365
+ }
366
+ </style>
367
+ </head>
368
+ <body>
369
+ <div id="game-container">
370
+ <div class="ui-overlay">
371
+ <div class="header">
372
+ <div class="logo">PAC<span>-MAN</span> 3D</div>
373
+ <a href="https://huggingface.co/spaces/akhaliq/anycoder" class="built-with" target="_blank">Built with anycoder</a>
374
+ <div class="stats">
375
+ <div class="stat">
376
+ <div class="stat-label">Score</div>
377
+ <div class="stat-value" id="score">0</div>
378
+ </div>
379
+ <div class="stat">
380
+ <div class="stat-label">High Score</div>
381
+ <div class="stat-value" id="high-score">0</div>
382
+ </div>
383
+ <div class="stat">
384
+ <div class="stat-label">Lives</div>
385
+ <div class="lives-container" id="lives">
386
+ <div class="life"></div>
387
+ <div class="life"></div>
388
+ <div class="life"></div>
389
+ </div>
390
+ </div>
391
+ </div>
392
+ </div>
393
+ </div>
394
+
395
+ <div class="power-indicator" id="power-indicator">POWER UP!</div>
396
+ <div class="level-indicator" id="level-indicator">LEVEL 1</div>
397
+
398
+ <div class="screen-overlay" id="start-screen">
399
+ <div class="title">PAC-MAN 3D</div>
400
+ <div class="subtitle">Navigate the maze, eat all pellets, avoid the ghosts!</div>
401
+ <button class="start-btn" id="start-btn">START GAME</button>
402
+ <div class="controls-info">
403
+ <h3>Controls</h3>
404
+ <div class="keys">
405
+ <div class="key">W</div>
406
+ <div class="key">A</div>
407
+ <div class="key">S</div>
408
+ <div class="key">D</div>
409
+ <span style="color: #666; margin: 0 10px;">or</span>
410
+ <div class="key">&#8593;</div>
411
+ <div class="key">&#8592;</div>
412
+ <div class="key">&#8595;</div>
413
+ <div class="key">&#8594;</div>
414
+ </div>
415
+ </div>
416
+ </div>
417
+
418
+ <div class="screen-overlay hidden" id="game-over-screen">
419
+ <div class="game-over-text">GAME OVER</div>
420
+ <div class="final-score">Final Score: <span id="final-score">0</span></div>
421
+ <button class="start-btn" id="restart-btn">PLAY AGAIN</button>
422
+ </div>
423
+
424
+ <div class="screen-overlay hidden" id="win-screen">
425
+ <div class="win-text">LEVEL COMPLETE!</div>
426
+ <div class="final-score">Score: <span id="level-score">0</span></div>
427
+ <button class="start-btn" id="next-level-btn">NEXT LEVEL</button>
428
+ </div>
429
+
430
+ <div class="mobile-controls">
431
+ <button class="mobile-btn up" data-dir="up">&#9650;</button>
432
+ <button class="mobile-btn left" data-dir="left">&#9664;</button>
433
+ <button class="mobile-btn right" data-dir="right">&#9654;</button>
434
+ <button class="mobile-btn down" data-dir="down">&#9660;</button>
435
+ </div>
436
+ </div>
437
+
438
+ <script type="module">
439
+ import * as THREE from 'three';
440
+
441
+ // Game Constants
442
+ const CELL_SIZE = 1;
443
+ const PACMAN_SPEED = 0.08;
444
+ const GHOST_SPEED = 0.05;
445
+ const POWER_DURATION = 8000;
446
+
447
+ // Maze Layout (1 = wall, 0 = path, 2 = pellet, 3 = power pellet, 4 = ghost spawn)
448
+ const MAZE_TEMPLATE = [
449
+ [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],
450
+ [1,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,1],
451
+ [1,3,1,1,2,1,1,1,2,1,2,1,1,1,2,1,1,3,1],
452
+ [1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1],
453
+ [1,2,1,1,2,1,2,1,1,1,1,1,2,1,2,1,1,2,1],
454
+ [1,2,2,2,2,1,2,2,2,1,2,2,2,1,2,2,2,2,1],
455
+ [1,1,1,1,2,1,1,1,0,1,0,1,1,1,2,1,1,1,1],
456
+ [0,0,0,1,2,1,0,0,0,0,0,0,0,1,2,1,0,0,0],
457
+ [1,1,1,1,2,1,0,1,1,4,1,1,0,1,2,1,1,1,1],
458
+ [0,0,0,0,2,0,0,1,4,4,4,1,0,0,2,0,0,0,0],
459
+ [1,1,1,1,2,1,0,1,1,1,1,1,0,1,2,1,1,1,1],
460
+ [0,0,0,1,2,1,0,0,0,0,0,0,0,1,2,1,0,0,0],
461
+ [1,1,1,1,2,1,0,1,1,1,1,1,0,1,2,1,1,1,1],
462
+ [1,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,1],
463
+ [1,2,1,1,2,1,1,1,2,1,2,1,1,1,2,1,1,2,1],
464
+ [1,3,2,1,2,2,2,2,2,0,2,2,2,2,2,1,2,3,1],
465
+ [1,1,2,1,2,1,2,1,1,1,1,1,2,1,2,1,2,1,1],
466
+ [1,2,2,2,2,1,2,2,2,1,2,2,2,1,2,2,2,2,1],
467
+ [1,2,1,1,1,1,1,1,2,1,2,1,1,1,1,1,1,2,1],
468
+ [1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1],
469
+ [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]
470
+ ];
471
+
472
+ // Game State
473
+ let scene, camera, renderer;
474
+ let pacman, pacmanMouth;
475
+ let ghosts = [];
476
+ let pellets = [];
477
+ let powerPellets = [];
478
+ let walls = [];
479
+ let maze = [];
480
+ let score = 0;
481
+ let highScore = parseInt(localStorage.getItem('pacmanHighScore') || '0');
482
+ let lives = 3;
483
+ let gameRunning = false;
484
+ let powerMode = false;
485
+ let powerTimer = null;
486
+ let currentDirection = null;
487
+ let nextDirection = null;
488
+ let level = 1;
489
+ let totalPellets = 0;
490
+ let pelletsEaten = 0;
491
+
492
+ // Ghost colors
493
+ const GHOST_COLORS = [
494
+ 0xff0000, // Blinky (Red)
495
+ 0xffb8ff, // Pinky (Pink)
496
+ 0x00ffff, // Inky (Cyan)
497
+ 0xffb852 // Clyde (Orange)
498
+ ];
499
+
500
+ // Initialize Three.js
501
+ function initThree() {
502
+ scene = new THREE.Scene();
503
+ scene.background = new THREE.Color(0x0a0a12);
504
+ scene.fog = new THREE.Fog(0x0a0a12, 10, 30);
505
+
506
+ camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 100);
507
+ camera.position.set(9, 15, 12);
508
+ camera.lookAt(9, 0, 10);
509
+
510
+ renderer = new THREE.WebGLRenderer({ antialias: true });
511
+ renderer.setSize(window.innerWidth, window.innerHeight);
512
+ renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
513
+ renderer.shadowMap.enabled = true;
514
+ renderer.shadowMap.type = THREE.PCFSoftShadowMap;
515
+ document.getElementById('game-container').insertBefore(renderer.domElement, document.querySelector('.ui-overlay'));
516
+
517
+ // Lighting
518
+ const ambientLight = new THREE.AmbientLight(0x404080, 0.5);
519
+ scene.add(ambientLight);
520
+
521
+ const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
522
+ directionalLight.position.set(10, 20, 10);
523
+ directionalLight.castShadow = true;
524
+ directionalLight.shadow.mapSize.width = 2048;
525
+ directionalLight.shadow.mapSize.height = 2048;
526
+ directionalLight.shadow.camera.near = 0.5;
527
+ directionalLight.shadow.camera.far = 50;
528
+ directionalLight.shadow.camera.left = -20;
529
+ directionalLight.shadow.camera.right = 20;
530
+ directionalLight.shadow.camera.top = 20;
531
+ directionalLight.shadow.camera.bottom = -20;
532
+ scene.add(directionalLight);
533
+
534
+ // Point lights for atmosphere
535
+ const pointLight1 = new THREE.PointLight(0x0000ff, 0.5, 20);
536
+ pointLight1.position.set(0, 5, 0);
537
+ scene.add(pointLight1);
538
+
539
+ const pointLight2 = new THREE.PointLight(0xffcc00, 0.5, 20);
540
+ pointLight2.position.set(18, 5, 20);
541
+ scene.add(pointLight2);
542
+
543
+ // Floor
544
+ const floorGeometry = new THREE.PlaneGeometry(30, 30);
545
+ const floorMaterial = new THREE.MeshStandardMaterial({
546
+ color: 0x0a0a20,
547
+ roughness: 0.9,
548
+ metalness: 0.1
549
+ });
550
+ const floor = new THREE.Mesh(floorGeometry, floorMaterial);
551
+ floor.rotation.x = -Math.PI / 2;
552
+ floor.position.set(9, -0.5, 10);
553
+ floor.receiveShadow = true;
554
+ scene.add(floor);
555
+
556
+ window.addEventListener('resize', onWindowResize);
557
+ }
558
+
559
+ function onWindowResize() {
560
+ camera.aspect = window.innerWidth / window.innerHeight;
561
+ camera.updateProjectionMatrix();
562
+ renderer.setSize(window.innerWidth, window.innerHeight);
563
+ }
564
+
565
+ // Create Pac-Man
566
+ function createPacman() {
567
+ const group = new THREE.Group();
568
+
569
+ // Body
570
+ const bodyGeometry = new THREE.SphereGeometry(0.4, 32, 32);
571
+ const bodyMaterial = new THREE.MeshStandardMaterial({
572
+ color: 0xffcc00,
573
+ emissive: 0xffcc00,
574
+ emissiveIntensity: 0.3,
575
+ roughness: 0.3,
576
+ metalness: 0.5
577
+ });
578
+ const body = new THREE.Mesh(bodyGeometry, bodyMaterial);
579
+ body.castShadow = true;
580
+ group.add(body);
581
+
582
+ // Mouth (using a wedge shape)
583
+ const mouthGeometry = new THREE.ConeGeometry(0.45, 0.5, 8, 1, true, 0, Math.PI * 0.4);
584
+ const mouthMaterial = new THREE.MeshStandardMaterial({
585
+ color: 0x0a0a12,
586
+ side: THREE.DoubleSide
587
+ });
588
+ pacmanMouth = new THREE.Mesh(mouthGeometry, mouthMaterial);
589
+ pacmanMouth.rotation.z = Math.PI / 2;
590
+ pacmanMouth.position.x = 0.1;
591
+ group.add(pacmanMouth);
592
+
593
+ // Eyes
594
+ const eyeGeometry = new THREE.SphereGeometry(0.08, 16, 16);
595
+ const eyeMaterial = new THREE.MeshStandardMaterial({ color: 0x000000 });
596
+
597
+ const leftEye = new THREE.Mesh(eyeGeometry, eyeMaterial);
598
+ leftEye.position.set(0.1, 0.15, 0.3);
599
+ group.add(leftEye);
600
+
601
+ const rightEye = new THREE.Mesh(eyeGeometry, eyeMaterial);
602
+ rightEye.position.set(0.1, 0.15, -0.3);
603
+ group.add(rightEye);
604
+
605
+ // Starting position
606
+ group.position.set(9, 0, 15);
607
+ group.userData = { gridX: 9, gridZ: 15, targetX: 9, targetZ: 15 };
608
+
609
+ scene.add(group);
610
+ return group;
611
+ }
612
+
613
+ // Create Ghost
614
+ function createGhost(color, x, z) {
615
+ const group = new THREE.Group();
616
+
617
+ // Body (dome top + wavy bottom)
618
+ const bodyGeometry = new THREE.CylinderGeometry(0.35, 0.4, 0.6, 16, 1, false, 0, Math.PI * 2);
619
+ const bodyMaterial = new THREE.MeshStandardMaterial({
620
+ color: color,
621
+ emissive: color,
622
+ emissiveIntensity: 0.2,
623
+ roughness: 0.4,
624
+ metalness: 0.3
625
+ });
626
+ const body = new THREE.Mesh(bodyGeometry, bodyMaterial);
627
+ body.position.y = 0.3;
628
+ body.castShadow = true;
629
+ group.add(body);
630
+
631
+ // Head (sphere top)
632
+ const headGeometry = new THREE.SphereGeometry(0.4, 16, 16, 0, Math.PI * 2, 0, Math.PI / 2);
633
+ const head = new THREE.Mesh(headGeometry, bodyMaterial);
634
+ head.position.y = 0.6;
635
+ head.castShadow = true;
636
+ group.add(head);
637
+
638
+ // Wavy bottom
639
+ for (let i = 0; i < 6; i++) {
640
+ const waveGeometry = new THREE.SphereGeometry(0.12, 8, 8);
641
+ const wave = new THREE.Mesh(waveGeometry, bodyMaterial);
642
+ const angle = (i / 6) * Math.PI * 2;
643
+ wave.position.set(Math.cos(angle) * 0.28, 0, Math.sin(angle) * 0.28);
644
+ group.add(wave);
645
+ }
646
+
647
+ // Eyes
648
+ const eyeWhiteGeometry = new THREE.SphereGeometry(0.12, 16, 16);
649
+ const eyeWhiteMaterial = new THREE.MeshStandardMaterial({ color: 0xffffff });
650
+
651
+ const leftEyeWhite = new THREE.Mesh(eyeWhiteGeometry, eyeWhiteMaterial);
652
+ leftEyeWhite.position.set(0.2, 0.65, 0.18);
653
+ group.add(leftEyeWhite);
654
+
655
+ const rightEyeWhite = new THREE.Mesh(eyeWhiteGeometry, eyeWhiteMaterial);
656
+ rightEyeWhite.position.set(0.2, 0.65, -0.18);
657
+ group.add(rightEyeWhite);
658
+
659
+ // Pupils
660
+ const pupilGeometry = new THREE.SphereGeometry(0.06, 16, 16);
661
+ const pupilMaterial = new THREE.MeshStandardMaterial({ color: 0x0000ff });
662
+
663
+ const leftPupil = new THREE.Mesh(pupilGeometry, pupilMaterial);
664
+ leftPupil.position.set(0.3, 0.65, 0.18);
665
+ group.add(leftPupil);
666
+
667
+ const rightPupil = new THREE.Mesh(pupilGeometry, pupilMaterial);
668
+ rightPupil.position.set(0.3, 0.65, -0.18);
669
+ group.add(rightPupil);
670
+
671
+ group.position.set(x, 0, z);
672
+ group.userData = {
673
+ gridX: x,
674
+ gridZ: z,
675
+ targetX: x,
676
+ targetZ: z,
677
+ originalColor: color,
678
+ direction: Math.floor(Math.random() * 4),
679
+ scared: false
680
+ };
681
+
682
+ scene.add(group);
683
+ return group;
684
+ }
685
+
686
+ // Create Wall
687
+ function createWall(x, z) {
688
+ const geometry = new THREE.BoxGeometry(CELL_SIZE, 0.8, CELL_SIZE);
689
+ const material = new THREE.MeshStandardMaterial({
690
+ color: 0x1a1aff,
691
+ emissive: 0x0000aa,
692
+ emissiveIntensity: 0.3,
693
+ roughness: 0.2,
694
+ metalness: 0.8
695
+ });
696
+ const wall = new THREE.Mesh(geometry, material);
697
+ wall.position.set(x, 0.4, z);
698
+ wall.castShadow = true;
699
+ wall.receiveShadow = true;
700
+ scene.add(wall);
701
+ return wall;
702
+ }
703
+
704
+ // Create Pellet
705
+ function createPellet(x, z) {
706
+ const geometry = new THREE.SphereGeometry(0.1, 16, 16);
707
+ const material = new THREE.MeshStandardMaterial({
708
+ color: 0xffcc00,
709
+ emissive: 0xffcc00,
710
+ emissiveIntensity: 0.5,
711
+ roughness: 0.2,
712
+ metalness: 0.5
713
+ });
714
+ const pellet = new THREE.Mesh(geometry, material);
715
+ pellet.position.set(x, 0.2, z);
716
+ pellet.userData = { gridX: x, gridZ: z };
717
+ scene.add(pellet);
718
+ return pellet;
719
+ }
720
+
721
+ // Create Power Pellet
722
+ function createPowerPellet(x, z) {
723
+ const geometry = new THREE.SphereGeometry(0.2, 16, 16);
724
+ const material = new THREE.MeshStandardMaterial({
725
+ color: 0xff00ff,
726
+ emissive: 0xff00ff,
727
+ emissiveIntensity: 0.8,
728
+ roughness: 0.1,
729
+ metalness: 0.5
730
+ });
731
+ const pellet = new THREE.Mesh(geometry, material);
732
+ pellet.position.set(x, 0.3, z);
733
+ pellet.userData = { gridX: x, gridZ: z, isPower: true };
734
+ scene.add(pellet);
735
+ return pellet;
736
+ }
737
+
738
+ // Build Maze
739
+ function buildMaze() {
740
+ // Clear existing
741
+ walls.forEach(w => scene.remove(w));
742
+ pellets.forEach(p => scene.remove(p));
743
+ powerPellets.forEach(p => scene.remove(p));
744
+ walls = [];
745
+ pellets = [];
746
+ powerPellets = [];
747
+ totalPellets = 0;
748
+ pelletsEaten = 0;
749
+
750
+ // Deep copy maze template
751
+ maze = MAZE_TEMPLATE.map(row => [...row]);
752
+
753
+ for (let z = 0; z < maze.length; z++) {
754
+ for (let x = 0; x < maze[z].length; x++) {
755
+ const cell = maze[z][x];
756
+ if (cell === 1) {
757
+ walls.push(createWall(x, z));
758
+ } else if (cell === 2) {
759
+ pellets.push(createPellet(x, z));
760
+ totalPellets++;
761
+ } else if (cell === 3) {
762
+ powerPellets.push(createPowerPellet(x, z));
763
+ totalPellets++;
764
+ }
765
+ }
766
+ }
767
+ }
768
+
769
+ // Initialize Game
770
+ function initGame() {
771
+ if (pacman) scene.remove(pacman);
772
+ ghosts.forEach(g => scene.remove(g));
773
+ ghosts = [];
774
+
775
+ buildMaze();
776
+
777
+ // Create Pac-Man
778
+ pacman = createPacman();
779
+
780
+ // Create Ghosts
781
+ const ghostPositions = [
782
+ { x: 8, z: 9 },
783
+ { x: 9, z: 9 },
784
+ { x: 10, z: 9 },
785
+ { x: 9, z: 10 }
786
+ ];
787
+
788
+ ghostPositions.forEach((pos, i) => {
789
+ ghosts.push(createGhost(GHOST_COLORS[i], pos.x, pos.z));
790
+ });
791
+
792
+ score = 0;
793
+ lives = 3;
794
+ powerMode = false;
795
+ currentDirection = null;
796
+ nextDirection = null;
797
+
798
+ updateUI();
799
+ }
800
+
801
+ // Update UI
802
+ function updateUI() {
803
+ document.getElementById('score').textContent = score;
804
+ document.getElementById('high-score').textContent = highScore;
805
+
806
+ const livesContainer = document.getElementById('lives');
807
+ livesContainer.innerHTML = '';
808
+ for (let i = 0; i < lives; i++) {
809
+ const life = document.createElement('div');
810
+ life.className = 'life';
811
+ livesContainer.appendChild(life);
812
+ }
813
+ }
814
+
815
+ // Check if position is valid
816
+ function isValidPosition(x, z) {
817
+ const gridX = Math.round(x);
818
+ const gridZ = Math.round(z);
819
+
820
+ if (gridZ < 0 || gridZ >= maze.length) return true; // Allow tunnel
821
+ if (gridX < 0 || gridX >= maze[0].length) return true; // Allow tunnel
822
+
823
+ return maze[gridZ][gridX] !== 1;
824
+ }
825
+
826
+ // Move Pac-Man
827
+ function movePacman() {
828
+ if (!gameRunning) return;
829
+
830
+ const pos = pacman.userData;
831
+ let dx = 0, dz = 0;
832
+
833
+ // Try next direction first
834
+ if (nextDirection) {
835
+ switch (nextDirection) {
836
+ case 'up': dz = -1; break;
837
+ case 'down': dz = 1; break;
838
+ case 'left': dx = -1; break;
839
+ case 'right': dx = 1; break;
840
+ }
841
+
842
+ const nextX = pos.gridX + dx;
843
+ const nextZ = pos.gridZ + dz;
844
+
845
+ if (isValidPosition(nextX, nextZ)) {
846
+ currentDirection = nextDirection;
847
+ nextDirection = null;
848
+ }
849
+ }
850
+
851
+ // Move in current direction
852
+ dx = 0; dz = 0;
853
+ if (currentDirection) {
854
+ switch (currentDirection) {
855
+ case 'up': dz = -1; break;
856
+ case 'down': dz = 1; break;
857
+ case 'left': dx = -1; break;
858
+ case 'right': dx = 1; break;
859
+ }
860
+
861
+ const nextX = pos.gridX + dx;
862
+ const nextZ = pos.gridZ + dz;
863
+
864
+ if (isValidPosition(nextX, nextZ)) {
865
+ pos.targetX = nextX;
866
+ pos.targetZ = nextZ;
867
+ }
868
+ }
869
+
870
+ // Smooth movement
871
+ const speed = PACMAN_SPEED * (1 + level * 0.1);
872
+ if (Math.abs(pacman.position.x - pos.targetX) > 0.01) {
873
+ pacman.position.x += (pos.targetX - pacman.position.x) * speed * 2;
874
+ } else {
875
+ pacman.position.x = pos.targetX;
876
+ }
877
+
878
+ if (Math.abs(pacman.position.z - pos.targetZ) > 0.01) {
879
+ pacman.position.z += (pos.targetZ - pacman.position.z) * speed * 2;
880
+ } else {
881
+ pacman.position.z = pos.targetZ;
882
+ }
883
+
884
+ // Update grid position
885
+ pos.gridX = Math.round(pacman.position.x);
886
+ pos.gridZ = Math.round(pacman.position.z);
887
+
888
+ // Tunnel wrap
889
+ if (pacman.position.x < -0.5) {
890
+ pacman.position.x = maze[0].length - 0.5;
891
+ pos.gridX = maze[0].length - 1;
892
+ pos.targetX = pos.gridX;
893
+ } else if (pacman.position.x > maze[0].length - 0.5) {
894
+ pacman.position.x = 0.5;
895
+ pos.gridX = 0;
896
+ pos.targetX = 0;
897
+ }
898
+
899
+ // Rotation based on direction
900
+ if (currentDirection) {
901
+ let targetRotation = 0;
902
+ switch (currentDirection) {
903
+ case 'right': targetRotation = 0; break;
904
+ case 'down': targetRotation = Math.PI / 2; break;
905
+ case 'left': targetRotation = Math.PI; break;
906
+ case 'up': targetRotation = -Math.PI / 2; break;
907
+ }
908
+ pacman.rotation.y = targetRotation;
909
+ }
910
+
911
+ // Animate mouth
912
+ const time = Date.now() * 0.01;
913
+ pacmanMouth.rotation.y = Math.sin(time) * 0.3;
914
+
915
+ // Check pellet collision
916
+ checkPelletCollision();
917
+
918
+ // Check ghost collision
919
+ checkGhostCollision();
920
+ }
921
+
922
+ // Move Ghosts
923
+ function moveGhosts() {
924
+ if (!gameRunning) return;
925
+
926
+ const speed = GHOST_SPEED * (1 + level * 0.05);
927
+
928
+ ghosts.forEach((ghost, index) => {
929
+ const pos = ghost.userData;
930
+
931
+ // Get valid directions
932
+ const directions = [];
933
+ const testDirs = [
934
+ { dir: 0, dx: 1, dz: 0 }, // right
935
+ { dir: 1, dx: 0, dz: 1 }, // down
936
+ { dir: 2, dx: -1, dz: 0 }, // left
937
+ { dir: 3, dx: 0, dz: -1 } // up
938
+ ];
939
+
940
+ testDirs.forEach(d => {
941
+ if (isValidPosition(pos.gridX + d.dx, pos.gridZ + d.dz)) {
942
+ directions.push(d);
943
+ }
944
+ });
945
+
946
+ // Choose direction (chase or scatter)
947
+ if (directions.length > 0) {
948
+ let chosenDir;
949
+
950
+ if (powerMode && ghost.userData.scared) {
951
+ // Run away from Pac-Man
952
+ let maxDist = -1;
953
+ directions.forEach(d => {
954
+ const dist = Math.hypot(
955
+ (pos.gridX + d.dx) - pacman.userData.gridX,
956
+ (pos.gridZ + d.dz) - pacman.userData.gridZ
957
+ );
958
+ if (dist > maxDist) {
959
+ maxDist = dist;
960
+ chosenDir = d;
961
+ }
962
+ });
963
+ } else {
964
+ // Chase Pac-Man (with some randomness based on ghost type)
965
+ if (Math.random() < 0.7 + index * 0.05) {
966
+ let minDist = Infinity;
967
+ directions.forEach(d => {
968
+ const dist = Math.hypot(
969
+ (pos.gridX + d.dx) - pacman.userData.gridX,
970
+ (pos.gridZ + d.dz) - pacman.userData.gridZ
971
+ );
972
+ if (dist < minDist) {
973
+ minDist = dist;
974
+ chosenDir = d;
975
+ }
976
+ });
977
+ } else {
978
+ chosenDir = directions[Math.floor(Math.random() * directions.length)];
979
+ }
980
+ }
981
+
982
+ if (chosenDir) {
983
+ pos.targetX = pos.gridX + chosenDir.dx;
984
+ pos.targetZ = pos.gridZ + chosenDir.dz;
985
+ ghost.userData.direction = chosenDir.dir;
986
+ }
987
+ }
988
+
989
+ // Smooth movement
990
+ if (Math.abs(ghost.position.x - pos.targetX) > 0.01) {
991
+ ghost.position.x += (pos.targetX - ghost.position.x) * speed * 2;
992
+ } else {
993
+ ghost.position.x = pos.targetX;
994
+ }
995
+
996
+ if (Math.abs(ghost.position.z - pos.targetZ) > 0.01) {
997
+ ghost.position.z += (pos.targetZ - ghost.position.z) * speed * 2;
998
+ } else {
999
+ ghost.position.z = pos.targetZ;
1000
+ }
1001
+
1002
+ pos.gridX = Math.round(ghost.position.x);
1003
+ pos.gridZ = Math.round(ghost.position.z);
1004
+
1005
+ // Tunnel wrap
1006
+ if (ghost.position.x < -0.5) {
1007
+ ghost.position.x = maze[0].length - 0.5;
1008
+ pos.gridX = maze[0].length - 1;
1009
+ pos.targetX = pos.gridX;
1010
+ } else if (ghost.position.x > maze[0].length - 0.5) {
1011
+ ghost.position.x = 0.5;
1012
+ pos.gridX = 0;
1013
+ pos.targetX = 0;
1014
+ }
1015
+
1016
+ // Rotation
1017
+ ghost.rotation.y = ghost.userData.direction * Math.PI / 2;
1018
+
1019
+ // Bobbing animation
1020
+ ghost.position.y = Math.sin(Date.now() * 0.005 + index) * 0.05;
1021
+ });
1022
+ }
1023
+
1024
+ // Check Pellet Collision
1025
+ function checkPelletCollision() {
1026
+ const px = Math.round(pacman.position.x);
1027
+ const pz = Math.round(pacman.position.z);
1028
+
1029
+ // Regular pellets
1030
+ for (let i = pellets.length - 1; i >= 0; i--) {
1031
+ const pellet = pellets[i];
1032
+ if (Math.round(pellet.position.x) === px && Math.round(pellet.position.z) === pz) {
1033
+ scene.remove(pellet);
1034
+ pellets.splice(i, 1);
1035
+ score += 10;
1036
+ pelletsEaten++;
1037
+ updateUI();
1038
+ }
1039
+ }
1040
+
1041
+ // Power pellets
1042
+ for (let i = powerPellets.length - 1; i >= 0; i--) {
1043
+ const pellet = powerPellets[i];
1044
+ if (Math.round(pellet.position.x) === px && Math.round(pellet.position.z) === pz) {
1045
+ scene.remove(pellet);
1046
+ powerPellets.splice(i, 1);
1047
+ score += 50;
1048
+ pelletsEaten++;
1049
+ activatePowerMode();
1050
+ updateUI();
1051
+ }
1052
+ }
1053
+
1054
+ // Check win condition
1055
+ if (pelletsEaten >= totalPellets) {
1056
+ winLevel();
1057
+ }
1058
+ }
1059
+
1060
+ // Check Ghost Collision
1061
+ function checkGhostCollision() {
1062
+ const px = pacman.position.x;
1063
+ const pz = pacman.position.z;
1064
+
1065
+ ghosts.forEach((ghost, index) => {
1066
+ const dist = Math.hypot(ghost.position.x - px, ghost.position.z - pz);
1067
+
1068
+ if (dist < 0.6) {
1069
+ if (powerMode && ghost.userData.scared) {
1070
+ // Eat ghost
1071
+ score += 200;
1072
+ resetGhostPosition(ghost, index);
1073
+ updateUI();
1074
+ } else if (!ghost.userData.scared) {
1075
+ // Lose life
1076
+ loseLife();
1077
+ }
1078
+ }
1079
+ });
1080
+ }
1081
+
1082
+ // Reset Ghost Position
1083
+ function resetGhostPosition(ghost, index) {
1084
+ const positions = [
1085
+ { x: 8, z: 9 },
1086
+ { x: 9, z: 9 },
1087
+ { x: 10, z: 9 },
1088
+ { x: 9, z: 10 }
1089
+ ];
1090
+ ghost.position.set(positions[index].x, 0, positions[index].z);
1091
+ ghost.userData.gridX = positions[index].x;
1092
+ ghost.userData.gridZ = positions[index].z;
1093
+ ghost.userData.targetX = positions[index].x;
1094
+ ghost.userData.targetZ = positions[index].z;
1095
+ ghost.userData.scared = false;
1096
+
1097
+ // Reset color
1098
+ ghost.children.forEach(child => {
1099
+ if (child.material && child.material.emissive) {
1100
+ child.material.color.setHex(ghost.userData.originalColor);
1101
+ child.material.emiss