Add tank stage 3 and per-stage entry links

Two stages were not much of a ladder. Add STAGE 3 · 강철 회랑: 31 steel
cells the shells cannot pass, a double brick cap over the base and two
narrow funnels into it, 8 enemies with 4 on the field.

Getting to a later stage meant clearing the earlier ones. Read the stage
from the URL (#3 or ?stage=3) at load and on hashchange, so the shell's
nav chips and a plain link both land where they say. The stage buttons
and key hints are built from STAGES, so a new stage needs no UI edit.

Simulated 10 idle and 10 random-bot runs per stage: stage 3 leaves the
base standing in 6 of 10 idle runs, and its 144 reachable cells keep the
player out of a sealed pocket - an earlier layout had only 8.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-11 16:38:20 +09:00
co-authored by Claude Opus 5
parent f771a6b3e5
commit f208619ac4
3 changed files with 112 additions and 21 deletions
+74 -16
View File
@@ -62,6 +62,7 @@
font-family:inherit;
}
.note{margin-top:12px;font-size:11px;color:#8e7a5f;line-height:1.7}
.note code{color:#c0a87f;background:#2f251c;padding:0 3px;border-radius:3px}
button{
margin-top:14px; width:100%; cursor:pointer;
background:linear-gradient(#9a6a35,#7a5026);
@@ -70,8 +71,11 @@
}
button:hover{filter:brightness(1.1)}
button:active{transform:translateY(1px); border-bottom-width:2px}
.btns{display:flex; gap:8px}
.btns button{background:linear-gradient(#5c4a34,#463725); border-color:#3a2c1c}
.btns{display:flex; flex-wrap:wrap; gap:6px}
.btns button{
flex:0 0 calc(33.333% - 4px); margin-top:8px; padding:7px 0; font-size:12px;
background:linear-gradient(#5c4a34,#463725); border-color:#3a2c1c;
}
</style>
</head>
<body>
@@ -89,17 +93,15 @@
<div><kbd>←</kbd><kbd>→</kbd><kbd>↑</kbd><kbd>↓</kbd> 이동</div>
<div><kbd>Space</kbd> 발사</div>
<div><kbd>P</kbd> 일시정지 <kbd>R</kbd> 다시시작</div>
<div><kbd>1</kbd><kbd>2</kbd> 단계 직접 선택</div>
<div id="stagekeys"></div>
</div>
<div class="note">
갈색 벽돌은 포탄에 부서지고, 회색 강철은 부서지지 않습니다.
기지가 파괴되거나 탱크 3대를 모두 잃으면 게임 오버.
<br>1단계 적 4대 → 돌파 시 2단계(미로, 적 6대) 진입.
</div>
<div class="btns">
<button id="b1">1단계</button>
<button id="b2">2단계</button>
<br>돌파하면 다음 단계로 넘어가고, 아래 버튼이나 <code>#2</code>·<code>#3</code>
같은 주소로 각 단계에 바로 들어갈 수도 있습니다.
</div>
<div class="btns" id="stagebtns"></div>
<button id="btn">현재 단계 다시 시작 (R)</button>
</aside>
</div>
@@ -170,6 +172,28 @@ var STAGES=[
"...B...BBB...B...",
".S.B...BEB...B.S."
]
},
{
name:"STAGE 3 · 강철 회랑",
maxOnField:4,
enemies:["hunter","heavy","scout","hunter","heavy","guard","hunter","heavy"],
map:[
".................",
".SSSSS.S.S.SSSSS.",
".S.....B.B.....S.",
".S.BBBBB.BBBBB.S.",
"...B.........B...",
".BBB.SSBSBSS.BBB.",
".B...S.....S...B.",
".B.B.S.BBB.S.B.B.",
".B.B...B.B...B.B.",
".B.BBBBB.BBBBB.B.",
".B.....B.B.....B.",
".BSSS...B...SSSB.",
"...B.B.BBB.B.B...",
".BBB...BBB...BBB.",
"...B...BEB...B..."
]
}
];
@@ -235,6 +259,11 @@ 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");
(function(){ /* 단계 키 안내 */
var t="",i;
for(i=1;i<=STAGES.length;i++) t+="<kbd>"+i+"</kbd>";
document.getElementById("stagekeys").innerHTML=t+" 단계 직접 선택";
})();
var grid, player, enemies, bullets, fx, spawns, enemyQueue;
var state, msg, msg2, score, kills, livesLeft, shake, flashT, elapsed;
@@ -278,7 +307,7 @@ function startStage(n,keep){
spawnPlayer(0.4);
fillEnemySpawns(0.9);
state="ready"; msg=st.name; msg2="화면을 클릭한 뒤 방향키 · 스페이스";
syncHud();
syncHud(); syncUrl();
}
function reset(){ startStage(1,false); }
@@ -705,12 +734,20 @@ window.addEventListener("blur",function(){ held.length=0; });
document.getElementById("btn").addEventListener("click",function(){
audio(); startStage(state==="win"?1:stage,false); state="play"; msg=null;
});
document.getElementById("b1").addEventListener("click",function(){
audio(); startStage(1,false); state="play"; msg=null;
});
document.getElementById("b2").addEventListener("click",function(){
audio(); startStage(2,false); state="play"; msg=null;
});
(function(){ /* 단계 버튼은 STAGES 를 보고 만든다 */
var box=document.getElementById("stagebtns");
for(var i=1;i<=STAGES.length;i++){
(function(n){
var b=document.createElement("button");
b.textContent=n+"단계";
b.title=STAGES[n-1].name;
b.addEventListener("click",function(){
audio(); startStage(n,false); state="play"; msg=null;
});
box.appendChild(b);
})(i);
}
})();
cv.addEventListener("mousedown",function(){
audio();
try{ window.focus(); cv.focus(); }catch(e){} /* iframe 안에서도 키 입력을 받도록 */
@@ -974,7 +1011,28 @@ function frame(ts){
requestAnimationFrame(frame);
}
reset();
/* URL 로 단계 직접 진입: tank.html#3 또는 tank.html?stage=3 */
function stageFromUrl(){
var h=String(location.hash||"").replace(/^#\/?/,"");
var m=h.match(/^(?:stage)?([0-9]+)$/);
if(!m){
var q=String(location.search||"").match(/[?&]stage=([0-9]+)/);
if(q) m=[0,q[1]];
}
if(!m) return 0;
var n=parseInt(m[1],10);
return (n>=1 && n<=STAGES.length) ? n : 0;
}
function syncUrl(){
/* 셸의 iframe 안에서는 주소의 주인이 셸이다. 단독으로 열렸을 때만 동기화한다 */
try{ if(window.top===window) history.replaceState(null,"","#"+stage); }catch(e){}
}
/* 셸이 iframe 의 조각만 바꿀 때는 문서가 다시 읽히지 않으므로 hashchange 로 받는다 */
window.addEventListener("hashchange",function(){
startStage(stageFromUrl()||1,false);
});
startStage(stageFromUrl()||1,false);
requestAnimationFrame(frame);
})();
</script>