이 글은 Today I Learn 시리즈의 241번째 기록입니다. (총 244개)

현지화는 번역만이 아니라 자판 문제이기도 하다

오른쪽 Ctrl이 안 눌렸다

한 키보드로 두 명이 노는 구조라 키를 좌우로 갈랐다. 왼손은 R D F G와 왼쪽 Shift·Ctrl, 오른손은 방향키와 오른쪽 Shift·Ctrl. 거울처럼 대칭이라 헷갈리지 않는 배치다.

눌러 보니 오른쪽 Ctrl만 아무 반응이 없었다. 다른 열한 개는 다 들어오는데 그것만 한 번도 안 들어왔다. 오른쪽 Alt도 같았다.

한국어 키보드는 그 자리가 한/영 키와 한자 키다. 모양은 Ctrl·Alt처럼 생겼는데 실제로 보내는 신호가 다르다. 윈도우의 입력기(IME — 한글을 조합해 주는 시스템)가 한글 전환으로 가로채서 게임까지 오지 않는다.

판단에 쓰는 방식

키 배치는 손가락이 닿는 자리만 보고 정하면 안 된다. 그 자판이 어느 나라 것인지에 따라 같은 자리가 다른 키다.

이건 번역 문제와 결이 다르다. 글자를 바꾸는 게 아니라 물리적으로 없는 키를 쓰려 한 것이다. 앞서 Unreal Engine MCP TIL 2에서 에디터 표시 언어 때문에 블루프린트 노드 이름이 한글로 바뀌어 고생한 적이 있는데, 그건 소프트웨어 안의 이름이었고 이번엔 자판 자체였다.

오른손 영역에서 안전하게 쓸 수 있는 조합키는 오른쪽 Shift까지다. 그래서 두 번째 플레이어의 점프를 /로 옮겼다. 오른쪽 Shift 바로 옆이라 방향키에서 손을 안 떼고 누를 수 있다.

같은 판에서 키 매트릭스(키보드 안의 격자 회로)도 걱정했다. 같은 줄에 걸친 키를 여러 개 누르면 일부가 씹히는 고스팅 현상이 있어서, 두 사람이 각자 대각선으로 달리면 최소 네 개가 동시에 눌린다. 여덟 개까지 눌러 봤는데 하나도 안 씹혔다. 이 키보드는 N키 롤오버(동시에 누른 키를 전부 보고하는 방식)를 지원한다.


맞아도 재보기 전에는 모른다

내가 물어서 쟀다

한 키보드로 두 명이 논다. 그러면 키를 눌렀을 때 그 신호가 어디로 가는지가 문제가 된다. 두 갈래가 있다.

둘 모두에게 간다    각자 "나는 이 키만 듣는다"고 정해 두면 끝난다
한 명에게만 간다    그 한 명이 전부 받아서 나머지 몫을 대신 전달해야 한다

게임 안에서 사람마다 번호가 붙는다. 이걸 플레이어 인덱스라고 하고, 각 사람에게는 입력을 받아 처리하는 플레이어 컨트롤러가 하나씩 붙는다. 질문은 이렇게 바뀐다. 2번 플레이어의 컨트롤러도 키보드를 보는가.

Claude는 이미 “1번에게만 간다”로 판단하고 그 위에 구조를 지어 놓은 상태였다. 근거를 물으니 캐릭터가 하나만 움직이더라는 관찰과, 원래 그렇다고 알려져 있다는 것이었다. 둘 다 정황이었다.

그래서 재보자고 했다. 게임에게 매 순간 “2번 플레이어 기준으로 지금 위쪽 방향키가 눌려 있니?”를 묻게 하고, 내가 그 키를 3초 눌렀다. 수백 번 물어본 셈인데 한 번도 눌렸다는 대답이 없었다.

판단은 맞았다. 하지만 맞았다는 것과 확인했다는 것은 다르다. 틀렸으면 그 위에 지은 구조를 전부 다시 짜야 했다.

판단에 쓰는 방식

추측 위에 구조를 지을 거면, 그 추측을 재는 데 얼마나 드는지 먼저 본다. 이번 것은 몇 분이었다. 몇 분으로 하루치 작업의 전제를 확인할 수 있으면 재는 게 맞다.

그리고 이건 도구의 한계와 성격이 다르다. 도구가 막으면 대개 다른 길이 있지만(Unreal Engine MCP TIL 2·궁 미로 PalaceMaze TIL 2에서 그랬다), 플랫폼이 정해 둔 것은 우회로 자체가 없다. 그래서 더더욱 어느 쪽인지 먼저 재야 한다. 우회로를 찾을지 설계를 맞출지가 거기서 갈린다.

이번엔 설계를 맞췄다. 입력을 받는 자리를 하나로 정하고, 그 하나가 열두 개 키를 전부 읽어 “이건 1번 거, 저건 2번 거” 하고 나눠 준다. 2번 플레이어의 컨트롤러는 화면과 카메라만 맡는다.


상태는 겪는 주체마다 따로 둔다

둘이 같이 서니 한 명이 지워졌다

문과 상자 앞에는 보이지 않는 발판이 깔려 있다. 그 위에 올라서면 오버랩(두 물체가 겹쳤다는 신호)이 발생하고, 올라선 사람만 문을 열 수 있다.

처음에는 발판마다 “지금 누가 올라와 있나”를 숫자 하나로 적었다. 아무도 없으면 빈 값, 1번이면 1, 2번이면 2.

둘이 같이 문 앞에 섰더니 2번이 문을 못 열었다. 1번이 먼저 올라가서 숫자가 1이었고, 2번이 올라와도 덮어쓰지 않았다. 덮어쓰게 했으면 이번엔 1번이 못 열었을 것이다. 한 명만 나가도 기록이 통째로 지워졌다.

“누가 올라와 있나”를 하나로 적을 수 있다고 본 게 잘못이었다. 칸이 하나라 동시에 둘을 담지 못한다.

판단에 쓰는 방식

여러 주체가 같은 것을 동시에 겪을 수 있으면, 상태도 주체마다 따로 있어야 한다. 1번이 올라왔는지와 2번이 올라왔는지는 별개의 사실이고, 한 칸에 욱여넣으면 나중 것이 앞 것을 지운다.

바꾸고 나니 나가는 처리도 자연스러워졌다. 예전에는 누가 나가든 전부 지웠는데, 이제는 나간 사람 것만 지운다. 남은 사람은 계속 열 수 있다.

혼자 하는 게임을 둘이 하는 게임으로 바꿀 때 가장 많이 걸리는 자리가 여기일 것 같다. 혼자일 때는 “열려 있나”, “누가 있나”를 하나로 적어도 아무 문제가 없다.


계산으로 맞추는 대신 배치로 없앤다

출발선을 어떻게 공평하게 할까

둘이 같은 미로에서 먼저 나가는 쪽이 이기는 게임이다. 그러면 출발 위치가 공평해야 한다.

처음에는 두 사람을 서로 다른 마당에 두고 출구까지 거리를 계산해 맞추는 걸 생각했다. 그런데 이 미로는 DFS(한 방향으로 끝까지 파고들다 막히면 되돌아오는 길 뚫기 방식)로 만들어서, 마당이 다르면 출구까지 실제로 걸어야 하는 거리가 다르다. 직선거리가 같아도 길이 다르면 소용없다.

그래서 두 사람을 같은 마당에 나란히 세웠다. 같은 칸에서 출발하면 출구까지 경로가 정확히 같다. 계산할 것이 없다.

판단에 쓰는 방식

공평함을 숫자로 맞추려 하면 검증할 것이 계속 늘어난다. 미로를 다시 뚫을 때마다 다시 재야 하고, 길이 바뀌면 어긋난다.

조건을 계산으로 만족시키는 대신, 구조상 어길 수 없게 배치하면 검증이 사라진다. 같은 칸에서 출발하는 한 경로 길이는 항상 같다. 미로가 바뀌어도 그렇다.

앞서 궁 미로 PalaceMaze TIL 1에서 상자 보상을 나눌 때도 비슷한 선택을 했다. 어느 상자에 무엇이 들었는지 관리하는 대신, 섞어 둔 목록에서 순서대로 꺼내게 했더니 “어느 상자인가”를 알 필요가 없어졌다.


Series: Today I Learn

1 | C++ 자료형(Data Type) 2 | MD5 vs pHash 3 | C++에서 함수의 선언과 정의 4 | Tkinter padx, pady 5 | 메모리와 포인터 변수 6 | Call by Value, Call by Reference, Call by Pointer 비교 7 | const 8 | Gemfile — Jekyll 프로젝트의 의존성 파일 9 | kramdown-parser-gfm — Jekyll의 GFM 파서 10 | 파서(Parser) 11 | AHU vs OHU 12 | I might try it vs I'll try it 뉘앙스 차이 13 | SESSION_EXPIRE_AT_BROWSER_CLOSE=True 14 | configuration key 15 | Git stash vs discard 16 | subprocess.Popen으로 Windows 탐색기에 명령어를 전달 17 | Post 잔디 분석하기 18 | Google Sheets Sync 최적화 19 | DSL (Domain Specific Language)과 GPL (General Purpose Language) 20 | 마크다운 표 그리는 방법 21 | 쿼리 파라미터(Query Parameter). 기존 QR코드 재활용 22 | Django 보안 취약점 점검 및 수정 23 | OOP Object-Oriented Programming 객체 지향 프로그래밍 24 | Fernet 대칭 암호화 25 | Jekyll 코드블록 안의 Liquid 태그 26 | 습관만들기 Pawbit TIL 1 27 | insertOnConflictUpdate vs DoUpdate(target) 28 | 세션 필터 29 | 아코디언(Accordiaon) UI를 펼친상태로 만들기 30 | input의 step 31 | 습관만들기 Pawbit TIL 2 32 | 습관만들기 Pawbit TIL 3 33 | Word Cloud 34 | Google Sheets를 데이터 버스로(with AppSheet) 35 | Django 모델 텍스트 필드 자동 수집 패턴 36 | localStorage로 섹션 토글 상태 유지 37 | 순차 ID 생성(`select_for_update()` + `max()` 조합) 38 | 역참조 검색과 distinct() 39 | xlsx 다운로드와 로딩 오버레이 충돌 40 | Android 파일 공유 MIME 타입 41 | AssetManifest — Flutter 빌드 타임 asset 목록 런타임 조회 42 | 크레센도 Phaser + TypeScript TIL 1 43 | 크레센도 Phaser + TypeScript TIL 2 44 | 크레센도 Phaser + TypeScript TIL 3 45 | 크레센도 Phaser + TypeScript TIL 4 46 | 크레센도 Phaser + TypeScript TIL 5 47 | 여둘까 Office Layout TIL 1 48 | 습관만들기 Pawbit TIL 4 49 | 여둘까 Office Layout TIL 2 50 | 여둘까 Office Layout TIL 3 51 | 크레센도 Phaser + TypeScript TIL 6 52 | UTF-8 BOM과 PowerShell 파일 쓰기 53 | 소리꽃 KeyBloom TIL 1 54 | 습관만들기 Pawbit TIL 5 55 | 여둘까 Office Layout TIL 4 56 | 소리꽃 KeyBloom TIL 2 57 | 소리꽃 KeyBloom TIL 3 58 | 메트로놈 Simple Metronome TIL 1 59 | 소리꽃 KeyBloom TIL 4 60 | 메트로놈 Simple Metronome TIL 2 61 | 소리꽃 KeyBloom TIL 5 62 | 습관만들기 Pawbit TIL 6 63 | 크레센도 Phaser + TypeScript TIL 7 64 | 메트로놈 Simple Metronome TIL 3 65 | 소설 타임라인 관리 도구 Lumior TIL 1 66 | 습관만들기 Pawbit TIL 7 67 | 정적 블로그 SEO 정비와 Pagefind 검색 도입 68 | 크레센도 Phaser + TypeScript TIL 8 69 | 메트로놈 Simple Metronome TIL 4 70 | 습관만들기 Pawbit TIL 8 71 | 안드로이드 AudioTrack 연속 재생, 실측 피커 정렬, 카메라 토치 플래시 72 | 메트로놈 Simple Metronome TIL 5 73 | 모래게임 Sandrop TIL 1 74 | 습관만들기 Pawbit TIL 9 75 | 메트로놈 Simple Metronome TIL 6 76 | 모래게임 Sandrop TIL 2 77 | 모래게임 Sandrop TIL 3 78 | 습관만들기 Pawbit TIL 10 79 | 습관만들기 Pawbit TIL 11 80 | 메트로놈 Simple Metronome TIL 7 81 | 모래게임 Sandrop TIL 4 82 | 모래게임 Sandrop TIL 5 83 | 습관만들기 Pawbit TIL 12 84 | 모래게임 Sandrop TIL 6 85 | 소리꽃 KeyBloom TIL 6 86 | 모래게임 Sandrop TIL 7 87 | 모래게임 Sandrop TIL 8 88 | 소리꽃 KeyBloom TIL 7 89 | 모래게임 Sandrop TIL 9 90 | 소리꽃 KeyBloom TIL 8 91 | 모래게임 Sandrop TIL 10 92 | 소리꽃 KeyBloom TIL 9 93 | 콘티온 ContiOn TIL 1 94 | 소리꽃 KeyBloom TIL 10 95 | 콘티온 ContiOn TIL 2 96 | 습관만들기 Pawbit TIL 13 97 | 콘티온 ContiOn TIL 3 98 | 룸톤 Roomtone TIL 1 99 | 모래게임 Sandrop TIL 11 100 | 소리꽃 KeyBloom TIL 11 101 | 여둘까 Office Layout TIL 5 102 | 모래게임 Sandrop TIL 12 103 | 모래게임 Sandrop TIL 13 104 | 콘티온 ContiOn TIL 4 105 | 모래게임 Sandrop TIL 14 106 | 소리꽃 KeyBloom TIL 12 107 | 습관만들기 Pawbit TIL 14 108 | 모래게임 Sandrop TIL 15 109 | 모래게임 Sandrop TIL 16 110 | 소리꽃 KeyBloom TIL 13 111 | 온실 GreenHouse TIL 1 112 | 콘티온 ContiOn TIL 5 113 | 모래게임 Sandrop TIL 17 114 | 소리꽃 KeyBloom TIL 14 115 | 온실 GreenHouse TIL 2 116 | 콘티온 ContiOn TIL 6 117 | 모래게임 Sandrop TIL 18 118 | 콘티온 ContiOn TIL 7 119 | 모래게임 Sandrop TIL 19 120 | 소리꽃 KeyBloom TIL 15 121 | 룸톤 Roomtone TIL 2 122 | 모래게임 Sandrop TIL 20 123 | 모래게임 Sandrop TIL 21 124 | 소리꽃 KeyBloom TIL 16 125 | 소리꽃 KeyBloom TIL 17 126 | 소리꽃 KeyBloom TIL 18 127 | 여둘까 Office Layout TIL 6 128 | 소리꽃 KeyBloom TIL 19 129 | 소리꽃 KeyBloom TIL 20 130 | 콘티온 ContiOn TIL 8 131 | 콘티온 ContiOn TIL 9 132 | 로그스톤 상표 셀프 출원 TIL 1 133 | 소리꽃 KeyBloom TIL 21 134 | 로그스톤 샵 LogStone Shop TIL 1 135 | 소리꽃 KeyBloom TIL 22 136 | 로그스톤 샵 LogStone Shop TIL 2 137 | 룸톤 Roomtone TIL 3 138 | 소리꽃 KeyBloom TIL 23 139 | 모래게임 Sandrop TIL 22 140 | 모래게임 Sandrop TIL 23 141 | 소리꽃 KeyBloom TIL 24 142 | 로그스톤 샵 LogStone Shop TIL 3 143 | 모래게임 Sandrop TIL 24 144 | 소리꽃 KeyBloom TIL 25 145 | 모래게임 Sandrop TIL 25 146 | MiniMacro TIL 1 147 | 룸톤 Roomtone TIL 4 148 | 모래게임 Sandrop TIL 26 149 | 콘티온 ContiOn TIL 10 150 | Astro가 무엇인지, 왜 옮기는지 151 | 룸톤 Roomtone TIL 5 152 | 룸톤 Roomtone TIL 6 153 | 모래게임 Sandrop TIL 27 154 | 소리꽃 KeyBloom TIL 26 155 | MiniMacro TIL 2 156 | 로그스톤 샵 LogStone Shop TIL 4 157 | 메트로놈 Simple Metronome TIL 8 158 | 모래게임 Sandrop TIL 28 159 | 여둘까 Office Layout TIL 7 160 | 룸톤 Roomtone TIL 7 161 | 메트로놈 Simple Metronome TIL 9 162 | 블로그 Astro 이관 TIL 163 | 여둘까 Office Layout TIL 8 164 | 로그스톤 샵 LogStone Shop TIL 5 165 | 모래게임 Sandrop TIL 29 166 | 오픈데이 Openday TIL 1 167 | 로그스톤 샵 LogStone Shop TIL 6 168 | 모래게임 Sandrop TIL 30 169 | 오픈데이 Openday TIL 2 170 | 모래게임 Sandrop TIL 31 171 | Unreal Engine MCP TIL 1 172 | 모래게임 Sandrop TIL 32 173 | 소리꽃 KeyBloom TIL 27 174 | 콘티온 ContiOn TIL 11 175 | 오픈데이 Openday TIL 3 176 | 로그스톤 샵 LogStone Shop TIL 7 177 | 모래게임 Sandrop TIL 33 178 | 소리꽃 KeyBloom TIL 28 179 | 오픈데이 Openday TIL 4 180 | 여둘까 Office Layout TIL 9 181 | 오픈데이 Openday TIL 5 182 | ScorePlayer TIL 1 183 | 모래게임 Sandrop TIL 34 184 | BuildingHub TIL 1 185 | ScorePlayer TIL 2 186 | 여둘까 Office Layout TIL 10 187 | BuildingHub TIL 2 188 | ScorePlayer TIL 3 189 | Unreal Engine MCP TIL 2 190 | 궁 미로 PalaceMaze TIL 1 191 | 메트로놈 Simple Metronome TIL 11 192 | 메트로놈 Simple Metronome TIL 10 193 | 오픈데이 Openday TIL 6 194 | BuildingHub TIL 3 195 | Obsidian vault에서 코드만 빼기 — directory junction 196 | ScorePlayer TIL 4 197 | 여둘까 Office Layout TIL 11 198 | 오픈데이 Openday TIL 7 199 | URL은 바뀔 수 있는 것에 묶지 않는다 200 | 도구는 기능이 아니라 내 일에 맞는지로 고른다 201 | 돌이키기 힘든 변경은 작게 먼저 확인한다 202 | 궁 미로 PalaceMaze TIL 3 203 | 궁 미로 PalaceMaze TIL 2 204 | 소리꽃 KeyBloom TIL 29 205 | 오픈데이 Openday TIL 8 206 | 룸톤 Roomtone TIL 8 207 | BuildingHub TIL 4 208 | ScorePlayer TIL 5 209 | 로그스톤 샵 LogStone Shop TIL 8 210 | 여둘까 Office Layout TIL 12 211 | 오픈데이 Openday TIL 9 212 | BuildingHub TIL 5 213 | MiniMacro TIL 3 214 | 룸톤 Roomtone TIL 10 215 | 룸톤 Roomtone TIL 9 216 | 소리꽃 KeyBloom TIL 30 217 | 오픈데이 Openday TIL 10 218 | 여둘까 Office Layout TIL 13 219 | 룸톤 Roomtone TIL 11 220 | 소리꽃 KeyBloom TIL 31 221 | 궁 미로 PalaceMaze TIL 4 222 | 룸톤 Roomtone TIL 12 223 | 로그스톤 샵 LogStone Shop TIL 9 224 | 룸톤 Roomtone TIL 13 225 | 로그스톤 샵 LogStone Shop TIL 10 226 | 룸톤 Roomtone TIL 14 227 | 소리꽃 KeyBloom TIL 32 228 | 룸톤 Roomtone TIL 15 229 | 룸톤 Roomtone TIL 16 230 | 로그스톤 샵 LogStone Shop TIL 11 231 | 룸톤 Roomtone TIL 17 232 | 룸톤 Roomtone TIL 18 233 | 메트로놈 Simple Metronome TIL 12 234 | 콘티온 ContiOn TIL 12 235 | 소리꽃 KeyBloom TIL 33 236 | 온실 GreenHouse TIL 3 237 | 로그스톤 샵 LogStone Shop TIL 12 238 | 룸톤 Roomtone TIL 19 239 | 모래게임 Sandrop TIL 35 240 | 여러 세션을 INBOX 하나로 굴리기 241 | 궁 미로 PalaceMaze TIL 5 읽는 중 242 | CS2 Practice Host TIL 1 243 | 룸톤 Roomtone TIL 20 244 | 여둘까 Office Layout TIL 14