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:
@@ -19,16 +19,27 @@
|
||||
|
||||
기본으로 열리는 탭은 한국 문학이다. 각 페이지는 단독으로 열어도 동작한다. 셸(`/`)의 탭은 해시로 상태를 남기므로
|
||||
`/#korean`, `/#russian`, `/#british`, `/#french`, `/#german`, `/#japanese`, `/#tank` 로 바로 들어갈 수 있고, 탭을 다시 눌러도 풀던 내용은 유지된다.
|
||||
휴게실 탱크게임의 단계별 진입은 `/#tank1`, `/#tank2`, `/#tank3` 이다.
|
||||
|
||||
## 휴게실
|
||||
|
||||
| 탭 | 경로 | 내용 |
|
||||
|---|---|---|
|
||||
| 탱크게임 | `/tank.html` | 2단계 구성 — 1단계 요새(적 4대) / 2단계 미로(적 6대). 방향키 이동, 스페이스 발사 |
|
||||
| 탭 | 경로 | 단계 | 적 | 특징 |
|
||||
|---|---|---|---|---|
|
||||
| 탱크게임 | `/tank.html` | 1단계 요새 | 4대 (동시 3) | 벽돌 밀도 25%, 넓은 기동 공간 |
|
||||
| ↳ | `/tank.html#2` | 2단계 미로 | 6대 (동시 4) | 밀도 43%, 1칸 폭 통로가 얽힌 미로 |
|
||||
| ↳ | `/tank.html#3` | 3단계 강철 회랑 | 8대 (동시 4) | 강철 31칸, 기지 외곽 2중 + 좁은 진입로 |
|
||||
|
||||
방향키 이동, 스페이스 발사. 한 단계를 돌파하면 다음 단계로 이어지고, 점수와 남은 탱크를
|
||||
그대로 가져간다. **단계를 거치지 않고 바로 들어가는 방법**은 셋이다.
|
||||
|
||||
- 왼쪽 탭의 `휴게실 > 탱크게임` 아래 단계 칩 (셸 주소로는 `/#tank2`, `/#tank3`)
|
||||
- 게임 화면 오른쪽 아래의 단계 버튼, 또는 `1` ~ `3` 키
|
||||
- 주소 직접 입력 — `/tank.html#3`, `/tank.html?stage=3` (범위를 벗어난 값은 1단계)
|
||||
|
||||
낱말풀이와 공유하는 자산이 없는 단독 페이지다. 캔버스와 WebAudio 만 쓰므로 자산 해시
|
||||
스탬프(아래 캐시 항목)의 영향도 받지 않는다. iframe 안에서는 **화면을 한 번 클릭해
|
||||
포커스를 준 뒤** 키를 눌러야 한다.
|
||||
포커스를 준 뒤** 키를 눌러야 한다. 셸이 iframe 의 조각(`#2`)만 바꿀 때는 문서가 다시
|
||||
읽히지 않으므로, 게임 쪽에서 `hashchange` 를 받아 단계를 바꾼다.
|
||||
|
||||
## 구성
|
||||
|
||||
|
||||
+23
-1
@@ -54,6 +54,20 @@ body{
|
||||
.nav .label.rest{
|
||||
margin-top:14px;padding-top:15px;border-top:1px solid var(--line);
|
||||
}
|
||||
.nav .stagejump{
|
||||
display:flex;flex-wrap:wrap;gap:6px;padding:9px 17px 11px;
|
||||
border-bottom:1px solid #221E19;
|
||||
}
|
||||
.nav .stagejump a{
|
||||
flex:0 0 calc(33.333% - 4px);display:block;padding:6px 0;border:1px solid var(--line);
|
||||
border-radius:4px;text-align:center;text-decoration:none;
|
||||
font-size:11px;font-weight:700;letter-spacing:.02em;
|
||||
color:var(--slate);background:#1A1712;
|
||||
}
|
||||
.nav .stagejump a:hover{color:var(--amber);background:#221E19}
|
||||
.nav .stagejump a[aria-current="page"]{
|
||||
background:var(--vermilion);border-color:var(--amber);color:#fff;
|
||||
}
|
||||
.nav a{
|
||||
display:block;text-decoration:none;color:inherit;
|
||||
padding:12px 18px 12px 17px;border-left:3px solid transparent;
|
||||
@@ -156,6 +170,11 @@ body{
|
||||
<span class="kr">탱크게임</span>
|
||||
<span class="og">Tank Battle</span>
|
||||
</a>
|
||||
<div class="stagejump">
|
||||
<a href="tank.html#1" target="stage" data-key="tank1" title="1단계 요새">1단계</a>
|
||||
<a href="tank.html#2" target="stage" data-key="tank2" title="2단계 미로">2단계</a>
|
||||
<a href="tank.html#3" target="stage" data-key="tank3" title="3단계 강철 회랑">3단계</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rail-foot">
|
||||
@@ -177,7 +196,10 @@ var PAGES = {
|
||||
french: {file:'french.html', name:'프랑스 문학'},
|
||||
german: {file:'german.html', name:'독일 문학'},
|
||||
japanese:{file:'japanese.html', name:'일본 문학'},
|
||||
tank: {file:'tank.html', name:'휴게실 · 탱크게임'}
|
||||
tank: {file:'tank.html', name:'휴게실 · 탱크게임'},
|
||||
tank1: {file:'tank.html#1', name:'휴게실 · 탱크게임 1단계'},
|
||||
tank2: {file:'tank.html#2', name:'휴게실 · 탱크게임 2단계'},
|
||||
tank3: {file:'tank.html#3', name:'휴게실 · 탱크게임 3단계'}
|
||||
};
|
||||
var DEFAULT = 'korean';
|
||||
|
||||
|
||||
+74
-16
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user