Deigomax02 commited on
Commit
4160c18
·
verified ·
1 Parent(s): ef5bf33

Crea un videojuego de aventura estilo "Zelda" en una sola página web (HTML + CSS + JavaScript) con las siguientes especificaciones: 1) Modo de juego: - Local, **2 jugadores** simultáneos en **pantalla dividida** (split-screen). Cada jugador controla a su personaje y mueve su propia cámara en el mismo mapa grande. - La pantalla debe dividirse **verticalmente** (jugador 1 a la izquierda, jugador 2 a la derecha). Si el tamaño del viewport es estrecho, usa división horizontal como respaldo. 2) Mecánica & diseño: - Vista superior (top-down) con movimiento 8 direcciones (arriba/abajo/izq/der y diagonales). - Mapa tile-based (mosaicos), tamaño razonable (por ejemplo 60x40 tiles) con zonas: bosque, pueblo, río, mazmorra pequeña. - Incluye exploración libre, objetos recogibles (llaves, pociones), puertas que requieren llaves, y al menos **una mazmorra** con 1 jefe. - Puzles simples (palancas, bloques que empujar) y enemigos básicos con IA sencilla (patrullan y persiguen si ven al jugador). - Inventario por jugador (3 ranuras visibles) y barra de vida por jugador. - Sistema sencillo de combate cuerpo a cuerpo: ataque con espada (ataque en frente), tiempo de invulnerabilidad corto al recibir daño. 3) Controles: - Jugador 1: WASD para moverse, F para atacar/usar, G para interactuar. - Jugador 2: Flechas para moverse, Numpad 0 (o tecla L) para atacar/usar, Numpad 1 (o tecla K) para interactuar. - Soporte para gamepad si es posible (pero no obligatorio). 4) Cámara & pantalla partida: - Cada mitad de pantalla muestra la cámara centrada en su jugador; las cámaras se mueven independientemente (no forzar unión). - HUD separado para cada jugador (vida, pociones, llaves) en su lado de la pantalla. - Minimapa pequeño compartido en la parte superior central (muestra la posición aproximada de ambos jugadores y la mazmorra si está explorada). 5) Estética y assets: - Estilo pixel art 16×16 o 32×32, paleta simple tipo 8-12 colores. Usa sprites simples generados por la IA o patrones CSS/Canvas si no hay assets externos. - Sonidos simples (pasos, golpe, recolectar objeto, abrir puerta, efecto de daño) generados por la web o sonidos cortos en base64. 6) Entregables: - Generar **un solo archivo HTML** funcional o un paquete descargable con HTML/CSS/JS y carpeta `assets/`. Debe ser fácil de ejecutar localmente (doble clic en el HTML). - Incluir comentarios en el código explicando las partes principales (mapa, cámara, colisiones, IA, controles). - Proveer en el archivo un pequeño README visible como comentario con instrucciones de control y cómo cambiar tamaño de mapa o añadir enemigos. 7) Rendimiento y fallback: - Optimizar para navegador moderno; si el navegador no soporta WebAudio o gamepad, debe seguir funcionando sin esas características. - Si no hay tiempo para generación de todo el mapa, crear una versión jugable limitada (un mapa pequeño con una mazmorra) claramente comentada en el código. Entrega: juego jugable en local con pantalla dividida, dos personajes, exploración, combate básico, mazmorra y jefe. - Initial Deployment

Browse files
Files changed (2) hide show
  1. README.md +7 -5
  2. index.html +1491 -19
README.md CHANGED
@@ -1,10 +1,12 @@
1
  ---
2
- title: Zfgjf
3
- emoji: 🌍
4
- colorFrom: pink
5
- colorTo: green
6
  sdk: static
7
  pinned: false
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
+ title: zfgjf
3
+ emoji: 🐳
4
+ colorFrom: blue
5
+ colorTo: pink
6
  sdk: static
7
  pinned: false
8
+ tags:
9
+ - deepsite
10
  ---
11
 
12
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
index.html CHANGED
@@ -1,19 +1,1491 @@
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>Pixel Quest - Split-Screen Adventure</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <style>
9
+ /* Custom CSS for game elements */
10
+ body {
11
+ margin: 0;
12
+ padding: 0;
13
+ overflow: hidden;
14
+ background-color: #222;
15
+ font-family: 'Courier New', monospace;
16
+ }
17
+
18
+ #game-container {
19
+ position: relative;
20
+ width: 100vw;
21
+ height: 100vh;
22
+ display: flex;
23
+ flex-direction: row;
24
+ }
25
+
26
+ @media (max-width: 768px) {
27
+ #game-container {
28
+ flex-direction: column;
29
+ }
30
+ }
31
+
32
+ .player-view {
33
+ position: relative;
34
+ flex: 1;
35
+ overflow: hidden;
36
+ border: 2px solid #444;
37
+ }
38
+
39
+ .game-canvas {
40
+ position: absolute;
41
+ top: 0;
42
+ left: 0;
43
+ image-rendering: pixelated;
44
+ }
45
+
46
+ .hud {
47
+ position: absolute;
48
+ padding: 10px;
49
+ color: white;
50
+ text-shadow: 2px 2px 0 #000;
51
+ font-size: 16px;
52
+ z-index: 100;
53
+ }
54
+
55
+ .health-bar {
56
+ height: 10px;
57
+ background-color: #ff0000;
58
+ border: 2px solid #000;
59
+ margin-top: 5px;
60
+ }
61
+
62
+ .health-fill {
63
+ height: 100%;
64
+ background-color: #00ff00;
65
+ width: 100%;
66
+ transition: width 0.3s;
67
+ }
68
+
69
+ .inventory {
70
+ display: flex;
71
+ gap: 5px;
72
+ margin-top: 10px;
73
+ }
74
+
75
+ .inventory-slot {
76
+ width: 32px;
77
+ height: 32px;
78
+ background-color: rgba(0, 0, 0, 0.5);
79
+ border: 2px solid #555;
80
+ display: flex;
81
+ align-items: center;
82
+ justify-content: center;
83
+ }
84
+
85
+ #minimap {
86
+ position: absolute;
87
+ top: 10px;
88
+ left: 50%;
89
+ transform: translateX(-50%);
90
+ width: 200px;
91
+ height: 150px;
92
+ background-color: rgba(0, 0, 0, 0.7);
93
+ border: 2px solid #555;
94
+ z-index: 200;
95
+ image-rendering: pixelated;
96
+ }
97
+
98
+ .game-object {
99
+ position: absolute;
100
+ image-rendering: pixelated;
101
+ }
102
+
103
+ .damage-effect {
104
+ position: absolute;
105
+ width: 100%;
106
+ height: 100%;
107
+ background-color: rgba(255, 0, 0, 0.3);
108
+ pointer-events: none;
109
+ opacity: 0;
110
+ z-index: 90;
111
+ }
112
+
113
+ .title-screen {
114
+ position: absolute;
115
+ top: 0;
116
+ left: 0;
117
+ width: 100%;
118
+ height: 100%;
119
+ background-color: rgba(0, 0, 0, 0.8);
120
+ display: flex;
121
+ flex-direction: column;
122
+ justify-content: center;
123
+ align-items: center;
124
+ color: white;
125
+ z-index: 1000;
126
+ }
127
+
128
+ .title-screen h1 {
129
+ font-size: 48px;
130
+ margin-bottom: 30px;
131
+ text-shadow: 4px 4px 0 #8b4513;
132
+ color: #ffcc00;
133
+ }
134
+
135
+ .start-button {
136
+ padding: 15px 30px;
137
+ background-color: #8b4513;
138
+ color: white;
139
+ border: none;
140
+ font-size: 24px;
141
+ cursor: pointer;
142
+ border-radius: 5px;
143
+ transition: background-color 0.3s;
144
+ }
145
+
146
+ .start-button:hover {
147
+ background-color: #a0522d;
148
+ }
149
+
150
+ .controls {
151
+ margin-top: 30px;
152
+ text-align: center;
153
+ }
154
+
155
+ .controls h2 {
156
+ color: #ffcc00;
157
+ margin-bottom: 10px;
158
+ }
159
+
160
+ .control-group {
161
+ display: inline-block;
162
+ margin: 0 20px;
163
+ text-align: left;
164
+ }
165
+ </style>
166
+ </head>
167
+ <body>
168
+ <div id="game-container">
169
+ <!-- Player 1 View -->
170
+ <div class="player-view" id="player1-view">
171
+ <canvas class="game-canvas" id="player1-canvas"></canvas>
172
+ <div class="hud" id="player1-hud">
173
+ <div>Player 1</div>
174
+ <div class="health-bar">
175
+ <div class="health-fill" id="player1-health"></div>
176
+ </div>
177
+ <div class="inventory" id="player1-inventory">
178
+ <div class="inventory-slot"></div>
179
+ <div class="inventory-slot"></div>
180
+ <div class="inventory-slot"></div>
181
+ </div>
182
+ </div>
183
+ <div class="damage-effect" id="player1-damage"></div>
184
+ </div>
185
+
186
+ <!-- Player 2 View -->
187
+ <div class="player-view" id="player2-view">
188
+ <canvas class="game-canvas" id="player2-canvas"></canvas>
189
+ <div class="hud" id="player2-hud">
190
+ <div>Player 2</div>
191
+ <div class="health-bar">
192
+ <div class="health-fill" id="player2-health"></div>
193
+ </div>
194
+ <div class="inventory" id="player2-inventory">
195
+ <div class="inventory-slot"></div>
196
+ <div class="inventory-slot"></div>
197
+ <div class="inventory-slot"></div>
198
+ </div>
199
+ </div>
200
+ <div class="damage-effect" id="player2-damage"></div>
201
+ </div>
202
+
203
+ <!-- Minimap -->
204
+ <canvas id="minimap"></canvas>
205
+
206
+ <!-- Title Screen -->
207
+ <div class="title-screen" id="title-screen">
208
+ <h1>Pixel Quest</h1>
209
+ <button class="start-button" id="start-button">Start Adventure</button>
210
+ <div class="controls">
211
+ <h2>Controls</h2>
212
+ <div class="control-group">
213
+ <h3>Player 1</h3>
214
+ <p>WASD: Move</p>
215
+ <p>F: Attack/Use</p>
216
+ <p>G: Interact</p>
217
+ </div>
218
+ <div class="control-group">
219
+ <h3>Player 2</h3>
220
+ <p>Arrow Keys: Move</p>
221
+ <p>L: Attack/Use</p>
222
+ <p>K: Interact</p>
223
+ </div>
224
+ </div>
225
+ </div>
226
+ </div>
227
+
228
+ <script>
229
+ /*
230
+ * PIXEL QUEST - Split-Screen Adventure Game
231
+ *
232
+ * Features:
233
+ * - Split-screen multiplayer (vertical/horizontal based on screen size)
234
+ * - Top-down 8-directional movement
235
+ * - Tile-based map with different zones
236
+ * - Collectible items (keys, potions)
237
+ * - Locked doors requiring keys
238
+ * - Dungeon with a boss
239
+ * - Simple puzzles (levers, pushable blocks)
240
+ * - Enemy AI (patrol and chase)
241
+ * - Health system and inventory
242
+ * - Melee combat with invulnerability frames
243
+ * - Minimap showing player positions
244
+ */
245
+
246
+ // Game Constants
247
+ const TILE_SIZE = 32;
248
+ const MAP_WIDTH = 60;
249
+ const MAP_HEIGHT = 40;
250
+ const PLAYER_SPEED = 3;
251
+ const ATTACK_RANGE = 20;
252
+ const ATTACK_DURATION = 300; // ms
253
+ const INVULNERABILITY_DURATION = 1000; // ms
254
+ const CAMERA_SMOOTHING = 0.1;
255
+
256
+ // Game State
257
+ let gameState = {
258
+ players: [],
259
+ enemies: [],
260
+ items: [],
261
+ doors: [],
262
+ levers: [],
263
+ blocks: [],
264
+ boss: null,
265
+ gameTime: 0,
266
+ gameStarted: false
267
+ };
268
+
269
+ // Tile Types
270
+ const TILE_TYPES = {
271
+ GRASS: 0,
272
+ WATER: 1,
273
+ SAND: 2,
274
+ STONE: 3,
275
+ WOOD: 4,
276
+ BRICK: 5,
277
+ LAVA: 6
278
+ };
279
+
280
+ // Game Objects
281
+ class GameObject {
282
+ constructor(x, y, width, height) {
283
+ this.x = x;
284
+ this.y = y;
285
+ this.width = width;
286
+ this.height = height;
287
+ this.vx = 0;
288
+ this.vy = 0;
289
+ }
290
+
291
+ get centerX() {
292
+ return this.x + this.width / 2;
293
+ }
294
+
295
+ get centerY() {
296
+ return this.y + this.height / 2;
297
+ }
298
+
299
+ collidesWith(other) {
300
+ return this.x < other.x + other.width &&
301
+ this.x + this.width > other.x &&
302
+ this.y < other.y + other.height &&
303
+ this.y + this.height > other.y;
304
+ }
305
+
306
+ distanceTo(other) {
307
+ const dx = this.centerX - other.centerX;
308
+ const dy = this.centerY - other.centerY;
309
+ return Math.sqrt(dx * dx + dy * dy);
310
+ }
311
+
312
+ update(deltaTime) {
313
+ this.x += this.vx * deltaTime;
314
+ this.y += this.vy * deltaTime;
315
+ }
316
+ }
317
+
318
+ class Player extends GameObject {
319
+ constructor(x, y, playerId) {
320
+ super(x, y, 24, 24);
321
+ this.playerId = playerId;
322
+ this.maxHealth = 100;
323
+ this.health = this.maxHealth;
324
+ this.inventory = [];
325
+ this.facing = { x: 0, y: 1 }; // Facing down by default
326
+ this.isAttacking = false;
327
+ this.attackCooldown = 0;
328
+ this.isInvulnerable = false;
329
+ this.invulnerabilityTimer = 0;
330
+ this.keys = 0;
331
+ this.color = playerId === 1 ? '#3498db' : '#e74c3c';
332
+ }
333
+
334
+ update(deltaTime) {
335
+ super.update(deltaTime);
336
+
337
+ // Update attack cooldown
338
+ if (this.attackCooldown > 0) {
339
+ this.attackCooldown -= deltaTime;
340
+ if (this.attackCooldown <= 0) {
341
+ this.isAttacking = false;
342
+ }
343
+ }
344
+
345
+ // Update invulnerability
346
+ if (this.isInvulnerable) {
347
+ this.invulnerabilityTimer -= deltaTime;
348
+ if (this.invulnerabilityTimer <= 0) {
349
+ this.isInvulnerable = false;
350
+ }
351
+ }
352
+
353
+ // Apply friction
354
+ this.vx *= 0.9;
355
+ this.vy *= 0.9;
356
+
357
+ // Update facing direction if moving
358
+ if (Math.abs(this.vx) > 0.1 || Math.abs(this.vy) > 0.1) {
359
+ this.facing = { x: Math.sign(this.vx), y: Math.sign(this.vy) };
360
+ }
361
+
362
+ // Keep player within bounds
363
+ this.x = Math.max(0, Math.min(MAP_WIDTH * TILE_SIZE - this.width, this.x));
364
+ this.y = Math.max(0, Math.min(MAP_HEIGHT * TILE_SIZE - this.height, this.y));
365
+ }
366
+
367
+ attack() {
368
+ if (!this.isAttacking && this.attackCooldown <= 0) {
369
+ this.isAttacking = true;
370
+ this.attackCooldown = ATTACK_DURATION;
371
+ return true;
372
+ }
373
+ return false;
374
+ }
375
+
376
+ takeDamage(amount) {
377
+ if (!this.isInvulnerable) {
378
+ this.health = Math.max(0, this.health - amount);
379
+ this.isInvulnerable = true;
380
+ this.invulnerabilityTimer = INVULNERABILITY_DURATION;
381
+
382
+ // Show damage effect
383
+ const damageEffect = document.getElementById(`player${this.playerId}-damage`);
384
+ damageEffect.style.opacity = 1;
385
+ setTimeout(() => {
386
+ damageEffect.style.opacity = 0;
387
+ }, 200);
388
+
389
+ return true;
390
+ }
391
+ return false;
392
+ }
393
+
394
+ useItem(index) {
395
+ if (index >= 0 && index < this.inventory.length) {
396
+ const item = this.inventory[index];
397
+ if (item.type === 'potion') {
398
+ this.health = Math.min(this.maxHealth, this.health + 30);
399
+ this.inventory.splice(index, 1);
400
+ playSound('potion');
401
+ return true;
402
+ }
403
+ }
404
+ return false;
405
+ }
406
+
407
+ addToInventory(item) {
408
+ if (this.inventory.length < 3) {
409
+ this.inventory.push(item);
410
+ return true;
411
+ }
412
+ return false;
413
+ }
414
+
415
+ draw(ctx, cameraX, cameraY) {
416
+ const screenX = this.x - cameraX;
417
+ const screenY = this.y - cameraY;
418
+
419
+ // Draw player body
420
+ ctx.fillStyle = this.color;
421
+ ctx.fillRect(screenX, screenY, this.width, this.height);
422
+
423
+ // Draw facing indicator (head)
424
+ ctx.fillStyle = '#fff';
425
+ const headX = screenX + this.width / 2 + this.facing.x * 5;
426
+ const headY = screenY + this.height / 2 + this.facing.y * 5;
427
+ ctx.beginPath();
428
+ ctx.arc(headX, headY, 5, 0, Math.PI * 2);
429
+ ctx.fill();
430
+
431
+ // Draw attack if attacking
432
+ if (this.isAttacking) {
433
+ ctx.fillStyle = 'rgba(255, 255, 0, 0.5)';
434
+ const attackX = screenX + this.width / 2 + this.facing.x * ATTACK_RANGE;
435
+ const attackY = screenY + this.height / 2 + this.facing.y * ATTACK_RANGE;
436
+ ctx.beginPath();
437
+ ctx.arc(attackX, attackY, 15, 0, Math.PI * 2);
438
+ ctx.fill();
439
+ }
440
+ }
441
+ }
442
+
443
+ class Enemy extends GameObject {
444
+ constructor(x, y, type) {
445
+ super(x, y, 24, 24);
446
+ this.type = type;
447
+ this.health = type === 'boss' ? 150 : 50;
448
+ this.speed = type === 'boss' ? 1.5 : 1;
449
+ this.damage = type === 'boss' ? 20 : 10;
450
+ this.detectionRange = type === 'boss' ? 300 : 150;
451
+ this.attackCooldown = 0;
452
+ this.patrolPoints = [];
453
+ this.currentPatrolIndex = 0;
454
+ this.color = type === 'boss' ? '#8b0000' : '#9b59b6';
455
+ }
456
+
457
+ update(deltaTime, players) {
458
+ super.update(deltaTime);
459
+
460
+ // Update attack cooldown
461
+ if (this.attackCooldown > 0) {
462
+ this.attackCooldown -= deltaTime;
463
+ }
464
+
465
+ // Find closest player
466
+ let closestPlayer = null;
467
+ let minDistance = Infinity;
468
+
469
+ for (const player of players) {
470
+ const distance = this.distanceTo(player);
471
+ if (distance < minDistance) {
472
+ minDistance = distance;
473
+ closestPlayer = player;
474
+ }
475
+ }
476
+
477
+ // Behavior based on distance to player
478
+ if (closestPlayer && minDistance < this.detectionRange) {
479
+ // Chase player
480
+ const dx = closestPlayer.centerX - this.centerX;
481
+ const dy = closestPlayer.centerY - this.centerY;
482
+ const distance = Math.sqrt(dx * dx + dy * dy);
483
+
484
+ if (distance > 0) {
485
+ this.vx = (dx / distance) * this.speed;
486
+ this.vy = (dy / distance) * this.speed;
487
+ }
488
+
489
+ // Attack if close enough
490
+ if (minDistance < ATTACK_RANGE && this.attackCooldown <= 0) {
491
+ closestPlayer.takeDamage(this.damage);
492
+ this.attackCooldown = 1000; // 1 second cooldown
493
+ }
494
+ } else {
495
+ // Patrol behavior
496
+ if (this.patrolPoints.length > 0) {
497
+ const target = this.patrolPoints[this.currentPatrolIndex];
498
+ const dx = target.x - this.centerX;
499
+ const dy = target.y - this.centerY;
500
+ const distance = Math.sqrt(dx * dx + dy * dy);
501
+
502
+ if (distance > 10) {
503
+ this.vx = (dx / distance) * this.speed * 0.5;
504
+ this.vy = (dy / distance) * this.speed * 0.5;
505
+ } else {
506
+ this.currentPatrolIndex = (this.currentPatrolIndex + 1) % this.patrolPoints.length;
507
+ }
508
+ } else {
509
+ // Random wandering if no patrol points
510
+ if (Math.random() < 0.02) {
511
+ this.vx = (Math.random() - 0.5) * this.speed * 0.5;
512
+ this.vy = (Math.random() - 0.5) * this.speed * 0.5;
513
+ }
514
+ }
515
+ }
516
+
517
+ // Apply friction
518
+ this.vx *= 0.95;
519
+ this.vy *= 0.95;
520
+ }
521
+
522
+ takeDamage(amount) {
523
+ this.health = Math.max(0, this.health - amount);
524
+ return this.health <= 0;
525
+ }
526
+
527
+ draw(ctx, cameraX, cameraY) {
528
+ const screenX = this.x - cameraX;
529
+ const screenY = this.y - cameraY;
530
+
531
+ // Draw enemy body
532
+ ctx.fillStyle = this.color;
533
+ ctx.fillRect(screenX, screenY, this.width, this.height);
534
+
535
+ // Draw health bar for bosses
536
+ if (this.type === 'boss') {
537
+ const healthWidth = 30;
538
+ const healthHeight = 5;
539
+ const healthX = screenX + (this.width - healthWidth) / 2;
540
+ const healthY = screenY - 10;
541
+
542
+ ctx.fillStyle = '#ff0000';
543
+ ctx.fillRect(healthX, healthY, healthWidth, healthHeight);
544
+
545
+ ctx.fillStyle = '#00ff00';
546
+ ctx.fillRect(healthX, healthY, healthWidth * (this.health / 150), healthHeight);
547
+ }
548
+ }
549
+ }
550
+
551
+ class Item extends GameObject {
552
+ constructor(x, y, type) {
553
+ super(x, y, 16, 16);
554
+ this.type = type;
555
+ this.color = this.getItemColor();
556
+ }
557
+
558
+ getItemColor() {
559
+ switch (this.type) {
560
+ case 'key': return '#ffcc00';
561
+ case 'potion': return '#ff0000';
562
+ case 'sword': return '#999999';
563
+ default: return '#ffffff';
564
+ }
565
+ }
566
+
567
+ draw(ctx, cameraX, cameraY) {
568
+ const screenX = this.x - cameraX;
569
+ const screenY = this.y - cameraY;
570
+
571
+ ctx.fillStyle = this.color;
572
+
573
+ if (this.type === 'key') {
574
+ // Draw key shape
575
+ ctx.fillRect(screenX + 5, screenY + 3, 10, 2); // Key handle
576
+ ctx.fillRect(screenX + 10, screenY, 2, 10); // Key shaft
577
+ ctx.fillRect(screenX + 12, screenY + 2, 3, 2); // Teeth
578
+ ctx.fillRect(screenX + 12, screenY + 6, 3, 2); // Teeth
579
+ } else if (this.type === 'potion') {
580
+ // Draw potion shape
581
+ ctx.beginPath();
582
+ ctx.moveTo(screenX + 5, screenY + 12);
583
+ ctx.lineTo(screenX + 5, screenY + 5);
584
+ ctx.lineTo(screenX + 11, screenY + 5);
585
+ ctx.lineTo(screenX + 11, screenY + 12);
586
+ ctx.lineTo(screenX + 8, screenY + 15);
587
+ ctx.lineTo(screenX + 5, screenY + 12);
588
+ ctx.fill();
589
+
590
+ // Draw potion liquid
591
+ ctx.fillStyle = '#ff6666';
592
+ ctx.beginPath();
593
+ ctx.moveTo(screenX + 6, screenY + 11);
594
+ ctx.lineTo(screenX + 6, screenY + 7);
595
+ ctx.lineTo(screenX + 10, screenY + 7);
596
+ ctx.lineTo(screenX + 10, screenY + 11);
597
+ ctx.lineTo(screenX + 8, screenY + 13);
598
+ ctx.lineTo(screenX + 6, screenY + 11);
599
+ ctx.fill();
600
+ } else if (this.type === 'sword') {
601
+ // Draw sword shape
602
+ ctx.fillRect(screenX + 7, screenY, 2, 12); // Blade
603
+ ctx.fillRect(screenX + 3, screenY + 10, 10, 2); // Crossguard
604
+ ctx.fillRect(screenX + 6, screenY + 12, 4, 4); // Hilt
605
+ }
606
+ }
607
+ }
608
+
609
+ class Door extends GameObject {
610
+ constructor(x, y, width, height, locked, keyId) {
611
+ super(x, y, width, height);
612
+ this.locked = locked;
613
+ this.keyId = keyId;
614
+ this.isOpen = false;
615
+ }
616
+
617
+ unlock(player) {
618
+ if (this.locked) {
619
+ // Check if player has the key
620
+ for (let i = 0; i < player.inventory.length; i++) {
621
+ const item = player.inventory[i];
622
+ if (item.type === 'key' && item.keyId === this.keyId) {
623
+ player.inventory.splice(i, 1);
624
+ this.locked = false;
625
+ playSound('door');
626
+ return true;
627
+ }
628
+ }
629
+ return false;
630
+ }
631
+ return true;
632
+ }
633
+
634
+ draw(ctx, cameraX, cameraY) {
635
+ const screenX = this.x - cameraX;
636
+ const screenY = this.y - cameraY;
637
+
638
+ if (this.isOpen) {
639
+ ctx.fillStyle = 'rgba(139, 69, 19, 0.5)';
640
+ } else if (this.locked) {
641
+ ctx.fillStyle = '#8b4513';
642
+ } else {
643
+ ctx.fillStyle = '#a0522d';
644
+ }
645
+
646
+ ctx.fillRect(screenX, screenY, this.width, this.height);
647
+
648
+ // Draw door details
649
+ if (!this.isOpen) {
650
+ ctx.fillStyle = '#8b0000';
651
+ ctx.beginPath();
652
+ ctx.arc(screenX + this.width - 5, screenY + this.height / 2, 3, 0, Math.PI * 2);
653
+ ctx.fill();
654
+
655
+ if (this.locked) {
656
+ // Draw lock icon
657
+ ctx.fillStyle = '#ffcc00';
658
+ ctx.fillRect(screenX + this.width - 10, screenY + this.height / 2 - 5, 5, 8);
659
+ ctx.fillRect(screenX + this.width - 12, screenY + this.height / 2 + 3, 9, 2);
660
+ }
661
+ }
662
+ }
663
+ }
664
+
665
+ class Lever extends GameObject {
666
+ constructor(x, y, target) {
667
+ super(x, y, 16, 16);
668
+ this.target = target;
669
+ this.isActivated = false;
670
+ }
671
+
672
+ activate() {
673
+ this.isActivated = !this.isActivated;
674
+ playSound('lever');
675
+
676
+ if (this.target) {
677
+ if (Array.isArray(this.target)) {
678
+ this.target.forEach(t => t.trigger());
679
+ } else {
680
+ this.target.trigger();
681
+ }
682
+ }
683
+ return true;
684
+ }
685
+
686
+ draw(ctx, cameraX, cameraY) {
687
+ const screenX = this.x - cameraX;
688
+ const screenY = this.y - cameraY;
689
+
690
+ // Draw lever base
691
+ ctx.fillStyle = '#999999';
692
+ ctx.fillRect(screenX + 6, screenY + 4, 4, 8);
693
+
694
+ // Draw lever handle
695
+ ctx.fillStyle = '#666666';
696
+ if (this.isActivated) {
697
+ ctx.fillRect(screenX + 4, screenY + 8, 8, 2);
698
+ } else {
699
+ ctx.fillRect(screenX + 8, screenY + 2, 2, 8);
700
+ }
701
+ }
702
+ }
703
+
704
+ class PushableBlock extends GameObject {
705
+ constructor(x, y) {
706
+ super(x, y, TILE_SIZE, TILE_SIZE);
707
+ this.isBeingPushed = false;
708
+ this.pushDirection = { x: 0, y: 0 };
709
+ }
710
+
711
+ push(directionX, directionY) {
712
+ this.vx = directionX * 0.5;
713
+ this.vy = directionY * 0.5;
714
+ this.isBeingPushed = true;
715
+ this.pushDirection = { x: directionX, y: directionY };
716
+ return true;
717
+ }
718
+
719
+ update(deltaTime) {
720
+ super.update(deltaTime);
721
+
722
+ // Stop pushing if no velocity
723
+ if (Math.abs(this.vx) < 0.1 && Math.abs(this.vy) < 0.1) {
724
+ this.isBeingPushed = false;
725
+ }
726
+
727
+ // Snap to grid when not being pushed
728
+ if (!this.isBeingPushed) {
729
+ const targetX = Math.round(this.x / TILE_SIZE) * TILE_SIZE;
730
+ const targetY = Math.round(this.y / TILE_SIZE) * TILE_SIZE;
731
+
732
+ if (Math.abs(this.x - targetX) > 0.1 || Math.abs(this.y - targetY) > 0.1) {
733
+ this.x += (targetX - this.x) * 0.2;
734
+ this.y += (targetY - this.y) * 0.2;
735
+ } else {
736
+ this.x = targetX;
737
+ this.y = targetY;
738
+ this.vx = 0;
739
+ this.vy = 0;
740
+ }
741
+ }
742
+ }
743
+
744
+ draw(ctx, cameraX, cameraY) {
745
+ const screenX = this.x - cameraX;
746
+ const screenY = this.y - cameraY;
747
+
748
+ // Draw block
749
+ ctx.fillStyle = '#8b4513';
750
+ ctx.fillRect(screenX, screenY, this.width, this.height);
751
+
752
+ // Draw wood grain
753
+ ctx.fillStyle = '#a0522d';
754
+ for (let i = 0; i < 3; i++) {
755
+ ctx.fillRect(screenX + 2, screenY + 6 + i * 6, this.width - 4, 2);
756
+ }
757
+ }
758
+ }
759
+
760
+ // Game Map
761
+ class GameMap {
762
+ constructor(width, height) {
763
+ this.width = width;
764
+ this.height = height;
765
+ this.tiles = [];
766
+ this.initMap();
767
+ }
768
+
769
+ initMap() {
770
+ // Create empty map
771
+ for (let y = 0; y < this.height; y++) {
772
+ this.tiles[y] = [];
773
+ for (let x = 0; x < this.width; x++) {
774
+ // Default to grass
775
+ this.tiles[y][x] = TILE_TYPES.GRASS;
776
+ }
777
+ }
778
+
779
+ // Create a river
780
+ for (let x = 15; x < 45; x++) {
781
+ for (let y = 15; y < 25; y++) {
782
+ this.tiles[y][x] = TILE_TYPES.WATER;
783
+ }
784
+ }
785
+
786
+ // Create a village
787
+ for (let x = 5; x < 15; x++) {
788
+ for (let y = 5; y < 15; y++) {
789
+ this.tiles[y][x] = TILE_TYPES.WOOD;
790
+ }
791
+ }
792
+
793
+ // Create a forest
794
+ for (let x = 40; x < 55; x++) {
795
+ for (let y = 5; y < 15; y++) {
796
+ if (Math.random() > 0.3) {
797
+ this.tiles[y][x] = TILE_TYPES.GRASS;
798
+ } else {
799
+ this.tiles[y][x] = TILE_TYPES.SAND;
800
+ }
801
+ }
802
+ }
803
+
804
+ // Create a dungeon
805
+ for (let x = 25; x < 35; x++) {
806
+ for (let y = 30; y < 38; y++) {
807
+ this.tiles[y][x] = TILE_TYPES.STONE;
808
+ }
809
+ }
810
+
811
+ // Dungeon entrance
812
+ this.tiles[29][30] = TILE_TYPES.GRASS;
813
+ this.tiles[29][31] = TILE_TYPES.GRASS;
814
+
815
+ // Boss room
816
+ for (let x = 28; x < 32; x++) {
817
+ for (let y = 35; y < 38; y++) {
818
+ this.tiles[y][x] = TILE_TYPES.BRICK;
819
+ }
820
+ }
821
+
822
+ // Lava around boss
823
+ this.tiles[34][30] = TILE_TYPES.LAVA;
824
+ this.tiles[34][31] = TILE_TYPES.LAVA;
825
+ this.tiles[34][32] = TILE_TYPES.LAVA;
826
+ this.tiles[34][33] = TILE_TYPES.LAVA;
827
+ }
828
+
829
+ getTileColor(tileType) {
830
+ switch (tileType) {
831
+ case TILE_TYPES.GRASS: return '#2ecc71';
832
+ case TILE_TYPES.WATER: return '#3498db';
833
+ case TILE_TYPES.SAND: return '#f1c40f';
834
+ case TILE_TYPES.STONE: return '#95a5a6';
835
+ case TILE_TYPES.WOOD: return '#8b4513';
836
+ case TILE_TYPES.BRICK: return '#c0392b';
837
+ case TILE_TYPES.LAVA: return '#e74c3c';
838
+ default: return '#2ecc71';
839
+ }
840
+ }
841
+
842
+ isSolid(x, y) {
843
+ // Check if coordinates are out of bounds
844
+ if (x < 0 || x >= this.width || y < 0 || y >= this.height) {
845
+ return true;
846
+ }
847
+
848
+ const tile = this.tiles[y][x];
849
+ return tile === TILE_TYPES.WATER || tile === TILE_TYPES.LAVA;
850
+ }
851
+
852
+ draw(ctx, cameraX, cameraY, viewWidth, viewHeight) {
853
+ // Calculate visible tile range
854
+ const startX = Math.max(0, Math.floor(cameraX / TILE_SIZE));
855
+ const startY = Math.max(0, Math.floor(cameraY / TILE_SIZE));
856
+ const endX = Math.min(this.width, Math.ceil((cameraX + viewWidth) / TILE_SIZE) + 1);
857
+ const endY = Math.min(this.height, Math.ceil((cameraY + viewHeight) / TILE_SIZE) + 1);
858
+
859
+ // Draw visible tiles
860
+ for (let y = startY; y < endY; y++) {
861
+ for (let x = startX; x < endX; x++) {
862
+ const tileX = x * TILE_SIZE - cameraX;
863
+ const tileY = y * TILE_SIZE - cameraY;
864
+
865
+ // Draw tile
866
+ ctx.fillStyle = this.getTileColor(this.tiles[y][x]);
867
+ ctx.fillRect(tileX, tileY, TILE_SIZE, TILE_SIZE);
868
+
869
+ // Add some variation to grass tiles
870
+ if (this.tiles[y][x] === TILE_TYPES.GRASS && Math.random() > 0.7) {
871
+ ctx.fillStyle = '#27ae60';
872
+ ctx.beginPath();
873
+ ctx.arc(
874
+ tileX + Math.random() * TILE_SIZE,
875
+ tileY + Math.random() * TILE_SIZE,
876
+ Math.random() * 2 + 1,
877
+ 0,
878
+ Math.PI * 2
879
+ );
880
+ ctx.fill();
881
+ }
882
+
883
+ // Add waves to water
884
+ if (this.tiles[y][x] === TILE_TYPES.WATER) {
885
+ ctx.fillStyle = '#2980b9';
886
+ for (let i = 0; i < 3; i++) {
887
+ ctx.beginPath();
888
+ ctx.arc(
889
+ tileX + Math.random() * TILE_SIZE,
890
+ tileY + Math.random() * TILE_SIZE,
891
+ Math.random() * 1.5,
892
+ 0,
893
+ Math.PI * 2
894
+ );
895
+ ctx.fill();
896
+ }
897
+ }
898
+
899
+ // Add wood grain
900
+ if (this.tiles[y][x] === TILE_TYPES.WOOD) {
901
+ ctx.fillStyle = '#a0522d';
902
+ for (let i = 0; i < 3; i++) {
903
+ ctx.fillRect(
904
+ tileX + 2,
905
+ tileY + 5 + i * 3,
906
+ TILE_SIZE - 4,
907
+ 1
908
+ );
909
+ }
910
+ }
911
+
912
+ // Add brick pattern
913
+ if (this.tiles[y][x] === TILE_TYPES.BRICK) {
914
+ ctx.fillStyle = '#e74c3c';
915
+ for (let row = 0; row < 2; row++) {
916
+ for (let col = 0; col < 2; col++) {
917
+ const offset = row % 2 === 0 ? 0 : 8;
918
+ ctx.fillRect(
919
+ tileX + offset + col * 8,
920
+ tileY + row * 8,
921
+ 6,
922
+ 6
923
+ );
924
+ }
925
+ }
926
+ }
927
+
928
+ // Add lava bubbles
929
+ if (this.tiles[y][x] === TILE_TYPES.LAVA) {
930
+ ctx.fillStyle = '#f39c12';
931
+ for (let i = 0; i < 2; i++) {
932
+ ctx.beginPath();
933
+ ctx.arc(
934
+ tileX + Math.random() * TILE_SIZE,
935
+ tileY + Math.random() * TILE_SIZE,
936
+ Math.random() * 3 + 1,
937
+ 0,
938
+ Math.PI * 2
939
+ );
940
+ ctx.fill();
941
+ }
942
+ }
943
+ }
944
+ }
945
+ }
946
+
947
+ drawMinimap(ctx, minimapWidth, minimapHeight) {
948
+ // Calculate scale factors
949
+ const scaleX = minimapWidth / this.width;
950
+ const scaleY = minimapHeight / this.height;
951
+
952
+ // Draw all tiles
953
+ for (let y = 0; y < this.height; y++) {
954
+ for (let x = 0; x < this.width; x++) {
955
+ ctx.fillStyle = this.getTileColor(this.tiles[y][x]);
956
+ ctx.fillRect(x * scaleX, y * scaleY, scaleX, scaleY);
957
+ }
958
+ }
959
+ }
960
+ }
961
+
962
+ // Camera
963
+ class Camera {
964
+ constructor(target, viewWidth, viewHeight) {
965
+ this.target = target;
966
+ this.x = target.x;
967
+ this.y = target.y;
968
+ this.viewWidth = viewWidth;
969
+ this.viewHeight = viewHeight;
970
+ }
971
+
972
+ update() {
973
+ // Smoothly follow the target
974
+ this.x += (this.target.x - this.x) * CAMERA_SMOOTHING;
975
+ this.y += (this.target.y - this.y) * CAMERA_SMOOTHING;
976
+
977
+ // Keep camera within bounds
978
+ const halfWidth = this.viewWidth / 2;
979
+ const halfHeight = this.viewHeight / 2;
980
+
981
+ this.x = Math.max(halfWidth, Math.min(MAP_WIDTH * TILE_SIZE - halfWidth, this.x));
982
+ this.y = Math.max(halfHeight, Math.min(MAP_HEIGHT * TILE_SIZE - halfHeight, this.y));
983
+ }
984
+ }
985
+
986
+ // Game Initialization
987
+ function initGame() {
988
+ // Create canvases
989
+ const player1Canvas = document.getElementById('player1-canvas');
990
+ const player2Canvas = document.getElementById('player2-canvas');
991
+ const minimapCanvas = document.getElementById('minimap');
992
+
993
+ // Set canvas sizes based on container
994
+ resizeCanvases();
995
+
996
+ // Create game map
997
+ const gameMap = new GameMap(MAP_WIDTH, MAP_HEIGHT);
998
+
999
+ // Create players
1000
+ const player1 = new Player(100, 100, 1);
1001
+ const player2 = new Player(150, 100, 2);
1002
+ gameState.players = [player1, player2];
1003
+
1004
+ // Create cameras
1005
+ const player1Camera = new Camera(player1, player1Canvas.width, player1Canvas.height);
1006
+ const player2Camera = new Camera(player2, player2Canvas.width, player2Canvas.height);
1007
+
1008
+ // Create items
1009
+ gameState.items = [
1010
+ new Item(200, 150, 'key'),
1011
+ new Item(250, 200, 'potion'),
1012
+ new Item(300, 250, 'key'),
1013
+ new Item(350, 300, 'potion'),
1014
+ new Item(400, 350, 'sword'),
1015
+ new Item(500, 400, 'key')
1016
+ ];
1017
+
1018
+ // Create doors
1019
+ const dungeonDoor = new Door(30 * TILE_SIZE, 29 * TILE_SIZE, TILE_SIZE, TILE_SIZE * 2, true, 1);
1020
+ const bossDoor = new Door(28 * TILE_SIZE, 34 * TILE_SIZE, TILE_SIZE * 4, TILE_SIZE, true, 2);
1021
+ gameState.doors = [dungeonDoor, bossDoor];
1022
+
1023
+ // Create levers
1024
+ const lever1 = new Lever(28 * TILE_SIZE, 32 * TILE_SIZE, bossDoor);
1025
+ gameState.levers = [lever1];
1026
+
1027
+ // Create pushable blocks
1028
+ const block1 = new PushableBlock(26 * TILE_SIZE, 32 * TILE_SIZE);
1029
+ const block2 = new PushableBlock(27 * TILE_SIZE, 32 * TILE_SIZE);
1030
+ gameState.blocks = [block1, block2];
1031
+
1032
+ // Create enemies
1033
+ const enemy1 = new Enemy(400, 300, 'normal');
1034
+ enemy1.patrolPoints = [
1035
+ { x: 400, y: 300 },
1036
+ { x: 450, y: 300 },
1037
+ { x: 450, y: 350 },
1038
+ { x: 400, y: 350 }
1039
+ ];
1040
+
1041
+ const enemy2 = new Enemy(500, 400, 'normal');
1042
+ enemy2.patrolPoints = [
1043
+ { x: 500, y: 400 },
1044
+ { x: 550, y: 400 },
1045
+ { x: 550, y: 450 },
1046
+ { x: 500, y: 450 }
1047
+ ];
1048
+
1049
+ const boss = new Enemy(30 * TILE_SIZE, 36 * TILE_SIZE, 'boss');
1050
+ gameState.enemies = [enemy1, enemy2, boss];
1051
+ gameState.boss = boss;
1052
+
1053
+ // Set up input handlers
1054
+ setupInput();
1055
+
1056
+ // Start game loop
1057
+ let lastTime = 0;
1058
+ function gameLoop(timestamp) {
1059
+ const deltaTime = timestamp - lastTime;
1060
+ lastTime = timestamp;
1061
+
1062
+ // Update game state
1063
+ updateGame(deltaTime);
1064
+
1065
+ // Render game
1066
+ renderGame();
1067
+
1068
+ // Continue loop
1069
+ requestAnimationFrame(gameLoop);
1070
+ }
1071
+
1072
+ // Start the game loop
1073
+ requestAnimationFrame(gameLoop);
1074
+
1075
+ // Handle window resize
1076
+ window.addEventListener('resize', resizeCanvases);
1077
+
1078
+ // Start button
1079
+ document.getElementById('start-button').addEventListener('click', () => {
1080
+ document.getElementById('title-screen').style.display = 'none';
1081
+ gameState.gameStarted = true;
1082
+ playSound('start');
1083
+ });
1084
+
1085
+ function resizeCanvases() {
1086
+ const container = document.getElementById('game-container');
1087
+ const isVertical = container.offsetWidth > container.offsetHeight;
1088
+
1089
+ if (isVertical) {
1090
+ // Vertical split (side by side)
1091
+ player1Canvas.width = container.offsetWidth / 2;
1092
+ player1Canvas.height = container.offsetHeight;
1093
+ player2Canvas.width = container.offsetWidth / 2;
1094
+ player2Canvas.height = container.offsetHeight;
1095
+ } else {
1096
+ // Horizontal split (top and bottom)
1097
+ player1Canvas.width = container.offsetWidth;
1098
+ player1Canvas.height = container.offsetHeight / 2;
1099
+ player2Canvas.width = container.offsetWidth;
1100
+ player2Canvas.height = container.offsetHeight / 2;
1101
+ }
1102
+
1103
+ // Update camera view sizes
1104
+ player1Camera.viewWidth = player1Canvas.width;
1105
+ player1Camera.viewHeight = player1Canvas.height;
1106
+ player2Camera.viewWidth = player2Canvas.width;
1107
+ player2Camera.viewHeight = player2Canvas.height;
1108
+ }
1109
+
1110
+ function updateGame(deltaTime) {
1111
+ if (!gameState.gameStarted) return;
1112
+
1113
+ gameState.gameTime += deltaTime;
1114
+
1115
+ // Update players
1116
+ gameState.players.forEach(player => player.update(deltaTime));
1117
+
1118
+ // Update enemies
1119
+ gameState.enemies.forEach(enemy => enemy.update(deltaTime, gameState.players));
1120
+
1121
+ // Update cameras
1122
+ player1Camera.update();
1123
+ player2Camera.update();
1124
+
1125
+ // Update blocks
1126
+ gameState.blocks.forEach(block => block.update(deltaTime));
1127
+
1128
+ // Check collisions between players and enemies
1129
+ checkPlayerEnemyCollisions();
1130
+
1131
+ // Check collisions between players and items
1132
+ checkPlayerItemCollisions();
1133
+
1134
+ // Check collisions between players and doors
1135
+ checkPlayerDoorCollisions();
1136
+
1137
+ // Check if players are attacking enemies
1138
+ checkPlayerAttacks();
1139
+
1140
+ // Remove dead enemies
1141
+ gameState.enemies = gameState.enemies.filter(enemy => enemy.health > 0);
1142
+
1143
+ // Check if boss is dead
1144
+ if (gameState.boss && gameState.boss.health <= 0) {
1145
+ // Boss is dead - open the door permanently
1146
+ gameState.doors.forEach(door => {
1147
+ door.locked = false;
1148
+ door.isOpen = true;
1149
+ });
1150
+ }
1151
+
1152
+ // Update HUD
1153
+ updateHUD();
1154
+ }
1155
+
1156
+ function renderGame() {
1157
+ if (!gameState.gameStarted) return;
1158
+
1159
+ // Get contexts
1160
+ const ctx1 = player1Canvas.getContext('2d');
1161
+ const ctx2 = player2Canvas.getContext('2d');
1162
+ const minimapCtx = minimapCanvas.getContext('2d');
1163
+
1164
+ // Clear canvases
1165
+ ctx1.clearRect(0, 0, player1Canvas.width, player1Canvas.height);
1166
+ ctx2.clearRect(0, 0, player2Canvas.width, player2Canvas.height);
1167
+ minimapCtx.clearRect(0, 0, minimapCanvas.width, minimapCanvas.height);
1168
+
1169
+ // Draw map for each player
1170
+ gameMap.draw(ctx1, player1Camera.x - player1Canvas.width / 2, player1Camera.y - player1Canvas.height / 2, player1Canvas.width, player1Canvas.height);
1171
+ gameMap.draw(ctx2, player2Camera.x - player2Canvas.width / 2, player2Camera.y - player2Canvas.height / 2, player2Canvas.width, player2Canvas.height);
1172
+
1173
+ // Draw doors
1174
+ gameState.doors.forEach(door => {
1175
+ door.draw(ctx1, player1Camera.x - player1Canvas.width / 2, player1Camera.y - player1Canvas.height / 2);
1176
+ door.draw(ctx2, player2Camera.x - player2Canvas.width / 2, player2Camera.y - player2Canvas.height / 2);
1177
+ });
1178
+
1179
+ // Draw levers
1180
+ gameState.levers.forEach(lever => {
1181
+ lever.draw(ctx1, player1Camera.x - player1Canvas.width / 2, player1Camera.y - player1Canvas.height / 2);
1182
+ lever.draw(ctx2, player2Camera.x - player2Canvas.width / 2, player2Camera.y - player2Canvas.height / 2);
1183
+ });
1184
+
1185
+ // Draw blocks
1186
+ gameState.blocks.forEach(block => {
1187
+ block.draw(ctx1, player1Camera.x - player1Canvas.width / 2, player1Camera.y - player1Canvas.height / 2);
1188
+ block.draw(ctx2, player2Camera.x - player2Canvas.width / 2, player2Camera.y - player2Canvas.height / 2);
1189
+ });
1190
+
1191
+ // Draw items
1192
+ gameState.items.forEach(item => {
1193
+ item.draw(ctx1, player1Camera.x - player1Canvas.width / 2, player1Camera.y - player1Canvas.height / 2);
1194
+ item.draw(ctx2, player2Camera.x - player2Canvas.width / 2, player2Camera.y - player2Canvas.height / 2);
1195
+ });
1196
+
1197
+ // Draw enemies
1198
+ gameState.enemies.forEach(enemy => {
1199
+ enemy.draw(ctx1, player1Camera.x - player1Canvas.width / 2, player1Camera.y - player1Canvas.height / 2);
1200
+ enemy.draw(ctx2, player2Camera.x - player2Canvas.width / 2, player2Camera.y - player2Canvas.height / 2);
1201
+ });
1202
+
1203
+ // Draw players
1204
+ player1.draw(ctx1, player1Camera.x - player1Canvas.width / 2, player1Camera.y - player1Canvas.height / 2);
1205
+ player2.draw(ctx2, player2Camera.x - player2Canvas.width / 2, player2Camera.y - player2Canvas.height / 2);
1206
+
1207
+ // Draw minimap
1208
+ drawMinimap(minimapCtx);
1209
+ }
1210
+
1211
+ function drawMinimap(ctx) {
1212
+ // Draw the map
1213
+ gameMap.drawMinimap(ctx, minimapCanvas.width, minimapCanvas.height);
1214
+
1215
+ // Draw players
1216
+ ctx.fillStyle = '#3498db';
1217
+ ctx.beginPath();
1218
+ ctx.arc(
1219
+ gameState.players[0].x / MAP_WIDTH * minimapCanvas.width,
1220
+ gameState.players[0].y / MAP_HEIGHT * minimapCanvas.height,
1221
+ 3,
1222
+ 0,
1223
+ Math.PI * 2
1224
+ );
1225
+ ctx.fill();
1226
+
1227
+ ctx.fillStyle = '#e74c3c';
1228
+ ctx.beginPath();
1229
+ ctx.arc(
1230
+ gameState.players[1].x / MAP_WIDTH * minimapCanvas.width,
1231
+ gameState.players[1].y / MAP_HEIGHT * minimapCanvas.height,
1232
+ 3,
1233
+ 0,
1234
+ Math.PI * 2
1235
+ );
1236
+ ctx.fill();
1237
+
1238
+ // Draw dungeon
1239
+ ctx.fillStyle = '#ffffff';
1240
+ ctx.fillRect(
1241
+ 25 / MAP_WIDTH * minimapCanvas.width,
1242
+ 30 / MAP_HEIGHT * minimapCanvas.height,
1243
+ 10 / MAP_WIDTH * minimapCanvas.width,
1244
+ 8 / MAP_HEIGHT * minimapCanvas.height
1245
+ );
1246
+ }
1247
+
1248
+ function updateHUD() {
1249
+ // Update player 1 HUD
1250
+ document.getElementById('player1-health').style.width = `${(gameState.players[0].health / gameState.players[0].maxHealth) * 100}%`;
1251
+ const inventory1 = document.getElementById('player1-inventory');
1252
+
1253
+ // Clear inventory slots
1254
+ for (let i = 0; i < 3; i++) {
1255
+ const slot = inventory1.children[i];
1256
+ slot.innerHTML = '';
1257
+
1258
+ if (i < gameState.players[0].inventory.length) {
1259
+ const item = gameState.players[0].inventory[i];
1260
+ if (item.type === 'key') {
1261
+ slot.innerHTML = '🔑';
1262
+ } else if (item.type === 'potion') {
1263
+ slot.innerHTML = '❤️';
1264
+ } else if (item.type === 'sword') {
1265
+ slot.innerHTML = '⚔️';
1266
+ }
1267
+ }
1268
+ }
1269
+
1270
+ // Update player 2 HUD
1271
+ document.getElementById('player2-health').style.width = `${(gameState.players[1].health / gameState.players[1].maxHealth) * 100}%`;
1272
+ const inventory2 = document.getElementById('player2-inventory');
1273
+
1274
+ // Clear inventory slots
1275
+ for (let i = 0; i < 3; i++) {
1276
+ const slot = inventory2.children[i];
1277
+ slot.innerHTML = '';
1278
+
1279
+ if (i < gameState.players[1].inventory.length) {
1280
+ const item = gameState.players[1].inventory[i];
1281
+ if (item.type === 'key') {
1282
+ slot.innerHTML = '🔑';
1283
+ } else if (item.type === 'potion') {
1284
+ slot.innerHTML = '❤️';
1285
+ } else if (item.type === 'sword') {
1286
+ slot.innerHTML = '⚔️';
1287
+ }
1288
+ }
1289
+ }
1290
+ }
1291
+
1292
+ function checkPlayerEnemyCollisions() {
1293
+ for (const player of gameState.players) {
1294
+ for (const enemy of gameState.enemies) {
1295
+ if (player.collidesWith(enemy) && enemy.attackCooldown <= 0) {
1296
+ player.takeDamage(enemy.damage);
1297
+ enemy.attackCooldown = 1000; // 1 second cooldown
1298
+ }
1299
+ }
1300
+ }
1301
+ }
1302
+
1303
+ function checkPlayerItemCollisions() {
1304
+ for (let i = gameState.items.length - 1; i >= 0; i--) {
1305
+ const item = gameState.items[i];
1306
+
1307
+ for (const player of gameState.players) {
1308
+ if (player.collidesWith(item)) {
1309
+ if (player.addToInventory(item)) {
1310
+ gameState.items.splice(i, 1);
1311
+ playSound('item');
1312
+ break;
1313
+ }
1314
+ }
1315
+ }
1316
+ }
1317
+ }
1318
+
1319
+ function checkPlayerDoorCollisions() {
1320
+ for (const door of gameState.doors) {
1321
+ for (const player of gameState.players) {
1322
+ if (player.collidesWith(door) && door.locked) {
1323
+ // Player is trying to open a locked door
1324
+ if (player.keys > 0) {
1325
+ door.unlock(player);
1326
+ player.keys--;
1327
+ }
1328
+ }
1329
+ }
1330
+ }
1331
+ }
1332
+
1333
+ function checkPlayerAttacks() {
1334
+ for (const player of gameState.players) {
1335
+ if (player.isAttacking) {
1336
+ for (const enemy of gameState.enemies) {
1337
+ // Calculate attack position
1338
+ const attackX = player.centerX + player.facing.x * ATTACK_RANGE;
1339
+ const attackY = player.centerY + player.facing.y * ATTACK_RANGE;
1340
+
1341
+ // Check if enemy is within attack range
1342
+ const dx = attackX - enemy.centerX;
1343
+ const dy = attackY - enemy.centerY;
1344
+ const distance = Math.sqrt(dx * dx + dy * dy);
1345
+
1346
+ if (distance < 20) {
1347
+ if (enemy.takeDamage(25)) {
1348
+ playSound('enemy-death');
1349
+ } else {
1350
+ playSound('hit');
1351
+ }
1352
+ }
1353
+ }
1354
+ }
1355
+ }
1356
+ }
1357
+ }
1358
+
1359
+ // Input Handling
1360
+ function setupInput() {
1361
+ const keys = {
1362
+ // Player 1
1363
+ w: false,
1364
+ a: false,
1365
+ s: false,
1366
+ d: false,
1367
+ f: false,
1368
+ g: false,
1369
+
1370
+ // Player 2
1371
+ ArrowUp: false,
1372
+ ArrowLeft: false,
1373
+ ArrowDown: false,
1374
+ ArrowRight: false,
1375
+ l: false,
1376
+ k: false
1377
+ };
1378
+
1379
+ // Keyboard event listeners
1380
+ window.addEventListener('keydown', (e) => {
1381
+ if (e.key in keys) {
1382
+ keys[e.key] = true;
1383
+ e.preventDefault();
1384
+ }
1385
+
1386
+ // Player 1 item use
1387
+ if (e.key === '1' && gameState.players[0]) {
1388
+ gameState.players[0].useItem(0);
1389
+ } else if (e.key === '2' && gameState.players[0]) {
1390
+ gameState.players[0].useItem(1);
1391
+ } else if (e.key === '3' && gameState.players[0]) {
1392
+ gameState.players[0].useItem(2);
1393
+ }
1394
+
1395
+ // Player 2 item use
1396
+ if (e.key === '7' && gameState.players[1]) {
1397
+ gameState.players[1].useItem(0);
1398
+ } else if (e.key === '8' && gameState.players[1]) {
1399
+ gameState.players[1].useItem(1);
1400
+ } else if (e.key === '9' && gameState.players[1]) {
1401
+ gameState.players[1].useItem(2);
1402
+ }
1403
+ });
1404
+
1405
+ window.addEventListener('keyup', (e) => {
1406
+ if (e.key in keys) {
1407
+ keys[e.key] = false;
1408
+ e.preventDefault();
1409
+ }
1410
+ });
1411
+
1412
+ // Gamepad support
1413
+ let gamepads = [];
1414
+
1415
+ window.addEventListener("gamepadconnected", (e) => {
1416
+ console.log("Gamepad connected:", e.gamepad);
1417
+ gamepads[e.gamepad.index] = e.gamepad;
1418
+ });
1419
+
1420
+ window.addEventListener("gamepaddisconnected", (e) => {
1421
+ console.log("Gamepad disconnected:", e.gamepad);
1422
+ delete gamepads[e.gamepad.index];
1423
+ });
1424
+
1425
+ // Input update loop
1426
+ function updateInput() {
1427
+ if (!gameState.gameStarted) return;
1428
+
1429
+ // Player 1 controls (WASD, F, G)
1430
+ const player1 = gameState.players[0];
1431
+ if (player1) {
1432
+ player1.vx = 0;
1433
+ player1.vy = 0;
1434
+
1435
+ if (keys.w) player1.vy -= PLAYER_SPEED;
1436
+ if (keys.s) player1.vy += PLAYER_SPEED;
1437
+ if (keys.a) player1.vx -= PLAYER_SPEED;
1438
+ if (keys.d) player1.vx += PLAYER_SPEED;
1439
+
1440
+ // Normalize diagonal movement
1441
+ if (player1.vx !== 0 && player1.vy !== 0) {
1442
+ player1.vx *= 0.7071; // 1/sqrt(2)
1443
+ player1.vy *= 0.7071;
1444
+ }
1445
+
1446
+ // Attack
1447
+ if (keys.f) {
1448
+ if (player1.attack()) {
1449
+ playSound('sword');
1450
+ }
1451
+ }
1452
+
1453
+ // Interact (use levers, push blocks)
1454
+ if (keys.g) {
1455
+ checkPlayerInteractions(player1);
1456
+ }
1457
+ }
1458
+
1459
+ // Player 2 controls (Arrows, L, K)
1460
+ const player2 = gameState.players[1];
1461
+ if (player2) {
1462
+ player2.vx = 0;
1463
+ player2.vy = 0;
1464
+
1465
+ if (keys.ArrowUp) player2.vy -= PLAYER_SPEED;
1466
+ if (keys.ArrowDown) player2.vy += PLAYER_SPEED;
1467
+ if (keys.ArrowLeft) player2.vx -= PLAYER_SPEED;
1468
+ if (keys.ArrowRight) player2.vx += PLAYER_SPEED;
1469
+
1470
+ // Normalize diagonal movement
1471
+ if (player2.vx !== 0 && player2.vy !== 0) {
1472
+ player2.vx *= 0.7071; // 1/sqrt(2)
1473
+ player2.vy *= 0.7071;
1474
+ }
1475
+
1476
+ // Attack
1477
+ if (keys.l) {
1478
+ if (player2.attack()) {
1479
+ playSound('sword');
1480
+ }
1481
+ }
1482
+
1483
+ // Interact (use levers, push blocks)
1484
+ if (keys.k) {
1485
+ checkPlayerInteractions(player2);
1486
+ }
1487
+ }
1488
+
1489
+ // Gamepad controls
1490
+ <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=Deigomax02/zfgjf" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
1491
+ </html>