cutechicken commited on
Commit
dd37820
·
verified ·
1 Parent(s): c70fefd

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +602 -579
index.html CHANGED
@@ -102,7 +102,8 @@
102
  }
103
  </style>
104
  </head>
105
- <div id="instructions">
 
106
  Controls:<br>
107
  WASD - Move tank<br>
108
  Mouse - Aim<br>
@@ -115,149 +116,176 @@
115
  <button id="nextRound" class="button">Next Round</button>
116
  <button id="restart" class="button">Restart Game</button>
117
  <canvas id="gameCanvas"></canvas>
118
- <div id="titleScreen">
119
- <h1>TANK WAR</h1>
120
- <button id="startButton">START GAME</button>
121
- </div>
122
-
123
- <div id="shop" style="display:none; position:fixed; top:50%; left:50%; transform:translate(-50%,-50%); background:rgba(0,0,0,0.9); padding:20px; border-radius:10px; color:white; z-index:1000;">
124
- <h2>Tank Shop</h2>
125
- <div style="display:flex; gap:20px;">
126
- <div id="tank1" style="text-align:center;">
127
- <h3>PZ.IV</h3>
128
- <img src="player2.png" width="90" height="50">
129
- <p>300 Gold</p>
130
- <p style="color: #4CAF50;">+50% HP</p>
131
- <button onclick="buyTank('player2.png', 300, 'tank1')">Buy</button>
132
- </div>
133
- <div id="tank2" style="text-align:center;">
134
- <h3>TIGER</h3>
135
- <img src="player3.png" width="110" height="55">
136
- <p>500 Gold</p>
137
- <p style="color: #4CAF50;">+100% HP</p>
138
- <p style="color: #ff6b6b;">-30% Speed</p>
139
- <button onclick="buyTank('player3.png', 500, 'tank2')">Buy</button>
140
- </div>
141
- <div id="bf109" style="text-align:center;">
142
- <h3>BF-109</h3>
143
- <img src="bf109.png" width="100" height="100">
144
- <p>1000 Gold</p>
145
- <p style="color: #4CAF50;">Air support from BF-109</p>
146
- <button onclick="buyBF109()">Buy</button>
147
- </div>
148
- <div id="ju87" style="text-align:center;">
149
- <h3>JU-87</h3>
150
- <img src="ju87.png" width="100" height="100">
151
- <p>1500 Gold</p>
152
- <p style="color: #4CAF50;">Get ju-87 air support</p>
153
- <button onclick="buyJU87()">Buy</button>
 
 
 
 
 
 
 
 
154
  </div>
155
- <div id="apcr" style="text-align:center;">
156
- <h3>APCR</h3>
157
- <img src="apcr.png" width="80" height="20"> <!-- 여기를 80x20으로 수정 -->
158
- <p>1000 Gold</p>
159
- <p style="color: #4CAF50;">+100% Bullet Speed</p>
160
- <button onclick="buyAPCR()">Buy</button>
161
- </div>
162
  </div>
163
- </div>
164
  <button id="bossButton" class="button">Fight Boss!</button>
165
  <div id="winMessage" class="button" style="font-size: 72px; background: none;">You Win!</div>
166
-
167
-
168
- <script>
169
- const canvas = document.getElementById('gameCanvas');
170
- const ctx = canvas.getContext('2d');
171
- const nextRoundBtn = document.getElementById('nextRound');
172
- const restartBtn = document.getElementById('restart');
173
- const weaponInfo = document.getElementById('weaponInfo');
174
- const countdownEl = document.getElementById('countdown');
175
- const bossButton = document.getElementById('bossButton');
176
- canvas.width = window.innerWidth;
177
- canvas.height = window.innerHeight;
178
- // Game state
179
- let currentRound = 1;
180
- let gameOver = false;
181
- let currentWeapon = 'cannon';
182
- let enemies = [];
183
- let bullets = [];
184
- let items = [];
185
- let lastShot = 0;
186
- let isCountingDown = true;
187
- let countdownTime = 3;
188
- let autoFire = false;
189
- let gold = 0;
190
- let isBossStage = false;
191
- let effects = [];
192
- let hasAPCR = false; // APCR 구매 여부
193
- let hasBF109 = false; // BF-109 구매 여부
194
- let hasJU87 = false; // JU-87 구매 여부
195
- let lastJU87Spawn = 0; // 마지막 JU-87 생성 시간
196
- let supportUnits = []; // 지원 유닛 배열
197
- let lastSupportSpawn = 0; // 마지막 지원 유닛 생성 시간
198
- // Load assets
199
- const backgroundImg = new Image();
200
- backgroundImg.src = 'city.png';
201
- const playerImg = new Image();
202
- playerImg.src = 'player.png';
203
- const enemyImg = new Image();
204
- enemyImg.src = 'enemy.png';
205
- const bulletImg = new Image(); // APCR 총알 이미지
206
- bulletImg.src = 'apcr2.png';
207
- // Audio setup
208
- const cannonSound = new Audio('firemn.ogg');
209
- const machinegunSound = new Audio('firemg.ogg');
210
- const enemyFireSound = new Audio('fireenemy.ogg');
211
- let bgm = new Audio('title.ogg'); // 'title.ogg'로 초기화하고 let으로 선언
212
- const countSound = new Audio('count.ogg');
213
- const deathSound = new Audio('death.ogg');
214
- bgm.loop = true;
215
- enemyFireSound.volume = 0.5;
216
- const weapons = {
217
- cannon: {
218
- fireRate: 1000,
219
- damage: 0.25,
220
- bulletSize: 5,
221
- sound: cannonSound
222
- },
223
- machinegun: {
224
- fireRate: 200,
225
- damage: 0.05,
226
- bulletSize: 2,
227
- sound: machinegunSound
228
- }
229
- };
230
- // Player setup
231
- const player = {
232
- x: canvas.width/2,
233
- y: canvas.height/2,
234
- speed: 5,
235
- angle: 0,
236
- width: 100,
237
- height: 45,
238
- health: 1000,
239
- maxHealth: 1000
240
- };
241
- function startCountdown() {
242
- isCountingDown = true;
243
- countdownTime = 3;
244
- countdownEl.style.display = 'block';
245
- countdownEl.textContent = countdownTime;
246
- bgm.pause();
247
- countSound.play();
248
- const countInterval = setInterval(() => {
249
- countdownTime--;
250
- if(countdownTime <= 0) {
251
- clearInterval(countInterval);
252
- countdownEl.style.display = 'none';
253
- isCountingDown = false;
254
- bgm.play();
255
- }
256
- countdownEl.textContent = countdownTime > 0 ? countdownTime : 'GO!';
257
- }, 1000);
258
  }
259
-
260
- class Effect {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
261
  constructor(x, y, duration, type, angle = 0, parent = null) {
262
  this.x = x;
263
  this.y = y;
@@ -265,8 +293,8 @@
265
  this.duration = duration;
266
  this.type = type;
267
  this.angle = angle;
268
- this.parent = parent; // 부모 유닛 (발사한 유닛)
269
- this.offset = { x: Math.cos(angle) * 30, y: Math.sin(angle) * 30 }; // 부모로부터의 오프셋
270
  this.img = new Image();
271
  this.img.src = type === 'death' ? 'bang.png' : 'fire2.png';
272
  }
@@ -283,7 +311,8 @@
283
  return Date.now() - this.startTime > this.duration;
284
  }
285
  }
286
- class SupportUnit {
 
287
  constructor(yPosition) {
288
  this.x = 0;
289
  this.y = yPosition;
@@ -294,15 +323,13 @@
294
  this.angle = 0;
295
  this.img = new Image();
296
  this.img.src = 'bf109.png';
297
- this.hasPlayedSound = false; // 첫 등장 소리용
298
- this.mgSound = null; // 기관총 소리 객체
299
  }
300
 
301
  update() {
302
- // 이동
303
  this.x += this.speed;
304
 
305
- // 카운트다운 중이면 소리 정지 및 초기화
306
  if (isCountingDown) {
307
  if (this.mgSound) {
308
  this.mgSound.pause();
@@ -311,7 +338,6 @@
311
  this.hasPlayedSound = false;
312
  }
313
 
314
- // 발사 (1초에 5발)
315
  const now = Date.now();
316
  if (now - this.lastShot > 200 && !isCountingDown) {
317
  this.shoot();
@@ -321,7 +347,6 @@
321
  }
322
 
323
  shoot() {
324
- // 최초 등장시에만 bf109mg.ogg 재생
325
  if (!this.hasPlayedSound) {
326
  const firstSound = new Audio('bf109mg.ogg');
327
  firstSound.volume = 1.0;
@@ -329,10 +354,9 @@
329
  this.hasPlayedSound = true;
330
  }
331
 
332
- // 발사할 때마다 새로운 bf109mgse.ogg 재생
333
  if (!isCountingDown) {
334
  const shootSound = new Audio('bf109mgse.ogg');
335
- shootSound.volume = 0.5; // 볼륨 낮춤
336
  shootSound.play();
337
  }
338
 
@@ -347,7 +371,8 @@
347
  });
348
  }
349
  }
350
- class JU87 {
 
351
  constructor() {
352
  this.x = canvas.width;
353
  this.y = 50;
@@ -364,7 +389,7 @@
364
  this.hasPlayedMGSound = false;
365
  this.isReturning = false;
366
  this.circleAngle = 0;
367
- this.returningToCenter = false; // 중앙으로 돌아가는 상태 추가
368
  }
369
 
370
  selectTarget() {
@@ -372,35 +397,34 @@
372
  enemies[Math.floor(Math.random() * enemies.length)] : null;
373
  }
374
 
375
- shoot() {
376
- // 카운트다운 중이 아닐 때만 소리 재생
377
- if (!this.hasPlayedMGSound && !isCountingDown) {
378
- const mgSound = new Audio('ju87mg.ogg');
379
- mgSound.volume = 1.0;
380
- mgSound.currentTime = 0;
381
- mgSound.play().catch(error => console.error('Audio play failed:', error));
382
- this.hasPlayedMGSound = true;
383
- }
384
-
385
- // 100x100 픽셀 기준으로 날개 위치 좌표 설정
386
- [[20, 50], [80, 50]].forEach(([x, y]) => {
387
- const offsetX = x - 50;
388
- const offsetY = y - 50;
389
-
390
- const rotatedX = this.x + (Math.cos(this.angle) * offsetX - Math.sin(this.angle) * offsetY);
391
- const rotatedY = this.y + (Math.sin(this.angle) * offsetX + Math.cos(this.angle) * offsetY);
392
 
393
- bullets.push({
394
- x: rotatedX,
395
- y: rotatedY,
396
- angle: this.angle,
397
- speed: 10,
398
- isEnemy: false,
399
- damage: weapons.machinegun.damage * 2,
400
- size: weapons.machinegun.bulletSize
 
 
 
 
 
 
 
 
401
  });
402
- });
403
- }
404
  update() {
405
  if (!this.hasPlayedSound) {
406
  const sirenSound = new Audio('ju87siren.ogg');
@@ -464,89 +488,126 @@
464
  return true;
465
  }
466
  }
467
-
468
- class Enemy {
469
- constructor(isBoss = false) {
470
- this.x = Math.random() * canvas.width;
471
- this.y = Math.random() * canvas.height;
472
- this.health = isBoss ? 15000 : 1000;
473
- this.maxHealth = this.health;
474
- this.speed = isBoss ? 1 : 2;
475
- this.lastShot = 0;
476
- this.shootInterval = isBoss ? 1000 : 1000;
477
- this.angle = 0;
478
- this.width = 100;
479
- this.height = 45;
480
- this.moveTimer = 0;
481
- this.moveInterval = Math.random() * 2000 + 1000;
482
- this.moveAngle = Math.random() * Math.PI * 2;
483
- this.isBoss = isBoss;
484
-
485
- if (isBoss) {
486
- this.enemyImg = new Image();
487
- this.enemyImg.src = 'boss.png';
488
- } else if (currentRound >= 7) {
489
- this.enemyImg = new Image();
490
- this.enemyImg.src = 'enemy3.png';
491
- } else if (currentRound >= 4) {
492
- this.enemyImg = new Image();
493
- this.enemyImg.src = 'enemy2.png';
494
- }
495
- }
496
- update() {
497
- if(isCountingDown) return;
498
- const now = Date.now();
499
-
500
- if (now - this.moveTimer > this.moveInterval) {
501
- this.moveAngle = Math.random() * Math.PI * 2;
502
- this.moveTimer = now;
503
- }
504
- this.x += Math.cos(this.moveAngle) * this.speed;
505
- this.y += Math.sin(this.moveAngle) * this.speed;
506
- this.x = Math.max(this.width/2, Math.min(canvas.width - this.width/2, this.x));
507
- this.y = Math.max(this.height/2, Math.min(canvas.height - this.height/2, this.y));
508
- this.angle = Math.atan2(player.y - this.y, player.x - this.x);
509
-
510
- if (now - this.lastShot > this.shootInterval && !isCountingDown) {
511
- this.shoot();
512
- this.lastShot = now;
513
- }
514
- }
515
- shoot() {
516
- const sound = this.isBoss ? new Audio('firemn.ogg') : enemyFireSound.cloneNode();
517
- sound.play();
518
-
519
- // 발사 이펙트 추가
520
- effects.push(new Effect(
521
- this.x + Math.cos(this.angle) * 30,
522
- this.y + Math.sin(this.angle) * 30,
523
- 500,
524
- 'fire',
525
- this.angle,
526
- this // 자신을 부모로 전달
527
- ));
528
-
529
- bullets.push({
530
- x: this.x + Math.cos(this.angle) * 30,
531
- y: this.y + Math.sin(this.angle) * 30,
532
- angle: this.angle,
533
- speed: this.isBoss ? 10 : 5,
534
- isEnemy: true,
535
- size: this.isBoss ? 5 : 3,
536
- damage: this.isBoss ? 300 : 150
537
- });
538
- }
539
  }
540
- function showShop() {
541
- document.getElementById('shop').style.display = 'block';
 
 
 
 
 
 
 
542
  }
543
- // 플레이어의 기본 상태를 저장
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
544
  const defaultPlayerStats = {
545
  maxHealth: 1000,
546
  speed: 5,
547
  width: 100,
548
  height: 45
549
  };
 
550
  function buyTank(tankImg, cost, tankId) {
551
  if (gold >= cost) {
552
  gold -= cost;
@@ -554,23 +615,24 @@ function buyTank(tankImg, cost, tankId) {
554
  document.getElementById(tankId).style.display = 'none';
555
  document.getElementById('shop').style.display = 'none';
556
 
557
- if (tankId === 'tank1') { // PZ.IV
558
  player.maxHealth = 1500;
559
- player.speed = defaultPlayerStats.speed; // 기본 이동속도로 복구
560
- player.width = 90; // PZ.IV의 크기 설정
561
- player.height = 50; // PZ.IV의 크기 설정
562
  }
563
- else if (tankId === 'tank2') { // TIGER
564
  player.maxHealth = 2000;
565
  player.speed = defaultPlayerStats.speed * 0.7;
566
- player.width = 100; // TIGER는 기본 크기 유지
567
- player.height = 45; // TIGER는 기본 크기 유지
568
  }
569
 
570
  player.health = player.maxHealth;
571
  }
572
  }
573
- function buyAPCR() {
 
574
  if (gold >= 1000 && !hasAPCR) {
575
  gold -= 1000;
576
  hasAPCR = true;
@@ -578,7 +640,8 @@ function buyTank(tankImg, cost, tankId) {
578
  document.getElementById('shop').style.display = 'none';
579
  }
580
  }
581
- function buyBF109() {
 
582
  if (gold >= 1000 && !hasBF109) {
583
  gold -= 1000;
584
  hasBF109 = true;
@@ -586,228 +649,277 @@ function buyTank(tankImg, cost, tankId) {
586
  document.getElementById('shop').style.display = 'none';
587
  }
588
  }
589
- function buyJU87() {
 
590
  if (gold >= 1500 && !hasJU87) {
591
  gold -= 1500;
592
  hasJU87 = true;
593
  document.getElementById('ju87').style.display = 'none';
594
  document.getElementById('shop').style.display = 'none';
595
- lastJU87Spawn = Date.now(); // 구매 즉시 스폰 타이머 초기화
596
  }
597
  }
598
- function initRound() {
599
- enemies = [];
600
- for(let i = 0; i < 1 * currentRound; i++) {
601
- enemies.push(new Enemy());
 
 
 
 
 
 
 
 
 
 
 
 
 
602
  }
603
- player.health = player.maxHealth;
604
- bullets = [];
605
- items = [];
606
- supportUnits = [];
607
- lastSupportSpawn = 0;
608
-
609
- // 카운트다운 시작
610
- startCountdown();
611
-
612
- // 카운트다운이 끝나면 JU87 스폰
613
- setTimeout(() => {
614
- if (hasJU87) {
615
- supportUnits.push(new JU87());
616
- lastJU87Spawn = Date.now();
617
- }
618
- }, 3000); // 3초 후에 스폰
619
  }
620
- function startBossStage() {
621
- isBossStage = true;
622
- enemies = [];
623
- enemies.push(new Enemy(true));
624
- player.health = player.maxHealth;
625
- bullets = [];
626
- items = [];
627
- document.getElementById('bossButton').style.display = 'none';
628
- bgm.src = 'BGM.ogg'; // 보스전 BGM으로 변경
629
- startCountdown();
630
- }
631
- canvas.addEventListener('mousemove', (e) => {
632
- player.angle = Math.atan2(e.clientY - player.y, e.clientX - player.x);
633
- });
634
- const keys = {};
635
- document.addEventListener('keydown', e => {
636
- keys[e.key] = true;
637
- if(e.key.toLowerCase() === 'c') {
638
- currentWeapon = currentWeapon === 'cannon' ? 'machinegun' : 'cannon';
639
- weaponInfo.textContent = `Current Weapon: ${currentWeapon.charAt(0).toUpperCase() + currentWeapon.slice(1)}`;
640
- } else if(e.key.toLowerCase() === 'r') {
641
- autoFire = !autoFire;
642
- }
643
- });
644
-
645
- document.addEventListener('keyup', e => keys[e.key] = false);
646
- function fireBullet() {
647
- if(isCountingDown) return;
648
- const weapon = weapons[currentWeapon];
649
- const now = Date.now();
650
- if ((keys[' '] || autoFire) && now - lastShot > weapon.fireRate) {
651
- weapon.sound.cloneNode().play();
652
- effects.push(new Effect(
653
- player.x + Math.cos(player.angle) * 30,
654
- player.y + Math.sin(player.angle) * 30,
655
- 500,
656
- 'fire',
657
- player.angle,
658
- player
659
- ));
660
-
661
- bullets.push({
662
- x: player.x + Math.cos(player.angle) * 30,
663
- y: player.y + Math.sin(player.angle) * 30,
664
- angle: player.angle,
665
- speed: hasAPCR ? 20 : 10, // APCR 적용 시 100% 증가
666
- isEnemy: false,
667
- damage: weapon.damage,
668
- size: weapon.bulletSize,
669
- isAPCR: hasAPCR
670
- });
671
- lastShot = now;
672
- }
673
- }
674
- function updateGame() {
675
- if(gameOver) return;
676
- if(!isCountingDown) {
677
- // 플레이어 움직임
678
- if(keys['w']) player.y -= player.speed;
679
- if(keys['s']) player.y += player.speed;
680
- if(keys['a']) player.x -= player.speed;
681
- if(keys['d']) player.x += player.speed;
682
- player.x = Math.max(player.width/2, Math.min(canvas.width - player.width/2, player.x));
683
- player.y = Math.max(player.height/2, Math.min(canvas.height - player.height/2, player.y));
684
- fireBullet();
685
  }
 
686
 
687
- // BF109 관련 코드
688
- if (hasBF109 && !isCountingDown) {
689
- const now = Date.now();
690
- if (now - lastSupportSpawn > 10000) { // 10초마다
691
- supportUnits.push(
692
- new SupportUnit(canvas.height * 0.2),
693
- new SupportUnit(canvas.height * 0.5),
694
- new SupportUnit(canvas.height * 0.8)
695
- );
696
- lastSupportSpawn = now;
697
- }
698
  }
 
699
 
700
- // JU87 관련 코드 추가
701
- if (hasJU87 && !isCountingDown) {
702
- const now = Date.now();
703
- if (now - lastJU87Spawn > 15000) { // 15초마다
704
- supportUnits.push(new JU87());
705
- lastJU87Spawn = now;
706
- }
707
- }
708
 
709
- // 모든 지원 유닛 업데이트 (BF109와 JU87 모두 처리)
710
- supportUnits = supportUnits.filter(unit => unit.update());
711
 
712
- // 적 업데이트 - 한 번만 실행
713
- enemies.forEach(enemy => enemy.update());
714
-
715
- if(!isCountingDown) {
716
- bullets = bullets.filter(bullet => {
717
- bullet.x += Math.cos(bullet.angle) * bullet.speed;
718
- bullet.y += Math.sin(bullet.angle) * bullet.speed;
719
- if(!bullet.isEnemy) {
720
- enemies = enemies.filter(enemy => {
721
- const dist = Math.hypot(bullet.x - enemy.x, bullet.y - enemy.y);
722
- if(dist < 30) {
723
- let damage = currentWeapon === 'cannon' ? 250 : 50; // 고정 데미지로 변경
724
- enemy.health -= damage;
725
- if(enemy.health <= 0) {
726
- spawnHealthItem(enemy.x, enemy.y);
727
- gold += 100;
728
- // 죽음 이펙트와 사운드 추가
729
- effects.push(new Effect(enemy.x, enemy.y, 1000, 'death'));
730
- deathSound.cloneNode().play();
731
- return false;
732
- }
733
- if(player.health <= 0) {
734
- gameOver = true;
735
- restartBtn.style.display = 'block';
736
- effects.push(new Effect(player.x, player.y, 1000, 'death'));
737
- deathSound.cloneNode().play();
738
- }
739
- return true;
740
- }
741
- return true;
742
- });
743
- } else {
744
- const dist = Math.hypot(bullet.x - player.x, bullet.y - player.y);
745
  if(dist < 30) {
746
- player.health -= bullet.damage || 100;
747
- if(player.health <= 0) {
748
- gameOver = true;
749
- restartBtn.style.display = 'block';
 
 
 
 
750
  }
751
- return false;
752
  }
753
- }
754
- return bullet.x >= 0 && bullet.x <= canvas.width &&
755
- bullet.y >= 0 && bullet.y <= canvas.height;
756
- });
757
-
758
- items = items.filter(item => {
759
- const dist = Math.hypot(item.x - player.x, item.y - player.y);
760
  if(dist < 30) {
761
- player.health = Math.min(player.health + 200, player.maxHealth);
 
 
 
 
 
 
762
  return false;
763
  }
764
- return true;
765
- });
766
-
767
- if(enemies.length === 0) {
768
- if (!isBossStage) {
769
- if(currentRound < 10) {
770
- nextRoundBtn.style.display = 'block';
771
- showShop();
772
- } else {
773
- document.getElementById('bossButton').style.display = 'block';
774
- }
 
 
 
 
 
 
 
 
775
  } else {
776
- gameOver = true;
777
- document.getElementById('winMessage').style.display = 'block';
778
- restartBtn.style.display = 'block';
779
- bgm.pause(); // 현재 BGM 정지
780
- const victorySound = new Audio('victory.ogg'); // 승리 사운드 생성
781
- victorySound.play(); // 승리 사운드 재생
782
  }
 
 
 
 
 
 
 
783
  }
784
  }
785
  }
786
- function spawnHealthItem(x, y) {
787
- items.push({x, y});
788
- }
789
- function drawHealthBar(x, y, health, maxHealth, width, height, color) {
790
- ctx.fillStyle = '#333';
791
- ctx.fillRect(x - width/2, y - height/2, width, height);
792
- ctx.fillStyle = color;
793
- ctx.fillRect(x - width/2, y - height/2, width * (health/maxHealth), height);
794
- }
795
- function drawGame() {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
796
  ctx.clearRect(0, 0, canvas.width, canvas.height);
797
  const pattern = ctx.createPattern(backgroundImg, 'repeat');
798
  ctx.fillStyle = pattern;
799
  ctx.fillRect(0, 0, canvas.width, canvas.height);
800
-
801
  // 플레이어 그리기
802
  ctx.save();
803
  ctx.translate(player.x, player.y);
804
  ctx.rotate(player.angle);
805
  ctx.drawImage(playerImg, -player.width/2, -player.height/2, player.width, player.height);
806
  ctx.restore();
807
-
808
- // 체력바
809
  drawHealthBar(canvas.width/2, 30, player.health, player.maxHealth, 200, 20, 'green');
810
-
811
  // 적 그리기
812
  enemies.forEach(enemy => {
813
  ctx.save();
@@ -818,31 +930,34 @@ function buyTank(tankImg, cost, tankId) {
818
  ctx.restore();
819
  drawHealthBar(enemy.x, enemy.y - 40, enemy.health, enemy.maxHealth, 60, 5, 'red');
820
  });
821
- supportUnits.forEach(unit => {
 
 
822
  ctx.save();
823
  ctx.translate(unit.x, unit.y);
824
  ctx.rotate(unit.angle);
825
  ctx.drawImage(unit.img, -unit.width/2, -unit.height/2, unit.width, unit.height);
826
  ctx.restore();
827
  });
828
- // 총알 그리기
829
- bullets.forEach(bullet => {
830
- if (bullet.isEnemy || !bullet.isAPCR) {
831
- ctx.beginPath();
832
- ctx.fillStyle = bullet.isEnemy ? 'red' : 'blue';
833
- ctx.arc(bullet.x, bullet.y, bullet.size, 0, Math.PI * 2);
834
- ctx.fill();
835
- } else {
836
- ctx.save();
837
- ctx.translate(bullet.x, bullet.y);
838
- ctx.rotate(bullet.angle);
839
- // 기관총일 때 크기 50% 감소
840
- const width = currentWeapon === 'machinegun' ? 10 : 20; // 기관총일 때 10, 캐논일 때 20
841
- const height = currentWeapon === 'machinegun' ? 5 : 10; // 기관총일 때 5, 캐논일 때 10
842
- ctx.drawImage(bulletImg, -width/2, -height/2, width, height);
843
- ctx.restore();
844
- }
845
- });
 
846
  // 아이템 그리기
847
  items.forEach(item => {
848
  ctx.beginPath();
@@ -850,123 +965,31 @@ function buyTank(tankImg, cost, tankId) {
850
  ctx.arc(item.x, item.y, 10, 0, Math.PI * 2);
851
  ctx.fill();
852
  });
853
-
854
  // UI 그리기
855
  ctx.fillStyle = 'white';
856
- ctx.font = '24px Arial';
857
- ctx.fillText(`Round ${currentRound}/10`, 10, 30);
858
- ctx.fillText(`Gold: ${gold}`, 10, 60);
859
- // 이펙트 그리기
860
- effects = effects.filter(effect => !effect.isExpired());
861
- effects.forEach(effect => {
862
- effect.update(); // 이펙트 위치 업데이트
863
- ctx.save();
864
- ctx.translate(effect.x, effect.y);
865
- if (effect.type === 'fire') ctx.rotate(effect.angle);
866
- // bang.png는 1.5배 크게
867
- const size = effect.type === 'death' ? 75 : 42; // death는 75px (1.5배), fire는 42px
868
- ctx.drawImage(effect.img, -size / 2, -size / 2, size, size);
869
- ctx.restore();
870
- });
871
-
872
- if (isCountingDown) {
873
- ctx.fillStyle = 'rgba(0, 0, 0, 0.5)';
874
- ctx.fillRect(0, 0, canvas.width, canvas.height);
875
- }
876
- }
877
 
878
- function gameLoop() {
879
- if (!gameOver) {
880
- updateGame();
881
- drawGame();
882
- requestAnimationFrame(gameLoop);
883
  }
884
  }
885
 
886
- document.addEventListener('DOMContentLoaded', () => {
887
- // DOM 요소 참조
888
- const titleScreen = document.getElementById('titleScreen');
889
- const startButton = document.getElementById('startButton');
890
- const instructions = document.getElementById('instructions');
891
- const weaponInfo = document.getElementById('weaponInfo');
892
- const gameCanvas = document.getElementById('gameCanvas');
893
- const nextRoundBtn = document.getElementById('nextRound'); // 다음 라운드 버튼 참조 추가
894
- const restartBtn = document.getElementById('restart'); // 재시작 버튼 참조 추가
895
-
896
- // 초기 상태
897
- instructions.style.display = 'none';
898
- weaponInfo.style.display = 'none';
899
- gameCanvas.style.display = 'none';
900
-
901
- // 디버깅용 확인 로그
902
- console.log("DOM Loaded");
903
-
904
- // 타이틀 음악 재생
905
- bgm.play().catch(err => console.error("Error playing title music:", err));
906
-
907
- // Start Button 클릭 이벤트
908
- startButton.addEventListener('click', () => {
909
- console.log("Start Button Clicked");
910
- if (!titleScreen || !instructions || !weaponInfo || !gameCanvas) {
911
- console.error("DOM elements not found");
912
- return;
913
- }
914
-
915
- titleScreen.style.display = 'none';
916
- instructions.style.display = 'block';
917
- weaponInfo.style.display = 'block';
918
- gameCanvas.style.display = 'block';
919
-
920
- bgm.pause();
921
- bgm.src = 'BGM2.ogg';
922
- bgm.play().catch(err => console.error("Error playing game music:", err));
923
-
924
- initRound();
925
- gameLoop();
926
- });
927
-
928
- // [추가] 다음 라운드 버튼 클릭 이벤트
929
- nextRoundBtn.addEventListener('click', () => {
930
- currentRound++;
931
- nextRoundBtn.style.display = 'none';
932
- document.getElementById('shop').style.display = 'none';
933
- initRound();
934
- console.log(`Starting Round ${currentRound}`); // 라운드 진행 확인용 로그
935
- });
936
-
937
- // [추가] 재시작 버튼 클릭 이벤트
938
- restartBtn.addEventListener('click', () => {
939
- gameOver = false;
940
- currentRound = 1;
941
- isBossStage = false;
942
- player.health = player.maxHealth;
943
- gold = 0;
944
- restartBtn.style.display = 'none';
945
- document.getElementById('winMessage').style.display = 'none';
946
- document.getElementById('shop').style.display = 'none';
947
- initRound();
948
- });
949
-
950
- // [추가] 보스 버튼 클릭 이벤트
951
- document.getElementById('bossButton').addEventListener('click', () => {
952
- startBossStage();
953
- });
954
-
955
- // 이미지 로드 확인
956
- Promise.all([
957
- new Promise(resolve => backgroundImg.onload = resolve),
958
- new Promise(resolve => playerImg.onload = resolve),
959
- new Promise(resolve => enemyImg.onload = resolve)
960
- ]).then(() => {
961
- console.log("Assets loaded successfully");
962
- }).catch(err => console.error("Error loading assets:", err));
963
-
964
- // 창 크기 변경 시 캔버스 크기 업데이트
965
- window.addEventListener('resize', () => {
966
- canvas.width = window.innerWidth;
967
- canvas.height = window.innerHeight;
968
- });
969
- });
970
- </script>
971
  </body>
972
  </html>
 
102
  }
103
  </style>
104
  </head>
105
+ <body>
106
+ <div id="instructions">
107
  Controls:<br>
108
  WASD - Move tank<br>
109
  Mouse - Aim<br>
 
116
  <button id="nextRound" class="button">Next Round</button>
117
  <button id="restart" class="button">Restart Game</button>
118
  <canvas id="gameCanvas"></canvas>
119
+ <div id="titleScreen">
120
+ <h1>TANK WAR</h1>
121
+ <button id="startButton">START GAME</button>
122
+ </div>
123
+
124
+ <div id="shop" style="display:none; position:fixed; top:50%; left:50%; transform:translate(-50%,-50%); background:rgba(0,0,0,0.9); padding:20px; border-radius:10px; color:white; z-index:1000;">
125
+ <h2>Tank Shop</h2>
126
+ <div style="display:flex; gap:20px;">
127
+ <div id="tank1" style="text-align:center;">
128
+ <h3>PZ.IV</h3>
129
+ <img src="player2.png" width="90" height="50">
130
+ <p>300 Gold</p>
131
+ <p style="color: #4CAF50;">+50% HP</p>
132
+ <button onclick="buyTank('player2.png', 300, 'tank1')">Buy</button>
133
+ </div>
134
+ <div id="tank2" style="text-align:center;">
135
+ <h3>TIGER</h3>
136
+ <img src="player3.png" width="110" height="55">
137
+ <p>500 Gold</p>
138
+ <p style="color: #4CAF50;">+100% HP</p>
139
+ <p style="color: #ff6b6b;">-30% Speed</p>
140
+ <button onclick="buyTank('player3.png', 500, 'tank2')">Buy</button>
141
+ </div>
142
+ <div id="bf109" style="text-align:center;">
143
+ <h3>BF-109</h3>
144
+ <img src="bf109.png" width="100" height="100">
145
+ <p>1000 Gold</p>
146
+ <p style="color: #4CAF50;">Air support from BF-109</p>
147
+ <button onclick="buyBF109()">Buy</button>
148
+ </div>
149
+ <div id="ju87" style="text-align:center;">
150
+ <h3>JU-87</h3>
151
+ <img src="ju87.png" width="100" height="100">
152
+ <p>1500 Gold</p>
153
+ <p style="color: #4CAF50;">Get ju-87 air support</p>
154
+ <button onclick="buyJU87()">Buy</button>
155
+ </div>
156
+ <div id="apcr" style="text-align:center;">
157
+ <h3>APCR</h3>
158
+ <img src="apcr.png" width="80" height="20">
159
+ <p>1000 Gold</p>
160
+ <p style="color: #4CAF50;">+100% Bullet Speed</p>
161
+ <button onclick="buyAPCR()">Buy</button>
162
+ </div>
163
  </div>
 
 
 
 
 
 
 
164
  </div>
 
165
  <button id="bossButton" class="button">Fight Boss!</button>
166
  <div id="winMessage" class="button" style="font-size: 72px; background: none;">You Win!</div>
167
+ <script>
168
+ const canvas = document.getElementById('gameCanvas');
169
+ const ctx = canvas.getContext('2d');
170
+ const nextRoundBtn = document.getElementById('nextRound');
171
+ const restartBtn = document.getElementById('restart');
172
+ const weaponInfo = document.getElementById('weaponInfo');
173
+ const countdownEl = document.getElementById('countdown');
174
+ const bossButton = document.getElementById('bossButton');
175
+ canvas.width = window.innerWidth;
176
+ canvas.height = window.innerHeight;
177
+
178
+ // Game state
179
+ let currentRound = 1;
180
+ let gameOver = false;
181
+ let currentWeapon = 'cannon';
182
+ let enemies = [];
183
+ let bullets = [];
184
+ let items = [];
185
+ let lastShot = 0;
186
+ let isCountingDown = true;
187
+ let countdownTime = 3;
188
+ let autoFire = false;
189
+ let gold = 0;
190
+ let isBossStage = false;
191
+ let effects = [];
192
+ let hasAPCR = false;
193
+ let hasBF109 = false;
194
+ let hasJU87 = false;
195
+ let lastJU87Spawn = 0;
196
+ let supportUnits = [];
197
+ let lastSupportSpawn = 0;
198
+
199
+ // Load assets
200
+ const backgroundImg = new Image();
201
+ backgroundImg.src = 'city.png';
202
+ const playerImg = new Image();
203
+ playerImg.src = 'player.png';
204
+ const enemyImg = new Image();
205
+ enemyImg.src = 'enemy.png';
206
+ const bulletImg = new Image();
207
+ bulletImg.src = 'apcr2.png';
208
+
209
+ // Audio setup
210
+ const cannonSound = new Audio('firemn.ogg');
211
+ const machinegunSound = new Audio('firemg.ogg');
212
+ const enemyFireSound = new Audio('fireenemy.ogg');
213
+ let bgm = new Audio('title.ogg');
214
+ const countSound = new Audio('count.ogg');
215
+ const deathSound = new Audio('death.ogg');
216
+ bgm.loop = true;
217
+ enemyFireSound.volume = 0.5;
218
+
219
+ const weapons = {
220
+ cannon: {
221
+ fireRate: 1000,
222
+ damage: 0.25,
223
+ bulletSize: 5,
224
+ sound: cannonSound
225
+ },
226
+ machinegun: {
227
+ fireRate: 200,
228
+ damage: 0.05,
229
+ bulletSize: 2,
230
+ sound: machinegunSound
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
231
  }
232
+ };
233
+
234
+ // Player setup
235
+ const player = {
236
+ x: canvas.width/2,
237
+ y: canvas.height/2,
238
+ speed: 5,
239
+ angle: 0,
240
+ width: 100,
241
+ height: 45,
242
+ health: 1000,
243
+ maxHealth: 1000
244
+ };
245
+
246
+ // 다음 라운드 버튼 이벤트 리스너 추가
247
+ nextRoundBtn.addEventListener('click', () => {
248
+ currentRound++;
249
+ document.getElementById('shop').style.display = 'none';
250
+ nextRoundBtn.style.display = 'none';
251
+ initRound();
252
+ });
253
+
254
+ // 재시작 버튼 이벤트 리스너 추가
255
+ restartBtn.addEventListener('click', () => {
256
+ currentRound = 1;
257
+ gameOver = false;
258
+ gold = 0;
259
+ player.health = player.maxHealth;
260
+ restartBtn.style.display = 'none';
261
+ document.getElementById('winMessage').style.display = 'none';
262
+ initRound();
263
+ });
264
+
265
+ // 보스 버튼 이벤트 리스너 추가
266
+ bossButton.addEventListener('click', () => {
267
+ startBossStage();
268
+ });
269
+
270
+ function startCountdown() {
271
+ isCountingDown = true;
272
+ countdownTime = 3;
273
+ countdownEl.style.display = 'block';
274
+ countdownEl.textContent = countdownTime;
275
+ bgm.pause();
276
+ countSound.play();
277
+ const countInterval = setInterval(() => {
278
+ countdownTime--;
279
+ if(countdownTime <= 0) {
280
+ clearInterval(countInterval);
281
+ countdownEl.style.display = 'none';
282
+ isCountingDown = false;
283
+ bgm.play();
284
+ }
285
+ countdownEl.textContent = countdownTime > 0 ? countdownTime : 'GO!';
286
+ }, 1000);
287
+ }
288
+ class Effect {
289
  constructor(x, y, duration, type, angle = 0, parent = null) {
290
  this.x = x;
291
  this.y = y;
 
293
  this.duration = duration;
294
  this.type = type;
295
  this.angle = angle;
296
+ this.parent = parent;
297
+ this.offset = { x: Math.cos(angle) * 30, y: Math.sin(angle) * 30 };
298
  this.img = new Image();
299
  this.img.src = type === 'death' ? 'bang.png' : 'fire2.png';
300
  }
 
311
  return Date.now() - this.startTime > this.duration;
312
  }
313
  }
314
+
315
+ class SupportUnit {
316
  constructor(yPosition) {
317
  this.x = 0;
318
  this.y = yPosition;
 
323
  this.angle = 0;
324
  this.img = new Image();
325
  this.img.src = 'bf109.png';
326
+ this.hasPlayedSound = false;
327
+ this.mgSound = null;
328
  }
329
 
330
  update() {
 
331
  this.x += this.speed;
332
 
 
333
  if (isCountingDown) {
334
  if (this.mgSound) {
335
  this.mgSound.pause();
 
338
  this.hasPlayedSound = false;
339
  }
340
 
 
341
  const now = Date.now();
342
  if (now - this.lastShot > 200 && !isCountingDown) {
343
  this.shoot();
 
347
  }
348
 
349
  shoot() {
 
350
  if (!this.hasPlayedSound) {
351
  const firstSound = new Audio('bf109mg.ogg');
352
  firstSound.volume = 1.0;
 
354
  this.hasPlayedSound = true;
355
  }
356
 
 
357
  if (!isCountingDown) {
358
  const shootSound = new Audio('bf109mgse.ogg');
359
+ shootSound.volume = 0.5;
360
  shootSound.play();
361
  }
362
 
 
371
  });
372
  }
373
  }
374
+
375
+ class JU87 {
376
  constructor() {
377
  this.x = canvas.width;
378
  this.y = 50;
 
389
  this.hasPlayedMGSound = false;
390
  this.isReturning = false;
391
  this.circleAngle = 0;
392
+ this.returningToCenter = false;
393
  }
394
 
395
  selectTarget() {
 
397
  enemies[Math.floor(Math.random() * enemies.length)] : null;
398
  }
399
 
400
+ shoot() {
401
+ if (!this.hasPlayedMGSound && !isCountingDown) {
402
+ const mgSound = new Audio('ju87mg.ogg');
403
+ mgSound.volume = 1.0;
404
+ mgSound.currentTime = 0;
405
+ mgSound.play().catch(error => console.error('Audio play failed:', error));
406
+ this.hasPlayedMGSound = true;
407
+ }
 
 
 
 
 
 
 
 
 
408
 
409
+ [[20, 50], [80, 50]].forEach(([x, y]) => {
410
+ const offsetX = x - 50;
411
+ const offsetY = y - 50;
412
+
413
+ const rotatedX = this.x + (Math.cos(this.angle) * offsetX - Math.sin(this.angle) * offsetY);
414
+ const rotatedY = this.y + (Math.sin(this.angle) * offsetX + Math.cos(this.angle) * offsetY);
415
+
416
+ bullets.push({
417
+ x: rotatedX,
418
+ y: rotatedY,
419
+ angle: this.angle,
420
+ speed: 10,
421
+ isEnemy: false,
422
+ damage: weapons.machinegun.damage * 2,
423
+ size: weapons.machinegun.bulletSize
424
+ });
425
  });
426
+ }
427
+
428
  update() {
429
  if (!this.hasPlayedSound) {
430
  const sirenSound = new Audio('ju87siren.ogg');
 
488
  return true;
489
  }
490
  }
491
+ class Enemy {
492
+ constructor(isBoss = false) {
493
+ this.x = Math.random() * canvas.width;
494
+ this.y = Math.random() * canvas.height;
495
+ this.health = isBoss ? 15000 : 1000;
496
+ this.maxHealth = this.health;
497
+ this.speed = isBoss ? 1 : 2;
498
+ this.lastShot = 0;
499
+ this.shootInterval = isBoss ? 1000 : 1000;
500
+ this.angle = 0;
501
+ this.width = 100;
502
+ this.height = 45;
503
+ this.moveTimer = 0;
504
+ this.moveInterval = Math.random() * 2000 + 1000;
505
+ this.moveAngle = Math.random() * Math.PI * 2;
506
+ this.isBoss = isBoss;
507
+
508
+ if (isBoss) {
509
+ this.enemyImg = new Image();
510
+ this.enemyImg.src = 'boss.png';
511
+ } else if (currentRound >= 7) {
512
+ this.enemyImg = new Image();
513
+ this.enemyImg.src = 'enemy3.png';
514
+ } else if (currentRound >= 4) {
515
+ this.enemyImg = new Image();
516
+ this.enemyImg.src = 'enemy2.png';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
517
  }
518
+ }
519
+
520
+ update() {
521
+ if(isCountingDown) return;
522
+ const now = Date.now();
523
+
524
+ if (now - this.moveTimer > this.moveInterval) {
525
+ this.moveAngle = Math.random() * Math.PI * 2;
526
+ this.moveTimer = now;
527
  }
528
+ this.x += Math.cos(this.moveAngle) * this.speed;
529
+ this.y += Math.sin(this.moveAngle) * this.speed;
530
+ this.x = Math.max(this.width/2, Math.min(canvas.width - this.width/2, this.x));
531
+ this.y = Math.max(this.height/2, Math.min(canvas.height - this.height/2, this.y));
532
+ this.angle = Math.atan2(player.y - this.y, player.x - this.x);
533
+
534
+ if (now - this.lastShot > this.shootInterval && !isCountingDown) {
535
+ this.shoot();
536
+ this.lastShot = now;
537
+ }
538
+ }
539
+
540
+ shoot() {
541
+ const sound = this.isBoss ? new Audio('firemn.ogg') : enemyFireSound.cloneNode();
542
+ sound.play();
543
+
544
+ effects.push(new Effect(
545
+ this.x + Math.cos(this.angle) * 30,
546
+ this.y + Math.sin(this.angle) * 30,
547
+ 500,
548
+ 'fire',
549
+ this.angle,
550
+ this
551
+ ));
552
+
553
+ bullets.push({
554
+ x: this.x + Math.cos(this.angle) * 30,
555
+ y: this.y + Math.sin(this.angle) * 30,
556
+ angle: this.angle,
557
+ speed: this.isBoss ? 10 : 5,
558
+ isEnemy: true,
559
+ size: this.isBoss ? 5 : 3,
560
+ damage: this.isBoss ? 300 : 150
561
+ });
562
+ }
563
+ }
564
+
565
+ nextRoundBtn.addEventListener('click', () => {
566
+ currentRound++;
567
+ nextRoundBtn.style.display = 'none';
568
+ document.getElementById('shop').style.display = 'none';
569
+ initRound();
570
+ });
571
+
572
+ restartBtn.addEventListener('click', () => {
573
+ currentRound = 1;
574
+ gameOver = false;
575
+ player.health = player.maxHealth;
576
+ gold = 0;
577
+ hasAPCR = false;
578
+ hasBF109 = false;
579
+ hasJU87 = false;
580
+ document.getElementById('apcr').style.display = 'block';
581
+ document.getElementById('bf109').style.display = 'block';
582
+ document.getElementById('ju87').style.display = 'block';
583
+ document.getElementById('tank1').style.display = 'block';
584
+ document.getElementById('tank2').style.display = 'block';
585
+ document.getElementById('winMessage').style.display = 'none';
586
+ restartBtn.style.display = 'none';
587
+ playerImg.src = 'player.png';
588
+ player.maxHealth = defaultPlayerStats.maxHealth;
589
+ player.speed = defaultPlayerStats.speed;
590
+ player.width = defaultPlayerStats.width;
591
+ player.height = defaultPlayerStats.height;
592
+ bgm.src = 'BGM2.ogg';
593
+ initRound();
594
+ });
595
+
596
+ bossButton.addEventListener('click', () => {
597
+ startBossStage();
598
+ });
599
+
600
+ function showShop() {
601
+ document.getElementById('shop').style.display = 'block';
602
+ }
603
+
604
  const defaultPlayerStats = {
605
  maxHealth: 1000,
606
  speed: 5,
607
  width: 100,
608
  height: 45
609
  };
610
+
611
  function buyTank(tankImg, cost, tankId) {
612
  if (gold >= cost) {
613
  gold -= cost;
 
615
  document.getElementById(tankId).style.display = 'none';
616
  document.getElementById('shop').style.display = 'none';
617
 
618
+ if (tankId === 'tank1') {
619
  player.maxHealth = 1500;
620
+ player.speed = defaultPlayerStats.speed;
621
+ player.width = 90;
622
+ player.height = 50;
623
  }
624
+ else if (tankId === 'tank2') {
625
  player.maxHealth = 2000;
626
  player.speed = defaultPlayerStats.speed * 0.7;
627
+ player.width = 100;
628
+ player.height = 45;
629
  }
630
 
631
  player.health = player.maxHealth;
632
  }
633
  }
634
+
635
+ function buyAPCR() {
636
  if (gold >= 1000 && !hasAPCR) {
637
  gold -= 1000;
638
  hasAPCR = true;
 
640
  document.getElementById('shop').style.display = 'none';
641
  }
642
  }
643
+
644
+ function buyBF109() {
645
  if (gold >= 1000 && !hasBF109) {
646
  gold -= 1000;
647
  hasBF109 = true;
 
649
  document.getElementById('shop').style.display = 'none';
650
  }
651
  }
652
+
653
+ function buyJU87() {
654
  if (gold >= 1500 && !hasJU87) {
655
  gold -= 1500;
656
  hasJU87 = true;
657
  document.getElementById('ju87').style.display = 'none';
658
  document.getElementById('shop').style.display = 'none';
659
+ lastJU87Spawn = Date.now();
660
  }
661
  }
662
+ function initRound() {
663
+ enemies = [];
664
+ for(let i = 0; i < 1 * currentRound; i++) {
665
+ enemies.push(new Enemy());
666
+ }
667
+ player.health = player.maxHealth;
668
+ bullets = [];
669
+ items = [];
670
+ supportUnits = [];
671
+ lastSupportSpawn = 0;
672
+
673
+ startCountdown();
674
+
675
+ setTimeout(() => {
676
+ if (hasJU87) {
677
+ supportUnits.push(new JU87());
678
+ lastJU87Spawn = Date.now();
679
  }
680
+ }, 3000);
681
+ }
682
+ function startBossStage() {
683
+ isBossStage = true;
684
+ enemies = [];
685
+ enemies.push(new Enemy(true));
686
+ player.health = player.maxHealth;
687
+ bullets = [];
688
+ items = [];
689
+ document.getElementById('bossButton').style.display = 'none';
690
+ bgm.src = 'BGM.ogg';
691
+ startCountdown();
 
 
 
 
692
  }
693
+ function updateGame() {
694
+ if(gameOver) return;
695
+ if(!isCountingDown) {
696
+ if(keys['w']) player.y -= player.speed;
697
+ if(keys['s']) player.y += player.speed;
698
+ if(keys['a']) player.x -= player.speed;
699
+ if(keys['d']) player.x += player.speed;
700
+ player.x = Math.max(player.width/2, Math.min(canvas.width - player.width/2, player.x));
701
+ player.y = Math.max(player.height/2, Math.min(canvas.height - player.height/2, player.y));
702
+ fireBullet();
703
+ }
704
+ if (hasBF109 && !isCountingDown) {
705
+ const now = Date.now();
706
+ if (now - lastSupportSpawn > 10000) {
707
+ supportUnits.push(
708
+ new SupportUnit(canvas.height * 0.2),
709
+ new SupportUnit(canvas.height * 0.5),
710
+ new SupportUnit(canvas.height * 0.8)
711
+ );
712
+ lastSupportSpawn = now;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
713
  }
714
+ }
715
 
716
+ if (hasJU87 && !isCountingDown) {
717
+ const now = Date.now();
718
+ if (now - lastJU87Spawn > 15000) {
719
+ supportUnits.push(new JU87());
720
+ lastJU87Spawn = now;
 
 
 
 
 
 
721
  }
722
+ }
723
 
724
+ supportUnits = supportUnits.filter(unit => unit.update());
 
 
 
 
 
 
 
725
 
726
+ enemies.forEach(enemy => enemy.update());
 
727
 
728
+ if(!isCountingDown) {
729
+ bullets = bullets.filter(bullet => {
730
+ bullet.x += Math.cos(bullet.angle) * bullet.speed;
731
+ bullet.y += Math.sin(bullet.angle) * bullet.speed;
732
+ if(!bullet.isEnemy) {
733
+ enemies = enemies.filter(enemy => {
734
+ const dist = Math.hypot(bullet.x - enemy.x, bullet.y - enemy.y);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
735
  if(dist < 30) {
736
+ let damage = currentWeapon === 'cannon' ? 250 : 50;
737
+ enemy.health -= damage;
738
+ if(enemy.health <= 0) {
739
+ spawnHealthItem(enemy.x, enemy.y);
740
+ gold += 100;
741
+ effects.push(new Effect(enemy.x, enemy.y, 1000, 'death'));
742
+ deathSound.cloneNode().play();
743
+ return false;
744
  }
745
+ return true;
746
  }
747
+ return true;
748
+ });
749
+ } else {
750
+ const dist = Math.hypot(bullet.x - player.x, bullet.y - player.y);
 
 
 
751
  if(dist < 30) {
752
+ player.health -= bullet.damage || 100;
753
+ if(player.health <= 0) {
754
+ gameOver = true;
755
+ restartBtn.style.display = 'block';
756
+ effects.push(new Effect(player.x, player.y, 1000, 'death'));
757
+ deathSound.cloneNode().play();
758
+ }
759
  return false;
760
  }
761
+ }
762
+ return bullet.x >= 0 && bullet.x <= canvas.width &&
763
+ bullet.y >= 0 && bullet.y <= canvas.height;
764
+ });
765
+
766
+ items = items.filter(item => {
767
+ const dist = Math.hypot(item.x - player.x, item.y - player.y);
768
+ if(dist < 30) {
769
+ player.health = Math.min(player.health + 200, player.maxHealth);
770
+ return false;
771
+ }
772
+ return true;
773
+ });
774
+
775
+ if(enemies.length === 0) {
776
+ if (!isBossStage) {
777
+ if(currentRound < 10) {
778
+ nextRoundBtn.style.display = 'block';
779
+ showShop();
780
  } else {
781
+ bossButton.style.display = 'block';
 
 
 
 
 
782
  }
783
+ } else {
784
+ gameOver = true;
785
+ document.getElementById('winMessage').style.display = 'block';
786
+ restartBtn.style.display = 'block';
787
+ bgm.pause();
788
+ const victorySound = new Audio('victory.ogg');
789
+ victorySound.play();
790
  }
791
  }
792
  }
793
+ }
794
+ function spawnHealthItem(x, y) {
795
+ items.push({x, y});
796
+ }
797
+ function drawHealthBar(x, y, health, maxHealth, width, height, color) {
798
+ ctx.fillStyle = '#333';
799
+ ctx.fillRect(x - width/2, y - height/2, width, height);
800
+ ctx.fillStyle = color;
801
+ ctx.fillRect(x - width/2, y - height/2, width * (health/maxHealth), height);
802
+ }
803
+ function gameLoop() {
804
+ if (!gameOver) {
805
+ updateGame();
806
+ drawGame();
807
+ requestAnimationFrame(gameLoop);
808
+ }
809
+ }
810
+ document.addEventListener('DOMContentLoaded', () => {
811
+ const titleScreen = document.getElementById('titleScreen');
812
+ const startButton = document.getElementById('startButton');
813
+ const instructions = document.getElementById('instructions');
814
+ const weaponInfo = document.getElementById('weaponInfo');
815
+ const gameCanvas = document.getElementById('gameCanvas');
816
+ instructions.style.display = 'none';
817
+ weaponInfo.style.display = 'none';
818
+ gameCanvas.style.display = 'none';
819
+
820
+ console.log("DOM Loaded");
821
+
822
+ bgm.play().catch(err => console.error("Error playing title music:", err));
823
+
824
+ startButton.addEventListener('click', () => {
825
+ console.log("Start Button Clicked");
826
+ if (!titleScreen || !instructions || !weaponInfo || !gameCanvas) {
827
+ console.error("DOM elements not found");
828
+ return;
829
+ }
830
+
831
+ titleScreen.style.display = 'none';
832
+ instructions.style.display = 'block';
833
+ weaponInfo.style.display = 'block';
834
+ gameCanvas.style.display = 'block';
835
+
836
+ bgm.pause();
837
+ bgm.src = 'BGM2.ogg';
838
+ bgm.play().catch(err => console.error("Error playing game music:", err));
839
+
840
+ initRound();
841
+ gameLoop();
842
+ });
843
+
844
+ Promise.all([
845
+ new Promise(resolve => backgroundImg.onload = resolve),
846
+ new Promise(resolve => playerImg.onload = resolve),
847
+ new Promise(resolve => enemyImg.onload = resolve)
848
+ ]).then(() => {
849
+ console.log("Assets loaded successfully");
850
+ }).catch(err => console.error("Error loading assets:", err));
851
+
852
+ window.addEventListener('resize', () => {
853
+ canvas.width = window.innerWidth;
854
+ canvas.height = window.innerHeight;
855
+ });
856
+ });
857
+ // 키보드 이벤트 리스너
858
+ const keys = {};
859
+ document.addEventListener('keydown', e => {
860
+ keys[e.key] = true;
861
+ if(e.key.toLowerCase() === 'c') {
862
+ currentWeapon = currentWeapon === 'cannon' ? 'machinegun' : 'cannon';
863
+ weaponInfo.textContent = `Current Weapon: ${currentWeapon.charAt(0).toUpperCase() + currentWeapon.slice(1)}`;
864
+ } else if(e.key.toLowerCase() === 'r') {
865
+ autoFire = !autoFire;
866
+ }
867
+ });
868
+
869
+ document.addEventListener('keyup', e => keys[e.key] = false);
870
+
871
+ // 마우스 이벤트 리스너
872
+ canvas.addEventListener('mousemove', (e) => {
873
+ player.angle = Math.atan2(e.clientY - player.y, e.clientX - player.x);
874
+ });
875
+
876
+ // 총알 발사 함수
877
+ function fireBullet() {
878
+ if(isCountingDown) return;
879
+ const weapon = weapons[currentWeapon];
880
+ const now = Date.now();
881
+ if ((keys[' '] || autoFire) && now - lastShot > weapon.fireRate) {
882
+ weapon.sound.cloneNode().play();
883
+ effects.push(new Effect(
884
+ player.x + Math.cos(player.angle) * 30,
885
+ player.y + Math.sin(player.angle) * 30,
886
+ 500,
887
+ 'fire',
888
+ player.angle,
889
+ player
890
+ ));
891
+
892
+ bullets.push({
893
+ x: player.x + Math.cos(player.angle) * 30,
894
+ y: player.y + Math.sin(player.angle) * 30,
895
+ angle: player.angle,
896
+ speed: hasAPCR ? 20 : 10,
897
+ isEnemy: false,
898
+ damage: weapon.damage,
899
+ size: weapon.bulletSize,
900
+ isAPCR: hasAPCR
901
+ });
902
+ lastShot = now;
903
+ }
904
+ }
905
+
906
+ // 게임 렌더링 함수
907
+ function drawGame() {
908
  ctx.clearRect(0, 0, canvas.width, canvas.height);
909
  const pattern = ctx.createPattern(backgroundImg, 'repeat');
910
  ctx.fillStyle = pattern;
911
  ctx.fillRect(0, 0, canvas.width, canvas.height);
912
+
913
  // 플레이어 그리기
914
  ctx.save();
915
  ctx.translate(player.x, player.y);
916
  ctx.rotate(player.angle);
917
  ctx.drawImage(playerImg, -player.width/2, -player.height/2, player.width, player.height);
918
  ctx.restore();
919
+
920
+ // 체력바 그리기
921
  drawHealthBar(canvas.width/2, 30, player.health, player.maxHealth, 200, 20, 'green');
922
+
923
  // 적 그리기
924
  enemies.forEach(enemy => {
925
  ctx.save();
 
930
  ctx.restore();
931
  drawHealthBar(enemy.x, enemy.y - 40, enemy.health, enemy.maxHealth, 60, 5, 'red');
932
  });
933
+
934
+ // 지원 유닛 그리기
935
+ supportUnits.forEach(unit => {
936
  ctx.save();
937
  ctx.translate(unit.x, unit.y);
938
  ctx.rotate(unit.angle);
939
  ctx.drawImage(unit.img, -unit.width/2, -unit.height/2, unit.width, unit.height);
940
  ctx.restore();
941
  });
942
+
943
+ // 총알 그리기
944
+ bullets.forEach(bullet => {
945
+ if (bullet.isEnemy || !bullet.isAPCR) {
946
+ ctx.beginPath();
947
+ ctx.fillStyle = bullet.isEnemy ? 'red' : 'blue';
948
+ ctx.arc(bullet.x, bullet.y, bullet.size, 0, Math.PI * 2);
949
+ ctx.fill();
950
+ } else {
951
+ ctx.save();
952
+ ctx.translate(bullet.x, bullet.y);
953
+ ctx.rotate(bullet.angle);
954
+ const width = currentWeapon === 'machinegun' ? 10 : 20;
955
+ const height = currentWeapon === 'machinegun' ? 5 : 10;
956
+ ctx.drawImage(bulletImg, -width/2, -height/2, width, height);
957
+ ctx.restore();
958
+ }
959
+ });
960
+
961
  // 아이템 그리기
962
  items.forEach(item => {
963
  ctx.beginPath();
 
965
  ctx.arc(item.x, item.y, 10, 0, Math.PI * 2);
966
  ctx.fill();
967
  });
968
+
969
  // UI 그리기
970
  ctx.fillStyle = 'white';
971
+ ctx.font = '24px Arial';
972
+ ctx.fillText(`Round ${currentRound}/10`, 10, 30);
973
+ ctx.fillText(`Gold: ${gold}`, 10, 60);
974
+
975
+ // 이펙트 그리기
976
+ effects = effects.filter(effect => !effect.isExpired());
977
+ effects.forEach(effect => {
978
+ effect.update();
979
+ ctx.save();
980
+ ctx.translate(effect.x, effect.y);
981
+ if (effect.type === 'fire') ctx.rotate(effect.angle);
982
+ const size = effect.type === 'death' ? 75 : 42;
983
+ ctx.drawImage(effect.img, -size / 2, -size / 2, size, size);
984
+ ctx.restore();
985
+ });
 
 
 
 
 
 
986
 
987
+ if (isCountingDown) {
988
+ ctx.fillStyle = 'rgba(0, 0, 0, 0.5)';
989
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
 
 
990
  }
991
  }
992
 
993
+ </script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
994
  </body>
995
  </html>