kolaslab commited on
Commit
7243134
·
verified ·
1 Parent(s): ae69bc4

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +289 -387
index.html CHANGED
@@ -1,415 +1,317 @@
1
  <!DOCTYPE html>
2
  <html lang="ko">
3
  <head>
4
- <meta charset="UTF-8">
5
- <title>라인배틀: AI 싱글플레이 예시</title>
6
- <style>
7
- /* 간단한 스타일 */
8
- body {
9
- margin: 20px;
10
- font-family: sans-serif;
11
- }
12
- h1 {
13
- margin-bottom: 10px;
14
- }
15
- #infoBar {
16
- margin-bottom: 10px;
17
- }
18
- #gameBoard {
19
- display: grid;
20
- grid-template-columns: repeat(10, 40px);
21
- grid-template-rows: repeat(6, 40px);
22
- gap: 2px;
23
- margin-bottom: 10px;
24
- }
25
- .cell {
26
- width: 40px;
27
- height: 40px;
28
- border: 1px solid #666;
29
- display: flex;
30
- align-items: center;
31
- justify-content: center;
32
- cursor: pointer;
33
- background-color: #ccc;
34
- }
35
- .red {
36
- background-color: #ffaaaa;
37
- }
38
- .blue {
39
- background-color: #aaaaff;
40
- }
41
- #endTurnBtn {
42
- padding: 6px 12px;
43
- font-size: 14px;
44
- cursor: pointer;
45
- margin-right: 10px;
46
- }
47
- </style>
48
  </head>
49
  <body>
50
- <h1>라인배틀: AI 싱글플레이 예시</h1>
51
- <div id="infoBar">
52
- <span id="turnInfo"></span> |
53
- <span id="selectedInfo"></span>
54
- </div>
55
- <div id="gameBoard"></div>
56
- <button id="endTurnBtn">턴 종료</button>
57
- <div id="message"></div>
58
-
59
- <script>
60
- /********************************************************
61
- * 1. 유닛/게임 파라미터 정의
62
- ********************************************************/
63
- const UNIT_TYPES = {
64
- INFANTRY: {
65
- name: "Infantry",
66
- hp: 25,
67
- attack: 3,
68
- defense: 3,
69
- move: 2,
70
- range: 1,
71
- advantage: "ARCHER", // 보병 > 궁수
72
- },
73
- ARCHER: {
74
- name: "Archer",
75
- hp: 20,
76
- attack: 2,
77
- defense: 2,
78
- move: 2,
79
- range: 3,
80
- advantage: "CAVALRY", // 궁수 > 기병
81
- },
82
- CAVALRY: {
83
- name: "Cavalry",
84
- hp: 22,
85
- attack: 4,
86
- defense: 2,
87
- move: 3,
88
- range: 1,
89
- advantage: "INFANTRY", // 기병 > 보병
90
- }
91
- };
92
-
93
- // 간단한 상성 보정(예: 1.5배)
94
- const ADVANTAGE_MULTIPLIER = 1.5;
95
-
96
- // 크기
97
- const WIDTH = 10;
98
- const HEIGHT = 6;
99
-
100
- // 팀 정의
101
- const RED = "RED";
102
- const BLUE = "BLUE";
103
-
104
- // 게임 상태
105
- let units = []; // 전체 유닛 객체를 보관
106
- let currentTeam = RED; // 현재 턴의 팀
107
- let selectedUnit = null; // 유저가 선택한 유닛(RED 턴 중)
108
-
109
- /********************************************************
110
- * 2. 유닛 클래스
111
- ********************************************************/
112
- class Unit {
113
- constructor(typeKey, team, x, y) {
114
- const data = UNIT_TYPES[typeKey];
115
- this.type = typeKey; // "INFANTRY", "ARCHER", "CAVALRY"
116
- this.team = team; // "RED" 또는 "BLUE"
117
- this.hp = data.hp;
118
- this.attack = data.attack;
119
- this.defense = data.defense;
120
- this.move = data.move;
121
- this.range = data.range;
122
- this.advantage = data.advantage;
123
- this.x = x;
124
- this.y = y;
125
- this.hasActed = false; // 이 턴 중 이동/공격 여부를 단순 체크(확장 가능)
126
- }
127
- get isAlive() {
128
- return this.hp > 0;
129
- }
130
- get displayName() {
131
- // 예: 'I25' (Infantry, HP 25)
132
- return this.type[0] + this.hp;
133
- }
134
- }
135
-
136
- /********************************************************
137
- * 3. 초기화: 유닛 배치
138
- ********************************************************/
139
- function initUnits() {
140
- units = [];
141
- // RED - 보병(6), 궁수(3), 기병(3) / 예시로 좌측에 배치
142
- for (let i = 0; i < 6; i++) {
143
- units.push(new Unit("INFANTRY", RED, 0, i % HEIGHT));
144
- }
145
- for (let i = 0; i < 3; i++) {
146
- units.push(new Unit("ARCHER", RED, 1, i));
147
- }
148
- for (let i = 0; i < 3; i++) {
149
- units.push(new Unit("CAVALRY", RED, 2, i + 3));
150
- }
151
- // BLUE - 보병(6), 궁수(3), 기병(3) / 예시로 우측에 배치
152
- for (let i = 0; i < 6; i++) {
153
- units.push(new Unit("INFANTRY", BLUE, WIDTH - 1, i % HEIGHT));
154
- }
155
- for (let i = 0; i < 3; i++) {
156
- units.push(new Unit("ARCHER", BLUE, WIDTH - 2, i));
157
- }
158
- for (let i = 0; i < 3; i++) {
159
- units.push(new Unit("CAVALRY", BLUE, WIDTH - 3, i + 3));
160
- }
161
- }
162
-
163
- /********************************************************
164
- * 4. 화면 렌더링
165
- ********************************************************/
166
- function renderBoard() {
167
- const board = document.getElementById("gameBoard");
168
- board.innerHTML = "";
169
 
170
- // 맵을 HEIGHT x WIDTH 순회하며 셀 생성
171
- for (let y = 0; y < HEIGHT; y++) {
172
- for (let x = 0; x < WIDTH; x++) {
173
- const cellDiv = document.createElement("div");
174
- cellDiv.classList.add("cell");
175
- cellDiv.dataset.x = x;
176
- cellDiv.dataset.y = y;
 
177
 
178
- // 이 좌표에 유닛이 있는지 확인
179
- const unitHere = units.find(u => u.isAlive && u.x === x && u.y === y);
180
- if (unitHere) {
181
- if (unitHere.team === RED) cellDiv.classList.add("red");
182
- else cellDiv.classList.add("blue");
183
- cellDiv.textContent = unitHere.displayName;
184
- }
 
 
 
 
 
 
 
 
 
 
 
 
185
 
186
- // 클릭 이벤트
187
- cellDiv.addEventListener("click", () => onCellClick(x, y));
188
- board.appendChild(cellDiv);
 
 
 
 
 
 
 
 
 
 
 
 
189
  }
190
- }
191
-
192
- // 상단 정보
193
- document.getElementById("turnInfo").textContent = `현재 턴: ${currentTeam}`;
194
- if (selectedUnit) {
195
- document.getElementById("selectedInfo").textContent =
196
- `선택 유닛: ${selectedUnit.type} (HP: ${selectedUnit.hp})`;
197
- } else {
198
- document.getElementById("selectedInfo").textContent = `선택 유닛: 없음`;
199
- }
200
- }
201
 
202
- /********************************************************
203
- * 5. 유저 입력 처리
204
- ********************************************************/
205
- function onCellClick(x, y) {
206
- if (currentTeam !== RED) {
207
- // 플레이어 턴이 아닐 때는 클릭 무시
208
- return;
209
- }
210
-
211
- // 클릭 지점에 유닛이 있는지 확인
212
- const clickedUnit = units.find(u => u.isAlive && u.x === x && u.y === y);
 
 
 
 
 
 
 
 
 
 
 
 
213
 
214
- if (!selectedUnit) {
215
- // 아직 유닛을 선택하지 않은 상태
216
- if (clickedUnit && clickedUnit.team === RED) {
217
- // 아군 유닛이면 선택
218
- if (!clickedUnit.hasActed) {
219
- selectedUnit = clickedUnit;
220
- }
 
221
  }
222
- } else {
223
- // 이미 유닛을 선택한 상태
224
- if (clickedUnit) {
225
- // 공격 시도 여부 확인
226
- if (clickedUnit.team !== RED) {
227
- // 적 유닛이면 공격 시도
228
- const dist = getDistance(selectedUnit, clickedUnit);
229
- if (dist <= selectedUnit.range) {
230
- // 공격
231
- attack(selectedUnit, clickedUnit);
232
- selectedUnit.hasActed = true;
233
- selectedUnit = null;
234
  }
235
- } else {
236
- // 같은 유닛 클릭 => 선택 유닛 변경(단, 이미 행동 끝난 유닛은 선택 불가)
237
- if (!clickedUnit.hasActed) {
238
- selectedUnit = clickedUnit;
 
 
239
  }
240
- }
241
- } else {
242
- // 빈 칸 클릭 -> 이동 시도
243
- const dist = Math.abs(selectedUnit.x - x) + Math.abs(selectedUnit.y - y);
244
- // (간단히 맨해튼 거리로만 계산. 대각 이동/장애물은 고려 안 함)
245
- if (dist <= selectedUnit.move) {
246
- selectedUnit.x = x;
247
- selectedUnit.y = y;
248
- // 이동만 하고 싶으면 여기서 hasActed를 true로 하거나,
249
- // 이동 후 공격을 허용할지 여부는 규칙에 따라 결정
250
- // 여기선 이동해도 공격 가능하도록 hasActed = false 유지
251
- }
252
  }
253
- }
254
- renderBoard();
255
- }
256
 
257
- // 공격 함수
258
- function attack(attacker, defender) {
259
- if (!attacker || !defender) return;
260
- const baseDamage = Math.max(0, attacker.attack - defender.defense);
261
- let finalDamage = baseDamage;
262
- // 상성 체크
263
- if (attacker.advantage === defender.type) {
264
- finalDamage = Math.floor(finalDamage * ADVANTAGE_MULTIPLIER);
265
- }
266
- defender.hp -= finalDamage;
267
- // 메세지 출력
268
- const msg =
269
- `[${attacker.team} ${attacker.type}]가 [${defender.team} ${defender.type}]를 공격! (데미지 ${finalDamage})`;
270
- showMessage(msg);
271
- // 사망 체크
272
- if (defender.hp <= 0) {
273
- defender.hp = 0;
274
- showMessage(`→ [${defender.team} ${defender.type}] 격파!`);
275
- }
276
- }
 
 
 
 
 
 
277
 
278
- // 메세지 표시(단순히 누적 출력)
279
- function showMessage(msg) {
280
- const messageDiv = document.getElementById("message");
281
- messageDiv.innerHTML += msg + "<br>";
282
- messageDiv.scrollTop = messageDiv.scrollHeight;
283
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
284
 
285
- /********************************************************
286
- * 6. 턴 종료 & AI 동작
287
- ********************************************************/
288
- function endTurn() {
289
- // 플레이어가 턴 종료 클릭
290
- if (currentTeam === RED) {
291
- // 모든 유닛의 hasActed 초기화
292
- units.forEach(u => {
293
- if (u.team === RED) {
294
- u.hasActed = false;
295
- }
296
  });
297
- // 턴 교체
298
- currentTeam = BLUE;
299
- selectedUnit = null;
300
- renderBoard();
301
- // AI 수행
302
- setTimeout(() => performAiTurn(), 500);
303
- }
304
- }
305
-
306
- // AI 간단 로직
307
- function performAiTurn() {
308
- // 1. 행동할 수 있는 BLUE 유닛 목록
309
- const blueUnits = units.filter(u => u.team === BLUE && u.isAlive);
310
 
311
- // 간단한 순차 처리: 각 유닛별로 제일 가까운 적(RED) 탐색→이동→공격
312
- for (const aiUnit of blueUnits) {
313
- // 가장 가까운 RED 유닛 찾기
314
- const enemies = units.filter(u => u.team === RED && u.isAlive);
315
- if (enemies.length === 0) break; // 적이 없으면 종료
316
-
317
- let closestEnemy = null;
318
- let minDist = 9999;
319
- for (const e of enemies) {
320
- const d = getDistance(aiUnit, e);
321
- if (d < minDist) {
322
- minDist = d;
323
- closestEnemy = e;
324
- }
 
325
  }
326
 
327
- if (!closestEnemy) continue;
328
-
329
- // 공격 범위 안에 있으면 공격
330
- if (minDist <= aiUnit.range) {
331
- attack(aiUnit, closestEnemy);
332
- } else {
333
- // 범위 밖이면 이동(가장 간단히, 가까워지도록 x,y를 1칸 이동)
334
- const moveStep = getSimpleMoveToward(aiUnit, closestEnemy);
335
- if (moveStep) {
336
- aiUnit.x = clamp(aiUnit.x + moveStep.dx, 0, WIDTH - 1);
337
- aiUnit.y = clamp(aiUnit.y + moveStep.dy, 0, HEIGHT - 1);
338
- // 이동 후, 사거리 안이면 다시 공격
339
- const distAfterMove = getDistance(aiUnit, closestEnemy);
340
- if (distAfterMove <= aiUnit.range) {
341
- attack(aiUnit, closestEnemy);
342
  }
343
- }
344
- }
345
- }
346
- // AI 턴 종료
347
- currentTeam = RED;
348
- // AI 유닛 hasActed 초기화
349
- units.forEach(u => {
350
- if (u.team === BLUE) {
351
- u.hasActed = false;
352
  }
353
- });
354
- renderBoard();
355
- checkWinCondition();
356
- }
357
-
358
- /********************************************************
359
- * 7. 보조 함수들
360
- ********************************************************/
361
- // 두 유닛 간 거리(간단히 맨해튼 거리)
362
- function getDistance(u1, u2) {
363
- return Math.abs(u1.x - u2.x) + Math.abs(u1.y - u2.y);
364
- }
365
-
366
- // AI 이동용: 목표 적 유닛에 조금 더 다가가는 방향 계산
367
- function getSimpleMoveToward(aiUnit, target) {
368
- // 이동력만큼 세밀히 계산하기보다는, 1칸만 전진(또는 2칸) 하는 식
369
- const dx = target.x - aiUnit.x;
370
- const dy = target.y - aiUnit.y;
371
- let stepX = 0;
372
- let stepY = 0;
373
-
374
- if (Math.abs(dx) > Math.abs(dy)) {
375
- stepX = dx > 0 ? 1 : -1;
376
- } else {
377
- stepY = dy > 0 ? 1 : -1;
378
- }
379
 
380
- // aiUnit.move 만큼 이동할 수도 있지만, 여기선 단순화해서 1칸만 이동
381
- return { dx: stepX, dy: stepY };
382
- }
383
-
384
- // 범위 제한 함수
385
- function clamp(value, min, max) {
386
- return Math.max(min, Math.min(max, value));
387
- }
388
-
389
- // 승리 조건 체크
390
- function checkWinCondition() {
391
- const redAlive = units.some(u => u.team === RED && u.isAlive);
392
- const blueAlive = units.some(u => u.team === BLUE && u.isAlive);
393
-
394
- if (!redAlive) {
395
- showMessage("BLUE 승리!");
396
- } else if (!blueAlive) {
397
- showMessage("RED 승리!");
398
- }
399
- }
400
 
401
- /********************************************************
402
- * 8. 초기 실행
403
- ********************************************************/
404
- // 초기 세팅
405
- initUnits();
406
- renderBoard();
 
 
 
 
 
 
 
407
 
408
- // 종료 버튼
409
- document.getElementById("endTurnBtn").addEventListener("click", () => {
410
- endTurn();
411
- checkWinCondition();
412
- });
413
- </script>
414
  </body>
415
- </html>
 
1
  <!DOCTYPE html>
2
  <html lang="ko">
3
  <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>턴제 전략 시뮬레이션</title>
7
+ <style>
8
+ body {
9
+ margin: 0;
10
+ padding: 20px;
11
+ background-color: #f0f0f0;
12
+ font-family: Arial, sans-serif;
13
+ }
14
+
15
+ #gameContainer {
16
+ display: flex;
17
+ flex-direction: column;
18
+ align-items: center;
19
+ }
20
+
21
+ #gameCanvas {
22
+ border: 2px solid #333;
23
+ box-shadow: 0 0 10px rgba(0,0,0,0.2);
24
+ background-color: #fff;
25
+ }
26
+
27
+ #gameInfo {
28
+ margin-top: 20px;
29
+ padding: 10px;
30
+ background-color: #fff;
31
+ border: 1px solid #ccc;
32
+ border-radius: 5px;
33
+ }
34
+
35
+ .controls {
36
+ margin-top: 10px;
37
+ display: flex;
38
+ gap: 10px;
39
+ }
40
+
41
+ button {
42
+ padding: 5px 10px;
43
+ cursor: pointer;
44
+ }
45
+ </style>
 
 
46
  </head>
47
  <body>
48
+ <div id="gameContainer">
49
+ <canvas id="gameCanvas"></canvas>
50
+ <div id="gameInfo">
51
+ <div id="turnInfo">현재 턴: 적색군</div>
52
+ <div id="selectedUnit">선택된 유닛: 없음</div>
53
+ <div class="controls">
54
+ <button id="endTurn">턴 종료</button>
55
+ <button id="resetGame">게임 재시작</button>
56
+ </div>
57
+ </div>
58
+ </div>
59
+
60
+ <script>
61
+ // 게임 상수
62
+ const GRID_SIZE = 80;
63
+ const COLS = 10;
64
+ const ROWS = 6;
65
+
66
+ // Canvas 설정
67
+ const canvas = document.getElementById('gameCanvas');
68
+ const ctx = canvas.getContext('2d');
69
+ canvas.width = GRID_SIZE * COLS;
70
+ canvas.height = GRID_SIZE * ROWS;
71
+
72
+ // 지형 데이터
73
+ const elevationData = [
74
+ [1, 1, 2, 2, 2, 2, 1, 1, 1, 1],
75
+ [1, 2, 2, 3, 3, 2, 2, 1, 1, 1],
76
+ [1, 2, 3, 3, 2, 2, 2, 2, 1, 1],
77
+ [1, 2, 2, 2, 2, 2, 3, 2, 2, 1],
78
+ [1, 1, 2, 2, 2, 3, 2, 2, 1, 1],
79
+ [1, 1, 1, 2, 2, 2, 2, 1, 1, 1]
80
+ ];
81
+
82
+ // 유닛 클래스
83
+ class Unit {
84
+ constructor(type, team, x, y) {
85
+ this.type = type;
86
+ this.team = team;
87
+ this.x = x;
88
+ this.y = y;
89
+ this.hp = this.getInitialHP();
90
+ this.moved = false;
91
+ this.attacked = false;
92
+
93
+ // 유닛 타입별 스탯
94
+ const stats = {
95
+ 'Infantry': { move: 2, attack: 3, defense: 3, range: 1 },
96
+ 'Archer': { move: 2, attack: 2, defense: 2, range: 3 },
97
+ 'Cavalry': { move: 3, attack: 4, defense: 2, range: 1 }
98
+ };
99
+
100
+ Object.assign(this, stats[type]);
101
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
102
 
103
+ getInitialHP() {
104
+ const hpValues = {
105
+ 'Infantry': 25,
106
+ 'Archer': 20,
107
+ 'Cavalry': 22
108
+ };
109
+ return hpValues[this.type];
110
+ }
111
 
112
+ draw() {
113
+ const x = this.x * GRID_SIZE;
114
+ const y = this.y * GRID_SIZE;
115
+
116
+ // 유닛 기본 모양
117
+ ctx.fillStyle = this.team === 'red' ? '#ff0000' : '#0000ff';
118
+ ctx.beginPath();
119
+ ctx.arc(x + GRID_SIZE/2, y + GRID_SIZE/2, GRID_SIZE/3, 0, Math.PI * 2);
120
+ ctx.fill();
121
+
122
+ // 유닛 타입 표시
123
+ ctx.fillStyle = '#ffffff';
124
+ ctx.font = '14px Arial';
125
+ ctx.textAlign = 'center';
126
+ ctx.fillText(this.type[0], x + GRID_SIZE/2, y + GRID_SIZE/2 + 5);
127
+
128
+ // HP 바
129
+ this.drawHealthBar(x, y);
130
+ }
131
 
132
+ drawHealthBar(x, y) {
133
+ const maxHP = this.getInitialHP();
134
+ const barWidth = GRID_SIZE * 0.8;
135
+ const barHeight = 5;
136
+ const barX = x + GRID_SIZE * 0.1;
137
+ const barY = y + GRID_SIZE * 0.8;
138
+
139
+ // 배경
140
+ ctx.fillStyle = '#ff0000';
141
+ ctx.fillRect(barX, barY, barWidth, barHeight);
142
+
143
+ // 현재 체력
144
+ ctx.fillStyle = '#00ff00';
145
+ ctx.fillRect(barX, barY, (this.hp / maxHP) * barWidth, barHeight);
146
+ }
147
  }
 
 
 
 
 
 
 
 
 
 
 
148
 
149
+ // 게임 상태
150
+ let gameState = {
151
+ units: [],
152
+ selectedUnit: null,
153
+ currentTurn: 'red',
154
+ turnCount: 1
155
+ };
156
+
157
+ // 게임 초기화
158
+ function initGame() {
159
+ gameState.units = [];
160
+ gameState.currentTurn = 'red';
161
+ gameState.turnCount = 1;
162
+ gameState.selectedUnit = null;
163
+
164
+ // 적색군 유닛 배치
165
+ for(let i = 0; i < 6; i++) {
166
+ gameState.units.push(new Unit('Infantry', 'red', 0, i));
167
+ }
168
+ for(let i = 0; i < 3; i++) {
169
+ gameState.units.push(new Unit('Archer', 'red', 1, i*2));
170
+ gameState.units.push(new Unit('Cavalry', 'red', 2, i*2));
171
+ }
172
 
173
+ // 청색군 유닛 배치
174
+ for(let i = 0; i < 6; i++) {
175
+ gameState.units.push(new Unit('Infantry', 'blue', 9, i));
176
+ }
177
+ for(let i = 0; i < 3; i++) {
178
+ gameState.units.push(new Unit('Archer', 'blue', 8, i*2));
179
+ gameState.units.push(new Unit('Cavalry', 'blue', 7, i*2));
180
+ }
181
  }
182
+
183
+ // 격자 그리기
184
+ function drawGrid() {
185
+ ctx.strokeStyle = '#ccc';
186
+ ctx.lineWidth = 1;
187
+
188
+ for(let x = 0; x <= canvas.width; x += GRID_SIZE) {
189
+ ctx.beginPath();
190
+ ctx.moveTo(x, 0);
191
+ ctx.lineTo(x, canvas.height);
192
+ ctx.stroke();
 
193
  }
194
+
195
+ for(let y = 0; y <= canvas.height; y += GRID_SIZE) {
196
+ ctx.beginPath();
197
+ ctx.moveTo(0, y);
198
+ ctx.lineTo(canvas.width, y);
199
+ ctx.stroke();
200
  }
 
 
 
 
 
 
 
 
 
 
 
 
201
  }
 
 
 
202
 
203
+ // 지형 그리기
204
+ function drawTerrain() {
205
+ // 배경
206
+ ctx.fillStyle = '#e8e8d0';
207
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
208
+
209
+ // 높낮이 표시
210
+ elevationData.forEach((row, y) => {
211
+ row.forEach((height, x) => {
212
+ ctx.fillStyle = `rgba(0,0,0,${height * 0.1})`;
213
+ ctx.fillRect(x * GRID_SIZE, y * GRID_SIZE, GRID_SIZE, GRID_SIZE);
214
+ });
215
+ });
216
+
217
+ //
218
+ ctx.strokeStyle = '#4040ff';
219
+ ctx.lineWidth = 20;
220
+ ctx.beginPath();
221
+ ctx.moveTo(0, canvas.height/2);
222
+ ctx.bezierCurveTo(
223
+ canvas.width/4, canvas.height/2 - 40,
224
+ canvas.width*3/4, canvas.height/2 + 40,
225
+ canvas.width, canvas.height/2
226
+ );
227
+ ctx.stroke();
228
+ }
229
 
230
+ // 게임 화면 갱신
231
+ function render() {
232
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
233
+
234
+ drawTerrain();
235
+ drawGrid();
236
+
237
+ // 유닛 그리기
238
+ gameState.units.forEach(unit => unit.draw());
239
+
240
+ // 선택된 유닛 하이라이트
241
+ if(gameState.selectedUnit) {
242
+ const x = gameState.selectedUnit.x * GRID_SIZE;
243
+ const y = gameState.selectedUnit.y * GRID_SIZE;
244
+ ctx.strokeStyle = '#ffff00';
245
+ ctx.lineWidth = 3;
246
+ ctx.strokeRect(x, y, GRID_SIZE, GRID_SIZE);
247
+ }
248
+
249
+ // 턴 정보 업데이트
250
+ document.getElementById('turnInfo').textContent =
251
+ `현재 턴: ${gameState.currentTurn === 'red' ? '적색군' : '청색군'} (${gameState.turnCount}턴)`;
252
+
253
+ document.getElementById('selectedUnit').textContent =
254
+ gameState.selectedUnit ?
255
+ `선택된 유닛: ${gameState.selectedUnit.type} (HP: ${gameState.selectedUnit.hp})` :
256
+ '선택된 유닛: 없음';
257
+ }
258
 
259
+ // 이벤트 리스너
260
+ canvas.addEventListener('click', handleClick);
261
+ document.getElementById('endTurn').addEventListener('click', endTurn);
262
+ document.getElementById('resetGame').addEventListener('click', () => {
263
+ initGame();
264
+ render();
 
 
 
 
 
265
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
266
 
267
+ function handleClick(e) {
268
+ const rect = canvas.getBoundingClientRect();
269
+ const x = Math.floor((e.clientX - rect.left) / GRID_SIZE);
270
+ const y = Math.floor((e.clientY - rect.top) / GRID_SIZE);
271
+
272
+ const clickedUnit = gameState.units.find(unit => unit.x === x && unit.y === y);
273
+
274
+ if(clickedUnit && clickedUnit.team === gameState.currentTurn) {
275
+ gameState.selectedUnit = clickedUnit;
276
+ } else if(gameState.selectedUnit) {
277
+ // 이동 또는 공격 로직
278
+ moveUnit(gameState.selectedUnit, x, y);
279
+ }
280
+
281
+ render();
282
  }
283
 
284
+ function moveUnit(unit, targetX, targetY) {
285
+ if(!unit.moved && isValidMove(unit, targetX, targetY)) {
286
+ unit.x = targetX;
287
+ unit.y = targetY;
288
+ unit.moved = true;
 
 
 
 
 
 
 
 
 
 
289
  }
 
 
 
 
 
 
 
 
 
290
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
291
 
292
+ function isValidMove(unit, targetX, targetY) {
293
+ const dx = Math.abs(targetX - unit.x);
294
+ const dy = Math.abs(targetY - unit.y);
295
+ return dx + dy <= unit.move;
296
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
297
 
298
+ function endTurn() {
299
+ gameState.currentTurn = gameState.currentTurn === 'red' ? 'blue' : 'red';
300
+ if(gameState.currentTurn === 'red') gameState.turnCount++;
301
+
302
+ // 유닛 상태 초기화
303
+ gameState.units.forEach(unit => {
304
+ unit.moved = false;
305
+ unit.attacked = false;
306
+ });
307
+
308
+ gameState.selectedUnit = null;
309
+ render();
310
+ }
311
 
312
+ // 게임 시작
313
+ initGame();
314
+ render();
315
+ </script>
 
 
316
  </body>
317
+ </html>