달무티 CPU 플레이어 만들기 Part 6: 사람이 열 판 중 여섯 판을 이기는 hard 봇
이 글은 Claude Fable 5.1 을 이용해 초안이 작성되었으며, 이후 퇴고를 거쳤습니다.
이 글은 누구를 위한 것인가#
Part 5 와 마찬가지로 코드와 수식 없이 씁니다. 달무티 규칙을 아는 분이라면 누구나 읽을 수 있습니다.
Part 5 는 이렇게 끝났습니다. 여러 편에 걸쳐 설계한 hard 봇이 규칙 세 줄짜리 easy 봇을 이기긴 했는데, 그 차이가 평균 0.07등 이었습니다. 그리고 “앞으로 해볼 만한 것” 으로 다섯 가지를 적었는데, 맨 위 두 개가 이것이었습니다.
1. 리드를 다시 공략한다.
2. 사람과 겨루게 하고 그 판을 기록한다. 지금 있는 근거는 전부 CPU 대 CPU 입니다.
이번 세 편은 그 두 숙제를 한 이야기입니다. 순서는 2번이 먼저였습니다. 사람과 겨룬 기록을 열어 보니 생각보다 나쁜 숫자가 나왔고, 그 숫자가 1번을 어떻게 풀어야 하는지 알려 주었습니다.
| 편 | 다루는 것 |
|---|---|
| Part 6 (이 글) | 무엇이 문제였나. 사람이 얼마나 이기고 있었나 |
| Part 7 | 잘 두는 사람은 봇과 어디서 다르게 두는가 |
| Part 8 | 봇을 어떻게 고쳤고, 얼마나 세졌나 |
봇 이야기에 필요한 만큼만 규칙을 다시 옮깁니다.
- 카드는 숫자 1~12 가 있고 숫자가 곧 장수 입니다. 1은 1장, 12는 12장입니다. 여기에 광대 2장을 더해 80장입니다
- 숫자가 작을수록 강합니다. 1이 최강, 12가 최약입니다
- 인원이 적으면 약한 숫자 쪽을 덜어 내고 합니다. 달무티 공식 룰북의 인원별 덱 규칙대로 4인이면 1~10 (57장), 5인이면 1~11 (68장), 6인 이상이면 1~12 전부 (80장) 를 씁니다. 그래서 4인 방에서는 10이 가장 약한 카드입니다
- 테이블이 비었을 때 먼저 내는 것을 리드 라고 합니다. 리드하는 사람이 같은 숫자를 몇 장 낼지 정합니다
- 뒤에 오는 사람은 정확히 같은 장수 를 더 강한 숫자 로 내야 합니다. 이것을 이 글에서는 받기 라고 부릅니다. 못 내거나 안 내면 패스입니다
- 모두 패스하면 마지막에 낸 사람이 다시 리드합니다
- 손패를 먼저 비운 순서대로 등수가 정해지고, 등수에 따라 다음 판에 세금 을 주고받습니다
1. 공정한 봇이라면 사람은 몇 번 이겨야 하는가#
혼자하기는 사람 한 명이 봇 서너 명과 한 테이블에 앉는 방식입니다. 네 명이 앉으면 사람 한 명에 봇 세 명입니다.
봇이 사람과 비슷한 실력 이라면, 네 명 중 누가 1등을 할지는 고르게 나뉩니다. 사람이 1등을 할 비율은 넷 중 하나, 25% 입니다. 다섯 명이면 20%, 여섯 명이면 약 17% 입니다.
이 숫자가 기준선입니다. 사람이 25% 보다 많이 이기면 봇이 사람보다 약한 것이고, 적게 이기면 봇이 센 것입니다.
한 가지 짚어 둘 것이 있습니다. hard 라는 이름을 단 봇이라면 기준선 근처 가 목표입니다. 사람이 5% 밖에 못 이기는 봇은 세긴 하지만 재미가 없습니다. 반대로 사람이 절반 넘게 이기는 봇은 hard 라는 이름이 민망합니다.
2. 실제로는 몇 번 이기고 있었는가#
게임을 연 뒤 약 두 달 동안 쌓인 혼자하기 기록을 집계했습니다. 끝까지 진행된 판만 세었고, 시험용으로 패를 조작한 판은 뺐습니다.
| 상대 봇 | 인원 | 사람이 1등한 판의 비율 | 공정할 때의 기준선 |
|---|---|---|---|
hard | 4인 | 59.8% | 25% |
hard | 5인 | 45.8% | 20% |
hard | 6인 | 48.0% | 16.7% |
easy | 4인 | 62.7% | 25% |
easy | 6인 | 53.2% | 16.7% |
hard 봇 세 명과 앉은 4인 방에서 사람이 열 판 중 여섯 판 을 1등으로 끝냈습니다. 기준선의 두 배를 훌쩍 넘습니다. 6인 방은 더 심해서, 기준선이 17% 인데 48% 였습니다. 세 배에 가깝습니다.
그리고 표의 위아래를 비교해 보시면 더 아픈 사실이 보입니다. hard 와 easy 의 숫자가 거의 같습니다.
3. 잘 두는 사람에게는 hard 와 easy 가 같은 봇이었다#
기록을 사용자별로 나눠 보니 그림이 또렷해졌습니다. 기록의 대부분은 가장 많이 플레이한 사용자 한 분 에게서 나왔습니다. 이 분의 숫자는 이랬습니다.
| 상대 봇 | 이 사용자가 1등한 판의 비율 |
|---|---|
hard | 59.5% |
easy | 59.7% |
차이가 0.2%p 입니다. 이 분에게 난이도 선택은 아무 의미가 없었습니다. hard 를 고르든 easy 를 고르든 똑같이 열 판 중 여섯 판을 이겼습니다.
반면 그 밖의 사용자들 은 hard 를 상대로 1등 비율이 25.9% 였습니다. 기준선 25% 와 거의 같습니다. 다만 이쪽은 판 수가 많지 않아서 숫자를 그대로 믿기는 어렵고, 방향만 참고할 만합니다.
두 숫자를 나란히 놓으면 이런 해석이 나옵니다.
hard봇은 처음 만나는 사람에게는 제 몫을 합니다. 그러나 패턴을 익힌 사람에게는 뚫립니다.
이 해석을 뒷받침하는 숫자가 하나 더 있습니다. hard 상대 사람의 1등 비율을 달별로 보면 첫 달이 35.9%, 둘째 달이 58.5% 였습니다. 봇은 그대로인데 사람이 이기는 비율이 올랐습니다. 봇이 변한 것이 아니라 사람이 봇을 배운 것 입니다.
Part 5 에서 “특히 볼 것은 사람이 CPU 의 패턴을 몇 판 만에 읽어내는가” 라고 적었는데, 답은 “한 달이면 충분하다” 였습니다.
4. 판 하나가 아니라 게임 전체로 보면 더 벌어진다#
달무티는 보통 여러 판을 이어서 하고 점수를 합산해 최종 승자를 가립니다. 위 숫자는 판 하나하나 의 1등 비율이었습니다. 여러 판을 묶은 게임 전체 의 1등 비율은 이보다 높았습니다.
| 지표 | hard 상대 사람의 비율 |
|---|---|
| 판 하나를 1등으로 끝냄 | 약 56% |
| 게임 전체를 1등으로 끝냄 | 약 70% |
판마다 56% 를 이기는 사람이 게임 전체로는 70% 를 가져갑니다. 이 차이는 세금 에서 나옵니다.
flowchart LR
A["첫 판에서<br/>사람이 1등"] --> B["다음 판 시작 전<br/>꼴찌가 가장 강한 카드<br/>두 장을 1등에게 바친다"]
B --> C["사람이 더 좋은 패로<br/>다음 판을 시작"]
C --> D["다음 판도<br/>1등할 가능성이 커진다"]
D --> B
style A fill:#FFD700,color:#000000
style D fill:#FFA07A,color:#000000
한 번 앞서면 세금이 그 우위를 다음 판으로 넘겨 줍니다. 이것은 봇의 잘못이 아니라 달무티의 규칙입니다. 봇이 아무리 세져도 이 고리는 남습니다. 다만 봇이 약하면 사람이 첫 판을 너무 쉽게 가져가고, 그 뒤로는 규칙이 사람 편을 들어 줍니다.
5. 구버전 hard 봇은 실제로 무엇을 하고 있었나#
문제를 확인했으니 봇을 다시 들여다볼 차례입니다. Part 5 에서 hard 는 “안 나온 카드를 세고, 상대의 패스를 기억하고, 나가려는 사람을 막는” 봇이라고 소개했습니다. 틀린 소개는 아닙니다. 그런데 그 능력이 쓰이는 자리가 한쪽에 몰려 있었습니다.
flowchart TD
A["내 차례"] --> B{"테이블에<br/>카드가 깔려 있나?"}
B -- "비어 있다 (내가 리드)" --> C["후보마다 점수를 매긴다"]
C --> D["<b>그 점수를 버린다</b>"]
D --> E["easy 봇과 똑같이<br/>가장 약한 숫자를 통째로 낸다"]
B -- "깔려 있다 (받는 상황)" --> F["안 나온 카드를 세고<br/>상대의 패스를 떠올린다"]
F --> G["받는 수와 패스에<br/>점수를 매겨 비교한다"]
G --> H["가장 점수가 높은 것을 고른다"]
E --> I["제출 또는 패스"]
H --> I
style A fill:#FFD700,color:#000000
style D fill:#FF9999,color:#000000
style E fill:#FF9999,color:#000000
style H fill:#90EE90,color:#000000
style I fill:#90EE90,color:#000000
붉은 칸이 문제의 자리입니다. 리드할 때 구버전 hard 는 점수를 계산해 놓고도 쓰지 않았습니다. 대신 easy 와 똑같이 가장 약한 숫자를 통째로 냈습니다.
일부러 그렇게 만든 것입니다. Part 4 에서 리드를 점수에 맡겼더니 봇이 오히려 약해졌고, 그 뒤에 리드를 바꾸는 방식을 세 가지 더 시험했는데 셋 다 크게 졌습니다. 평균 0.78등에서 0.96등까지 나빠졌습니다. 거의 한 등수를 통째로 잃은 셈입니다. “가장 약한 숫자를 통째로” 는 단순해 보여도 쉽게 이길 수 없는 규칙이었습니다.
그래서 구버전 hard 의 실력은 전부 받기 에서 나왔습니다. 리드는 easy 와 같았습니다.
6. 사람 눈에는 이것이 어떻게 보이는가#
봇 입장에서는 “검증된 안전한 규칙” 이지만, 마주 앉은 사람 입장에서는 이야기가 다릅니다.
첫째, 완전히 예측됩니다. 봇이 리드를 잡으면 무엇을 낼지 사람은 미리 압니다. 손에 든 것 중 가장 약한 숫자 전부입니다. 몇 판만 해 보면 누구나 알아챕니다.
둘째, 끝내는 순서를 계획하지 않습니다. 구버전 hard 는 “내 손패를 최소 몇 번에 비울 수 있는가” 는 셉니다. 그러나 “어느 순서로 내야 마지막까지 리드를 쥐고 있는가” 는 보지 않습니다. 잘 두는 사람은 종반에 이것부터 읽습니다. “이 카드를 내면 아무도 못 받고, 다음 리드도 내 것이고, 그다음에 남은 걸 털면 끝난다.”
셋째, 한 수 앞만 봅니다. 받을 때 매기는 점수는 이번 한 번 의 기대값입니다. 두세 번 뒤에 어떻게 되는지는 들어 있지 않습니다.
작업을 시작할 때 세운 가설은 다섯 개였습니다.
| 가설 | 내용 |
|---|---|
| 1 | 리드가 약하다 |
| 2 | 종반 계획이 없다 |
| 3 | 한 수 앞만 본다 |
| 4 | 세금이 격차를 굳힌다 |
| 5 | 봇이 이번 판 안의 정보만 쓴다 |
여기까지는 추측 입니다. Part 4 에서 배운 것이 있다면 “설계자의 직관은 재 보기 전까지 믿지 않는다” 입니다. 그래서 봇을 고치기 전에 재는 장치부터 만들었습니다.
7. 고치기 전에 재는 장치부터#
지금까지 쓰던 시뮬레이션에는 구멍이 있었습니다. hard 와 easy 를 맞붙이는 것 만 쟀습니다. 그런데 사람이 실제로 겪는 상황은 그것이 아닙니다. 사람은 hard 봇 여럿 사이에 혼자 앉습니다.
그래서 시뮬레이션에 같은 구성을 넣었습니다.
flowchart LR
subgraph NEW["새로 넣은 비교"]
direction TB
N1["시험하려는 새 봇 1명"]
N2["구버전 hard 봇 3명"]
end
subgraph OLD["지금까지의 비교"]
direction TB
O1["hard 봇"]
O2["easy 봇"]
end
style N1 fill:#90EE90,color:#000000
style N2 fill:#D3D3D3,color:#000000
style O1 fill:#87CEEB,color:#000000
style O2 fill:#D3D3D3,color:#000000
사람이 앉던 자리에 시험하려는 새 봇 을 앉히고, 나머지 자리는 사람들이 실제로 상대했던 구버전 봇 으로 채웁니다. 새 봇이 그 테이블에서 몇 등을 하는지를 잽니다. 구버전 봇은 이후에 코드가 아무리 바뀌어도 똑같이 재현되도록 따로 얼려 두었습니다.
여기서 하지 않은 것이 하나 있습니다. 사람을 흉내 내는 봇은 만들지 않았습니다. “사람처럼 두는 봇” 을 만들어 그것과 겨루게 하면 그럴듯해 보이지만, 사람의 실력이 어떻게 분포하는지 모르는 상태에서 만든 흉내는 만든 사람의 짐작일 뿐입니다. 대신 두 가지를 확인하기로 했습니다.
- 새 봇 한 명이 구버전 봇들 사이에서 기준선보다 자주 이기는가
- 새 봇끼리만 앉혔을 때 각자 기준선만큼 이기는가 (자기 자신에게만 통하는 꼼수가 아닌지)
마치며#
이번 편을 세 줄로 줄이면 이렇습니다.
첫째, 공정하다면 25% 여야 할 사람의 1등 비율이
hard상대 4인 방에서 59.8% 였습니다.둘째, 가장 많이 플레이한 사용자에게
hard와easy는 59.5% 대 59.7% 로 같은 봇이었습니다. 처음 만나는 사람에게는 통했지만 패턴을 익힌 사람에게는 뚫렸습니다.셋째, 구버전
hard는 리드할 때easy와 똑같이 두었습니다. 그것이 안전한 선택이었던 이유도 있고, 사람에게 읽힌 이유도 거기 있습니다.
숫자가 아프긴 했지만 반가운 면도 있었습니다. Part 5 까지는 봇끼리 겨룬 결과밖에 없어서 “사람이 체감하기 어려운 폭” 이라는 말을 추측으로 했는데, 이제 그것이 실제 기록 으로 확인되었습니다.
그리고 이 기록에는 더 쓸모 있는 것이 들어 있었습니다. 봇을 열 판 중 여섯 판 이긴 그 사람이 매 순간 무엇을 냈는지 가 전부 남아 있었습니다. 다음 편 에서는 그 결정 6,746개를 봇의 결정과 하나씩 나란히 놓아 봅니다.
| 편 | 다루는 것 |
|---|---|
| Part 1 | 규칙 기반 easy 봇 |
| Part 2 | hard 봇의 정보 모델과 확실승수 판정 |
| Part 3 | 수 평가와 전략적 패스 |
| Part 4 | 만들어 보니 hard 봇이 더 약했다 |
| Part 5 | 코드 없이 보는 easy 봇과 hard 봇의 차이 |
| Part 6 (이 글) | 사람이 열 판 중 여섯 판을 이기는 hard 봇 |
| Part 7 | 잘 두는 사람은 봇과 어디서 다르게 두는가 |
| Part 8 | 봇을 고치다: 리드 지키기 계획과 아껴 두기 |