Spaces:
Running
Running
Update index.html
Browse files- 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 |
-
|
|
|
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 =
|
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;
|
1193 |
}
|
1194 |
|
1195 |
draw(ctx) {
|
1196 |
ctx.beginPath();
|
1197 |
ctx.strokeStyle = 'red';
|
1198 |
-
ctx.lineWidth = 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,
|
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) {
|
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) {
|
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) {
|
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 |
-
|
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) {
|
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;
|
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;
|
2253 |
-
this.height = 67;
|
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,
|
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,
|
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,
|
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 = [];
|
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,
|
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;
|
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 },
|
2536 |
-
2: { width: 60, height: 60 },
|
2537 |
-
3: { width: 80, height: 80 }
|
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;
|
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') {
|
2741 |
console.log("iOS detected, standalone mode is false, and beforeinstallprompt likely not supported. Showing instructions.");
|
2742 |
-
iosInstructionsDiv.classList.add('show-instructions');
|
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);
|
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';
|
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>
|