Files
joke-app/apps/web/src/no-money-wording.test.ts
EJClaw ad7af30daa M6: 주사위 땅따먹기 규칙·화면·테스트
- packages/games/src/marble: 판 데이터(board.ts, 40칸·가격표·행운카드 20장), 상태 기계(rules.ts),
  게임 정의(index.ts). 더블·3더블 무인도, 무인도/탈출권/탈출비, 우주여행·탑승료, 사회복지기금,
  통행료(관광지 2곳 이상 2배·면제권), 빚 갚기·파산(채권자에게 매각 대금), 인수·랜드마크·독점 승리·교환
  옵션, 라운드 제한과 총자산 순위(동점이면 포인트 비교). 카드 더미 순서는 비공개(남은 장 수만),
  시스템 전용 TIMEOUT(굴리기/안 사기/싼 것부터 매각/거절), onLeave = 기권.
- 테스트 80개: 문서 §10 체크리스트 전 항목, 1,000판 시드 무작위 대국(포인트 장부·소유 일관성·
  onTimeout 합법), 엔진 randomPlayout·리플레이 결정성, 시간 초과만으로도 끝나는지.
- 화면: SVG 정사각형 판(도시 이름·소유 색·건물 표시·말), 가운데 주사위·결정 프롬프트, 행운카드 공개,
  우주여행 칸 고르기와 미리 보기, 빚 갚기 막대·매각 목록, 교환 시트, 진행 기록, PC·휴대폰·다크 모드.
  화폐 대신 놀이용 포인트(P) 표기, no-money-wording 검사 대상에 추가.
- 등록: GAMES, CATALOG(주사위 땅따먹기), 웹 registry, GAME_ICON, @bg/games/marble/board export.
- E2E marble 계획: PC 방장 + 휴대폰 손님, 15라운드·200P로 결과 화면까지 실제 진행.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 13:31:22 +09:00

40 lines
1.6 KiB
TypeScript

/**
* Guard (docs/04 §6, reviewer request): betting-game UI text must never look like real money.
* Scans the betting games' screens and shared chip/card components for currency-like wording.
*/
import { expect, test } from 'bun:test';
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
import { join } from 'node:path';
const ROOTS = ['games/poker', 'games/seotda', 'games/gostop', 'games/marble', 'components/cards'].map((p) => join(import.meta.dir, p));
const FORBIDDEN: [RegExp, string][] = [
[/₩|\$\s?\d|\d\s?\$/, '통화 기호'],
[/\d[\d,]*\s*(만\s*)?원(?![가-힣])/, '"원" 단위'],
[/판돈|돈을|돈이|돈은|현금|머니|골드/, '돈을 떠올리는 낱말'],
[/충전|환전|입금|출금|송금|결제|구매/, '거래 낱말'],
];
function files(dir: string): string[] {
if (!existsSync(dir)) return [];
return readdirSync(dir).flatMap((f) => {
const p = join(dir, f);
return statSync(p).isDirectory() ? files(p) : /\.(tsx?|css)$/.test(f) ? [p] : [];
});
}
/** Only string literals / JSX text matter; comments are ignored. */
function visibleText(src: string): string {
return src.replace(/\/\*[\s\S]*?\*\//g, '').replace(/(^|[^:])\/\/.*$/gm, '$1');
}
test('no currency-like wording in betting game UI', () => {
const problems: string[] = [];
for (const f of ROOTS.flatMap(files)) {
const text = visibleText(readFileSync(f, 'utf8'));
text.split('\n').forEach((line, i) => {
for (const [re, why] of FORBIDDEN) if (re.test(line)) problems.push(`${f.split('/src/')[1]}:${i + 1} ${why}: ${line.trim().slice(0, 80)}`);
});
}
expect(problems).toEqual([]);
});