Add tank stage 4 with water and supply crates
Stages so far only varied the walls. Give stage 4 rules of its own. Water blocks tanks but not shells, so the centre of the map is crossed only at the fords while both sides can still shoot across it. It is one predicate - rectBlocked is used for tank movement alone, so adding WATER there leaves bullets untouched. Supply crates appear on a timer and grant a 3s enemy freeze or 10s of steel around the base; the shovel effect puts brick back when it lapses, so broken walls are repaired. Active effects show a countdown badge. Stages carry a specials line: shown on the start overlay and in the HUD on entry, and previewed on the preceding stage's clear screen. Checked headlessly: over 247k sampled tank positions none sat on water while 7591 bullet frames did, crates were taken 8 times in 8 runs, and stage 4 leaves the base standing in 7 of 10 idle runs with 98 cells the player can reach without firing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+211
-27
@@ -38,14 +38,14 @@
|
||||
background:var(--panel);
|
||||
border:2px solid var(--line);
|
||||
border-radius:10px;
|
||||
padding:14px 14px 16px;
|
||||
padding:13px 13px 14px;
|
||||
box-shadow:0 18px 50px rgba(0,0,0,.45);
|
||||
}
|
||||
.hud h1{margin:0 0 2px; font-size:19px; letter-spacing:.06em; color:var(--accent)}
|
||||
.hud .sub{font-size:11px; color:var(--dim); margin-bottom:14px; letter-spacing:.12em}
|
||||
.hud .sub{font-size:11px; color:var(--dim); margin-bottom:11px; letter-spacing:.12em}
|
||||
.row{
|
||||
display:flex; align-items:center; justify-content:space-between;
|
||||
font-size:13px; padding:7px 0; border-bottom:1px dashed #3d2f22;
|
||||
font-size:13px; padding:5px 0; border-bottom:1px dashed #3d2f22;
|
||||
}
|
||||
.row:last-of-type{border-bottom:0}
|
||||
.row b{font-size:16px; color:#fff; font-variant-numeric:tabular-nums}
|
||||
@@ -53,7 +53,7 @@
|
||||
.pip{width:12px;height:12px;border-radius:2px;background:#dcc76d;box-shadow:inset 0 -3px 0 rgba(0,0,0,.28)}
|
||||
.pip.e{background:#b9c2cc}
|
||||
.pip.off{background:#463728; box-shadow:none}
|
||||
.keys{margin-top:14px; font-size:12px; line-height:1.85; color:#cbb79a}
|
||||
.keys{margin-top:11px; font-size:12px; line-height:1.72; color:#cbb79a}
|
||||
kbd{
|
||||
display:inline-block; min-width:20px; text-align:center;
|
||||
background:var(--panel2); border:1px solid var(--line);
|
||||
@@ -61,19 +61,28 @@
|
||||
padding:1px 6px; margin-right:5px; font-size:11px; color:#f5ead4;
|
||||
font-family:inherit;
|
||||
}
|
||||
.note{margin-top:12px;font-size:11px;color:#8e7a5f;line-height:1.7}
|
||||
.note{margin-top:10px;font-size:10.5px;color:#8e7a5f;line-height:1.62}
|
||||
.note code{color:#c0a87f;background:#2f251c;padding:0 3px;border-radius:3px}
|
||||
.spec{
|
||||
margin-top:10px;padding-top:9px;border-top:1px dashed #3d2f22;
|
||||
font-size:10.5px;line-height:1.62;color:#9fd6b4;
|
||||
}
|
||||
.spec:empty{display:none}
|
||||
.spec b{
|
||||
display:block;font-size:10px;letter-spacing:.22em;color:#6f8f79;
|
||||
font-weight:900;margin-bottom:4px;
|
||||
}
|
||||
button{
|
||||
margin-top:14px; width:100%; cursor:pointer;
|
||||
margin-top:10px; width:100%; cursor:pointer;
|
||||
background:linear-gradient(#9a6a35,#7a5026);
|
||||
color:#fff6e6; border:1px solid #5d3a1c; border-bottom-width:3px;
|
||||
border-radius:7px; padding:8px; font-size:13px; font-family:inherit; letter-spacing:.04em;
|
||||
}
|
||||
button:hover{filter:brightness(1.1)}
|
||||
button:active{transform:translateY(1px); border-bottom-width:2px}
|
||||
.btns{display:flex; flex-wrap:wrap; gap:6px}
|
||||
.btns{display:flex; gap:4px; margin-top:10px}
|
||||
.btns button{
|
||||
flex:0 0 calc(33.333% - 4px); margin-top:8px; padding:7px 0; font-size:12px;
|
||||
flex:1 1 0; min-width:0; margin-top:0; padding:6px 0; font-size:11px;
|
||||
background:linear-gradient(#5c4a34,#463725); border-color:#3a2c1c;
|
||||
}
|
||||
</style>
|
||||
@@ -96,11 +105,11 @@
|
||||
<div id="stagekeys"></div>
|
||||
</div>
|
||||
<div class="note">
|
||||
갈색 벽돌은 포탄에 부서지고, 회색 강철은 부서지지 않습니다.
|
||||
기지가 파괴되거나 탱크 3대를 모두 잃으면 게임 오버.
|
||||
<br>돌파하면 다음 단계로 넘어가고, 아래 버튼이나 <code>#2</code>·<code>#3</code>
|
||||
같은 주소로 각 단계에 바로 들어갈 수도 있습니다.
|
||||
갈색 벽돌은 부서지고 회색 강철은 부서지지 않습니다. 기지가 파괴되거나
|
||||
탱크 3대를 모두 잃으면 게임 오버. 아래 버튼·숫자키·<code>#3</code> 주소로
|
||||
각 단계에 바로 진입.
|
||||
</div>
|
||||
<div class="spec" id="spec"></div>
|
||||
<div class="btns" id="stagebtns"></div>
|
||||
<button id="btn">현재 단계 다시 시작 (R)</button>
|
||||
</aside>
|
||||
@@ -113,7 +122,7 @@
|
||||
var COLS=17, ROWS=15, CELL=32;
|
||||
var W=COLS*CELL, H=ROWS*CELL;
|
||||
var SIZE=28, OFF=(CELL-SIZE)/2;
|
||||
var EMPTY=0, BRICK=1, STEEL=2, BASE=3, RUIN=4;
|
||||
var EMPTY=0, BRICK=1, STEEL=2, BASE=3, RUIN=4, WATER=5;
|
||||
|
||||
var BASE_C=8, BASE_R=14;
|
||||
var PLAYER_SPAWN={c:5,r:13};
|
||||
@@ -194,6 +203,31 @@ var STAGES=[
|
||||
".BBB...BBB...BBB.",
|
||||
"...B...BEB...B..."
|
||||
]
|
||||
},
|
||||
{
|
||||
name:"STAGE 4 · 참호",
|
||||
maxOnField:4,
|
||||
enemies:["hunter","heavy","scout","guard","hunter","heavy","scout","hunter","heavy","guard"],
|
||||
items:["freeze","shield"],
|
||||
itemEvery:14,
|
||||
specials:"물길(탱크 통행 불가, 포탄은 통과) · 보급 상자(시간 정지 / 기지 강화)",
|
||||
map:[
|
||||
".................",
|
||||
".BBBBB.....BBBBB.",
|
||||
".B...B..S..B...B.",
|
||||
".B.S.B.....B.S.B.",
|
||||
".BBB.BWW.WWB.BBB.",
|
||||
"...B...B.B...B...",
|
||||
".BB.BBWW.WWBB.BB.",
|
||||
"SS.....B.B.....SS",
|
||||
"..B.BWW...WWB.B..",
|
||||
".BBB.B.B.B.B.BBB.",
|
||||
".B..B.......B..B.",
|
||||
".B.SBB..B..BBS.B.",
|
||||
".B...B.BBB.B...B.",
|
||||
".B.B...BBB...B.B.",
|
||||
".SS....BEB....SS."
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
@@ -250,6 +284,9 @@ var sfx={
|
||||
boom:function(){ noise(0.42,0.46,1200,90); tone("sine",180,40,0.38,0.17); },
|
||||
bigboom:function(){ noise(0.85,0.58,900,50); tone("sine",140,28,0.8,0.24); tone("square",90,30,0.5,0.10,0.05); },
|
||||
spawn:function(){ tone("triangle",300,900,0.18,0.06); tone("triangle",500,1200,0.14,0.05,0.1); },
|
||||
item:function(){ tone("sine",900,1400,0.10,0.05); tone("sine",1400,1900,0.10,0.04,0.09); },
|
||||
pickup:function(){ var n=[784,1047,1319],i; for(i=0;i<3;i++) tone("square",n[i],n[i],0.11,0.09,i*0.07); },
|
||||
splash:function(){ noise(0.16,0.13,900,240,"bandpass"); },
|
||||
win:function(){ var n=[523,659,784,1047],i; for(i=0;i<4;i++) tone("square",n[i],n[i],0.16,0.11,i*0.14); },
|
||||
lose:function(){ var n=[392,330,262,196],i; for(i=0;i<4;i++) tone("sawtooth",n[i],n[i]*0.95,0.26,0.12,i*0.18); }
|
||||
};
|
||||
@@ -259,6 +296,7 @@ var cv=document.getElementById("cv"), ctx=cv.getContext("2d");
|
||||
var elE=document.getElementById("epips"), elP=document.getElementById("ppips");
|
||||
var elS=document.getElementById("score"), elK=document.getElementById("kills");
|
||||
var elStage=document.getElementById("stage");
|
||||
var elSpec=document.getElementById("spec");
|
||||
(function(){ /* 단계 키 안내 */
|
||||
var t="",i;
|
||||
for(i=1;i<=STAGES.length;i++) t+="<kbd>"+i+"</kbd>";
|
||||
@@ -266,8 +304,10 @@ var elStage=document.getElementById("stage");
|
||||
})();
|
||||
|
||||
var grid, player, enemies, bullets, fx, spawns, enemyQueue;
|
||||
var state, msg, msg2, score, kills, livesLeft, shake, flashT, elapsed;
|
||||
var state, msg, msg2, msg3, score, kills, livesLeft, shake, flashT, elapsed;
|
||||
var stage=1, totalEnemies=4, maxOnField=3;
|
||||
var items, itemT, freezeT, shieldT, shieldCells, powerT;
|
||||
function curStage(){ return STAGES[stage-1]; }
|
||||
|
||||
function pendingEnemies(){
|
||||
var n=0;
|
||||
@@ -284,7 +324,7 @@ function buildGrid(src){
|
||||
var row=[];
|
||||
for(c=0;c<COLS;c++){
|
||||
var ch=line.charAt(c);
|
||||
row.push(ch==="B"?BRICK:ch==="S"?STEEL:ch==="E"?BASE:EMPTY);
|
||||
row.push(ch==="B"?BRICK:ch==="S"?STEEL:ch==="E"?BASE:ch==="W"?WATER:EMPTY);
|
||||
}
|
||||
g.push(row);
|
||||
}
|
||||
@@ -297,6 +337,7 @@ function startStage(n,keep){
|
||||
var st=STAGES[stage-1];
|
||||
grid=buildGrid(st.map);
|
||||
enemies=[]; bullets=[]; fx=[]; spawns=[];
|
||||
items=[]; itemT=(st.itemEvery||0); freezeT=0; shieldT=0; shieldCells=null; powerT=0;
|
||||
if(!keep){ score=0; kills=0; livesLeft=PLAYER_TANKS-1; }
|
||||
shake=0; flashT=0; elapsed=0;
|
||||
maxOnField=st.maxOnField;
|
||||
@@ -307,6 +348,7 @@ function startStage(n,keep){
|
||||
spawnPlayer(0.4);
|
||||
fillEnemySpawns(0.9);
|
||||
state="ready"; msg=st.name; msg2="화면을 클릭한 뒤 방향키 · 스페이스";
|
||||
msg3=st.specials?("특수 요소 — "+st.specials):null;
|
||||
syncHud(); syncUrl();
|
||||
}
|
||||
function reset(){ startStage(1,false); }
|
||||
@@ -323,17 +365,21 @@ function syncHud(){
|
||||
elS.textContent=score;
|
||||
elK.textContent=kills;
|
||||
elStage.textContent=stage+" / "+STAGES.length;
|
||||
var sp=curStage().specials;
|
||||
elSpec.innerHTML=sp?("<b>특수 요소</b>"+sp):"";
|
||||
}
|
||||
|
||||
/* ===================== 지형 유틸 ===================== */
|
||||
function isSolid(t){ return t===BRICK||t===STEEL||t===BASE; }
|
||||
/* 물길: 탱크는 막고 포탄은 넘어간다. rectBlocked 는 탱크 이동에만 쓰인다 */
|
||||
function blocksTank(t){ return isSolid(t)||t===WATER; }
|
||||
function rectBlocked(x,y,w,h){
|
||||
var c1=Math.floor(x/CELL), c2=Math.floor((x+w-1)/CELL);
|
||||
var r1=Math.floor(y/CELL), r2=Math.floor((y+h-1)/CELL);
|
||||
for(var r=r1;r<=r2;r++){
|
||||
for(var c=c1;c<=c2;c++){
|
||||
if(c<0||r<0||c>=COLS||r>=ROWS) return true;
|
||||
if(isSolid(grid[r][c])) return true;
|
||||
if(blocksTank(grid[r][c])) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
@@ -438,18 +484,68 @@ function canStep(t,d){
|
||||
}
|
||||
|
||||
function fire(t){
|
||||
var limit=t.isPlayer?2:1, mine=0, i;
|
||||
var boost=(t.isPlayer && powerT>0);
|
||||
var limit=t.isPlayer?(boost?3:2):1, mine=0, i;
|
||||
for(i=0;i<bullets.length;i++) if(bullets[i].owner===t && !bullets[i].gone) mine++;
|
||||
if(mine>=limit) return false;
|
||||
var cx=t.x+SIZE/2, cy=t.y+SIZE/2;
|
||||
bullets.push({
|
||||
x:cx+t.dx*(SIZE/2+2)-3, y:cy+t.dy*(SIZE/2+2)-3,
|
||||
dx:t.dx, dy:t.dy, spd:t.bspd, owner:t, player:t.isPlayer, gone:false
|
||||
dx:t.dx, dy:t.dy, spd:t.bspd*(boost?1.35:1), owner:t, player:t.isPlayer, gone:false
|
||||
});
|
||||
if(t.isPlayer) sfx.fire(); else sfx.efire();
|
||||
return true;
|
||||
}
|
||||
|
||||
/* ===================== 보급 상자 ===================== */
|
||||
var SHELL=[[7,13],[8,13],[9,13],[7,14],[9,14]]; /* 기지 외곽 */
|
||||
var ITEM_INFO={
|
||||
freeze:{label:"시간 정지", note:"적 3초 정지", col:"#8fdcff", glyph:"II"},
|
||||
shield:{label:"기지 강화", note:"외곽 강철 10초", col:"#cfd8e2", glyph:"[]"},
|
||||
power: {label:"화력 강화", note:"연사·탄속 10초", col:"#ffd27a", glyph:"^^"}
|
||||
};
|
||||
function spawnItem(){
|
||||
var kinds=curStage().items;
|
||||
if(!kinds||!kinds.length) return;
|
||||
var tries, c, r;
|
||||
for(tries=0;tries<120;tries++){
|
||||
c=Math.floor(Math.random()*COLS); r=Math.floor(Math.random()*ROWS);
|
||||
if(grid[r][c]!==EMPTY) continue;
|
||||
if(Math.abs(c-BASE_C)<3 && r>ROWS-4) continue; /* 기지 앞은 비운다 */
|
||||
if(r<2) continue; /* 적 출현 지점 피하기 */
|
||||
var x=c*CELL+OFF, y=r*CELL+OFF;
|
||||
if(tankHits(null,x,y)) continue;
|
||||
items.push({x:x,y:y,kind:kinds[Math.floor(Math.random()*kinds.length)],t:0});
|
||||
sfx.item();
|
||||
return;
|
||||
}
|
||||
}
|
||||
function raiseShield(){
|
||||
shieldCells=[];
|
||||
for(var i=0;i<SHELL.length;i++){
|
||||
var c=SHELL[i][0], r=SHELL[i][1];
|
||||
shieldCells.push([c,r]);
|
||||
grid[r][c]=STEEL;
|
||||
}
|
||||
}
|
||||
function dropShield(){
|
||||
if(!shieldCells) return;
|
||||
for(var i=0;i<shieldCells.length;i++){
|
||||
var c=shieldCells[i][0], r=shieldCells[i][1];
|
||||
if(grid[r][c]===STEEL) grid[r][c]=BRICK; /* 강철이 벽돌로 되돌아온다 */
|
||||
}
|
||||
shieldCells=null;
|
||||
}
|
||||
function takeItem(it){
|
||||
if(it.kind==="freeze"){ freezeT=3.0; }
|
||||
else if(it.kind==="shield"){ raiseShield(); shieldT=10.0; }
|
||||
else if(it.kind==="power"){ powerT=10.0; }
|
||||
score+=50;
|
||||
sfx.pickup();
|
||||
fx.push({x:it.x+SIZE/2,y:it.y+SIZE/2,t:0.4,max:0.4,r:34,ring:true});
|
||||
syncHud();
|
||||
}
|
||||
|
||||
/* ===================== 적 AI ===================== */
|
||||
var DIRS=[[0,-1],[0,1],[-1,0],[1,0]];
|
||||
function baseCenter(){ return {x:BASE_C*CELL+CELL/2, y:BASE_R*CELL+CELL/2}; }
|
||||
@@ -662,9 +758,11 @@ function afterKill(){
|
||||
state="clear";
|
||||
msg="STAGE "+stage+" CLEAR";
|
||||
msg2="Space / Enter 로 "+(stage+1)+"단계 진입";
|
||||
var nx=STAGES[stage];
|
||||
msg3=nx&&nx.specials?("다음 단계 특수 요소 — "+nx.specials):null;
|
||||
}else{
|
||||
state="win";
|
||||
msg="ALL CLEAR!"; msg2="모든 단계 돌파 · R 키로 재도전";
|
||||
msg="ALL CLEAR!"; msg2="모든 단계 돌파 · R 키로 재도전"; msg3=null;
|
||||
}
|
||||
sfx.win(); syncHud();
|
||||
}
|
||||
@@ -687,7 +785,7 @@ function killPlayer(){
|
||||
function gameOver(reason){
|
||||
if(state==="over"||state==="win") return;
|
||||
state="over";
|
||||
msg="GAME OVER"; msg2=reason+" · R 키로 다시 시작";
|
||||
msg="GAME OVER"; msg2=reason+" · R 키로 다시 시작"; msg3=null;
|
||||
sfx.lose();
|
||||
}
|
||||
|
||||
@@ -711,7 +809,7 @@ window.addEventListener("keydown",function(ev){
|
||||
startStage(stage+1,true); state="play"; msg=null; return;
|
||||
}
|
||||
if(k==="p"||k==="P"){
|
||||
if(state==="play"){ state="pause"; msg="PAUSED"; msg2="P 키로 계속"; }
|
||||
if(state==="play"){ state="pause"; msg="PAUSED"; msg2="P 키로 계속"; msg3=null; }
|
||||
else if(state==="pause"){ state="play"; msg=null; }
|
||||
return;
|
||||
}
|
||||
@@ -796,6 +894,24 @@ function drawSteelCell(c,r){
|
||||
ctx.fillRect(bx+h-3.9,by+1.5,2.4,h-3);
|
||||
}
|
||||
}
|
||||
function drawWaterCell(c,r){
|
||||
var x=c*CELL, y=r*CELL;
|
||||
ctx.fillStyle="#16384a";
|
||||
ctx.fillRect(x,y,CELL,CELL);
|
||||
ctx.fillStyle="#1d4a61";
|
||||
ctx.fillRect(x+1,y+1,CELL-2,CELL-2);
|
||||
/* 잔물결 — 시간에 따라 흐른다 */
|
||||
var t=waveT;
|
||||
for(var i=0;i<3;i++){
|
||||
var yy=y+6+i*9+Math.sin(t*1.6+i*1.1+c*0.7)*1.6;
|
||||
ctx.fillStyle=(i===1)?"rgba(160,225,245,.30)":"rgba(120,195,220,.20)";
|
||||
var off=((t*9+i*13+r*5)%CELL);
|
||||
ctx.fillRect(x+off-CELL,yy,CELL*0.55,2);
|
||||
ctx.fillRect(x+off,yy,CELL*0.55,2);
|
||||
}
|
||||
ctx.strokeStyle="rgba(8,26,36,.55)"; ctx.lineWidth=1;
|
||||
ctx.strokeRect(x+0.5,y+0.5,CELL-1,CELL-1);
|
||||
}
|
||||
function drawBase(c,r,ruin){
|
||||
var x=c*CELL, y=r*CELL;
|
||||
ctx.fillStyle="#2a221a";
|
||||
@@ -830,6 +946,7 @@ function drawMap(){
|
||||
else if(t===STEEL) drawSteelCell(c,r);
|
||||
else if(t===BASE) drawBase(c,r,false);
|
||||
else if(t===RUIN) drawBase(c,r,true);
|
||||
else if(t===WATER) drawWaterCell(c,r);
|
||||
}
|
||||
}
|
||||
function drawTank(t){
|
||||
@@ -893,6 +1010,43 @@ function drawSpawn(s){
|
||||
ctx.closePath(); ctx.stroke();
|
||||
ctx.restore();
|
||||
}
|
||||
function drawItem(it){
|
||||
var info=ITEM_INFO[it.kind]||ITEM_INFO.freeze;
|
||||
var bob=Math.sin(it.t*4)*1.8;
|
||||
var x=it.x, y=it.y+bob, s=SIZE;
|
||||
ctx.save();
|
||||
ctx.globalAlpha=0.35+0.25*Math.sin(it.t*5);
|
||||
ctx.strokeStyle=info.col; ctx.lineWidth=2;
|
||||
ctx.strokeRect(x-2,y-2,s+4,s+4);
|
||||
ctx.globalAlpha=1;
|
||||
ctx.fillStyle="#2b2318";
|
||||
ctx.fillRect(x+2,y+2,s-4,s-4);
|
||||
ctx.fillStyle=info.col;
|
||||
ctx.fillRect(x+2,y+2,s-4,3);
|
||||
ctx.strokeStyle=info.col; ctx.lineWidth=1.6;
|
||||
ctx.strokeRect(x+4.5,y+4.5,s-9,s-9);
|
||||
ctx.fillStyle=info.col;
|
||||
ctx.font="bold 11px 'Segoe UI',sans-serif";
|
||||
ctx.textAlign="center";
|
||||
ctx.fillText(info.glyph,x+s/2,y+s/2+5);
|
||||
ctx.textAlign="left";
|
||||
ctx.restore();
|
||||
}
|
||||
function drawBadges(){
|
||||
var list=[],i;
|
||||
if(freezeT>0) list.push(["시간 정지",freezeT,"#8fdcff"]);
|
||||
if(shieldT>0) list.push(["기지 강화",shieldT,"#cfd8e2"]);
|
||||
if(powerT>0) list.push(["화력 강화",powerT,"#ffd27a"]);
|
||||
for(i=0;i<list.length;i++){
|
||||
var y=6+i*22;
|
||||
ctx.fillStyle="rgba(12,9,6,.72)";
|
||||
ctx.fillRect(6,y,104,18);
|
||||
ctx.fillStyle=list[i][2];
|
||||
ctx.fillRect(6,y,3,18);
|
||||
ctx.font="bold 11px 'Segoe UI','Malgun Gothic',sans-serif";
|
||||
ctx.fillText(list[i][0]+" "+list[i][1].toFixed(1)+"s",14,y+13);
|
||||
}
|
||||
}
|
||||
function drawBullet(b){
|
||||
ctx.fillStyle="rgba(255,170,80,.30)";
|
||||
ctx.fillRect(b.x-b.dx*6,b.y-b.dy*6,6,6);
|
||||
@@ -918,19 +1072,25 @@ function drawFx(){
|
||||
}
|
||||
function drawOverlay(){
|
||||
if(!msg) return;
|
||||
var bh=msg3?150:124;
|
||||
ctx.fillStyle="rgba(12,9,6,.74)";
|
||||
ctx.fillRect(0,H/2-62,W,124);
|
||||
ctx.fillRect(0,H/2-bh/2,W,bh);
|
||||
ctx.strokeStyle="rgba(232,178,92,.35)";
|
||||
ctx.lineWidth=1;
|
||||
ctx.beginPath(); ctx.moveTo(0,H/2-62.5); ctx.lineTo(W,H/2-62.5);
|
||||
ctx.moveTo(0,H/2+61.5); ctx.lineTo(W,H/2+61.5); ctx.stroke();
|
||||
ctx.beginPath(); ctx.moveTo(0,H/2-bh/2-0.5); ctx.lineTo(W,H/2-bh/2-0.5);
|
||||
ctx.moveTo(0,H/2+bh/2+0.5); ctx.lineTo(W,H/2+bh/2+0.5); ctx.stroke();
|
||||
ctx.textAlign="center";
|
||||
ctx.fillStyle="#e8b25c";
|
||||
ctx.font="bold 40px 'Segoe UI',sans-serif";
|
||||
ctx.fillText(msg,W/2,H/2-4);
|
||||
ctx.fillStyle="#e9dcc2";
|
||||
ctx.font="15px 'Segoe UI','Malgun Gothic',sans-serif";
|
||||
ctx.fillText(msg2||"",W/2,H/2+30);
|
||||
ctx.fillText(msg2||"",W/2,H/2+28);
|
||||
if(msg3){
|
||||
ctx.fillStyle="#9fd6b4";
|
||||
ctx.font="12.5px 'Segoe UI','Malgun Gothic',sans-serif";
|
||||
ctx.fillText(msg3,W/2,H/2+52);
|
||||
}
|
||||
ctx.textAlign="left";
|
||||
}
|
||||
function render(){
|
||||
@@ -951,8 +1111,10 @@ function render(){
|
||||
for(i=0;i<spawns.length;i++) drawSpawn(spawns[i]);
|
||||
for(i=0;i<enemies.length;i++) drawTank(enemies[i]);
|
||||
if(player) drawTank(player);
|
||||
for(i=0;i<items.length;i++) drawItem(items[i]);
|
||||
for(i=0;i<bullets.length;i++) drawBullet(bullets[i]);
|
||||
drawFx();
|
||||
drawBadges();
|
||||
ctx.restore();
|
||||
|
||||
if(flashT>0){
|
||||
@@ -963,13 +1125,14 @@ function render(){
|
||||
}
|
||||
|
||||
/* ===================== 루프 ===================== */
|
||||
var last=0;
|
||||
var last=0, waveT=0;
|
||||
function frame(ts){
|
||||
var dt=(ts-last)/1000;
|
||||
last=ts;
|
||||
if(!isFinite(dt)||dt<0) dt=0;
|
||||
if(dt>0.05) dt=0.05;
|
||||
|
||||
waveT+=dt;
|
||||
if(shake>0) shake=Math.max(0,shake-dt*28);
|
||||
if(flashT>0) flashT=Math.max(0,flashT-dt*2.2);
|
||||
|
||||
@@ -982,6 +1145,21 @@ function frame(ts){
|
||||
|
||||
if(state==="play"){
|
||||
elapsed+=dt;
|
||||
|
||||
/* 보급 상자와 효과 시간 */
|
||||
if(curStage().itemEvery){
|
||||
itemT-=dt;
|
||||
if(itemT<=0){
|
||||
if(items.length===0) spawnItem();
|
||||
itemT=curStage().itemEvery;
|
||||
}
|
||||
}
|
||||
if(freezeT>0) freezeT=Math.max(0,freezeT-dt);
|
||||
if(powerT>0) powerT=Math.max(0,powerT-dt);
|
||||
if(shieldT>0){
|
||||
shieldT=Math.max(0,shieldT-dt);
|
||||
if(shieldT===0) dropShield();
|
||||
}
|
||||
for(var s=spawns.length-1;s>=0;s--){
|
||||
spawns[s].t-=dt;
|
||||
if(spawns[s].t<=0){
|
||||
@@ -999,10 +1177,16 @@ function frame(ts){
|
||||
player.dy=parseInt(d[1],10);
|
||||
tryMove(player,dt);
|
||||
}
|
||||
for(var ii=items.length-1;ii>=0;ii--){
|
||||
items[ii].t+=dt;
|
||||
if(boxesHit(player.x,player.y,items[ii].x,items[ii].y)){
|
||||
takeItem(items[ii]); items.splice(ii,1);
|
||||
}
|
||||
}
|
||||
}
|
||||
for(var e=0;e<enemies.length;e++){
|
||||
if(enemies[e].hitFlash>0) enemies[e].hitFlash-=dt;
|
||||
updateEnemy(enemies[e],dt);
|
||||
if(freezeT<=0) updateEnemy(enemies[e],dt); /* 시간 정지 중에는 멈춘다 */
|
||||
}
|
||||
updateBullets(dt);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user