AntDX316
commited on
Commit
·
3917e9e
1
Parent(s):
105b77b
updated
Browse files- index.html +27 -0
- script.js +412 -0
- 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 |
+
}
|