From f208619ac4188d6106714aac6356eaac7c5a9056 Mon Sep 17 00:00:00 2001 From: plpark Date: Fri, 11 Sep 2026 16:38:20 +0900 Subject: [PATCH] Add tank stage 3 and per-stage entry links MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- README.md | 19 ++++++++--- site/index.html | 24 ++++++++++++- site/tank.html | 90 ++++++++++++++++++++++++++++++++++++++++--------- 3 files changed, 112 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 356d97f..ec55b7f 100644 --- a/README.md +++ b/README.md @@ -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` 를 받아 단계를 바꾼다. ## 구성 diff --git a/site/index.html b/site/index.html index aaee58d..98292f2 100644 --- a/site/index.html +++ b/site/index.html @@ -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{ 탱크게임 Tank Battle +
@@ -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'; diff --git a/site/tank.html b/site/tank.html index 7ed1e37..13697df 100644 --- a/site/tank.html +++ b/site/tank.html @@ -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; + } @@ -89,17 +93,15 @@
←→↑↓ 이동
Space 발사
P 일시정지 R 다시시작
-
12 단계 직접 선택
+
갈색 벽돌은 포탄에 부서지고, 회색 강철은 부서지지 않습니다. 기지가 파괴되거나 탱크 3대를 모두 잃으면 게임 오버. -
1단계 적 4대 → 돌파 시 2단계(미로, 적 6대) 진입. -
-
- - +
돌파하면 다음 단계로 넘어가고, 아래 버튼이나 #2·#3 + 같은 주소로 각 단계에 바로 들어갈 수도 있습니다.
+
@@ -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+=""+i+""; + 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); })();