- 규칙 모듈 packages/games/src/las-vegas: 지폐 54장 더미·카지노 채우기(합 5 이상), 굴리기/놓기(같은 눈 전부), 중립(흰) 주사위 변형(auto/on/off, 3명은 라운드 시작 하우스 주사위 2개 자동 배치), 동점 무효 정산, 하우스·남은 지폐 더미 아래로(카지노 1→6, 큰 금액부터), 라운드마다 선 이동, 장 수 동점 처리, 시간 초과(굴리기 자동 / 개수→지폐→작은 수 순 자동 놓기)와 3회 연속 자리 비움, 기권, hideMoney 시 남의 금액은 장 수만 보이는 view, 놓기 미리 보기(1등/동점 주의) 계산 - 테스트: 문서 10절 시나리오 전부, 1,000시드 무작위 대국(타임아웃·기권 포함, 지폐·주사위 보존, 정보 유출, onTimeout 합법, 결정성) + 엔진 randomPlayout 100시드 - 화면 apps/web/src/games/las-vegas: 카지노 6곳(PC 한 줄, 폰 3열×2행), 색깔 주사위 쌓기, 놀이용 지폐, 굴림 애니메이션, 숫자별 묶음 버튼 → 카지노 강조와 예상 순위 → 확정, 라운드 정산(동점 무효) 표시, 최종 결과표, 폰에서는 굴리기/놓기 영역이 화면 아래에 고정 - 등록: GAMES, 카탈로그, 화면 레지스트리, 아이콘, 놀이용 점수 문구 검사 대상에 추가 - E2E: PC 방장 + 폰 친구, 3라운드로 바꿔 결과까지 실제 플레이(키보드 선택, 카지노 카드 선택, 동점 무효 확인) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
40 lines
1.6 KiB
TypeScript
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', 'games/las-vegas', '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([]);
|
|
});
|