AntDX316 commited on
Commit
3917e9e
·
1 Parent(s): 105b77b
Files changed (3) hide show
  1. index.html +27 -0
  2. script.js +412 -0
  3. styles.css +95 -0
index.html ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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>Battle Simulator</title>
7
+ <link rel="stylesheet" href="styles.css">
8
+ </head>
9
+ <body>
10
+ <div class="container">
11
+ <h1>Battle Simulator</h1>
12
+ <div class="battlefield">
13
+ <canvas id="map" width="800" height="600"></canvas>
14
+ <div id="info-panel" class="hidden">
15
+ <h3 id="unit-name">Unit Name</h3>
16
+ <p id="unit-type">Type: <span></span></p>
17
+ <p id="unit-health">Health: <span></span></p>
18
+ <p id="unit-attack">Attack: <span></span></p>
19
+ <p id="unit-defense">Defense: <span></span></p>
20
+ <p id="unit-status">Status: <span></span></p>
21
+ <button id="close-info">Close</button>
22
+ </div>
23
+ </div>
24
+ </div>
25
+ <script src="script.js"></script>
26
+ </body>
27
+ </html>
script.js ADDED
@@ -0,0 +1,412 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Initialize elements when the page loads
2
+ let canvas, ctx;
3
+ window.addEventListener('DOMContentLoaded', () => {
4
+ canvas = document.getElementById('map');
5
+ ctx = canvas.getContext('2d');
6
+ initializeGame();
7
+ });
8
+
9
+ // Constants for unit types and teams
10
+ const UNIT_TYPES = {
11
+ INFANTRY: {
12
+ name: 'Infantry',
13
+ baseHealth: 100,
14
+ baseAttack: 10,
15
+ baseDefense: 5,
16
+ speed: 1,
17
+ attackRange: 50,
18
+ radius: 12,
19
+ color: '#3498db'
20
+ },
21
+ TANK: {
22
+ name: 'Tank',
23
+ baseHealth: 200,
24
+ baseAttack: 20,
25
+ baseDefense: 15,
26
+ speed: 0.5,
27
+ attackRange: 100,
28
+ radius: 18,
29
+ color: '#e74c3c'
30
+ },
31
+ ARTILLERY: {
32
+ name: 'Artillery',
33
+ baseHealth: 80,
34
+ baseAttack: 30,
35
+ baseDefense: 3,
36
+ speed: 0.3,
37
+ attackRange: 150,
38
+ radius: 15,
39
+ color: '#f39c12'
40
+ }
41
+ };
42
+
43
+ const TEAMS = {
44
+ RED: {
45
+ name: 'Red Forces',
46
+ primaryColor: '#e74c3c',
47
+ secondaryColor: '#c0392b'
48
+ },
49
+ BLUE: {
50
+ name: 'Blue Forces',
51
+ primaryColor: '#3498db',
52
+ secondaryColor: '#2980b9'
53
+ }
54
+ };
55
+
56
+ // Class to represent a military unit
57
+ class Unit {
58
+ constructor(id, type, team, x, y) {
59
+ this.id = id;
60
+ this.type = type;
61
+ this.team = team;
62
+ this.x = x;
63
+ this.y = y;
64
+ this.targetX = x;
65
+ this.targetY = y;
66
+ this.health = type.baseHealth;
67
+ this.maxHealth = type.baseHealth;
68
+ this.attack = type.baseAttack;
69
+ this.defense = type.baseDefense;
70
+ this.radius = type.radius;
71
+ this.speed = type.speed;
72
+ this.attackRange = type.attackRange;
73
+ this.attackCooldown = 0;
74
+ this.target = null;
75
+ this.selected = false;
76
+ this.status = 'Idle';
77
+ this.deadTimer = 0;
78
+ this.isAlive = true;
79
+ }
80
+
81
+ // Update the unit's position and state
82
+ update(units, deltaTime) {
83
+ if (!this.isAlive) {
84
+ this.deadTimer += deltaTime;
85
+ if (this.deadTimer > 3000) {
86
+ this.respawn();
87
+ }
88
+ return;
89
+ }
90
+
91
+ if (!this.target || !this.target.isAlive) {
92
+ this.findNewTarget(units);
93
+ }
94
+
95
+ if (this.attackCooldown > 0) {
96
+ this.attackCooldown -= deltaTime;
97
+ }
98
+
99
+ if (this.target) {
100
+ // Calculate distance to target
101
+ const dx = this.target.x - this.x;
102
+ const dy = this.target.y - this.y;
103
+ const distance = Math.sqrt(dx * dx + dy * dy);
104
+
105
+ if (distance <= this.attackRange) {
106
+ // Attack if in range
107
+ this.status = 'Attacking';
108
+ if (this.attackCooldown <= 0) {
109
+ this.attackTarget();
110
+ }
111
+ } else {
112
+ // Move towards target
113
+ this.status = 'Moving';
114
+ const angle = Math.atan2(dy, dx);
115
+ const moveDistance = this.speed * deltaTime / 16;
116
+
117
+ this.x += Math.cos(angle) * moveDistance;
118
+ this.y += Math.sin(angle) * moveDistance;
119
+ }
120
+ } else {
121
+ this.status = 'Idle';
122
+ // Random wandering
123
+ if (Math.random() < 0.01) {
124
+ this.targetX = Math.random() * canvas.width;
125
+ this.targetY = Math.random() * canvas.height;
126
+ }
127
+
128
+ // Move towards random target
129
+ const dx = this.targetX - this.x;
130
+ const dy = this.targetY - this.y;
131
+ const distance = Math.sqrt(dx * dx + dy * dy);
132
+
133
+ if (distance > 5) {
134
+ const angle = Math.atan2(dy, dx);
135
+ const moveDistance = this.speed * deltaTime / 32;
136
+
137
+ this.x += Math.cos(angle) * moveDistance;
138
+ this.y += Math.sin(angle) * moveDistance;
139
+ }
140
+ }
141
+ }
142
+
143
+ // Find a new target from the opposing team
144
+ findNewTarget(units) {
145
+ let closestDistance = Infinity;
146
+ let closestTarget = null;
147
+
148
+ for (const unit of units) {
149
+ if (unit.team !== this.team && unit.isAlive) {
150
+ const dx = unit.x - this.x;
151
+ const dy = unit.y - this.y;
152
+ const distance = Math.sqrt(dx * dx + dy * dy);
153
+
154
+ if (distance < closestDistance) {
155
+ closestDistance = distance;
156
+ closestTarget = unit;
157
+ }
158
+ }
159
+ }
160
+
161
+ this.target = closestTarget;
162
+ }
163
+
164
+ // Attack the current target
165
+ attackTarget() {
166
+ if (!this.target || !this.target.isAlive) return;
167
+
168
+ const damage = Math.max(1, this.attack - this.target.defense / 2);
169
+ this.target.takeDamage(damage);
170
+ this.attackCooldown = 1000; // 1 second cooldown
171
+
172
+ // Visual feedback (particle effect could be added here)
173
+ }
174
+
175
+ // Take damage from an attack
176
+ takeDamage(amount) {
177
+ this.health -= amount;
178
+ if (this.health <= 0) {
179
+ this.die();
180
+ }
181
+ }
182
+
183
+ // Die and set a timer for respawn
184
+ die() {
185
+ this.health = 0;
186
+ this.isAlive = false;
187
+ this.deadTimer = 0;
188
+ this.status = 'Dead';
189
+ }
190
+
191
+ // Respawn the unit at a random position on its team's side
192
+ respawn() {
193
+ this.health = this.maxHealth;
194
+ this.isAlive = true;
195
+ this.status = 'Idle';
196
+
197
+ // Respawn on team's side
198
+ if (this.team === TEAMS.RED) {
199
+ this.x = Math.random() * (canvas.width / 3);
200
+ } else {
201
+ this.x = canvas.width - Math.random() * (canvas.width / 3);
202
+ }
203
+ this.y = Math.random() * canvas.height;
204
+
205
+ this.targetX = this.x;
206
+ this.targetY = this.y;
207
+ }
208
+
209
+ // Draw the unit on the canvas
210
+ draw(ctx) {
211
+ const alpha = this.isAlive ? 1 : 0.3;
212
+
213
+ // Draw health bar
214
+ if (this.isAlive) {
215
+ const healthPercent = this.health / this.maxHealth;
216
+ ctx.fillStyle = `rgba(0, 0, 0, ${alpha * 0.2})`;
217
+ ctx.fillRect(this.x - this.radius, this.y - this.radius - 10, this.radius * 2, 5);
218
+
219
+ ctx.fillStyle = healthPercent > 0.5 ? `rgba(46, 204, 113, ${alpha})` :
220
+ healthPercent > 0.2 ? `rgba(241, 196, 15, ${alpha})` :
221
+ `rgba(231, 76, 60, ${alpha})`;
222
+ ctx.fillRect(this.x - this.radius, this.y - this.radius - 10, this.radius * 2 * healthPercent, 5);
223
+ }
224
+
225
+ // Draw unit body
226
+ ctx.beginPath();
227
+ ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
228
+ ctx.fillStyle = `rgba(${this.team === TEAMS.RED ? '231, 76, 60' : '52, 152, 219'}, ${alpha})`;
229
+ ctx.fill();
230
+
231
+ // Draw outline if selected
232
+ if (this.selected) {
233
+ ctx.strokeStyle = '#2ecc71';
234
+ ctx.lineWidth = 2;
235
+ ctx.stroke();
236
+ }
237
+
238
+ // Draw unit type indicator
239
+ ctx.beginPath();
240
+ if (this.type === UNIT_TYPES.INFANTRY) {
241
+ ctx.moveTo(this.x - 5, this.y - 5);
242
+ ctx.lineTo(this.x + 5, this.y - 5);
243
+ ctx.lineTo(this.x, this.y + 5);
244
+ ctx.closePath();
245
+ } else if (this.type === UNIT_TYPES.TANK) {
246
+ ctx.rect(this.x - 6, this.y - 6, 12, 12);
247
+ } else if (this.type === UNIT_TYPES.ARTILLERY) {
248
+ ctx.arc(this.x, this.y, 5, 0, Math.PI * 2);
249
+ }
250
+ ctx.fillStyle = `rgba(255, 255, 255, ${alpha * 0.8})`;
251
+ ctx.fill();
252
+
253
+ // Draw attack line if attacking
254
+ if (this.target && this.isAlive && this.target.isAlive && this.status === 'Attacking') {
255
+ ctx.beginPath();
256
+ ctx.moveTo(this.x, this.y);
257
+ ctx.lineTo(this.target.x, this.target.y);
258
+ ctx.strokeStyle = `rgba(255, 0, 0, ${alpha * 0.5})`;
259
+ ctx.lineWidth = 1;
260
+ ctx.stroke();
261
+ }
262
+ }
263
+
264
+ // Check if a point is inside the unit (for click detection)
265
+ containsPoint(x, y) {
266
+ const dx = this.x - x;
267
+ const dy = this.y - y;
268
+ return dx * dx + dy * dy <= this.radius * this.radius;
269
+ }
270
+ }
271
+
272
+ // Create units for both teams
273
+ let units = [];
274
+ let selectedUnit = null;
275
+ let lastTime = 0;
276
+
277
+ function initializeUnits() {
278
+ units = [];
279
+
280
+ // Create Red team units
281
+ createUnits(10, UNIT_TYPES.INFANTRY, TEAMS.RED, 0, canvas.width / 3, 0, canvas.height);
282
+ createUnits(5, UNIT_TYPES.TANK, TEAMS.RED, 0, canvas.width / 3, 0, canvas.height);
283
+ createUnits(3, UNIT_TYPES.ARTILLERY, TEAMS.RED, 0, canvas.width / 3, 0, canvas.height);
284
+
285
+ // Create Blue team units
286
+ createUnits(10, UNIT_TYPES.INFANTRY, TEAMS.BLUE, canvas.width * 2/3, canvas.width, 0, canvas.height);
287
+ createUnits(5, UNIT_TYPES.TANK, TEAMS.BLUE, canvas.width * 2/3, canvas.width, 0, canvas.height);
288
+ createUnits(3, UNIT_TYPES.ARTILLERY, TEAMS.BLUE, canvas.width * 2/3, canvas.width, 0, canvas.height);
289
+ }
290
+
291
+ function createUnits(count, type, team, minX, maxX, minY, maxY) {
292
+ for (let i = 0; i < count; i++) {
293
+ const x = minX + Math.random() * (maxX - minX);
294
+ const y = minY + Math.random() * (maxY - minY);
295
+ const unit = new Unit(units.length, type, team, x, y);
296
+ units.push(unit);
297
+ }
298
+ }
299
+
300
+ // Main game loop
301
+ function gameLoop(currentTime) {
302
+ if (!lastTime) lastTime = currentTime;
303
+ const deltaTime = currentTime - lastTime;
304
+ lastTime = currentTime;
305
+
306
+ // Clear the canvas
307
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
308
+
309
+ // Update and draw all units
310
+ for (const unit of units) {
311
+ unit.update(units, deltaTime);
312
+ }
313
+
314
+ // Draw in two passes to ensure selected units appear on top
315
+ // First draw non-selected units
316
+ for (const unit of units) {
317
+ if (!unit.selected) {
318
+ unit.draw(ctx);
319
+ }
320
+ }
321
+
322
+ // Then draw selected units
323
+ for (const unit of units) {
324
+ if (unit.selected) {
325
+ unit.draw(ctx);
326
+ }
327
+ }
328
+
329
+ // Continue the game loop
330
+ requestAnimationFrame(gameLoop);
331
+ }
332
+
333
+ // Handle clicks on units
334
+ function handleCanvasClick(event) {
335
+ const rect = canvas.getBoundingClientRect();
336
+ const x = event.clientX - rect.left;
337
+ const y = event.clientY - rect.top;
338
+
339
+ console.log(`Click at coordinates: (${x}, ${y})`);
340
+
341
+ // Deselect any previously selected unit
342
+ if (selectedUnit) {
343
+ selectedUnit.selected = false;
344
+ selectedUnit = null;
345
+
346
+ // Make sure the info panel is hidden when deselecting
347
+ document.getElementById('info-panel').classList.add('hidden');
348
+ }
349
+
350
+ // Check if a unit was clicked
351
+ let unitClicked = false;
352
+
353
+ for (let i = units.length - 1; i >= 0; i--) {
354
+ const unit = units[i];
355
+ if (unit.containsPoint(x, y)) {
356
+ console.log(`Unit clicked: ${unit.team.name} ${unit.type.name} (${unit.id})`);
357
+ selectedUnit = unit;
358
+ unit.selected = true;
359
+ unitClicked = true;
360
+
361
+ // Update info panel with unit details
362
+ const nameElement = document.getElementById('unit-name');
363
+ const typeElement = document.getElementById('unit-type').querySelector('span');
364
+ const healthElement = document.getElementById('unit-health').querySelector('span');
365
+ const attackElement = document.getElementById('unit-attack').querySelector('span');
366
+ const defenseElement = document.getElementById('unit-defense').querySelector('span');
367
+ const statusElement = document.getElementById('unit-status').querySelector('span');
368
+
369
+ nameElement.textContent = `${unit.team.name} ${unit.type.name}`;
370
+ typeElement.textContent = unit.type.name;
371
+ healthElement.textContent = `${Math.max(0, Math.floor(unit.health))} / ${unit.maxHealth}`;
372
+ attackElement.textContent = unit.attack;
373
+ defenseElement.textContent = unit.defense;
374
+ statusElement.textContent = unit.status;
375
+
376
+ // Set team color for the unit name
377
+ nameElement.style.color = unit.team.primaryColor;
378
+
379
+ // Show the info panel
380
+ const infoPanel = document.getElementById('info-panel');
381
+ infoPanel.classList.remove('hidden');
382
+
383
+ break;
384
+ }
385
+ }
386
+
387
+ if (!unitClicked) {
388
+ console.log("No unit clicked");
389
+ }
390
+ }
391
+
392
+ // Initialize the game
393
+ function initializeGame() {
394
+ // Initialize game components
395
+ initializeUnits();
396
+
397
+ // Add event listeners
398
+ canvas.addEventListener('click', handleCanvasClick);
399
+
400
+ document.getElementById('close-info').addEventListener('click', () => {
401
+ document.getElementById('info-panel').classList.add('hidden');
402
+ if (selectedUnit) {
403
+ selectedUnit.selected = false;
404
+ selectedUnit = null;
405
+ }
406
+ });
407
+
408
+ // Start the game loop
409
+ requestAnimationFrame(gameLoop);
410
+
411
+ console.log("Battle simulator initialized successfully");
412
+ }
styles.css ADDED
@@ -0,0 +1,95 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ * {
2
+ margin: 0;
3
+ padding: 0;
4
+ box-sizing: border-box;
5
+ }
6
+
7
+ body {
8
+ font-family: Arial, sans-serif;
9
+ background-color: #f0f0f0;
10
+ padding: 20px;
11
+ }
12
+
13
+ .container {
14
+ max-width: 1000px;
15
+ margin: 0 auto;
16
+ background-color: white;
17
+ padding: 20px;
18
+ border-radius: 10px;
19
+ box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
20
+ }
21
+
22
+ h1 {
23
+ text-align: center;
24
+ margin-bottom: 20px;
25
+ color: #333;
26
+ }
27
+
28
+ .battlefield {
29
+ position: relative;
30
+ }
31
+
32
+ canvas {
33
+ display: block;
34
+ margin: 0 auto;
35
+ border: 1px solid #ccc;
36
+ background-color: #e8f4e5;
37
+ }
38
+
39
+ #info-panel {
40
+ position: absolute;
41
+ top: 20px;
42
+ right: 20px;
43
+ background-color: rgba(255, 255, 255, 0.95);
44
+ padding: 20px;
45
+ border-radius: 8px;
46
+ border: 2px solid #4a6ea9;
47
+ box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3);
48
+ width: 300px;
49
+ z-index: 10;
50
+ transition: all 0.3s ease;
51
+ animation: fadeIn 0.3s ease-out;
52
+ }
53
+
54
+ @keyframes fadeIn {
55
+ from {
56
+ opacity: 0;
57
+ transform: translateY(-10px);
58
+ }
59
+ to {
60
+ opacity: 1;
61
+ transform: translateY(0);
62
+ }
63
+ }
64
+
65
+ #info-panel h3 {
66
+ margin-bottom: 15px;
67
+ color: #333;
68
+ border-bottom: 1px solid #ddd;
69
+ padding-bottom: 10px;
70
+ }
71
+
72
+ #info-panel p {
73
+ margin-bottom: 10px;
74
+ display: flex;
75
+ justify-content: space-between;
76
+ }
77
+
78
+ #close-info {
79
+ display: block;
80
+ margin: 20px auto 0;
81
+ padding: 8px 16px;
82
+ background-color: #4a6ea9;
83
+ color: white;
84
+ border: none;
85
+ border-radius: 4px;
86
+ cursor: pointer;
87
+ }
88
+
89
+ #close-info:hover {
90
+ background-color: #3a5a89;
91
+ }
92
+
93
+ .hidden {
94
+ display: none;
95
+ }