매치 시즈에 게임 바깥을 붙였습니다. 타이틀 화면, 하트, 닉네임, 순위표, 그리고 BGM. 규칙은 안 건드렸고 게임을 둘러싼 것들만 만들었습니다.
하트는 개수를 저장하지 않습니다
플레이 한 판에 하트 하나를 씁니다. 다섯 개까지 차고 5분에 하나씩 회복됩니다.
여기서 흔히 하는 실수가 개수와 시각을 둘 다 저장하는 것입니다. 하트 3개와 마지막 소비 시각을 따로 적어 두면, 저장이 한 번만 어긋나도 하트가 무한히 늘거나 영영 안 차오릅니다. 두 값이 서로를 검증하지 못하니까요.
그래서 회복 기준 시각 하나만 진실로 뒀습니다. 개수는 거기서 계산합니다. 흐른 시간을 5분으로 나눈 몫이 회복량이고, 기준 시각을 그만큼만 앞으로 밉니다.
여기에 함정이 둘 있어서 따로 막았습니다.
- 가득 찬 동안에는 기준 시각을 계속 현재로 민다. 안 밀면, 하루 동안 가득 찬 채
놔뒀다가 하나 쓰는 순간 그동안 쌓인 시간이 한꺼번에 들어와 즉시 다시 가득 찹니다
- 시계가 뒤로 갔으면 기준을 현재로 당긴다. 그냥 두면 뺄셈이 음수가 되어 회복이
멈춥니다
시계를 앞으로 돌려서 하트를 더 받는 건 막지 못합니다. 서버 없이는 불가능하고, 지금은 막지 않습니다. 대신 순위표 점수는 서버가 따로 봅니다.
웹에서는 저장이 브라우저에 남습니다. 시크릿 모드나 사이트 데이터 삭제에는 못 살아남습니다. 그건 그냥 그렇다고 적어 뒀습니다.
순위표는 위조를 막지 못합니다. 앞뒤만 봅니다
닉네임을 정하면 판이 끝날 때 점수가 서버로 올라갑니다. 저장은 Cloudflare D1, API는 게임을 서비스하는 워커가 같이 물고 있습니다.
점수는 클라이언트가 보고합니다. 서버가 게임을 다시 돌려보지 않는 한 완전한 위조 방지는 불가능하고, 이 규모에서 그건 할 일이 아닙니다. 그래서 목표를 낮춰 잡았습니다 — 말이 안 되는 값만 거릅니다.
- 15초보다 짧은 판은 거절
- 초당 400점을 넘으면 거절. 자동 플레이 봇이 초당 170점쯤 내니까 사람은 그보다
느립니다. 상한은 넉넉히 뒀습니다. 정상 플레이를 막는 것보다 터무니없는 값만 거르는 편이 낫습니다
- 같은 IP에서 10초 안에 두 번 올리면 거절
- 이름 12자, 점수·판 수에도 각각 상한
CORS는 열어 뒀습니다. 데스크톱 빌드에서도 같은 순위표를 봐야 하고, 어차피 공개 순위표라 읽기를 막을 이유가 없습니다. 쓰기는 CORS로 막아 봐야 소용없습니다 — 브라우저 밖에서 그냥 부르면 그만입니다.
테이블 만드는 문장은 워커 안에 두지 않았습니다. 매 요청마다 스키마를 확인하는 비용도 아깝지만, 그보다 실패가 배포가 아니라 플레이 중에 드러나기 때문입니다.
BGM은 두 번째 HUD입니다
BGM 세 트랙을 넣었습니다. 이것도 오디오 파일 없이 코드로 작곡합니다. calm, tense, danger 세 가지고 1.8초에 걸쳐 서로 넘어갑니다.
- 시간이 12초 아래거나 침식이 14칸 이상이면 danger
- 침식 5칸 이상이거나 3판 이상이면 tense
- 나머지는 calm
숫자를 안 보고도 지금이 급한지 알 수 있게 하려는 겁니다. 화음도 그렇게 갈랐습니다. calm과 tense는 무난한 마이너 진행이고, danger만 화음 하나를 어둡게 바꿔 뒀습니다.
이어 붙는 소리를 만드는 데 두 가지가 필요했습니다. 사인의 위상을 마디가 아니라 트랙 전체 시간으로 계산할 것 — 마디마다 0에서 다시 시작하면 딱딱 끊깁니다. 그리고 패드가 마디 안에서 부풀었다 사라지게 해서 경계에 소리가 없게 만들 것.
부팅할 때 세 트랙을 한 번에 굽습니다. 도중에 만들면 프레임이 얼어붙습니다. 613KB에 0.33초 걸립니다. 효과음과 마찬가지로 작곡 함수는 노드도 오디오 장치도 필요 없는 순수 함수라, 창 없는 셀프테스트가 파형을 직접 열어 검사합니다.
광고 버튼은 아직 껍데기입니다
타이틀에 "광고 보고 하트 충전" 버튼이 있습니다. 지금은 광고 SDK가 없어서 누르면 그냥 하트가 찹니다. 자리만 잡아 둔 상태라고 적어 두는 게 맞겠습니다.
지금 상태
셀프테스트 164항목이 통과합니다. 자동 플레이 1200스텝에서 특수 블록 252개가 만들어져 133개가 발동했고 최대 연쇄는 12단, 1500스텝 침식 시뮬레이션에서 보드가 붕괴한 판은 0판이었습니다. 무작위 400판을 대조해 매치 검출 두 경로가 어긋나지 않는 것도 봅니다.
다만 이건 전부 기계가 본 것입니다. 재미있는지는 아직 아무도 모릅니다. 다음은 숫자가 아니라 플레이입니다.