본문 바로가기

반응형

전체 글

(127)
게임 탭을 인기순으로 바꾸면서 배운 것들 — 원본 배열, 안정 정렬, 그리고 일부러 넣은 캐시 PlayTalk 랭킹 화면에는 게임별 탭이 가로로 나열돼 있습니다. 게임이 늘면서 탭이 화면을 넘겼고, 정작 사람들이 제일 많이 하는 게임이 가로 스크롤 뒤쪽에 묻히는 일이 생겼습니다. 탭 순서가 그냥 코드에 적힌 배열 순서였거든요."플레이 수 많은 순으로 배치하자." 한 줄짜리 요구인데, 하다 보니 결정할 게 꽤 나왔습니다.1. 판수를 어디서 세나 — plays vs 집계 테이블먼저 게임별 총 플레이 수가 필요했습니다. 기존 랭킹 응답에는 top-10 플레이어의 개인 판수만 있어서 게임 전체 합계를 알 수 없었어요.DB에는 후보가 둘 있었습니다.plays — 판 단위 원천 테이블. SELECT game, COUNT(*) FROM plays GROUP BY game 이면 끝.player_game_stat..
position:fixed가 화면이 아니라 카드에 붙어 있었다 — 애니메이션이 PC에서만 안 보인 이유 PlayTalk에 혼자 하는 스도쿠를 붙이면서 '도움' 기능을 넣었습니다. 버튼을 누르면 빈칸 하나가 정답으로 채워지는 건데, 그냥 값이 툭 나타나면 심심하잖아요. 그래서 숫자가 버튼에서 그 칸까지 날아가는 애니메이션을 넣었습니다. 외부 라이브러리 없이 CSS transition만으로요.구현은 단순합니다.버튼과 목적지 칸의 화면 좌표를 getBoundingClientRect()로 잽니다.그 좌표에 position: fixed로 숫자 하나를 띄웁니다.다음 프레임에 목적지 좌표로 left/top을 바꿔 transition을 태웁니다.460ms 뒤 도착하면 오버레이를 지우고 보드에 실제 값을 확정합니다. jsxconst FLY_MS = 460;function FlyingNumber({ value, from, ..
3편: 화이트리스트로 쓴 조건은 시간이 지나면 조용히 틀려진다 레이아웃을 정리하다가 이상한 걸 발견했습니다. 혼자 하기 화면에서 프로필을 수정하려는데 "대화 중에는 수정할 수 없어요" 같은 안내가 떴습니다. 혼자 게임하는 화면인데요.허용 목록은 새 상태를 자동으로 차단한다원인은 이 조건이었습니다. jsconst canEditProfile = status === STATUS.IDLE || status === STATUS.PARTNER_LEFT;프로필 수정을 허용할 상태만 나열한 화이트리스트입니다. 이걸 처음 짤 때는 앱에 화면이 그 두 개 + 대화/게임뿐이었어요. 정확한 코드였습니다.문제는 그 뒤에 화면이 계속 늘었다는 겁니다. SOLO_SELECT(혼자 하기 선택), SOLO_PLAYING(혼자 게임 중), FRIENDS(친구 목록)… 이 상태들은 목록에 없으니 자..
2편: 여백은 한 곳에서만 준다 지난 편에서 높이를 flex로 정리했더니, 그동안 안 보이던 게 보이기 시작했습니다. 화면마다 여백이 제각각이었어요.혼자 하기 화면을 예로 들면 이랬습니다.게임 목록 ↔ '뒤로' 버튼: 18px'뒤로' 버튼 ↔ 하단 XP 배너: 0px버튼이 배너에 딱 붙어 있으니 손가락으로 누를 때 오터치가 나기 쉬웠습니다. 메인 화면도 마지막 버튼이 배너에 붙어 있었고, 대화 화면은 '대화 종료' 버튼이 붙어 있었어요. 화면마다 따로 생긴 문제처럼 보였지만 실은 하나의 문제였습니다.화면별로 고치기 시작하면 끝이 없다처음엔 화면별로 고쳤습니다. 혼자 하기에는 paddingBottom, 메인에는 idleBottomGap이라는 이름의 여백을 넣고… 이런 식으로요.그런데 메인 화면에서 바로 막혔습니다. 이 화면은 PWA 설치..
1편: calc(100svh - 240px)는 왜 계속 어긋나는가 PlayTalk에 게임이 하나둘 늘면서 '혼자 하기' 목록도 길어졌습니다. 목록이 카드 밖으로 넘치지 않게 하려고 이런 코드를 넣어 뒀었어요. jssoloGrid: { maxHeight: 'calc(100svh - 240px)', overflowY: 'auto',}"화면 높이에서 목록 말고 카드가 쓰는 것들(로고, 설명, 뒤로 버튼, XP 배너…) 240px쯤 빼면 되겠지." 흔히 쓰는 방식이고, 당시엔 잘 맞았습니다.그런데 몇 달 뒤, PC에서 혼자 하기 화면이 위아래로 잘려 있었습니다.위아래로 "똑같이" 잘리는 이상한 증상증상이 좀 독특했어요. 보통 콘텐츠가 넘치면 아래쪽이 잘리고 스크롤이 생기잖아요. 이건 위와 아래가 대칭으로 잘려 있었고, 스크롤로 닿을 수도 없었습니다.원인은 PlayTalk의..
스도쿠 문제를 직접 만들어 봤습니다 — 150줄짜리 생성기와 "재시도를 넣지 않은" 이유 PlayTalk에 혼자 하는 스도쿠를 붙였습니다. 문제를 어디서 받아올까 잠깐 고민했는데, 스도쿠는 생성 알고리즘 자체가 잘 알려져 있어서 직접 만들기로 했어요. 결과적으로 로직 파일은 150줄이 됐고, 외부 라이브러리도 서버 통신도 없습니다. 브라우저에서 즉석으로 만들어 냅니다.과정을 정리해 봤습니다.1단계: 완성판부터 만든다스도쿠 문제를 만드는 순서는 직관과 반대입니다. 빈칸이 있는 문제를 만드는 게 아니라, 꽉 찬 정답판을 먼저 만들고 거기서 칸을 지웁니다.완성판 생성은 평범한 백트래킹입니다. 0번 칸부터 81번 칸까지 순서대로, 규칙에 맞는 숫자를 넣어보고 막히면 되돌아가요. jsfunction fill(board, pos, rand) { if (pos === 81) return true; c..
4편: plays 테이블의 회수 — 주간/월간 랭킹과 유령 기록 1편에서 plays(판 단위 원천)와 player_game_stats(집계)를 둘 다 만들어 뒀다고 했습니다. 당시엔 집계만 있어도 서비스가 돌았어요. 레벨도, 전체 랭킹도 집계만 읽으면 되니까요. plays는 "언젠가 쓰겠지" 하고 넣어둔 테이블이었습니다.그 언젠가가 왔습니다. 주간·월간 랭킹입니다.회귀 위험 0으로 얹기기존 랭킹 API는 잘 돌고 있었습니다. 여기에 기간 개념을 넣으면서 제일 피하고 싶었던 건, 기존 랭킹이 미묘하게 달라지는 것이었어요. 순위 하나가 바뀌어도 유저 입장에서는 버그로 보입니다.그래서 규칙을 이렇게 잡았습니다. GET /rankings?period=daily|weekly|monthly|all (기본 all)period가 없거나 all이면 기존 함수를 그대로 호출합니다...
3편: FOREIGN KEY constraint failed — 크래시를 두 번 잡은 이야기 SQLite 전환 배포 다음날, 서버가 이따금 죽었습니다. 도커가 알아서 재시작해 줘서 서비스가 오래 멈추진 않았지만, 접속해 있던 사람들의 소켓은 전부 끊기죠. 로그에 남은 건 한 줄이었습니다. FOREIGN KEY constraint failed파일 저장소에는 없던 종류의 사고입니다. JSON은 아무 키나 써도 받아주니까요. 외래 키를 켠 순간, 그동안 조용히 넘어가던 잘못된 쓰기가 전부 예외로 바뀐 겁니다. 1차: 두 줄의 순서 문제역추적해 보니 점수 제출(submit_score) 경로였습니다. 코드는 대충 이런 모양이었어요. jssubmitScore(...) // score_rankings INSERT — player_key가 players를 FK 참조recordPlayRow(...) ..

반응형