Phase 2+3: full solo app (3 modes, scoring, menus, settings, records)

- engine: 점수 시스템(§5) 추가 — scoreClear/levelForLines/gravityMsForLevel + 테스트(27개 통과).
- 클라이언트를 멀티스크린 PWA로 확장:
  · 메인 ①(라이트 네오브루탈, 3카드+시즌바+탭바+광고 슬롯)
  · 솔로 모드선택 시트 ⑧(SPRINT/TIME ATTACK/ENDLESS + 베스트 기록)
  · 인게임 ②(§11 터치, 모드별 중력/레벨/타이머/점수)
  · 솔로 결과 화면(기록 갱신·NEW RECORD·광고 슬롯)
  · 설정(DAS/ARR/SDF 슬라이더·고스트·햅틱, localStorage)
  · 멀티/커스텀/리더보드/프로필은 서버 단계까지 "준비 중"
- SPRINT(40줄 타임)·TIME ATTACK(2분 점수·레벨가속)·ENDLESS(무한, 톱아웃 시 보드만 리셋) 구현.
- 기록은 localStorage 저장. 헤드리스로 전 화면·플레이·점수·네비게이션 검증(에러 0).
This commit is contained in:
tkrmagid
2026-08-22 15:57:36 +09:00
parent a8ff780248
commit c9463fe46b
4 changed files with 893 additions and 338 deletions

View File

@@ -4,6 +4,7 @@ import {
CONFIG, PIECE_TYPES, STATES, kickTable, makeRng, SevenBag,
createBoard, pieceCells, collides, clearLines, isBoardEmpty,
detectTSpin, isDifficult, TetrigGame,
levelForLines, gravityMsForLevel, scoreClear,
} from '../src/engine.js';
// ── 피스 상태/회전 ────────────────────────────────────────────────
@@ -219,3 +220,35 @@ test('톱아웃: 스폰 위치가 막히면 over', () => {
assert.equal(ok, false);
assert.equal(g.over, true);
});
// ── 점수·레벨 (명세서 §5) ──
test('레벨/중력 곡선', () => {
assert.equal(levelForLines(0), 1);
assert.equal(levelForLines(9), 1);
assert.equal(levelForLines(10), 2);
assert.equal(levelForLines(35), 4);
assert.equal(gravityMsForLevel(1), 1000);
assert.equal(gravityMsForLevel(2), 800);
assert.equal(gravityMsForLevel(20), 50); // 하한
});
test('점수표: 기본 클리어 ×레벨', () => {
const mk = (lines, tspin = 'none', allClear = false, combo = -1, b2b = -1) =>
({ lines, tspin, allClear, combo, b2b });
assert.equal(scoreClear(mk(1), 1), 100); // Single
assert.equal(scoreClear(mk(2), 1), 300); // Double
assert.equal(scoreClear(mk(4), 1), 800); // Quad
assert.equal(scoreClear(mk(1), 3), 300); // ×레벨
});
test('점수표: T-spin / B2B / 콤보 / All Clear', () => {
// T-spin Double = 1200
assert.equal(scoreClear({ lines: 2, tspin: 'tspin', allClear: false, combo: -1, b2b: -1 }, 1), 1200);
// B2B Quad = 800×1.5 = 1200
assert.equal(scoreClear({ lines: 4, tspin: 'none', allClear: false, combo: -1, b2b: 1 }, 1), 1200);
// 콤보: Single + 콤보1 → 100 + 50×1 = 150
assert.equal(scoreClear({ lines: 1, tspin: 'none', allClear: false, combo: 1, b2b: -1 }, 1), 150);
// All Clear Quad = 800 + 2000 = 2800; B2B AC Quad = 1200 + 3200 = 4400
assert.equal(scoreClear({ lines: 4, tspin: 'none', allClear: true, combo: -1, b2b: -1 }, 1), 2800);
assert.equal(scoreClear({ lines: 4, tspin: 'none', allClear: true, combo: -1, b2b: 1 }, 1), 4400);
});