달무티 CPU 플레이어 만들기 Part 7: 잘 두는 사람은 봇과 어디서 다르게 두는가
이 글은 Claude Fable 5.1 을 이용해 초안이 작성되었으며, 이후 퇴고를 거쳤습니다.
지난 편에서#
Part 6 에서 확인한 것은 이렇습니다. hard 봇 세 명과 앉은 4인 방에서 사람이 판의 59.8% 를 1등으로 끝냈습니다. 공정하다면 25% 여야 합니다. 가장 많이 플레이한 사용자에게는 hard 와 easy 가 구별되지 않았습니다.
이번 편은 그 사용자가 어떻게 이겼는지를 봅니다. 봇을 고치기 전에 이것부터 한 이유는 단순합니다. 봇을 이기는 방법을 가장 잘 아는 것은 봇을 이긴 사람입니다.
이 글에서 쓰는 말 두 가지만 다시 적습니다.
- 리드: 테이블이 비었을 때 먼저 내는 것. 몇 장짜리로 할지 정합니다
- 받기: 깔린 카드보다 강한 숫자를 같은 장수로 내는 것
그리고 덱에 관한 것 하나입니다. 달무티 공식 룰북의 인원별 덱 규칙대로, 인원이 적으면 약한 숫자를 덜어 내고 합니다. 4인이면 1~10, 5인이면 1~11, 6인 이상이면 1~12 를 씁니다. 분석한 판의 대부분이 4인 방이어서, 아래에 나오는 예시에서는 10이 가장 약한 카드 입니다.
1. 어떻게 비교했는가#
게임 서버에는 판마다 누가 어떤 카드를 받았고, 누가 언제 무엇을 냈고, 누가 패스했는지가 순서대로 남아 있습니다. 이 기록을 처음부터 다시 돌리면 사람이 결정을 내리던 바로 그 순간의 테이블 을 복원할 수 있습니다.
flowchart TD
A["기록에서 한 판을 꺼낸다"] --> B["처음부터 한 수씩 다시 돌린다"]
B --> C["사람 차례가 오면 멈춘다"]
C --> D["그 순간 사람이 볼 수 있던 것을 복원<br/>· 자기 손패<br/>· 테이블에 깔린 카드<br/>· 지금까지 나온 카드<br/>· 상대가 각각 몇 장 남았나"]
D --> E["같은 것을 봇에게 보여 주고<br/><b>너라면 무엇을 내겠느냐</b> 묻는다"]
E --> F{"사람이 낸 것과<br/>봇의 답이 같은가?"}
F -- "같다" --> G["일치로 센다"]
F -- "다르다" --> H["어떻게 다른지<br/>유형을 붙여 센다"]
G --> I["다음 수로"]
H --> I
I --> C
style A fill:#FFD700,color:#000000
style E fill:#87CEEB,color:#000000
style G fill:#90EE90,color:#000000
style H fill:#FFA07A,color:#000000
중요한 조건이 하나 있습니다. 봇에게는 사람이 볼 수 있던 것만 보여 줍니다. 상대 손패는 보여 주지 않습니다. 같은 정보를 놓고 판단이 어떻게 갈리는지를 보려는 것이기 때문입니다.
시간이 다 되어 자동으로 넘어간 수는 사람의 판단이 아니므로 세지 않았습니다.
봇에게 사람을 따라 하게 하지는 않았다#
이 기록으로 “사람처럼 두도록 봇을 학습시키면 되지 않느냐” 는 생각이 들 수 있습니다. 그렇게 하지 않았습니다. 이유는 셋입니다.
- 한 사람의 기록입니다. 그대로 배우면 그 사람의 버릇까지 배웁니다
- 봇은 같은 상황에서 늘 같은 수를 내도록 만들어져 있습니다. 그래야 문제가 생겼을 때 다시 돌려 보며 원인을 찾을 수 있습니다. 흉내 학습은 이 성질과 맞지 않습니다
- 알고 싶은 것은 “무엇을 냈는가” 가 아니라 “왜 그것이 좋은 수인가” 입니다. 이유를 알아야 봇의 판단 기준에 넣을 수 있습니다
그래서 이 기록은 어디를 고쳐야 하는지 가리키는 지도 로만 썼습니다.
2. 대부분은 같았다#
분석한 것은 hard 봇을 상대한 474판, 결정 6,746개 입니다.
| 국면 | 사람과 봇이 같은 수를 고른 비율 |
|---|---|
| 리드 | 71.8% |
| 받기 | 89.5% |
받을 때는 열 번 중 아홉 번이 같았습니다. 리드는 열 번 중 일곱 번이 같았습니다.
이것만 보면 “봇이 사람과 꽤 비슷하게 두네” 싶습니다. 그런데 승부는 나머지 에서 갈렸습니다. 전체 결정을 유형별로 나누면 이렇습니다.
| 유형 | 건수 | 비율 | 그 판에서 사람의 평균 등수 |
|---|---|---|---|
| 둘 다 패스 | 2,847 | 42.2% | 2.82 |
| 같은 카드로 받음 | 1,613 | 23.9% | 2.18 |
| 같은 리드 | 1,266 | 18.8% | 2.00 |
| 사람이 더 강한 숫자로 리드 | 471 | 7.0% | 1.44 |
| 사람은 패스, 봇은 냄 | 314 | 4.7% | 1.91 |
| 둘 다 내지만 다른 카드 | 201 | 3.0% | 1.61 |
| 같은 숫자인데 사람이 더 많이 냄 | 22 | 0.3% | 1.41 |
| 사람은 냄, 봇은 패스 | 8 | 0.1% | 2.12 |
| 같은 숫자인데 사람이 쪼개서 냄 | 4 | 0.1% | 1.25 |
마지막 열을 보시기 바랍니다. 등수는 작을수록 좋습니다. 사람과 봇이 같은 수를 고른 판에서 사람의 평균 등수는 2.0~2.8 이었습니다. 굵게 표시한 세 유형, 즉 사람이 봇과 다르게 둔 판에서는 1.4~1.9 였습니다.
세 유형을 합쳐도 전체의 15% 가 안 됩니다. 그러나 이 15% 가 이기는 판과 지는 판을 가르고 있었습니다. 하나씩 봅니다.
3. 습관 하나: 강한 한두 장으로 리드를 지킨다#
가장 많이 갈라진 유형입니다. 471건. 이 유형이 나온 판에서 사람의 평균 등수는 1.44 로, 거의 1등 아니면 2등이었습니다.
같은 순간에 봇과 사람이 낸 카드를 많이 나온 순서로 적으면 이렇습니다.
| 많이 나온 리드 | |
|---|---|
| 봇 | 10 네 장, 8 두 장, 8 세 장, 9 한 장, 9 세 장, 10 두 장 |
| 사람 | 2 한 장, 6 한 장, 5 한 장, 4 한 장, 7 두 장, 5 두 장, 3 한 장 |
봇은 약한 숫자를 뭉치로 냅니다. 사람은 강한 숫자를 한두 장 냅니다.
왜 이것이 좋은 수인지는 예를 들면 분명합니다. 4인 방에서 판이 중반을 넘어 1과 광대, 3부터 6, 그리고 8이 모두 나온 상황이라고 하겠습니다. 내 손에는 이렇게 남았습니다.
2 한 장 · 7 두 장 · 10 네 장
flowchart TD
S["내 손패<br/>2 한 장 · 7 두 장 · 10 네 장<br/>지금 내가 리드"]
S --> B1["<b>봇</b><br/>가장 약한 10 네 장을 낸다"]
B1 --> B2{"누가 네 장짜리로<br/>받을 수 있나?"}
B2 -- "9 네 장을 든 사람이 받는다" --> B3["리드를 빼앗긴다"]
B3 --> B4["2 와 7 두 장을 든 채<br/>남의 리드를 기다린다"]
B2 -- "아무도 못 받는다" --> B5["다시 내 리드"]
S --> H1["<b>사람</b><br/>2 한 장을 낸다"]
H1 --> H2["1 과 광대가 다 나왔으므로<br/>아무도 못 받는다"]
H2 --> H3["다시 내 리드<br/>7 두 장을 낸다"]
H3 --> H4["3 부터 6 이 다 나왔으므로<br/>두 장짜리로 받을 사람이 없다"]
H4 --> H5["다시 내 리드<br/>10 네 장을 내고 끝"]
style S fill:#FFD700,color:#000000
style B1 fill:#FF9999,color:#000000
style B3 fill:#FF9999,color:#000000
style B4 fill:#FF9999,color:#000000
style H1 fill:#90EE90,color:#000000
style H3 fill:#90EE90,color:#000000
style H5 fill:#90EE90,color:#000000
사람의 순서에서 눈여겨볼 것은 마지막 10 네 장 입니다. 이 뭉치는 누가 받든 상관없습니다. 내는 순간 내 손패가 비기 때문입니다. 그러니 받힐 위험이 가장 큰 뭉치를 맨 뒤로 보내는 것 이 정답입니다. 그 앞의 수들은 전부 “리드를 잃지 않는 수” 여야 합니다.
봇은 거꾸로 했습니다. 받힐 위험이 가장 큰 뭉치를 맨 먼저 냈습니다.
이 갈림은 판의 어느 시점에서나 나왔습니다. 손패가 12장인 첫 리드에서도, 세 장 남은 마무리에서도 고르게 있었습니다.
4. 습관 둘: 초반에 최상위 카드를 아낀다#
두 번째 유형은 받을 수 있는데 사람은 패스하고 봇은 낸 경우입니다. 314건.
이때 봇이 내려던 카드를 많이 나온 순서로 적으면 이렇습니다.
1 한 장, 2 한 장, 2 두 장, 3 두 장, 4 두 장, 3 한 장
전부 최상위 카드 입니다. 그리고 이 일이 벌어진 시점은 한쪽에 몰려 있었습니다.
| 그때 사람의 손패 | 건수 |
|---|---|
| 12장 | 111 |
| 11장 | 46 |
| 10장 | 43 |
| 9장 | 29 |
판 초반 입니다. 손패가 거의 그대로 남아 있을 때입니다.
봇은 “이 카드를 내면 리드를 가져올 수 있다” 는 계산이 서면 초반이라도 1이나 2를 씁니다. 사람은 넘깁니다. 초반에 리드를 한 번 가져오는 것보다, 그 카드를 종반까지 들고 있는 것 이 더 값지다고 보기 때문입니다.
앞의 습관과 이어 보면 이유가 보입니다. 종반에 “아무도 못 받는 한 장” 으로 리드를 지키려면 그 한 장이 손에 남아 있어야 합니다. 초반에 써 버리면 종반에 쓸 것이 없습니다.
봇이 이것을 놓친 데는 구조적인 이유가 있었습니다. 봇은 “이 카드가 지금 받힐 확률” 은 계산하지만, “이 카드 자체가 얼마나 귀한가” 는 따로 매기지 않았습니다. 초반에는 아직 나온 카드가 없어서 어떤 카드든 받힐 가능성이 있다고 계산됩니다. 그러니 1을 쓰는 것과 5를 쓰는 것의 아까움 이 점수에 드러나지 않았습니다.
5. 습관 셋: 받을 때 딱 이길 만큼만 쓴다#
세 번째 유형은 둘 다 받긴 받는데 다른 카드로 받은 경우입니다. 201건.
이 가운데 183건, 91% 에서 사람이 봇보다 약한 카드로 받았습니다.
| 사람이 낸 것 | 같은 순간 봇이 내려던 것 | 건수 |
|---|---|---|
| 3 한 장 | 2 한 장 | 11 |
| 5 한 장 | 4 한 장 | 10 |
| 4 두 장 | 3 두 장 | 8 |
| 3 한 장 | 1 한 장 | 8 |
깔린 카드를 이기기만 하면 되는데, 봇은 필요한 것보다 한 단계 강한 카드를 꺼냈습니다. 사람은 이길 수 있는 가장 약한 카드 로 받았습니다.
이 유형도 초반에 몰려 있었습니다. 평균 손패가 10.4장이었습니다.
습관 둘과 뿌리가 같습니다. 봇은 “확실하게 리드를 가져오는 것” 에 점수를 많이 주고 있었고, 더 강한 카드일수록 더 확실하므로 더 강한 카드를 골랐습니다. 그 확실함을 사느라 치른 값은 보지 않았습니다.
6. 승부처는 손패 네 장에서 여섯 장 사이#
손패가 몇 장 남았을 때 가장 많이 갈라지는지도 봤습니다.
| 남은 손패 | 리드가 같았던 비율 | 받기가 같았던 비율 |
|---|---|---|
| 1~3장 | 85.4% | 97.6% |
| 4~6장 | 60.9% | 95.1% |
| 7장 이상 | 68.9% | 84.9% |
세 장 이하로 남으면 선택지가 거의 없어서 사람이나 봇이나 비슷하게 둡니다. 일곱 장 이상일 때는 받기에서 좀 갈립니다(앞의 습관 둘과 셋입니다).
리드가 가장 많이 갈라지는 구간은 네 장에서 여섯 장 입니다. 열 번 중 네 번이 달랐습니다. 마지막 두세 수를 어떤 순서로 낼지를 정하는 구간입니다. 앞의 예시처럼 뭉치가 두세 개 남아서, 무엇을 먼저 내고 무엇을 마지막에 낼지가 곧 등수를 정하는 때입니다.
7. 이긴 판일수록 봇과 다르게 두었다#
마지막으로, 그 판에서 사람이 몇 등을 했는지에 따라 나눠 봤습니다.
| 그 판 사람의 등수 | 판마다 봇과 다르게 둔 횟수 | 리드가 같았던 비율 |
|---|---|---|
| 1등 | 2.83 | 62.4% |
| 2등 | 2.09 | 77.0% |
| 3등 | 1.09 | 87.2% |
| 4등 | 1.14 | 88.0% |
| 5등 | 1.43 | 89.7% |
1등한 판에서는 한 판에 평균 세 번 가까이 봇과 다르게 두었습니다. 3등 이하로 끝난 판에서는 한 번 남짓이었습니다.
이 표를 “봇과 다르게 두면 이긴다” 로 읽으면 안 됩니다. 좋은 패를 받으면 고를 수 있는 수가 많아집니다. 나쁜 패를 받으면 낼 수 있는 것이 뻔해서 누가 두어도 비슷합니다. 그러니 이긴 판에서 갈림이 많은 데에는 “패가 좋았다” 는 요인이 섞여 있습니다. 원인과 결과를 이 표만으로 가를 수는 없습니다.
그래도 방향은 분명합니다. 뒤집어 읽으면 이렇습니다.
구버전 봇처럼 둔 판은 사람도 이기지 못했다.
8. 상대가 easy 여도 습관은 같았다#
같은 사용자가 easy 봇을 상대한 기록도 같은 방법으로 봤습니다. 549판, 결정 8,584개 입니다.
| 유형 | hard 상대 | easy 상대 |
|---|---|---|
| 사람이 더 강한 숫자로 리드 | 471건 | 453건 |
| 사람은 패스, 봇은 냄 | 314건 | 442건 |
| 둘 다 내지만 다른 카드 | 201건 | 826건 |
같은 유형이 같은 모양으로 나왔습니다. 세 번째 유형은 easy 상대에서 훨씬 많았는데, 826건 가운데 790건이 “사람이 더 약한 카드로 받음” 이었습니다.
상대가 누구든 이 사용자의 두는 방식은 한결같았습니다. 즉 이 습관들은 특정 봇의 허점을 파고든 꼼수 가 아니라, 달무티를 잘 두는 일반적인 방법 에 가깝습니다. 봇에 넣을 가치가 있다는 뜻입니다.
9. 이 분석으로 말할 수 있는 것과 없는 것#
말할 수 있는 것
- 구버전
hard와 잘 두는 사람은 대부분의 수에서 같았고, 갈라진 자리는 세 가지 유형에 몰려 있었습니다 - 세 유형 모두 방향이 뚜렷합니다. 리드는 강한 한두 장으로, 초반 받기는 아끼는 쪽으로, 받을 때는 최소한으로
- 이 습관은 상대 봇이 달라져도 유지되었습니다
말할 수 없는 것
- 한 사람의 기록입니다. 다른 고수는 다르게 둘 수 있습니다
- “다르게 두어서 이겼다” 는 인과는 증명되지 않았습니다. 좋은 패의 효과가 섞여 있습니다
- 이 습관을 봇에 넣으면 봇이 세진다는 것도 아직 가설 입니다. 사람에게 좋은 수가 봇에게도 좋으리라는 보장은 없습니다. Part 4 에서 리드를 고치려다 세 번 진 기억이 있습니다
마지막 줄이 중요합니다. 그래서 다음 단계는 “넣고, 재 보는 것” 이었습니다.
마치며#
세 줄로 줄이면 이렇습니다.
첫째, 사람의 결정 6,746개를 같은 순간 봇이 냈을 수와 비교했습니다. 리드는 71.8%, 받기는 89.5% 가 같았습니다.
둘째, 갈라진 자리는 셋이었습니다. 강한 한두 장으로 리드를 지킨다. 초반에 최상위 카드를 아낀다. 받을 때 딱 이길 만큼만 쓴다.
셋째, 리드가 가장 많이 갈린 것은 손패 네 장에서 여섯 장 구간이었고, 이긴 판일수록 사람은 봇과 다르게 두었습니다.
세 습관을 한 문장으로 묶으면 “강한 카드는 리드를 지키는 데 쓰려고 아껴 둔다” 입니다. 달무티를 오래 하신 분에게는 당연한 이야기일 것입니다. 그런데 그 당연한 것이 봇의 점수표에는 없었습니다.
다음 편 에서는 이것을 봇에 어떻게 넣었는지, 바꾸기 전과 후의 판단 흐름을 그림으로 나란히 놓고 봅니다.
| 편 | 다루는 것 |
|---|---|
| Part 1 | 규칙 기반 easy 봇 |
| Part 2 | hard 봇의 정보 모델과 확실승수 판정 |
| Part 3 | 수 평가와 전략적 패스 |
| Part 4 | 만들어 보니 hard 봇이 더 약했다 |
| Part 5 | 코드 없이 보는 easy 봇과 hard 봇의 차이 |
| Part 6 | 사람이 열 판 중 여섯 판을 이기는 hard 봇 |
| Part 7 (이 글) | 잘 두는 사람은 봇과 어디서 다르게 두는가 |
| Part 8 | 봇을 고치다: 리드 지키기 계획과 아껴 두기 |