cutechicken commited on
Commit
7d282f7
·
verified ·
1 Parent(s): 75e5c0c

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +90 -397
index.html CHANGED
@@ -246,6 +246,18 @@
246
  z-index: 1000;
247
  font-size: 18px;
248
  }
 
 
 
 
 
 
 
 
 
 
 
 
249
  #leaderboard {
250
  position: fixed;
251
  top: 0;
@@ -365,6 +377,9 @@
365
  </div>
366
  <div id="weaponInfo">Current Weapon: Cannon</div>
367
  <div id="scoreBoard">Score: 0</div>
 
 
 
368
  <div id="countdown">3</div>
369
  <button id="nextRound" class="button">Next Round</button>
370
  <button id="restart" class="button">Restart Game</button>
@@ -401,7 +416,8 @@
401
  </div>
402
  <div class="button-container">
403
  <button id="leaderboardButton">Leaderboard</button>
404
- <button id="installPwaButton" style="display: none;">Install App</button>
 
405
  </div>
406
  <a href="https://discord.gg/openfreeai" target="_blank" class="discord-link">Join our Discord community: discord.gg/openfreeai</a>
407
  </div>
@@ -531,6 +547,7 @@
531
  const canvas = document.getElementById('gameCanvas');
532
  const ctx = canvas.getContext('2d');
533
  const scoreBoardElement = document.getElementById('scoreBoard');
 
534
 
535
  canvas.width = window.innerWidth;
536
  canvas.height = window.innerHeight;
@@ -544,7 +561,7 @@
544
  let bullets = [];
545
  let items = [];
546
  let lastShot = 0;
547
- let isCountingDown = true;
548
  let countdownTime = 3;
549
  let autoFire = false;
550
  let gold = 0;
@@ -636,6 +653,7 @@
636
  document.getElementById('weaponInfo').style.display = 'block';
637
  document.getElementById('gameCanvas').style.display = 'block';
638
  document.getElementById('scoreBoard').style.display = 'block';
 
639
 
640
  // 기존 BGM 정지
641
  bgm.pause();
@@ -663,6 +681,7 @@
663
  gameOver = false;
664
  gold = 0;
665
  score = 0;
 
666
  updateScoreDisplay();
667
  hasAPCR = false;
668
  hasBF109 = false;
@@ -687,7 +706,7 @@
687
  function startCountdown() {
688
  isCountingDown = true;
689
  countdownTime = 3;
690
- countdownEl = document.getElementById('countdown');
691
  countdownEl.style.display = 'block';
692
  countdownEl.textContent = countdownTime;
693
  bgm.pause();
@@ -745,11 +764,10 @@
745
  // 적 생성
746
  enemies = [];
747
  if (currentStage === 3) {
748
- // 배경 이미지 변경
749
  if (currentRound >= 11) {
750
  backgroundImg.src = 'city3a.png';
751
- // 11라운드 이후 일반 BGM으로 복귀
752
- if (!isBossStage) { // 보스전이 아닐 때만
753
  bgm.pause();
754
  bgm = new Audio('BGM4.ogg');
755
  bgm.volume = 0.7;
@@ -758,40 +776,30 @@
758
  }
759
  }
760
 
761
- // 보스 라운드 체크
762
  if (currentRound === 10 || currentRound === 20) {
763
- enemies = [new Enemy(true, false)];
764
- // 보스 BGM 설정
765
  bgm.pause();
766
  bgm = new Audio('BGM.ogg');
767
  bgm.volume = 0.7;
768
  bgm.loop = true;
769
  bgm.play();
770
  } else {
771
- // 11라운드 이후에는 적의 수가 10으로 고정
772
  const enemyCount = Math.min(currentRound, 10);
773
- // 일반 적 생성
774
  for (let i = 0; i < enemyCount; i++) {
775
  enemies.push(new Enemy(false, false));
776
  }
777
-
778
- // 정예 적 생성 (2라운드마다 1개씩 증가)
779
  const eliteCount = Math.floor((currentRound + 1) / 2);
780
  for (let i = 0; i < eliteCount; i++) {
781
  enemies.push(new Enemy(false, true));
782
  }
783
  }
784
 
785
- // 판터 유닛 추가 로직
786
  let currentPanzers = allyUnits.length;
787
  let addPanzers = 2;
788
-
789
- if (currentRound === 1) {
790
- addPanzers = 1;
791
- }
792
-
793
  let newPanzers = Math.min(5 - currentPanzers, addPanzers);
794
-
795
  for (let i = 0; i < newPanzers; i++) {
796
  allyUnits.push(new PanzerV());
797
  }
@@ -801,14 +809,12 @@
801
  for(let i = 0; i < enemyCount; i++) {
802
  let x, y;
803
  const edge = Math.floor(Math.random() * 4);
804
-
805
  switch(edge) {
806
  case 0: x = Math.random() * canvas.width; y = 0; break;
807
  case 1: x = canvas.width; y = Math.random() * canvas.height; break;
808
  case 2: x = Math.random() * canvas.width; y = canvas.height; break;
809
  case 3: x = 0; y = Math.random() * canvas.height; break;
810
  }
811
-
812
  const enemy = new Enemy();
813
  enemy.x = x;
814
  enemy.y = y;
@@ -819,14 +825,12 @@
819
  for(let i = 0; i < enemyCount; i++) {
820
  let x, y;
821
  const edge = Math.floor(Math.random() * 4);
822
-
823
  switch(edge) {
824
  case 0: x = Math.random() * canvas.width; y = 0; break;
825
  case 1: x = canvas.width; y = Math.random() * canvas.height; break;
826
  case 2: x = Math.random() * canvas.width; y = canvas.height; break;
827
  case 3: x = 0; y = Math.random() * canvas.height; break;
828
  }
829
-
830
  const enemy = new Enemy();
831
  enemy.x = x;
832
  enemy.y = y;
@@ -834,7 +838,6 @@
834
  }
835
  }
836
 
837
- // 게임 상태 초기화
838
  player.health = player.maxHealth;
839
  bullets = [];
840
  items = [];
@@ -844,14 +847,12 @@
844
  lastSpitfireSpawn = Date.now();
845
  lastSupportSpawn = 0;
846
 
847
- // 2스테이지에서 3호전차 지원 유닛 추가
848
  if (currentStage === 2 && allyUnits.length < 2) {
849
  allyUnits.push(new PanzerIII());
850
  }
851
 
852
  console.log(`Round ${currentRound} initialized with ${enemies.length} enemies`);
853
-
854
- // 카운트다운 시작
855
  startCountdown();
856
 
857
  // JU87 스폰 설정
@@ -867,14 +868,11 @@
867
  if(enemies.length === 0) {
868
  console.log(`Checking round clear: Current round ${currentRound}, Boss stage: ${isBossStage}`);
869
 
870
- // 하나의 랜덤한 음성만 재생
871
  if (!isBossStage) {
872
- // 이전 음성이 있다면 정지
873
  if (currentVoice) {
874
  currentVoice.pause();
875
  currentVoice.currentTime = 0;
876
  }
877
-
878
  const voiceFiles = ['voice1.ogg', 'voice2.ogg', 'voice3.ogg', 'voice4.ogg', 'voice5.ogg', 'voice6.ogg', 'voice7.ogg', 'voice8.ogg', 'voice9.ogg', 'voice10.ogg'];
879
  const randomIndex = Math.floor(Math.random() * voiceFiles.length);
880
  currentVoice = new Audio(voiceFiles[randomIndex]);
@@ -882,7 +880,7 @@
882
  currentVoice.play();
883
  }
884
 
885
- // 라운드 클리어 점수 추가
886
  const roundClearScore = currentRound * 100;
887
  score += roundClearScore;
888
  totalScore += roundClearScore;
@@ -890,16 +888,12 @@
890
 
891
  if (currentStage === 3) {
892
  if (currentRound === 10) {
893
- // 10라운드 보스 클리어 후 다음 라운드로
894
  document.getElementById('nextRound').style.display = 'block';
895
- showShop(); // 상점창도 함께 표시
896
  } else if (currentRound === 20) {
897
- // 20라운드 보스 클리어 후 게임 승리
898
  document.getElementById('winMessage').style.display = 'block';
899
  document.getElementById('restart').style.display = 'block';
900
  gameOver = true;
901
-
902
- // 모든 오디오 정지
903
  bgm.pause();
904
  bgm.currentTime = 0;
905
  cannonSound.pause();
@@ -911,32 +905,26 @@
911
  hitSounds.forEach(sound => { sound.pause(); sound.currentTime = 0; });
912
  reloadSounds.forEach(sound => { sound.pause(); sound.currentTime = 0; });
913
 
914
- // 승리 사운드 재생
915
  const victorySound = new Audio('victory.ogg');
916
  victorySound.volume = 1.0;
917
  victorySound.play();
918
 
919
- // 점수 제출 창 표시
920
  showScoreSubmission();
921
  } else if (currentRound < 20) {
922
- // 일반 라운드 클리어
923
  document.getElementById('nextRound').style.display = 'block';
924
  showShop();
925
  }
926
  } else if (!isBossStage) {
927
  if(currentRound < 10) {
928
- console.log('Normal round clear - showing next round button and shop');
929
  document.getElementById('nextRound').style.display = 'block';
930
  document.getElementById('bossButton').style.display = 'none';
931
  showShop();
932
  } else {
933
- console.log('Final round clear - showing boss button');
934
  document.getElementById('nextRound').style.display = 'none';
935
  document.getElementById('bossButton').style.display = 'block';
936
  document.getElementById('shop').style.display = 'none';
937
  }
938
  } else {
939
- console.log('Boss clear - showing victory message');
940
  gameOver = true;
941
  document.getElementById('winMessage').style.display = 'block';
942
  document.getElementById('bossButton').style.display = 'none';
@@ -947,7 +935,6 @@
947
  const victorySound = new Audio('victory.ogg');
948
  victorySound.play();
949
 
950
- // 점수 제출 창 표시
951
  showScoreSubmission();
952
  }
953
  }
@@ -959,8 +946,8 @@
959
 
960
  function updateScoreDisplay() {
961
  scoreBoardElement.textContent = `Score: ${score}`;
962
-
963
- // 하이스코어 업데이트
964
  if (totalScore > highScore) {
965
  highScore = totalScore;
966
  localStorage.setItem('tankGameHighScore', highScore);
@@ -992,34 +979,28 @@
992
  this.isReturning = false;
993
  this.circleAngle = 0;
994
  this.returningToCenter = false;
995
- this.ignoreCollisions = false; // 충돌 무시 상태 (타겟팅만 영향)
996
  }
997
 
998
  selectTarget() {
999
  if (enemies.length === 0) return null;
1000
-
1001
- // 중앙으로 이동 중일 때는 타겟팅 하지 않음
1002
  if (this.returningToCenter || this.ignoreCollisions) return null;
1003
-
1004
  let nearestEnemy = null;
1005
  let minDist = Infinity;
1006
 
1007
  enemies.forEach(enemy => {
1008
  if (enemy instanceof Spitfire) return;
1009
-
1010
  const dist = Math.hypot(enemy.x - this.x, enemy.y - this.y);
1011
  if (dist < minDist) {
1012
  minDist = dist;
1013
  nearestEnemy = enemy;
1014
  }
1015
  });
1016
-
1017
  return nearestEnemy;
1018
  }
1019
 
1020
  checkCollision() {
1021
  if (!this.target || this.ignoreCollisions) return false;
1022
-
1023
  const dist = Math.hypot(this.target.x - this.x, this.target.y - this.y);
1024
  return dist < (this.width + this.target.width) / 2;
1025
  }
@@ -1031,37 +1012,29 @@
1031
  const dist = Math.hypot(centerX - this.x, centerY - this.y);
1032
 
1033
  if (dist > 10) {
1034
- // 중앙으로 이동하는 동안 더 빠른 속도로 이동
1035
  const moveSpeed = this.speed * 1.5;
1036
  this.x += Math.cos(this.angle) * moveSpeed;
1037
  this.y += Math.sin(this.angle) * moveSpeed;
1038
  return false;
1039
  }
1040
-
1041
- // 중앙 도달 시 충돌 무시 상태 해제
1042
  this.ignoreCollisions = false;
1043
  this.returningToCenter = false;
1044
  return true;
1045
  }
1046
 
1047
  shoot() {
1048
- // 중앙으로 이동 중일 때는 발사하지 않음
1049
  if (this.returningToCenter || this.ignoreCollisions) return;
1050
-
1051
  if (!this.hasPlayedMGSound && !isCountingDown) {
1052
  const mgSound = new Audio('ju87mg.ogg');
1053
  mgSound.volume = 1.0;
1054
  mgSound.play();
1055
  this.hasPlayedMGSound = true;
1056
  }
1057
-
1058
  [[20, 50], [80, 50]].forEach(([x, y]) => {
1059
  const offsetX = x - 50;
1060
  const offsetY = y - 50;
1061
-
1062
  const rotatedX = this.x + (Math.cos(this.angle) * offsetX - Math.sin(this.angle) * offsetY);
1063
  const rotatedY = this.y + (Math.sin(this.angle) * offsetX + Math.cos(this.angle) * offsetY);
1064
-
1065
  bullets.push({
1066
  x: rotatedX,
1067
  y: rotatedY,
@@ -1081,9 +1054,7 @@
1081
  sirenSound.play();
1082
  this.hasPlayedSound = true;
1083
  }
1084
-
1085
  const timeSinceSpawn = Date.now() - this.spawnTime;
1086
-
1087
  if (timeSinceSpawn > 5000) {
1088
  if (!this.isReturning) {
1089
  this.isReturning = true;
@@ -1091,14 +1062,11 @@
1091
  this.returningToCenter = true;
1092
  }
1093
  }
1094
-
1095
- // 충돌 감지 및 중앙으로 이동 처리
1096
  if (this.checkCollision()) {
1097
  this.returningToCenter = true;
1098
- this.ignoreCollisions = true; // 충돌 후 타겟팅 무시 상태 활성화
1099
  this.target = null;
1100
  }
1101
-
1102
  if (this.isReturning) {
1103
  if (this.returningToCenter) {
1104
  if (this.moveToCenter()) {
@@ -1116,23 +1084,18 @@
1116
  this.moveToCenter();
1117
  }
1118
  }
1119
-
1120
  if (this.target && !this.ignoreCollisions) {
1121
  this.angle = Math.atan2(this.target.y - this.y, this.target.x - this.x);
1122
  this.x += Math.cos(this.angle) * this.speed;
1123
  this.y += Math.sin(this.angle) * this.speed;
1124
-
1125
  if (Date.now() - this.lastShot > 200) {
1126
  this.shoot();
1127
  this.lastShot = Date.now();
1128
  }
1129
  }
1130
  }
1131
-
1132
- // 화면 경계 체크
1133
  this.x = Math.max(this.width/2, Math.min(canvas.width - this.width/2, this.x));
1134
  this.y = Math.max(this.height/2, Math.min(canvas.height - this.height/2, this.y));
1135
-
1136
  return true;
1137
  }
1138
  }
@@ -1150,13 +1113,10 @@
1150
  }
1151
 
1152
  shoot() {
1153
- // 카운트다운 중에는 발사하지 않음
1154
  if (isCountingDown) return;
1155
-
1156
  const mgSound = new Audio('firemg.ogg');
1157
  mgSound.volume = 0.5;
1158
  mgSound.play();
1159
-
1160
  bullets.push({
1161
  x: this.x,
1162
  y: this.y,
@@ -1170,17 +1130,13 @@
1170
  }
1171
 
1172
  update() {
1173
- // 카운트다운 중이면 false를 반환하여 스핏파이어 제거
1174
  if (isCountingDown) return false;
1175
-
1176
  this.x -= this.speed;
1177
-
1178
  const now = Date.now();
1179
  if (now - this.lastShot > 200) {
1180
  this.shoot();
1181
  this.lastShot = now;
1182
  }
1183
-
1184
  return this.x > 0;
1185
  }
1186
  }
@@ -1189,19 +1145,19 @@
1189
  constructor(y) {
1190
  this.y = y;
1191
  this.startTime = Date.now();
1192
- this.duration = 750; // 0.75초
1193
  }
1194
 
1195
  draw(ctx) {
1196
  ctx.beginPath();
1197
  ctx.strokeStyle = 'red';
1198
- ctx.lineWidth = 5; // 선 두께를 5로 증가
1199
- ctx.setLineDash([10, 20]); // 점선 패턴도 더 크게 조정
1200
  ctx.moveTo(0, this.y);
1201
  ctx.lineTo(canvas.width, this.y);
1202
  ctx.stroke();
1203
  ctx.setLineDash([]);
1204
- ctx.lineWidth = 1; // 다른 그리기에 영향을 주지 않도록 리셋
1205
  }
1206
 
1207
  isExpired() {
@@ -1224,15 +1180,13 @@
1224
 
1225
  shoot() {
1226
  if (isCountingDown || document.getElementById('shop').style.display === 'block') return;
1227
-
1228
  const mgSound = new Audio('firemg.ogg');
1229
  mgSound.volume = 0.5;
1230
  mgSound.play();
1231
-
1232
  bullets.push({
1233
  x: this.x,
1234
  y: this.y,
1235
- angle: Math.PI / 2, // Shooting downward
1236
  speed: 10,
1237
  isEnemy: true,
1238
  damage: 100,
@@ -1243,15 +1197,12 @@
1243
 
1244
  update() {
1245
  if (isCountingDown) return true;
1246
-
1247
  this.y += this.speed;
1248
-
1249
  const now = Date.now();
1250
  if (now - this.lastShot > this.fireRate) {
1251
  this.shoot();
1252
  this.lastShot = now;
1253
  }
1254
-
1255
  return this.y < canvas.height;
1256
  }
1257
  }
@@ -1294,11 +1245,9 @@
1294
 
1295
  dropBomb() {
1296
  if (document.getElementById('shop').style.display === 'block') return;
1297
-
1298
  const bombSound = new Audio('bomb2.ogg');
1299
  bombSound.volume = 0.5;
1300
  bombSound.play();
1301
-
1302
  effects.push(new Effect(
1303
  this.x,
1304
  this.y,
@@ -1306,14 +1255,11 @@
1306
  'bomb',
1307
  0
1308
  ));
1309
-
1310
  const blastRadius = 100;
1311
-
1312
  const distToPlayer = Math.hypot(this.x - player.x, this.y - player.y);
1313
  if (distToPlayer < blastRadius) {
1314
  player.health -= 300;
1315
  }
1316
-
1317
  allyUnits.forEach(ally => {
1318
  const distToAlly = Math.hypot(this.x - ally.x, this.y - ally.y);
1319
  if (distToAlly < blastRadius) {
@@ -1324,15 +1270,12 @@
1324
 
1325
  update() {
1326
  if (isCountingDown) return true;
1327
-
1328
  this.x -= this.speed;
1329
-
1330
  const now = Date.now();
1331
  if (now - this.lastBombTime > this.bombInterval) {
1332
  this.dropBomb();
1333
  this.lastBombTime = now;
1334
  }
1335
-
1336
  return this.x > 0;
1337
  }
1338
  }
@@ -1363,20 +1306,16 @@
1363
  function spawnSpitfires() {
1364
  if (currentStage === 2 && !isCountingDown && !gameOver && gameStarted) {
1365
  const now = Date.now();
1366
-
1367
  if (now - lastSpitfireSpawn > 15000) {
1368
  console.log('Spawning Spitfires...');
1369
-
1370
  const positions = [
1371
  canvas.height * 0.2,
1372
  canvas.height * 0.5,
1373
  canvas.height * 0.8
1374
  ];
1375
-
1376
  positions.forEach(y => {
1377
  warningLines.push(new WarningLine(y));
1378
  });
1379
-
1380
  setTimeout(() => {
1381
  if (!isCountingDown && !gameOver) {
1382
  positions.forEach(y => {
@@ -1385,7 +1324,6 @@
1385
  console.log('Spitfires spawned:', spitfires.length);
1386
  }
1387
  }, 2000);
1388
-
1389
  lastSpitfireSpawn = now;
1390
  }
1391
  }
@@ -1408,7 +1346,6 @@
1408
 
1409
  update() {
1410
  this.x += this.speed;
1411
-
1412
  if (isCountingDown) {
1413
  if (this.mgSound) {
1414
  this.mgSound.pause();
@@ -1416,7 +1353,6 @@
1416
  }
1417
  this.hasPlayedSound = false;
1418
  }
1419
-
1420
  const now = Date.now();
1421
  if (now - this.lastShot > 200 && !isCountingDown) {
1422
  this.shoot();
@@ -1432,13 +1368,11 @@
1432
  firstSound.play();
1433
  this.hasPlayedSound = true;
1434
  }
1435
-
1436
  if (!isCountingDown) {
1437
  const shootSound = new Audio('bf109mgse.ogg');
1438
  shootSound.volume = 0.3;
1439
  shootSound.play();
1440
  }
1441
-
1442
  bullets.push({
1443
  x: this.x + Math.cos(this.angle) * 30,
1444
  y: this.y + Math.sin(this.angle) * 30,
@@ -1470,7 +1404,6 @@
1470
 
1471
  shoot() {
1472
  enemyFireSound.cloneNode().play();
1473
-
1474
  bullets.push({
1475
  x: this.x + Math.cos(this.angle) * 30,
1476
  y: this.y + Math.sin(this.angle) * 30,
@@ -1481,7 +1414,6 @@
1481
  size: 3,
1482
  color: 'blue'
1483
  });
1484
-
1485
  effects.push(new Effect(
1486
  this.x + Math.cos(this.angle) * 30,
1487
  this.y + Math.sin(this.angle) * 30,
@@ -1494,10 +1426,8 @@
1494
 
1495
  update() {
1496
  if(isCountingDown || this.isDead) return;
1497
-
1498
  let nearestEnemy = null;
1499
  let minDist = Infinity;
1500
-
1501
  enemies.forEach(enemy => {
1502
  const dist = Math.hypot(enemy.x - this.x, enemy.y - this.y);
1503
  if(dist < minDist) {
@@ -1513,11 +1443,9 @@
1513
 
1514
  if(nearestEnemy) {
1515
  this.angle = Math.atan2(nearestEnemy.y - this.y, nearestEnemy.x - this.x);
1516
-
1517
  if(minDist > 300) {
1518
  const newX = this.x + Math.cos(this.angle) * this.speed;
1519
  const newY = this.y + Math.sin(this.angle) * this.speed;
1520
-
1521
  let canMove = true;
1522
  allyUnits.forEach(ally => {
1523
  if (ally !== this) {
@@ -1527,20 +1455,17 @@
1527
  }
1528
  }
1529
  });
1530
-
1531
  if (canMove) {
1532
  this.x = newX;
1533
  this.y = newY;
1534
  }
1535
  }
1536
-
1537
  const now = Date.now();
1538
  if(now - this.lastShot > this.shootInterval) {
1539
  this.shoot();
1540
  this.lastShot = now;
1541
  }
1542
  }
1543
-
1544
  this.x = Math.max(this.width/2, Math.min(canvas.width - this.width/2, this.x));
1545
  this.y = Math.max(this.height/2, Math.min(canvas.height - this.height/2, this.y));
1546
  }
@@ -1573,7 +1498,6 @@
1573
 
1574
  shoot() {
1575
  enemyFireSound.cloneNode().play();
1576
-
1577
  bullets.push({
1578
  x: this.x + Math.cos(this.angle) * 30,
1579
  y: this.y + Math.sin(this.angle) * 30,
@@ -1584,7 +1508,6 @@
1584
  size: 3,
1585
  color: 'blue'
1586
  });
1587
-
1588
  effects.push(new Effect(
1589
  this.x + Math.cos(this.angle) * 30,
1590
  this.y + Math.sin(this.angle) * 30,
@@ -1597,10 +1520,8 @@
1597
 
1598
  update() {
1599
  if(isCountingDown || this.isDead) return;
1600
-
1601
  let nearestEnemy = null;
1602
  let minDist = Infinity;
1603
-
1604
  enemies.forEach(enemy => {
1605
  const dist = Math.hypot(enemy.x - this.x, enemy.y - this.y);
1606
  if(dist < minDist) {
@@ -1608,41 +1529,29 @@
1608
  nearestEnemy = enemy;
1609
  }
1610
  });
1611
-
1612
  if(this.health <= 0 && !this.isDead) {
1613
  this.die();
1614
  return;
1615
  }
1616
-
1617
  if(nearestEnemy) {
1618
  this.angle = Math.atan2(nearestEnemy.y - this.y, nearestEnemy.x - this.x);
1619
-
1620
  if(minDist > 300) {
1621
  this.x += Math.cos(this.angle) * this.speed;
1622
  this.y += Math.sin(this.angle) * this.speed;
1623
  }
1624
-
1625
  const now = Date.now();
1626
  if(now - this.lastShot > this.shootInterval) {
1627
  this.shoot();
1628
  this.lastShot = now;
1629
  }
1630
  }
1631
-
1632
  this.x = Math.max(this.width/2, Math.min(canvas.width - this.width/2, this.x));
1633
  this.y = Math.max(this.height/2, Math.min(canvas.height - this.height/2, this.y));
1634
  }
1635
 
1636
  die() {
1637
  this.isDead = true;
1638
-
1639
- effects.push(new Effect(
1640
- this.x,
1641
- this.y,
1642
- 1000,
1643
- 'death'
1644
- ));
1645
-
1646
  const deathSound = new Audio('death.ogg');
1647
  deathSound.volume = 1.0;
1648
  deathSound.play();
@@ -1667,8 +1576,8 @@
1667
  player.width = 100;
1668
  player.height = 45;
1669
  }
1670
-
1671
  player.health = player.maxHealth;
 
1672
  }
1673
  }
1674
 
@@ -1678,6 +1587,7 @@
1678
  hasAPCR = true;
1679
  document.getElementById('apcr').style.display = 'none';
1680
  document.getElementById('shop').style.display = 'none';
 
1681
  }
1682
  }
1683
 
@@ -1687,6 +1597,7 @@
1687
  hasBF109 = true;
1688
  document.getElementById('bf109').style.display = 'none';
1689
  document.getElementById('shop').style.display = 'none';
 
1690
  }
1691
  }
1692
 
@@ -1697,62 +1608,45 @@
1697
  document.getElementById('ju87').style.display = 'none';
1698
  document.getElementById('shop').style.display = 'none';
1699
  lastJU87Spawn = Date.now();
 
1700
  }
1701
  }
1702
 
1703
  function updateGame() {
1704
  if(gameOver) return;
1705
-
1706
  if(!isCountingDown) {
1707
- // 플레이어 움직임
1708
  if(keys['w']) player.y -= player.speed;
1709
  if(keys['s']) player.y += player.speed;
1710
  if(keys['a']) player.x -= player.speed;
1711
  if(keys['d']) player.x += player.speed;
1712
-
1713
- // 플레이어 화면 경계 체크
1714
  player.x = Math.max(player.width/2, Math.min(canvas.width - player.width/2, player.x));
1715
  player.y = Math.max(player.height/2, Math.min(canvas.height - player.height/2, player.y));
1716
-
1717
  fireBullet();
1718
  }
1719
-
1720
- // 플레이어 사망 체크
1721
  if(player.health <= 0) {
1722
  bgm.pause();
1723
  bgm.currentTime = 0;
1724
-
1725
  escapeSound.volume = 1.0;
1726
  escapeSound.play();
1727
-
1728
  setTimeout(() => {
1729
  deathSound.play();
1730
  }, 100);
1731
-
1732
  gameOver = true;
1733
  document.getElementById('restart').style.display = 'block';
1734
  effects.push(new Effect(player.x, player.y, 1000, 'death'));
1735
-
1736
- // 점수 제출 창 표시
1737
  showScoreSubmission();
1738
  }
1739
-
1740
- // P51 스폰 체크 (3스테이지일 때만)
1741
  if (currentStage === 3 && !isCountingDown && !gameOver && gameStarted) {
1742
  const now = Date.now();
1743
- if (now - lastP51Spawn > 15000) { // 15초마다
1744
  const positions = [
1745
- canvas.width * 0.25, // 왼쪽
1746
- canvas.width * 0.5, // 중앙
1747
- canvas.width * 0.75 // 오른쪽
1748
  ];
1749
-
1750
- // 경고선 생성
1751
  positions.forEach(x => {
1752
  p51WarningLines.push(new P51WarningLine(x));
1753
  });
1754
-
1755
- // 2초 후 P51 생성
1756
  setTimeout(() => {
1757
  if (!isCountingDown && !gameOver) {
1758
  positions.forEach(x => {
@@ -1760,27 +1654,17 @@
1760
  });
1761
  }
1762
  }, 2000);
1763
-
1764
  lastP51Spawn = now;
1765
  }
1766
- }
1767
-
1768
- // B-29 스폰 체크 (3스테이지일 때만)
1769
- if (currentStage === 3 && !isCountingDown && !gameOver && gameStarted) {
1770
- const now = Date.now();
1771
- if (now - lastB29Spawn > 15000) { // 15초마다
1772
  const positions = [
1773
- canvas.height * 0.2, // 상단
1774
- canvas.height * 0.5, // 중앙
1775
- canvas.height * 0.8 // 하단
1776
  ];
1777
-
1778
- // 먼저 경고선 생성
1779
  positions.forEach(y => {
1780
  b29WarningLines.push(new B29WarningLine(y));
1781
  });
1782
-
1783
- // 2초 후 B-29 생성
1784
  setTimeout(() => {
1785
  if (!isCountingDown && !gameOver) {
1786
  positions.forEach(y => {
@@ -1788,27 +1672,17 @@
1788
  });
1789
  }
1790
  }, 2000);
1791
-
1792
  lastB29Spawn = now;
1793
  }
1794
  }
1795
-
1796
- // P51 업데이트
1797
  p51s = p51s.filter(p51 => p51.update());
1798
-
1799
- // P51 경고선 업데이트
1800
  p51WarningLines = p51WarningLines.filter(line => !line.isExpired());
1801
-
1802
- // B-29 업데이트
1803
  b29s = b29s.filter(b29 => b29.update());
1804
-
1805
- // B-29 경고선 업데이트
1806
  b29WarningLines = b29WarningLines.filter(line => !line.isExpired());
1807
-
1808
- // BF109 관련 코드
1809
  if (hasBF109 && !isCountingDown) {
1810
  const now = Date.now();
1811
- if (now - lastSupportSpawn > 10000) { // 10초마다
1812
  supportUnits.push(
1813
  new SupportUnit(canvas.height * 0.2),
1814
  new SupportUnit(canvas.height * 0.5),
@@ -1817,149 +1691,91 @@
1817
  lastSupportSpawn = now;
1818
  }
1819
  }
1820
-
1821
- // JU87 관련 코드
1822
  if (hasJU87 && !isCountingDown) {
1823
  const now = Date.now();
1824
- if (now - lastJU87Spawn > 15000) { // 15초마다
1825
  supportUnits.push(new JU87());
1826
  lastJU87Spawn = now;
1827
  }
1828
  }
1829
-
1830
- // 스핏파이어 스폰 및 업데이트
1831
  spawnSpitfires();
1832
  spitfires = spitfires.filter(spitfire => spitfire.update());
1833
-
1834
- // BF109 데미지 업데이트
1835
- updateBF109Damage();
1836
-
1837
- // 경고선 업데이트
1838
  warningLines = warningLines.filter(line => !line.isExpired());
1839
-
1840
- // 지원 유닛 업데이트
1841
  supportUnits = supportUnits.filter(unit => unit.update());
1842
-
1843
- // 아군 유닛 업데이트
1844
  allyUnits.forEach(unit => unit.update());
1845
  allyUnits = allyUnits.filter(unit => unit.health > 0);
1846
-
1847
- // 적 업데이트
1848
  enemies.forEach(enemy => enemy.update());
1849
 
1850
  if(!isCountingDown) {
1851
- // 총알 처리
1852
  bullets = bullets.filter(bullet => {
1853
  bullet.x += Math.cos(bullet.angle) * bullet.speed;
1854
  bullet.y += Math.sin(bullet.angle) * bullet.speed;
1855
-
1856
  if(!bullet.isEnemy) {
1857
- // 플레이어의 총알이 적에게 명중
1858
  for(let i = 0; i < enemies.length; i++) {
1859
  const enemy = enemies[i];
1860
  const dist = Math.hypot(bullet.x - enemy.x, bullet.y - enemy.y);
1861
  if(dist < 30) {
1862
- // 명중 이펙트 생성
1863
- effects.push(new Effect(
1864
- bullet.x,
1865
- bullet.y,
1866
- 500,
1867
- 'hit'
1868
- ));
1869
- // HIT! 텍스트 이펙트 추가
1870
- textEffects.push(new TextEffect(
1871
- enemy.x,
1872
- enemy.y - 30,
1873
- "HIT!",
1874
- "#00ff00"
1875
- ));
1876
-
1877
- // 데미지 증가
1878
  let damage = currentWeapon === 'cannon' ? 1000 : 150;
1879
  enemy.health -= damage;
1880
-
1881
  if(enemy.health <= 0) {
1882
  spawnHealthItem(enemy.x, enemy.y);
1883
  gold += 100;
1884
- score += 200; // 적 파괴 시 점수 추가
1885
  totalScore += 200;
1886
  updateScoreDisplay();
1887
  effects.push(new Effect(enemy.x, enemy.y, 1000, 'death'));
1888
  deathSound.cloneNode().play();
1889
-
1890
  if (!currentHitSound || currentHitSound.ended) {
1891
  currentHitSound = hitSounds[Math.floor(Math.random() * hitSounds.length)];
1892
  currentHitSound.volume = 1.0;
1893
  currentHitSound.play();
1894
  }
1895
-
1896
  enemies.splice(i, 1);
1897
  }
1898
-
1899
- // 총알이 적중하면 즉시 소멸
1900
  return false;
1901
  }
1902
  }
1903
  } else {
1904
- // 적의 총알 처리
1905
  if (!document.getElementById('shop').style.display ||
1906
  document.getElementById('shop').style.display === 'none') {
1907
  const distToPlayer = Math.hypot(bullet.x - player.x, bullet.y - player.y);
1908
  if(distToPlayer < 30) {
1909
- effects.push(new Effect(
1910
- bullet.x,
1911
- bullet.y,
1912
- 500,
1913
- 'hit'
1914
- ));
1915
-
1916
  player.health -= bullet.damage || 100;
1917
  return false;
1918
  }
1919
-
1920
  for(let ally of allyUnits) {
1921
  const distToAlly = Math.hypot(bullet.x - ally.x, bullet.y - ally.y);
1922
  if(distToAlly < 30) {
1923
- effects.push(new Effect(
1924
- bullet.x,
1925
- bullet.y,
1926
- 500,
1927
- 'hit'
1928
- ));
1929
-
1930
  ally.health -= bullet.damage || 100;
1931
  return false;
1932
  }
1933
  }
1934
  }
1935
  }
1936
-
1937
- // 화면 밖으로 나가면 총알 제거
1938
  return bullet.x >= 0 && bullet.x <= canvas.width &&
1939
  bullet.y >= 0 && bullet.y <= canvas.height;
1940
  });
1941
-
1942
- // 아이템 처리
1943
  items = items.filter(item => {
1944
  const dist = Math.hypot(item.x - player.x, item.y - player.y);
1945
  if(dist < 30) {
1946
  player.health = Math.min(player.health + 200, player.maxHealth);
1947
- score += 50; // 아이템 획득 시 점수 추가
1948
  totalScore += 50;
1949
  updateScoreDisplay();
1950
  return false;
1951
  }
1952
  return true;
1953
  });
1954
-
1955
- // 아군 전차와 적 전차 충돌 체크
1956
  allyUnits.forEach(ally => {
1957
  enemies.forEach(enemy => {
1958
  const dist = Math.hypot(ally.x - enemy.x, ally.y - enemy.y);
1959
  if (dist < (ally.width + enemy.width) / 2) {
1960
  const angle = Math.atan2(ally.y - enemy.y, ally.x - enemy.x);
1961
  const pushDistance = ((ally.width + enemy.width) / 2 - dist) / 2;
1962
-
1963
  ally.x += Math.cos(angle) * pushDistance;
1964
  ally.y += Math.sin(angle) * pushDistance;
1965
  enemy.x -= Math.cos(angle) * pushDistance;
@@ -1967,31 +1783,23 @@
1967
  }
1968
  });
1969
  });
1970
-
1971
- // 라운드 클리어 체크
1972
  checkRoundClear();
1973
  }
1974
  }
1975
 
1976
  function drawGame() {
1977
  ctx.clearRect(0, 0, canvas.width, canvas.height);
1978
-
1979
- // 배경 그리기
1980
  const pattern = ctx.createPattern(backgroundImg, 'repeat');
1981
  ctx.fillStyle = pattern;
1982
  ctx.fillRect(0, 0, canvas.width, canvas.height);
1983
-
1984
- // 플레이어 그리기
1985
  ctx.save();
1986
  ctx.translate(player.x, player.y);
1987
  ctx.rotate(player.angle);
1988
  ctx.drawImage(playerImg, -player.width/2, -player.height/2, player.width, player.height);
1989
  ctx.restore();
1990
-
1991
- // 체력바 그리기
1992
  drawHealthBar(player.x, player.y - 40, player.health, player.maxHealth, 60, 5, 'green');
1993
-
1994
- // 적 그리기
1995
  enemies.forEach(enemy => {
1996
  ctx.save();
1997
  ctx.translate(enemy.x, enemy.y);
@@ -2002,7 +1810,6 @@
2002
  drawHealthBar(enemy.x, enemy.y - 40, enemy.health, enemy.maxHealth, 60, 5, 'red');
2003
  });
2004
 
2005
- // 아군 유닛 그리기
2006
  allyUnits.forEach(ally => {
2007
  ctx.save();
2008
  ctx.translate(ally.x, ally.y);
@@ -2012,16 +1819,10 @@
2012
  drawHealthBar(ally.x, ally.y - 40, ally.health, ally.maxHealth, 60, 5, 'blue');
2013
  });
2014
 
2015
- // 경고선 그리기
2016
  warningLines.forEach(line => line.draw(ctx));
2017
-
2018
- // 스핏파이어 경고선 그리기
2019
  p51WarningLines.forEach(line => line.draw(ctx));
2020
-
2021
- // B-29 경고선 그리기
2022
  b29WarningLines.forEach(line => line.draw(ctx));
2023
 
2024
- // P51 그리기
2025
  p51s.forEach(p51 => {
2026
  ctx.save();
2027
  ctx.translate(p51.x, p51.y);
@@ -2029,8 +1830,6 @@
2029
  ctx.drawImage(p51.img, -p51.width/2, -p51.height/2, p51.width, p51.height);
2030
  ctx.restore();
2031
  });
2032
-
2033
- // B-29 그리기
2034
  b29s.forEach(b29 => {
2035
  ctx.save();
2036
  ctx.translate(b29.x, b29.y);
@@ -2038,8 +1837,6 @@
2038
  ctx.drawImage(b29.img, -b29.width/2, -b29.height/2, b29.width, b29.height);
2039
  ctx.restore();
2040
  });
2041
-
2042
- // 스핏파이어 그리기
2043
  spitfires.forEach(spitfire => {
2044
  ctx.save();
2045
  ctx.translate(spitfire.x, spitfire.y);
@@ -2047,8 +1844,6 @@
2047
  ctx.drawImage(spitfire.img, -spitfire.width/2, -spitfire.height/2, spitfire.width, spitfire.height);
2048
  ctx.restore();
2049
  });
2050
-
2051
- // 지원 유닛 그리기
2052
  supportUnits.forEach(unit => {
2053
  ctx.save();
2054
  ctx.translate(unit.x, unit.y);
@@ -2056,8 +1851,6 @@
2056
  ctx.drawImage(unit.img, -unit.width/2, -unit.height/2, unit.width, unit.height);
2057
  ctx.restore();
2058
  });
2059
-
2060
- // 총알 그리기
2061
  bullets.forEach(bullet => {
2062
  if (bullet.isEnemy || !bullet.isAPCR) {
2063
  ctx.beginPath();
@@ -2074,23 +1867,17 @@
2074
  ctx.restore();
2075
  }
2076
  });
2077
-
2078
- // 아이템 그리기
2079
  items.forEach(item => {
2080
  ctx.beginPath();
2081
  ctx.fillStyle = 'green';
2082
  ctx.arc(item.x, item.y, 10, 0, Math.PI * 2);
2083
  ctx.fill();
2084
  });
2085
-
2086
- // 텍스트 이펙트 업데이트 및 그리기
2087
  textEffects = textEffects.filter(effect => !effect.isExpired());
2088
  textEffects.forEach(effect => {
2089
  effect.update();
2090
  effect.draw(ctx);
2091
  });
2092
-
2093
- // 이펙트 그리기
2094
  effects = effects.filter(effect => !effect.isExpired());
2095
  effects.forEach(effect => {
2096
  effect.update();
@@ -2099,19 +1886,14 @@
2099
  if (effect.type === 'fire' || effect.type === 'cannon_fire') {
2100
  ctx.rotate(effect.angle);
2101
  }
2102
-
2103
  if (effect.type === 'cannon_fire') {
2104
- // 캐논 발사 효과는 현재 단계의 크기에 맞춰 그리기
2105
  const size = effect.currentSize;
2106
  ctx.drawImage(effect.img, -size.width/2, -size.height/2, size.width, size.height);
2107
  } else {
2108
- // 다른 이펙트들은 기존 방식대로 그리기
2109
  ctx.drawImage(effect.img, -effect.size/2, -effect.size/2, effect.size, effect.size);
2110
  }
2111
  ctx.restore();
2112
  });
2113
-
2114
- // 카운트다운 오버레이
2115
  if (isCountingDown) {
2116
  ctx.fillStyle = 'rgba(0, 0, 0, 0.5)';
2117
  ctx.fillRect(0, 0, canvas.width, canvas.height);
@@ -2120,7 +1902,7 @@
2120
 
2121
  function updateBF109Damage() {
2122
  supportUnits = supportUnits.filter(unit => {
2123
- if (unit instanceof SupportUnit) { // BF109인 경우
2124
  let hitCount = 0;
2125
  bullets = bullets.filter(bullet => {
2126
  if (bullet.isSpitfireBullet) {
@@ -2132,7 +1914,6 @@
2132
  }
2133
  return true;
2134
  });
2135
-
2136
  if (hitCount >= 5) {
2137
  effects.push(new Effect(unit.x, unit.y, 1000, 'death'));
2138
  deathSound.cloneNode().play();
@@ -2156,7 +1937,6 @@
2156
  this.x = Math.random() * canvas.width;
2157
  this.y = Math.random() * canvas.height;
2158
  this.isElite = isElite;
2159
-
2160
  if (currentStage === 3) {
2161
  if (isBoss) {
2162
  this.health = 30000;
@@ -2169,7 +1949,7 @@
2169
  this.health = 3000;
2170
  this.maxHealth = 3000;
2171
  this.speed = 2;
2172
- this.shootInterval = currentRound >= 11 ? 2000 : 3000; // 11라운드 이후 2초로 변경
2173
  this.enemyImg = new Image();
2174
  this.enemyImg.src = currentRound >= 11 ? 'enemyus2.png' : 'enemyus0.png';
2175
  } else {
@@ -2241,18 +2021,14 @@
2241
  this.enemyImg.src = 'enemy.png';
2242
  }
2243
  }
2244
-
2245
  this.lastShot = 0;
2246
  this.angle = 0;
2247
  this.width = 100;
2248
  this.height = 45;
2249
-
2250
- // 20라운드 보스일 경우 크기 50% 증가
2251
  if (isBoss && currentStage === 3 && currentRound === 20) {
2252
- this.width = 150; // 원래 100에서 50% 증가
2253
- this.height = 67; // 원래 45에서 50% 증가
2254
  }
2255
-
2256
  this.moveTimer = 0;
2257
  this.moveInterval = Math.random() * 2000 + 1000;
2258
  this.moveAngle = Math.random() * Math.PI * 2;
@@ -2262,20 +2038,15 @@
2262
  update() {
2263
  if(isCountingDown) return;
2264
  const now = Date.now();
2265
-
2266
  if (now - this.moveTimer > this.moveInterval) {
2267
  this.moveAngle = Math.random() * Math.PI * 2;
2268
  this.moveTimer = now;
2269
  }
2270
-
2271
  this.x += Math.cos(this.moveAngle) * this.speed;
2272
  this.y += Math.sin(this.moveAngle) * this.speed;
2273
-
2274
  this.x = Math.max(this.width/2, Math.min(canvas.width - this.width/2, this.x));
2275
  this.y = Math.max(this.height/2, Math.min(canvas.height - this.height/2, this.y));
2276
-
2277
  this.angle = Math.atan2(player.y - this.y, player.x - this.x);
2278
-
2279
  if (now - this.lastShot > this.shootInterval && !isCountingDown) {
2280
  this.shoot();
2281
  this.lastShot = now;
@@ -2288,24 +2059,19 @@
2288
  const bossSound = new Audio('fireenemy2.ogg');
2289
  bossSound.volume = 0.5;
2290
  bossSound.play();
2291
-
2292
- // 보스의 총구 위치 계산 (좌우 양쪽에서 발사)
2293
  const gunPositions = [
2294
  { x: this.x + Math.cos(this.angle) * 30, y: this.y + Math.sin(this.angle) * 30 },
2295
  { x: this.x + Math.cos(this.angle + Math.PI) * 30, y: this.y + Math.sin(this.angle + Math.PI) * 30 }
2296
  ];
2297
-
2298
- // 각 총구에서 발사 효과 생성
2299
  gunPositions.forEach(pos => {
2300
  effects.push(new Effect(
2301
  pos.x,
2302
  pos.y,
2303
- 750, // 0.75초 동안 지속
2304
  'cannon_fire',
2305
  this.angle,
2306
  this
2307
  ));
2308
-
2309
  bullets.push({
2310
  x: pos.x,
2311
  y: pos.y,
@@ -2320,16 +2086,14 @@
2320
  const eliteSound = new Audio('fireenemy3.ogg');
2321
  eliteSound.volume = 0.5;
2322
  eliteSound.play();
2323
-
2324
  effects.push(new Effect(
2325
  this.x + Math.cos(this.angle) * 30,
2326
  this.y + Math.sin(this.angle) * 30,
2327
- 750, // 0.75초 동안 지속
2328
  'cannon_fire',
2329
  this.angle,
2330
  this
2331
  ));
2332
-
2333
  bullets.push({
2334
  x: this.x + Math.cos(this.angle) * 30,
2335
  y: this.y + Math.sin(this.angle) * 30,
@@ -2341,7 +2105,6 @@
2341
  });
2342
  } else {
2343
  enemyFireSound.cloneNode().play();
2344
-
2345
  effects.push(new Effect(
2346
  this.x + Math.cos(this.angle) * 30,
2347
  this.y + Math.sin(this.angle) * 30,
@@ -2350,7 +2113,6 @@
2350
  this.angle,
2351
  this
2352
  ));
2353
-
2354
  bullets.push({
2355
  x: this.x + Math.cos(this.angle) * 30,
2356
  y: this.y + Math.sin(this.angle) * 30,
@@ -2364,13 +2126,11 @@
2364
  } else {
2365
  const sound = this.isBoss ? new Audio('firemn.ogg') : enemyFireSound.cloneNode();
2366
  sound.play();
2367
-
2368
- // 보스와 정예 유닛은 cannon_fire 효과를 사용
2369
  if (this.isBoss || this.isElite) {
2370
  effects.push(new Effect(
2371
  this.x + Math.cos(this.angle) * 30,
2372
  this.y + Math.sin(this.angle) * 30,
2373
- 750, // 0.75초 동안 지속
2374
  'cannon_fire',
2375
  this.angle,
2376
  this
@@ -2385,7 +2145,6 @@
2385
  this
2386
  ));
2387
  }
2388
-
2389
  bullets.push({
2390
  x: this.x + Math.cos(this.angle) * 30,
2391
  y: this.y + Math.sin(this.angle) * 30,
@@ -2406,7 +2165,7 @@
2406
  player.health = player.maxHealth;
2407
  bullets = [];
2408
  items = [];
2409
- allyUnits = []; // 3호전차 초기화
2410
  if (currentStage === 2 && allyUnits.length < 2) {
2411
  allyUnits.push(new PanzerIII());
2412
  }
@@ -2434,24 +2193,18 @@
2434
  const now = Date.now();
2435
  if ((keys[' '] || autoFire) && now - lastShot > weapon.fireRate) {
2436
  weapon.sound.cloneNode().play();
2437
-
2438
- // 총구 위치 계산
2439
  const muzzleX = player.x + Math.cos(player.angle) * (player.width/2);
2440
  const muzzleY = player.y + Math.sin(player.angle) * (player.width/2);
2441
-
2442
- // 무기 타입에 따른 이펙트 생성
2443
  if (currentWeapon === 'cannon') {
2444
- // 캐논 발사 효과 (shot1->shot2->shot3 순서로 변경)
2445
  effects.push(new Effect(
2446
  muzzleX,
2447
  muzzleY,
2448
- 1500, // 전체 지���시간 1.5초
2449
- 'cannon_fire', // 캐논 발사 전용 타입
2450
  player.angle,
2451
  player
2452
  ));
2453
  } else {
2454
- // 기관총 발사 효과
2455
  effects.push(new Effect(
2456
  player.x + Math.cos(player.angle) * 30,
2457
  player.y + Math.sin(player.angle) * 30,
@@ -2461,10 +2214,8 @@
2461
  player
2462
  ));
2463
  }
2464
-
2465
- // 총알 생성
2466
  bullets.push({
2467
- x: muzzleX, // 총구 위치에서 발사
2468
  y: muzzleY,
2469
  angle: player.angle,
2470
  speed: hasAPCR ? 20 : 10,
@@ -2485,21 +2236,20 @@
2485
  this.color = color;
2486
  this.startTime = Date.now();
2487
  this.duration = duration;
2488
- this.fadeOut = 0.5; // 페이드아웃 시작 시점 (0.5 = 50% 시점부터)
2489
  this.initialY = y;
2490
- this.speed = -1; // 위로 올라가는 속도
2491
  }
2492
 
2493
  update() {
2494
  const elapsed = (Date.now() - this.startTime) / this.duration;
2495
- this.y = this.initialY + (this.speed * elapsed * 50); // 위로 올라가는 효과
2496
  }
2497
 
2498
  draw(ctx) {
2499
  const elapsed = (Date.now() - this.startTime) / this.duration;
2500
  const opacity = elapsed > this.fadeOut ?
2501
  1 - ((elapsed - this.fadeOut) / (1 - this.fadeOut)) : 1;
2502
-
2503
  ctx.save();
2504
  ctx.fillStyle = this.color;
2505
  ctx.globalAlpha = opacity;
@@ -2525,19 +2275,13 @@
2525
  this.parent = parent;
2526
  this.offset = { x: Math.cos(angle) * 30, y: Math.sin(angle) * 30 };
2527
  this.img = new Image();
2528
-
2529
- // 캐논 샷 효과를 위한 추가 속성
2530
  this.currentShot = 1;
2531
  this.lastShotChange = Date.now();
2532
-
2533
- // 각 샷별 크기 정의 (width, height)
2534
  this.shotSizes = {
2535
- 1: { width: 40, height: 40 }, // shot1.png - 작은 화염
2536
- 2: { width: 60, height: 60 }, // shot2.png - 중간 화염
2537
- 3: { width: 80, height: 80 } // shot3.png - 큰 화염
2538
  };
2539
-
2540
- // 이펙트 타입에 따른 이미지 설정
2541
  if (type === 'death') {
2542
  this.img.src = 'bang.png';
2543
  } else if (type === 'bomb') {
@@ -2546,16 +2290,12 @@
2546
  this.img.src = 'fire3.png';
2547
  } else if (type === 'cannon_fire') {
2548
  this.img.src = 'shot1.png';
2549
- this.duration = 750; // 총 0.75초 (0.25초 × 3)
2550
- this.currentSize = this.shotSizes[1]; // 초기 크기 설정
2551
  } else {
2552
  this.img.src = 'fire2.png';
2553
  }
2554
-
2555
- // 기본 이펙트 크기 설정
2556
- this.size = type === 'bomb' ? 100 :
2557
- type === 'death' ? 75 :
2558
- type === 'hit' ? 30 : 42;
2559
  }
2560
 
2561
  update() {
@@ -2564,11 +2304,8 @@
2564
  this.x = this.parent.x + Math.cos(this.parent.angle) * (this.parent.width/2);
2565
  this.y = this.parent.y + Math.sin(this.parent.angle) * (this.parent.width/2);
2566
  this.angle = this.parent.angle;
2567
-
2568
  const now = Date.now();
2569
  const elapsedTime = now - this.startTime;
2570
-
2571
- // 0.25초마다 이미지 변경하고, 마지막 shot3은 0.25초만 지속
2572
  if (elapsedTime < 250) {
2573
  this.currentShot = 1;
2574
  } else if (elapsedTime < 500) {
@@ -2576,7 +2313,6 @@
2576
  } else {
2577
  this.currentShot = 3;
2578
  }
2579
-
2580
  this.img.src = `shot${this.currentShot}.png`;
2581
  this.currentSize = this.shotSizes[this.currentShot];
2582
  } else if(this.type === 'fire') {
@@ -2627,45 +2363,30 @@
2627
  function fetchLeaderboardData() {
2628
  const leaderboardBody = document.getElementById('leaderboardBody');
2629
  leaderboardBody.innerHTML = '<tr><td colspan="5">Loading...</td></tr>';
2630
-
2631
- // 로컬 스토리지에서 리더보드 데이터 불러오기
2632
  let leaderboardData = JSON.parse(localStorage.getItem('tankGameLeaderboard') || '[]');
2633
-
2634
- // 데이터 정렬 (점수 내림차순)
2635
  leaderboardData.sort((a, b) => b.score - a.score);
2636
-
2637
- // 테이블에 데이터 표시
2638
  leaderboardBody.innerHTML = '';
2639
-
2640
  if (leaderboardData.length === 0) {
2641
  leaderboardBody.innerHTML = '<tr><td colspan="5">No scores yet</td></tr>';
2642
  return;
2643
  }
2644
-
2645
  leaderboardData.forEach((entry, index) => {
2646
  const row = document.createElement('tr');
2647
-
2648
  const rankCell = document.createElement('td');
2649
  rankCell.textContent = index + 1;
2650
-
2651
  const nameCell = document.createElement('td');
2652
  nameCell.textContent = entry.name;
2653
-
2654
  const stageCell = document.createElement('td');
2655
  stageCell.textContent = entry.stage;
2656
-
2657
  const waveCell = document.createElement('td');
2658
  waveCell.textContent = entry.wave;
2659
-
2660
  const scoreCell = document.createElement('td');
2661
  scoreCell.textContent = entry.score;
2662
-
2663
  row.appendChild(rankCell);
2664
  row.appendChild(nameCell);
2665
  row.appendChild(stageCell);
2666
  row.appendChild(waveCell);
2667
  row.appendChild(scoreCell);
2668
-
2669
  leaderboardBody.appendChild(row);
2670
  });
2671
  }
@@ -2678,19 +2399,13 @@
2678
 
2679
  function submitScore() {
2680
  const playerNameInput = document.getElementById('playerName').value.trim();
2681
-
2682
  if (!playerNameInput) {
2683
  alert('Please enter your name');
2684
  return;
2685
  }
2686
-
2687
  playerName = playerNameInput;
2688
  localStorage.setItem('tankGamePlayerName', playerName);
2689
-
2690
- // 로컬 스토리지에서 리더보드 데이터 불러오기
2691
  let leaderboardData = JSON.parse(localStorage.getItem('tankGameLeaderboard') || '[]');
2692
-
2693
- // 새 점수 추가
2694
  leaderboardData.push({
2695
  name: playerName,
2696
  stage: currentStage,
@@ -2698,19 +2413,12 @@
2698
  score: totalScore,
2699
  date: new Date().toISOString()
2700
  });
2701
-
2702
- // 데이터 정렬 및 저장
2703
  leaderboardData.sort((a, b) => b.score - a.score);
2704
  localStorage.setItem('tankGameLeaderboard', JSON.stringify(leaderboardData));
2705
-
2706
- // 점수 제출 창 닫기
2707
  document.getElementById('scoreSubmission').style.display = 'none';
2708
-
2709
- // 리더보드 표시
2710
  showLeaderboard();
2711
  }
2712
 
2713
- // iOS Installation instructions
2714
  function isIOS() {
2715
  return [
2716
  'iPad Simulator',
@@ -2725,37 +2433,27 @@
2725
 
2726
  const iosInstructionsDiv = document.getElementById('iosInstallInstructions');
2727
  const closeInstructionsButton = document.getElementById('closeInstallInstructions');
2728
-
2729
- // Check if it's iOS AND not running in standalone mode (already installed)
2730
  if (isIOS() && !window.navigator.standalone && iosInstructionsDiv) {
2731
- // Check if the 'beforeinstallprompt' event is NOT supported by this browser
2732
- // (This check is a bit indirect, we assume if it doesn't fire quickly, it's not supported)
2733
  let promptSupported = false;
2734
  window.addEventListener('beforeinstallprompt', () => {
2735
  promptSupported = true;
2736
  });
2737
-
2738
- // Wait a short time to see if beforeinstallprompt fires
2739
  setTimeout(() => {
2740
- if (!promptSupported && iosInstructionsDiv.style.display !== 'block') { // Double check it's not already shown
2741
  console.log("iOS detected, standalone mode is false, and beforeinstallprompt likely not supported. Showing instructions.");
2742
- iosInstructionsDiv.classList.add('show-instructions'); // Add class to trigger CSS display rule
2743
- // Optional: Add class to body to hide the other install button via CSS
2744
  document.body.classList.add('ios-device');
2745
  } else {
2746
  console.log("iOS detected, but PWA might be installed or prompt is supported.");
2747
  }
2748
- }, 1500); // Wait 1.5 seconds
2749
  }
2750
-
2751
- // Add listener for the dismiss button (if it exists)
2752
  if (closeInstructionsButton && iosInstructionsDiv) {
2753
  closeInstructionsButton.addEventListener('click', () => {
2754
- iosInstructionsDiv.style.display = 'none'; // Directly hide it
2755
  });
2756
  }
2757
 
2758
- // Button Event Listeners
2759
  document.getElementById('nextRound').addEventListener('click', () => {
2760
  currentRound++;
2761
  document.getElementById('nextRound').style.display = 'none';
@@ -2794,21 +2492,16 @@
2794
  });
2795
  }
2796
 
2797
- // Initialize the game when the page loads
2798
  window.addEventListener('load', function() {
2799
- // Set canvas dimensions
2800
  canvas.width = window.innerWidth;
2801
  canvas.height = window.innerHeight;
2802
-
2803
- // Set initial UI visibility
2804
  document.getElementById('instructions').style.display = 'none';
2805
  document.getElementById('weaponInfo').style.display = 'none';
2806
  document.getElementById('scoreBoard').style.display = 'none';
 
2807
  document.getElementById('gameCanvas').style.display = 'none';
2808
-
2809
- // Play background music
2810
  bgm.play().catch(err => console.error("Error playing title music:", err));
2811
  });
2812
  </script>
2813
  </body>
2814
- </html>
 
246
  z-index: 1000;
247
  font-size: 18px;
248
  }
249
+ /* 새로 추가: 돈(골드) 표시용 */
250
+ #moneyBoard {
251
+ position: fixed;
252
+ top: 50px;
253
+ left: 10px;
254
+ color: white;
255
+ background: rgba(0,0,0,0.7);
256
+ padding: 10px;
257
+ border-radius: 5px;
258
+ z-index: 1000;
259
+ font-size: 18px;
260
+ }
261
  #leaderboard {
262
  position: fixed;
263
  top: 0;
 
377
  </div>
378
  <div id="weaponInfo">Current Weapon: Cannon</div>
379
  <div id="scoreBoard">Score: 0</div>
380
+ <!-- 새로 추가: 돈(골드) 표시용 DIV -->
381
+ <div id="moneyBoard">Money: 0</div>
382
+
383
  <div id="countdown">3</div>
384
  <button id="nextRound" class="button">Next Round</button>
385
  <button id="restart" class="button">Restart Game</button>
 
416
  </div>
417
  <div class="button-container">
418
  <button id="leaderboardButton">Leaderboard</button>
419
+ <!-- style="display: none;" 제거하여 항상 기본적으로 표시 -->
420
+ <button id="installPwaButton">Install App</button>
421
  </div>
422
  <a href="https://discord.gg/openfreeai" target="_blank" class="discord-link">Join our Discord community: discord.gg/openfreeai</a>
423
  </div>
 
547
  const canvas = document.getElementById('gameCanvas');
548
  const ctx = canvas.getContext('2d');
549
  const scoreBoardElement = document.getElementById('scoreBoard');
550
+ const moneyBoardElement = document.getElementById('moneyBoard'); // 새로 추가
551
 
552
  canvas.width = window.innerWidth;
553
  canvas.height = window.innerHeight;
 
561
  let bullets = [];
562
  let items = [];
563
  let lastShot = 0;
564
+ let isCountingDown = false;
565
  let countdownTime = 3;
566
  let autoFire = false;
567
  let gold = 0;
 
653
  document.getElementById('weaponInfo').style.display = 'block';
654
  document.getElementById('gameCanvas').style.display = 'block';
655
  document.getElementById('scoreBoard').style.display = 'block';
656
+ document.getElementById('moneyBoard').style.display = 'block';
657
 
658
  // 기존 BGM 정지
659
  bgm.pause();
 
681
  gameOver = false;
682
  gold = 0;
683
  score = 0;
684
+ totalScore = 0; // 점수 누적도 초기화
685
  updateScoreDisplay();
686
  hasAPCR = false;
687
  hasBF109 = false;
 
706
  function startCountdown() {
707
  isCountingDown = true;
708
  countdownTime = 3;
709
+ let countdownEl = document.getElementById('countdown'); // let으로 선언 수정
710
  countdownEl.style.display = 'block';
711
  countdownEl.textContent = countdownTime;
712
  bgm.pause();
 
764
  // 적 생성
765
  enemies = [];
766
  if (currentStage === 3) {
 
767
  if (currentRound >= 11) {
768
  backgroundImg.src = 'city3a.png';
769
+ // 11라운드 이후 보스 아닌 경우 일반 BGM으로 복귀
770
+ if (!isBossStage) {
771
  bgm.pause();
772
  bgm = new Audio('BGM4.ogg');
773
  bgm.volume = 0.7;
 
776
  }
777
  }
778
 
 
779
  if (currentRound === 10 || currentRound === 20) {
780
+ enemies = [new Enemy(true, false)]; // 보스 유닛
781
+ // 보스 BGM
782
  bgm.pause();
783
  bgm = new Audio('BGM.ogg');
784
  bgm.volume = 0.7;
785
  bgm.loop = true;
786
  bgm.play();
787
  } else {
 
788
  const enemyCount = Math.min(currentRound, 10);
 
789
  for (let i = 0; i < enemyCount; i++) {
790
  enemies.push(new Enemy(false, false));
791
  }
 
 
792
  const eliteCount = Math.floor((currentRound + 1) / 2);
793
  for (let i = 0; i < eliteCount; i++) {
794
  enemies.push(new Enemy(false, true));
795
  }
796
  }
797
 
798
+ // 판터(PanzerV) 지원 유닛
799
  let currentPanzers = allyUnits.length;
800
  let addPanzers = 2;
801
+ if (currentRound === 1) addPanzers = 1;
 
 
 
 
802
  let newPanzers = Math.min(5 - currentPanzers, addPanzers);
 
803
  for (let i = 0; i < newPanzers; i++) {
804
  allyUnits.push(new PanzerV());
805
  }
 
809
  for(let i = 0; i < enemyCount; i++) {
810
  let x, y;
811
  const edge = Math.floor(Math.random() * 4);
 
812
  switch(edge) {
813
  case 0: x = Math.random() * canvas.width; y = 0; break;
814
  case 1: x = canvas.width; y = Math.random() * canvas.height; break;
815
  case 2: x = Math.random() * canvas.width; y = canvas.height; break;
816
  case 3: x = 0; y = Math.random() * canvas.height; break;
817
  }
 
818
  const enemy = new Enemy();
819
  enemy.x = x;
820
  enemy.y = y;
 
825
  for(let i = 0; i < enemyCount; i++) {
826
  let x, y;
827
  const edge = Math.floor(Math.random() * 4);
 
828
  switch(edge) {
829
  case 0: x = Math.random() * canvas.width; y = 0; break;
830
  case 1: x = canvas.width; y = Math.random() * canvas.height; break;
831
  case 2: x = Math.random() * canvas.width; y = canvas.height; break;
832
  case 3: x = 0; y = Math.random() * canvas.height; break;
833
  }
 
834
  const enemy = new Enemy();
835
  enemy.x = x;
836
  enemy.y = y;
 
838
  }
839
  }
840
 
 
841
  player.health = player.maxHealth;
842
  bullets = [];
843
  items = [];
 
847
  lastSpitfireSpawn = Date.now();
848
  lastSupportSpawn = 0;
849
 
850
+ // 2스테이지에서 3호전차 지원 유닛 추가 (예시)
851
  if (currentStage === 2 && allyUnits.length < 2) {
852
  allyUnits.push(new PanzerIII());
853
  }
854
 
855
  console.log(`Round ${currentRound} initialized with ${enemies.length} enemies`);
 
 
856
  startCountdown();
857
 
858
  // JU87 스폰 설정
 
868
  if(enemies.length === 0) {
869
  console.log(`Checking round clear: Current round ${currentRound}, Boss stage: ${isBossStage}`);
870
 
 
871
  if (!isBossStage) {
 
872
  if (currentVoice) {
873
  currentVoice.pause();
874
  currentVoice.currentTime = 0;
875
  }
 
876
  const voiceFiles = ['voice1.ogg', 'voice2.ogg', 'voice3.ogg', 'voice4.ogg', 'voice5.ogg', 'voice6.ogg', 'voice7.ogg', 'voice8.ogg', 'voice9.ogg', 'voice10.ogg'];
877
  const randomIndex = Math.floor(Math.random() * voiceFiles.length);
878
  currentVoice = new Audio(voiceFiles[randomIndex]);
 
880
  currentVoice.play();
881
  }
882
 
883
+ // 라운드 클리어 점수
884
  const roundClearScore = currentRound * 100;
885
  score += roundClearScore;
886
  totalScore += roundClearScore;
 
888
 
889
  if (currentStage === 3) {
890
  if (currentRound === 10) {
 
891
  document.getElementById('nextRound').style.display = 'block';
892
+ showShop();
893
  } else if (currentRound === 20) {
 
894
  document.getElementById('winMessage').style.display = 'block';
895
  document.getElementById('restart').style.display = 'block';
896
  gameOver = true;
 
 
897
  bgm.pause();
898
  bgm.currentTime = 0;
899
  cannonSound.pause();
 
905
  hitSounds.forEach(sound => { sound.pause(); sound.currentTime = 0; });
906
  reloadSounds.forEach(sound => { sound.pause(); sound.currentTime = 0; });
907
 
 
908
  const victorySound = new Audio('victory.ogg');
909
  victorySound.volume = 1.0;
910
  victorySound.play();
911
 
 
912
  showScoreSubmission();
913
  } else if (currentRound < 20) {
 
914
  document.getElementById('nextRound').style.display = 'block';
915
  showShop();
916
  }
917
  } else if (!isBossStage) {
918
  if(currentRound < 10) {
 
919
  document.getElementById('nextRound').style.display = 'block';
920
  document.getElementById('bossButton').style.display = 'none';
921
  showShop();
922
  } else {
 
923
  document.getElementById('nextRound').style.display = 'none';
924
  document.getElementById('bossButton').style.display = 'block';
925
  document.getElementById('shop').style.display = 'none';
926
  }
927
  } else {
 
928
  gameOver = true;
929
  document.getElementById('winMessage').style.display = 'block';
930
  document.getElementById('bossButton').style.display = 'none';
 
935
  const victorySound = new Audio('victory.ogg');
936
  victorySound.play();
937
 
 
938
  showScoreSubmission();
939
  }
940
  }
 
946
 
947
  function updateScoreDisplay() {
948
  scoreBoardElement.textContent = `Score: ${score}`;
949
+ moneyBoardElement.textContent = `Money: ${gold}`;
950
+
951
  if (totalScore > highScore) {
952
  highScore = totalScore;
953
  localStorage.setItem('tankGameHighScore', highScore);
 
979
  this.isReturning = false;
980
  this.circleAngle = 0;
981
  this.returningToCenter = false;
982
+ this.ignoreCollisions = false;
983
  }
984
 
985
  selectTarget() {
986
  if (enemies.length === 0) return null;
 
 
987
  if (this.returningToCenter || this.ignoreCollisions) return null;
 
988
  let nearestEnemy = null;
989
  let minDist = Infinity;
990
 
991
  enemies.forEach(enemy => {
992
  if (enemy instanceof Spitfire) return;
 
993
  const dist = Math.hypot(enemy.x - this.x, enemy.y - this.y);
994
  if (dist < minDist) {
995
  minDist = dist;
996
  nearestEnemy = enemy;
997
  }
998
  });
 
999
  return nearestEnemy;
1000
  }
1001
 
1002
  checkCollision() {
1003
  if (!this.target || this.ignoreCollisions) return false;
 
1004
  const dist = Math.hypot(this.target.x - this.x, this.target.y - this.y);
1005
  return dist < (this.width + this.target.width) / 2;
1006
  }
 
1012
  const dist = Math.hypot(centerX - this.x, centerY - this.y);
1013
 
1014
  if (dist > 10) {
 
1015
  const moveSpeed = this.speed * 1.5;
1016
  this.x += Math.cos(this.angle) * moveSpeed;
1017
  this.y += Math.sin(this.angle) * moveSpeed;
1018
  return false;
1019
  }
 
 
1020
  this.ignoreCollisions = false;
1021
  this.returningToCenter = false;
1022
  return true;
1023
  }
1024
 
1025
  shoot() {
 
1026
  if (this.returningToCenter || this.ignoreCollisions) return;
 
1027
  if (!this.hasPlayedMGSound && !isCountingDown) {
1028
  const mgSound = new Audio('ju87mg.ogg');
1029
  mgSound.volume = 1.0;
1030
  mgSound.play();
1031
  this.hasPlayedMGSound = true;
1032
  }
 
1033
  [[20, 50], [80, 50]].forEach(([x, y]) => {
1034
  const offsetX = x - 50;
1035
  const offsetY = y - 50;
 
1036
  const rotatedX = this.x + (Math.cos(this.angle) * offsetX - Math.sin(this.angle) * offsetY);
1037
  const rotatedY = this.y + (Math.sin(this.angle) * offsetX + Math.cos(this.angle) * offsetY);
 
1038
  bullets.push({
1039
  x: rotatedX,
1040
  y: rotatedY,
 
1054
  sirenSound.play();
1055
  this.hasPlayedSound = true;
1056
  }
 
1057
  const timeSinceSpawn = Date.now() - this.spawnTime;
 
1058
  if (timeSinceSpawn > 5000) {
1059
  if (!this.isReturning) {
1060
  this.isReturning = true;
 
1062
  this.returningToCenter = true;
1063
  }
1064
  }
 
 
1065
  if (this.checkCollision()) {
1066
  this.returningToCenter = true;
1067
+ this.ignoreCollisions = true;
1068
  this.target = null;
1069
  }
 
1070
  if (this.isReturning) {
1071
  if (this.returningToCenter) {
1072
  if (this.moveToCenter()) {
 
1084
  this.moveToCenter();
1085
  }
1086
  }
 
1087
  if (this.target && !this.ignoreCollisions) {
1088
  this.angle = Math.atan2(this.target.y - this.y, this.target.x - this.x);
1089
  this.x += Math.cos(this.angle) * this.speed;
1090
  this.y += Math.sin(this.angle) * this.speed;
 
1091
  if (Date.now() - this.lastShot > 200) {
1092
  this.shoot();
1093
  this.lastShot = Date.now();
1094
  }
1095
  }
1096
  }
 
 
1097
  this.x = Math.max(this.width/2, Math.min(canvas.width - this.width/2, this.x));
1098
  this.y = Math.max(this.height/2, Math.min(canvas.height - this.height/2, this.y));
 
1099
  return true;
1100
  }
1101
  }
 
1113
  }
1114
 
1115
  shoot() {
 
1116
  if (isCountingDown) return;
 
1117
  const mgSound = new Audio('firemg.ogg');
1118
  mgSound.volume = 0.5;
1119
  mgSound.play();
 
1120
  bullets.push({
1121
  x: this.x,
1122
  y: this.y,
 
1130
  }
1131
 
1132
  update() {
 
1133
  if (isCountingDown) return false;
 
1134
  this.x -= this.speed;
 
1135
  const now = Date.now();
1136
  if (now - this.lastShot > 200) {
1137
  this.shoot();
1138
  this.lastShot = now;
1139
  }
 
1140
  return this.x > 0;
1141
  }
1142
  }
 
1145
  constructor(y) {
1146
  this.y = y;
1147
  this.startTime = Date.now();
1148
+ this.duration = 750;
1149
  }
1150
 
1151
  draw(ctx) {
1152
  ctx.beginPath();
1153
  ctx.strokeStyle = 'red';
1154
+ ctx.lineWidth = 5;
1155
+ ctx.setLineDash([10, 20]);
1156
  ctx.moveTo(0, this.y);
1157
  ctx.lineTo(canvas.width, this.y);
1158
  ctx.stroke();
1159
  ctx.setLineDash([]);
1160
+ ctx.lineWidth = 1;
1161
  }
1162
 
1163
  isExpired() {
 
1180
 
1181
  shoot() {
1182
  if (isCountingDown || document.getElementById('shop').style.display === 'block') return;
 
1183
  const mgSound = new Audio('firemg.ogg');
1184
  mgSound.volume = 0.5;
1185
  mgSound.play();
 
1186
  bullets.push({
1187
  x: this.x,
1188
  y: this.y,
1189
+ angle: Math.PI / 2,
1190
  speed: 10,
1191
  isEnemy: true,
1192
  damage: 100,
 
1197
 
1198
  update() {
1199
  if (isCountingDown) return true;
 
1200
  this.y += this.speed;
 
1201
  const now = Date.now();
1202
  if (now - this.lastShot > this.fireRate) {
1203
  this.shoot();
1204
  this.lastShot = now;
1205
  }
 
1206
  return this.y < canvas.height;
1207
  }
1208
  }
 
1245
 
1246
  dropBomb() {
1247
  if (document.getElementById('shop').style.display === 'block') return;
 
1248
  const bombSound = new Audio('bomb2.ogg');
1249
  bombSound.volume = 0.5;
1250
  bombSound.play();
 
1251
  effects.push(new Effect(
1252
  this.x,
1253
  this.y,
 
1255
  'bomb',
1256
  0
1257
  ));
 
1258
  const blastRadius = 100;
 
1259
  const distToPlayer = Math.hypot(this.x - player.x, this.y - player.y);
1260
  if (distToPlayer < blastRadius) {
1261
  player.health -= 300;
1262
  }
 
1263
  allyUnits.forEach(ally => {
1264
  const distToAlly = Math.hypot(this.x - ally.x, this.y - ally.y);
1265
  if (distToAlly < blastRadius) {
 
1270
 
1271
  update() {
1272
  if (isCountingDown) return true;
 
1273
  this.x -= this.speed;
 
1274
  const now = Date.now();
1275
  if (now - this.lastBombTime > this.bombInterval) {
1276
  this.dropBomb();
1277
  this.lastBombTime = now;
1278
  }
 
1279
  return this.x > 0;
1280
  }
1281
  }
 
1306
  function spawnSpitfires() {
1307
  if (currentStage === 2 && !isCountingDown && !gameOver && gameStarted) {
1308
  const now = Date.now();
 
1309
  if (now - lastSpitfireSpawn > 15000) {
1310
  console.log('Spawning Spitfires...');
 
1311
  const positions = [
1312
  canvas.height * 0.2,
1313
  canvas.height * 0.5,
1314
  canvas.height * 0.8
1315
  ];
 
1316
  positions.forEach(y => {
1317
  warningLines.push(new WarningLine(y));
1318
  });
 
1319
  setTimeout(() => {
1320
  if (!isCountingDown && !gameOver) {
1321
  positions.forEach(y => {
 
1324
  console.log('Spitfires spawned:', spitfires.length);
1325
  }
1326
  }, 2000);
 
1327
  lastSpitfireSpawn = now;
1328
  }
1329
  }
 
1346
 
1347
  update() {
1348
  this.x += this.speed;
 
1349
  if (isCountingDown) {
1350
  if (this.mgSound) {
1351
  this.mgSound.pause();
 
1353
  }
1354
  this.hasPlayedSound = false;
1355
  }
 
1356
  const now = Date.now();
1357
  if (now - this.lastShot > 200 && !isCountingDown) {
1358
  this.shoot();
 
1368
  firstSound.play();
1369
  this.hasPlayedSound = true;
1370
  }
 
1371
  if (!isCountingDown) {
1372
  const shootSound = new Audio('bf109mgse.ogg');
1373
  shootSound.volume = 0.3;
1374
  shootSound.play();
1375
  }
 
1376
  bullets.push({
1377
  x: this.x + Math.cos(this.angle) * 30,
1378
  y: this.y + Math.sin(this.angle) * 30,
 
1404
 
1405
  shoot() {
1406
  enemyFireSound.cloneNode().play();
 
1407
  bullets.push({
1408
  x: this.x + Math.cos(this.angle) * 30,
1409
  y: this.y + Math.sin(this.angle) * 30,
 
1414
  size: 3,
1415
  color: 'blue'
1416
  });
 
1417
  effects.push(new Effect(
1418
  this.x + Math.cos(this.angle) * 30,
1419
  this.y + Math.sin(this.angle) * 30,
 
1426
 
1427
  update() {
1428
  if(isCountingDown || this.isDead) return;
 
1429
  let nearestEnemy = null;
1430
  let minDist = Infinity;
 
1431
  enemies.forEach(enemy => {
1432
  const dist = Math.hypot(enemy.x - this.x, enemy.y - this.y);
1433
  if(dist < minDist) {
 
1443
 
1444
  if(nearestEnemy) {
1445
  this.angle = Math.atan2(nearestEnemy.y - this.y, nearestEnemy.x - this.x);
 
1446
  if(minDist > 300) {
1447
  const newX = this.x + Math.cos(this.angle) * this.speed;
1448
  const newY = this.y + Math.sin(this.angle) * this.speed;
 
1449
  let canMove = true;
1450
  allyUnits.forEach(ally => {
1451
  if (ally !== this) {
 
1455
  }
1456
  }
1457
  });
 
1458
  if (canMove) {
1459
  this.x = newX;
1460
  this.y = newY;
1461
  }
1462
  }
 
1463
  const now = Date.now();
1464
  if(now - this.lastShot > this.shootInterval) {
1465
  this.shoot();
1466
  this.lastShot = now;
1467
  }
1468
  }
 
1469
  this.x = Math.max(this.width/2, Math.min(canvas.width - this.width/2, this.x));
1470
  this.y = Math.max(this.height/2, Math.min(canvas.height - this.height/2, this.y));
1471
  }
 
1498
 
1499
  shoot() {
1500
  enemyFireSound.cloneNode().play();
 
1501
  bullets.push({
1502
  x: this.x + Math.cos(this.angle) * 30,
1503
  y: this.y + Math.sin(this.angle) * 30,
 
1508
  size: 3,
1509
  color: 'blue'
1510
  });
 
1511
  effects.push(new Effect(
1512
  this.x + Math.cos(this.angle) * 30,
1513
  this.y + Math.sin(this.angle) * 30,
 
1520
 
1521
  update() {
1522
  if(isCountingDown || this.isDead) return;
 
1523
  let nearestEnemy = null;
1524
  let minDist = Infinity;
 
1525
  enemies.forEach(enemy => {
1526
  const dist = Math.hypot(enemy.x - this.x, enemy.y - this.y);
1527
  if(dist < minDist) {
 
1529
  nearestEnemy = enemy;
1530
  }
1531
  });
 
1532
  if(this.health <= 0 && !this.isDead) {
1533
  this.die();
1534
  return;
1535
  }
 
1536
  if(nearestEnemy) {
1537
  this.angle = Math.atan2(nearestEnemy.y - this.y, nearestEnemy.x - this.x);
 
1538
  if(minDist > 300) {
1539
  this.x += Math.cos(this.angle) * this.speed;
1540
  this.y += Math.sin(this.angle) * this.speed;
1541
  }
 
1542
  const now = Date.now();
1543
  if(now - this.lastShot > this.shootInterval) {
1544
  this.shoot();
1545
  this.lastShot = now;
1546
  }
1547
  }
 
1548
  this.x = Math.max(this.width/2, Math.min(canvas.width - this.width/2, this.x));
1549
  this.y = Math.max(this.height/2, Math.min(canvas.height - this.height/2, this.y));
1550
  }
1551
 
1552
  die() {
1553
  this.isDead = true;
1554
+ effects.push(new Effect(this.x, this.y, 1000, 'death'));
 
 
 
 
 
 
 
1555
  const deathSound = new Audio('death.ogg');
1556
  deathSound.volume = 1.0;
1557
  deathSound.play();
 
1576
  player.width = 100;
1577
  player.height = 45;
1578
  }
 
1579
  player.health = player.maxHealth;
1580
+ updateScoreDisplay(); // 돈 반영
1581
  }
1582
  }
1583
 
 
1587
  hasAPCR = true;
1588
  document.getElementById('apcr').style.display = 'none';
1589
  document.getElementById('shop').style.display = 'none';
1590
+ updateScoreDisplay(); // 돈 반영
1591
  }
1592
  }
1593
 
 
1597
  hasBF109 = true;
1598
  document.getElementById('bf109').style.display = 'none';
1599
  document.getElementById('shop').style.display = 'none';
1600
+ updateScoreDisplay(); // 돈 반영
1601
  }
1602
  }
1603
 
 
1608
  document.getElementById('ju87').style.display = 'none';
1609
  document.getElementById('shop').style.display = 'none';
1610
  lastJU87Spawn = Date.now();
1611
+ updateScoreDisplay(); // 돈 반영
1612
  }
1613
  }
1614
 
1615
  function updateGame() {
1616
  if(gameOver) return;
 
1617
  if(!isCountingDown) {
 
1618
  if(keys['w']) player.y -= player.speed;
1619
  if(keys['s']) player.y += player.speed;
1620
  if(keys['a']) player.x -= player.speed;
1621
  if(keys['d']) player.x += player.speed;
 
 
1622
  player.x = Math.max(player.width/2, Math.min(canvas.width - player.width/2, player.x));
1623
  player.y = Math.max(player.height/2, Math.min(canvas.height - player.height/2, player.y));
 
1624
  fireBullet();
1625
  }
 
 
1626
  if(player.health <= 0) {
1627
  bgm.pause();
1628
  bgm.currentTime = 0;
 
1629
  escapeSound.volume = 1.0;
1630
  escapeSound.play();
 
1631
  setTimeout(() => {
1632
  deathSound.play();
1633
  }, 100);
 
1634
  gameOver = true;
1635
  document.getElementById('restart').style.display = 'block';
1636
  effects.push(new Effect(player.x, player.y, 1000, 'death'));
 
 
1637
  showScoreSubmission();
1638
  }
 
 
1639
  if (currentStage === 3 && !isCountingDown && !gameOver && gameStarted) {
1640
  const now = Date.now();
1641
+ if (now - lastP51Spawn > 15000) {
1642
  const positions = [
1643
+ canvas.width * 0.25,
1644
+ canvas.width * 0.5,
1645
+ canvas.width * 0.75
1646
  ];
 
 
1647
  positions.forEach(x => {
1648
  p51WarningLines.push(new P51WarningLine(x));
1649
  });
 
 
1650
  setTimeout(() => {
1651
  if (!isCountingDown && !gameOver) {
1652
  positions.forEach(x => {
 
1654
  });
1655
  }
1656
  }, 2000);
 
1657
  lastP51Spawn = now;
1658
  }
1659
+ if (now - lastB29Spawn > 15000) {
 
 
 
 
 
1660
  const positions = [
1661
+ canvas.height * 0.2,
1662
+ canvas.height * 0.5,
1663
+ canvas.height * 0.8
1664
  ];
 
 
1665
  positions.forEach(y => {
1666
  b29WarningLines.push(new B29WarningLine(y));
1667
  });
 
 
1668
  setTimeout(() => {
1669
  if (!isCountingDown && !gameOver) {
1670
  positions.forEach(y => {
 
1672
  });
1673
  }
1674
  }, 2000);
 
1675
  lastB29Spawn = now;
1676
  }
1677
  }
 
 
1678
  p51s = p51s.filter(p51 => p51.update());
 
 
1679
  p51WarningLines = p51WarningLines.filter(line => !line.isExpired());
 
 
1680
  b29s = b29s.filter(b29 => b29.update());
 
 
1681
  b29WarningLines = b29WarningLines.filter(line => !line.isExpired());
1682
+
 
1683
  if (hasBF109 && !isCountingDown) {
1684
  const now = Date.now();
1685
+ if (now - lastSupportSpawn > 10000) {
1686
  supportUnits.push(
1687
  new SupportUnit(canvas.height * 0.2),
1688
  new SupportUnit(canvas.height * 0.5),
 
1691
  lastSupportSpawn = now;
1692
  }
1693
  }
 
 
1694
  if (hasJU87 && !isCountingDown) {
1695
  const now = Date.now();
1696
+ if (now - lastJU87Spawn > 15000) {
1697
  supportUnits.push(new JU87());
1698
  lastJU87Spawn = now;
1699
  }
1700
  }
 
 
1701
  spawnSpitfires();
1702
  spitfires = spitfires.filter(spitfire => spitfire.update());
 
 
 
 
 
1703
  warningLines = warningLines.filter(line => !line.isExpired());
 
 
1704
  supportUnits = supportUnits.filter(unit => unit.update());
 
 
1705
  allyUnits.forEach(unit => unit.update());
1706
  allyUnits = allyUnits.filter(unit => unit.health > 0);
 
 
1707
  enemies.forEach(enemy => enemy.update());
1708
 
1709
  if(!isCountingDown) {
 
1710
  bullets = bullets.filter(bullet => {
1711
  bullet.x += Math.cos(bullet.angle) * bullet.speed;
1712
  bullet.y += Math.sin(bullet.angle) * bullet.speed;
 
1713
  if(!bullet.isEnemy) {
 
1714
  for(let i = 0; i < enemies.length; i++) {
1715
  const enemy = enemies[i];
1716
  const dist = Math.hypot(bullet.x - enemy.x, bullet.y - enemy.y);
1717
  if(dist < 30) {
1718
+ effects.push(new Effect(bullet.x, bullet.y, 500, 'hit'));
1719
+ textEffects.push(new TextEffect(enemy.x, enemy.y - 30, "HIT!", "#00ff00"));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1720
  let damage = currentWeapon === 'cannon' ? 1000 : 150;
1721
  enemy.health -= damage;
 
1722
  if(enemy.health <= 0) {
1723
  spawnHealthItem(enemy.x, enemy.y);
1724
  gold += 100;
1725
+ score += 200;
1726
  totalScore += 200;
1727
  updateScoreDisplay();
1728
  effects.push(new Effect(enemy.x, enemy.y, 1000, 'death'));
1729
  deathSound.cloneNode().play();
 
1730
  if (!currentHitSound || currentHitSound.ended) {
1731
  currentHitSound = hitSounds[Math.floor(Math.random() * hitSounds.length)];
1732
  currentHitSound.volume = 1.0;
1733
  currentHitSound.play();
1734
  }
 
1735
  enemies.splice(i, 1);
1736
  }
 
 
1737
  return false;
1738
  }
1739
  }
1740
  } else {
 
1741
  if (!document.getElementById('shop').style.display ||
1742
  document.getElementById('shop').style.display === 'none') {
1743
  const distToPlayer = Math.hypot(bullet.x - player.x, bullet.y - player.y);
1744
  if(distToPlayer < 30) {
1745
+ effects.push(new Effect(bullet.x, bullet.y, 500, 'hit'));
 
 
 
 
 
 
1746
  player.health -= bullet.damage || 100;
1747
  return false;
1748
  }
 
1749
  for(let ally of allyUnits) {
1750
  const distToAlly = Math.hypot(bullet.x - ally.x, bullet.y - ally.y);
1751
  if(distToAlly < 30) {
1752
+ effects.push(new Effect(bullet.x, bullet.y, 500, 'hit'));
 
 
 
 
 
 
1753
  ally.health -= bullet.damage || 100;
1754
  return false;
1755
  }
1756
  }
1757
  }
1758
  }
 
 
1759
  return bullet.x >= 0 && bullet.x <= canvas.width &&
1760
  bullet.y >= 0 && bullet.y <= canvas.height;
1761
  });
 
 
1762
  items = items.filter(item => {
1763
  const dist = Math.hypot(item.x - player.x, item.y - player.y);
1764
  if(dist < 30) {
1765
  player.health = Math.min(player.health + 200, player.maxHealth);
1766
+ score += 50;
1767
  totalScore += 50;
1768
  updateScoreDisplay();
1769
  return false;
1770
  }
1771
  return true;
1772
  });
 
 
1773
  allyUnits.forEach(ally => {
1774
  enemies.forEach(enemy => {
1775
  const dist = Math.hypot(ally.x - enemy.x, ally.y - enemy.y);
1776
  if (dist < (ally.width + enemy.width) / 2) {
1777
  const angle = Math.atan2(ally.y - enemy.y, ally.x - enemy.x);
1778
  const pushDistance = ((ally.width + enemy.width) / 2 - dist) / 2;
 
1779
  ally.x += Math.cos(angle) * pushDistance;
1780
  ally.y += Math.sin(angle) * pushDistance;
1781
  enemy.x -= Math.cos(angle) * pushDistance;
 
1783
  }
1784
  });
1785
  });
 
 
1786
  checkRoundClear();
1787
  }
1788
  }
1789
 
1790
  function drawGame() {
1791
  ctx.clearRect(0, 0, canvas.width, canvas.height);
 
 
1792
  const pattern = ctx.createPattern(backgroundImg, 'repeat');
1793
  ctx.fillStyle = pattern;
1794
  ctx.fillRect(0, 0, canvas.width, canvas.height);
1795
+
 
1796
  ctx.save();
1797
  ctx.translate(player.x, player.y);
1798
  ctx.rotate(player.angle);
1799
  ctx.drawImage(playerImg, -player.width/2, -player.height/2, player.width, player.height);
1800
  ctx.restore();
 
 
1801
  drawHealthBar(player.x, player.y - 40, player.health, player.maxHealth, 60, 5, 'green');
1802
+
 
1803
  enemies.forEach(enemy => {
1804
  ctx.save();
1805
  ctx.translate(enemy.x, enemy.y);
 
1810
  drawHealthBar(enemy.x, enemy.y - 40, enemy.health, enemy.maxHealth, 60, 5, 'red');
1811
  });
1812
 
 
1813
  allyUnits.forEach(ally => {
1814
  ctx.save();
1815
  ctx.translate(ally.x, ally.y);
 
1819
  drawHealthBar(ally.x, ally.y - 40, ally.health, ally.maxHealth, 60, 5, 'blue');
1820
  });
1821
 
 
1822
  warningLines.forEach(line => line.draw(ctx));
 
 
1823
  p51WarningLines.forEach(line => line.draw(ctx));
 
 
1824
  b29WarningLines.forEach(line => line.draw(ctx));
1825
 
 
1826
  p51s.forEach(p51 => {
1827
  ctx.save();
1828
  ctx.translate(p51.x, p51.y);
 
1830
  ctx.drawImage(p51.img, -p51.width/2, -p51.height/2, p51.width, p51.height);
1831
  ctx.restore();
1832
  });
 
 
1833
  b29s.forEach(b29 => {
1834
  ctx.save();
1835
  ctx.translate(b29.x, b29.y);
 
1837
  ctx.drawImage(b29.img, -b29.width/2, -b29.height/2, b29.width, b29.height);
1838
  ctx.restore();
1839
  });
 
 
1840
  spitfires.forEach(spitfire => {
1841
  ctx.save();
1842
  ctx.translate(spitfire.x, spitfire.y);
 
1844
  ctx.drawImage(spitfire.img, -spitfire.width/2, -spitfire.height/2, spitfire.width, spitfire.height);
1845
  ctx.restore();
1846
  });
 
 
1847
  supportUnits.forEach(unit => {
1848
  ctx.save();
1849
  ctx.translate(unit.x, unit.y);
 
1851
  ctx.drawImage(unit.img, -unit.width/2, -unit.height/2, unit.width, unit.height);
1852
  ctx.restore();
1853
  });
 
 
1854
  bullets.forEach(bullet => {
1855
  if (bullet.isEnemy || !bullet.isAPCR) {
1856
  ctx.beginPath();
 
1867
  ctx.restore();
1868
  }
1869
  });
 
 
1870
  items.forEach(item => {
1871
  ctx.beginPath();
1872
  ctx.fillStyle = 'green';
1873
  ctx.arc(item.x, item.y, 10, 0, Math.PI * 2);
1874
  ctx.fill();
1875
  });
 
 
1876
  textEffects = textEffects.filter(effect => !effect.isExpired());
1877
  textEffects.forEach(effect => {
1878
  effect.update();
1879
  effect.draw(ctx);
1880
  });
 
 
1881
  effects = effects.filter(effect => !effect.isExpired());
1882
  effects.forEach(effect => {
1883
  effect.update();
 
1886
  if (effect.type === 'fire' || effect.type === 'cannon_fire') {
1887
  ctx.rotate(effect.angle);
1888
  }
 
1889
  if (effect.type === 'cannon_fire') {
 
1890
  const size = effect.currentSize;
1891
  ctx.drawImage(effect.img, -size.width/2, -size.height/2, size.width, size.height);
1892
  } else {
 
1893
  ctx.drawImage(effect.img, -effect.size/2, -effect.size/2, effect.size, effect.size);
1894
  }
1895
  ctx.restore();
1896
  });
 
 
1897
  if (isCountingDown) {
1898
  ctx.fillStyle = 'rgba(0, 0, 0, 0.5)';
1899
  ctx.fillRect(0, 0, canvas.width, canvas.height);
 
1902
 
1903
  function updateBF109Damage() {
1904
  supportUnits = supportUnits.filter(unit => {
1905
+ if (unit instanceof SupportUnit) {
1906
  let hitCount = 0;
1907
  bullets = bullets.filter(bullet => {
1908
  if (bullet.isSpitfireBullet) {
 
1914
  }
1915
  return true;
1916
  });
 
1917
  if (hitCount >= 5) {
1918
  effects.push(new Effect(unit.x, unit.y, 1000, 'death'));
1919
  deathSound.cloneNode().play();
 
1937
  this.x = Math.random() * canvas.width;
1938
  this.y = Math.random() * canvas.height;
1939
  this.isElite = isElite;
 
1940
  if (currentStage === 3) {
1941
  if (isBoss) {
1942
  this.health = 30000;
 
1949
  this.health = 3000;
1950
  this.maxHealth = 3000;
1951
  this.speed = 2;
1952
+ this.shootInterval = currentRound >= 11 ? 2000 : 3000;
1953
  this.enemyImg = new Image();
1954
  this.enemyImg.src = currentRound >= 11 ? 'enemyus2.png' : 'enemyus0.png';
1955
  } else {
 
2021
  this.enemyImg.src = 'enemy.png';
2022
  }
2023
  }
 
2024
  this.lastShot = 0;
2025
  this.angle = 0;
2026
  this.width = 100;
2027
  this.height = 45;
 
 
2028
  if (isBoss && currentStage === 3 && currentRound === 20) {
2029
+ this.width = 150;
2030
+ this.height = 67;
2031
  }
 
2032
  this.moveTimer = 0;
2033
  this.moveInterval = Math.random() * 2000 + 1000;
2034
  this.moveAngle = Math.random() * Math.PI * 2;
 
2038
  update() {
2039
  if(isCountingDown) return;
2040
  const now = Date.now();
 
2041
  if (now - this.moveTimer > this.moveInterval) {
2042
  this.moveAngle = Math.random() * Math.PI * 2;
2043
  this.moveTimer = now;
2044
  }
 
2045
  this.x += Math.cos(this.moveAngle) * this.speed;
2046
  this.y += Math.sin(this.moveAngle) * this.speed;
 
2047
  this.x = Math.max(this.width/2, Math.min(canvas.width - this.width/2, this.x));
2048
  this.y = Math.max(this.height/2, Math.min(canvas.height - this.height/2, this.y));
 
2049
  this.angle = Math.atan2(player.y - this.y, player.x - this.x);
 
2050
  if (now - this.lastShot > this.shootInterval && !isCountingDown) {
2051
  this.shoot();
2052
  this.lastShot = now;
 
2059
  const bossSound = new Audio('fireenemy2.ogg');
2060
  bossSound.volume = 0.5;
2061
  bossSound.play();
 
 
2062
  const gunPositions = [
2063
  { x: this.x + Math.cos(this.angle) * 30, y: this.y + Math.sin(this.angle) * 30 },
2064
  { x: this.x + Math.cos(this.angle + Math.PI) * 30, y: this.y + Math.sin(this.angle + Math.PI) * 30 }
2065
  ];
 
 
2066
  gunPositions.forEach(pos => {
2067
  effects.push(new Effect(
2068
  pos.x,
2069
  pos.y,
2070
+ 750,
2071
  'cannon_fire',
2072
  this.angle,
2073
  this
2074
  ));
 
2075
  bullets.push({
2076
  x: pos.x,
2077
  y: pos.y,
 
2086
  const eliteSound = new Audio('fireenemy3.ogg');
2087
  eliteSound.volume = 0.5;
2088
  eliteSound.play();
 
2089
  effects.push(new Effect(
2090
  this.x + Math.cos(this.angle) * 30,
2091
  this.y + Math.sin(this.angle) * 30,
2092
+ 750,
2093
  'cannon_fire',
2094
  this.angle,
2095
  this
2096
  ));
 
2097
  bullets.push({
2098
  x: this.x + Math.cos(this.angle) * 30,
2099
  y: this.y + Math.sin(this.angle) * 30,
 
2105
  });
2106
  } else {
2107
  enemyFireSound.cloneNode().play();
 
2108
  effects.push(new Effect(
2109
  this.x + Math.cos(this.angle) * 30,
2110
  this.y + Math.sin(this.angle) * 30,
 
2113
  this.angle,
2114
  this
2115
  ));
 
2116
  bullets.push({
2117
  x: this.x + Math.cos(this.angle) * 30,
2118
  y: this.y + Math.sin(this.angle) * 30,
 
2126
  } else {
2127
  const sound = this.isBoss ? new Audio('firemn.ogg') : enemyFireSound.cloneNode();
2128
  sound.play();
 
 
2129
  if (this.isBoss || this.isElite) {
2130
  effects.push(new Effect(
2131
  this.x + Math.cos(this.angle) * 30,
2132
  this.y + Math.sin(this.angle) * 30,
2133
+ 750,
2134
  'cannon_fire',
2135
  this.angle,
2136
  this
 
2145
  this
2146
  ));
2147
  }
 
2148
  bullets.push({
2149
  x: this.x + Math.cos(this.angle) * 30,
2150
  y: this.y + Math.sin(this.angle) * 30,
 
2165
  player.health = player.maxHealth;
2166
  bullets = [];
2167
  items = [];
2168
+ allyUnits = [];
2169
  if (currentStage === 2 && allyUnits.length < 2) {
2170
  allyUnits.push(new PanzerIII());
2171
  }
 
2193
  const now = Date.now();
2194
  if ((keys[' '] || autoFire) && now - lastShot > weapon.fireRate) {
2195
  weapon.sound.cloneNode().play();
 
 
2196
  const muzzleX = player.x + Math.cos(player.angle) * (player.width/2);
2197
  const muzzleY = player.y + Math.sin(player.angle) * (player.width/2);
 
 
2198
  if (currentWeapon === 'cannon') {
 
2199
  effects.push(new Effect(
2200
  muzzleX,
2201
  muzzleY,
2202
+ 1500,
2203
+ 'cannon_fire',
2204
  player.angle,
2205
  player
2206
  ));
2207
  } else {
 
2208
  effects.push(new Effect(
2209
  player.x + Math.cos(player.angle) * 30,
2210
  player.y + Math.sin(player.angle) * 30,
 
2214
  player
2215
  ));
2216
  }
 
 
2217
  bullets.push({
2218
+ x: muzzleX,
2219
  y: muzzleY,
2220
  angle: player.angle,
2221
  speed: hasAPCR ? 20 : 10,
 
2236
  this.color = color;
2237
  this.startTime = Date.now();
2238
  this.duration = duration;
2239
+ this.fadeOut = 0.5;
2240
  this.initialY = y;
2241
+ this.speed = -1;
2242
  }
2243
 
2244
  update() {
2245
  const elapsed = (Date.now() - this.startTime) / this.duration;
2246
+ this.y = this.initialY + (this.speed * elapsed * 50);
2247
  }
2248
 
2249
  draw(ctx) {
2250
  const elapsed = (Date.now() - this.startTime) / this.duration;
2251
  const opacity = elapsed > this.fadeOut ?
2252
  1 - ((elapsed - this.fadeOut) / (1 - this.fadeOut)) : 1;
 
2253
  ctx.save();
2254
  ctx.fillStyle = this.color;
2255
  ctx.globalAlpha = opacity;
 
2275
  this.parent = parent;
2276
  this.offset = { x: Math.cos(angle) * 30, y: Math.sin(angle) * 30 };
2277
  this.img = new Image();
 
 
2278
  this.currentShot = 1;
2279
  this.lastShotChange = Date.now();
 
 
2280
  this.shotSizes = {
2281
+ 1: { width: 40, height: 40 },
2282
+ 2: { width: 60, height: 60 },
2283
+ 3: { width: 80, height: 80 }
2284
  };
 
 
2285
  if (type === 'death') {
2286
  this.img.src = 'bang.png';
2287
  } else if (type === 'bomb') {
 
2290
  this.img.src = 'fire3.png';
2291
  } else if (type === 'cannon_fire') {
2292
  this.img.src = 'shot1.png';
2293
+ this.duration = 750;
2294
+ this.currentSize = this.shotSizes[1];
2295
  } else {
2296
  this.img.src = 'fire2.png';
2297
  }
2298
+ this.size = type === 'bomb' ? 100 : type === 'death' ? 75 : type === 'hit' ? 30 : 42;
 
 
 
 
2299
  }
2300
 
2301
  update() {
 
2304
  this.x = this.parent.x + Math.cos(this.parent.angle) * (this.parent.width/2);
2305
  this.y = this.parent.y + Math.sin(this.parent.angle) * (this.parent.width/2);
2306
  this.angle = this.parent.angle;
 
2307
  const now = Date.now();
2308
  const elapsedTime = now - this.startTime;
 
 
2309
  if (elapsedTime < 250) {
2310
  this.currentShot = 1;
2311
  } else if (elapsedTime < 500) {
 
2313
  } else {
2314
  this.currentShot = 3;
2315
  }
 
2316
  this.img.src = `shot${this.currentShot}.png`;
2317
  this.currentSize = this.shotSizes[this.currentShot];
2318
  } else if(this.type === 'fire') {
 
2363
  function fetchLeaderboardData() {
2364
  const leaderboardBody = document.getElementById('leaderboardBody');
2365
  leaderboardBody.innerHTML = '<tr><td colspan="5">Loading...</td></tr>';
 
 
2366
  let leaderboardData = JSON.parse(localStorage.getItem('tankGameLeaderboard') || '[]');
 
 
2367
  leaderboardData.sort((a, b) => b.score - a.score);
 
 
2368
  leaderboardBody.innerHTML = '';
 
2369
  if (leaderboardData.length === 0) {
2370
  leaderboardBody.innerHTML = '<tr><td colspan="5">No scores yet</td></tr>';
2371
  return;
2372
  }
 
2373
  leaderboardData.forEach((entry, index) => {
2374
  const row = document.createElement('tr');
 
2375
  const rankCell = document.createElement('td');
2376
  rankCell.textContent = index + 1;
 
2377
  const nameCell = document.createElement('td');
2378
  nameCell.textContent = entry.name;
 
2379
  const stageCell = document.createElement('td');
2380
  stageCell.textContent = entry.stage;
 
2381
  const waveCell = document.createElement('td');
2382
  waveCell.textContent = entry.wave;
 
2383
  const scoreCell = document.createElement('td');
2384
  scoreCell.textContent = entry.score;
 
2385
  row.appendChild(rankCell);
2386
  row.appendChild(nameCell);
2387
  row.appendChild(stageCell);
2388
  row.appendChild(waveCell);
2389
  row.appendChild(scoreCell);
 
2390
  leaderboardBody.appendChild(row);
2391
  });
2392
  }
 
2399
 
2400
  function submitScore() {
2401
  const playerNameInput = document.getElementById('playerName').value.trim();
 
2402
  if (!playerNameInput) {
2403
  alert('Please enter your name');
2404
  return;
2405
  }
 
2406
  playerName = playerNameInput;
2407
  localStorage.setItem('tankGamePlayerName', playerName);
 
 
2408
  let leaderboardData = JSON.parse(localStorage.getItem('tankGameLeaderboard') || '[]');
 
 
2409
  leaderboardData.push({
2410
  name: playerName,
2411
  stage: currentStage,
 
2413
  score: totalScore,
2414
  date: new Date().toISOString()
2415
  });
 
 
2416
  leaderboardData.sort((a, b) => b.score - a.score);
2417
  localStorage.setItem('tankGameLeaderboard', JSON.stringify(leaderboardData));
 
 
2418
  document.getElementById('scoreSubmission').style.display = 'none';
 
 
2419
  showLeaderboard();
2420
  }
2421
 
 
2422
  function isIOS() {
2423
  return [
2424
  'iPad Simulator',
 
2433
 
2434
  const iosInstructionsDiv = document.getElementById('iosInstallInstructions');
2435
  const closeInstructionsButton = document.getElementById('closeInstallInstructions');
 
 
2436
  if (isIOS() && !window.navigator.standalone && iosInstructionsDiv) {
 
 
2437
  let promptSupported = false;
2438
  window.addEventListener('beforeinstallprompt', () => {
2439
  promptSupported = true;
2440
  });
 
 
2441
  setTimeout(() => {
2442
+ if (!promptSupported && iosInstructionsDiv.style.display !== 'block') {
2443
  console.log("iOS detected, standalone mode is false, and beforeinstallprompt likely not supported. Showing instructions.");
2444
+ iosInstructionsDiv.classList.add('show-instructions');
 
2445
  document.body.classList.add('ios-device');
2446
  } else {
2447
  console.log("iOS detected, but PWA might be installed or prompt is supported.");
2448
  }
2449
+ }, 1500);
2450
  }
 
 
2451
  if (closeInstructionsButton && iosInstructionsDiv) {
2452
  closeInstructionsButton.addEventListener('click', () => {
2453
+ iosInstructionsDiv.style.display = 'none';
2454
  });
2455
  }
2456
 
 
2457
  document.getElementById('nextRound').addEventListener('click', () => {
2458
  currentRound++;
2459
  document.getElementById('nextRound').style.display = 'none';
 
2492
  });
2493
  }
2494
 
 
2495
  window.addEventListener('load', function() {
 
2496
  canvas.width = window.innerWidth;
2497
  canvas.height = window.innerHeight;
 
 
2498
  document.getElementById('instructions').style.display = 'none';
2499
  document.getElementById('weaponInfo').style.display = 'none';
2500
  document.getElementById('scoreBoard').style.display = 'none';
2501
+ document.getElementById('moneyBoard').style.display = 'none';
2502
  document.getElementById('gameCanvas').style.display = 'none';
 
 
2503
  bgm.play().catch(err => console.error("Error playing title music:", err));
2504
  });
2505
  </script>
2506
  </body>
2507
+ </html>