렉시오 CPU 플레이어 만들기 Part 7: 코드 없이 보는 easy 봇과 hard 봇의 차이
이 글은 Claude Opus 5 를 이용해 초안이 작성되었으며, 이후 퇴고를 거쳤습니다.
이 글은 누구를 위한 것인가#
Part 1 부터 Part 6 까지는 비트마스크, 동적 계획법, 평가 함수, 확률 모델 같은 이야기로 채워져 있습니다. 렉시오를 좋아하지만 개발자는 아닌 분에게는 읽을 이유가 없는 글입니다.
이번 편은 그 여섯 편의 결론만 을 코드와 수식 없이 옮긴 것입니다. 렉시오 규칙을 아는 분이라면 누구나 읽을 수 있습니다. 다루는 것은 셋입니다.
- 두 CPU 는 각각 무엇을 보고 수를 고르는가
- 둘을 어떻게 겨루게 했고, 결과가 어땠는가
- 그 결과로 무엇은 말할 수 있고, 무엇은 여전히 말할 수 없는가
세 번째가 이 글에서 가장 중요합니다. 숫자를 보여 주는 글은 많지만, 그 숫자가 무엇을 증명하지 못 하는지까지 적는 글은 드뭅니다.
이 시리즈가 전제하는 규칙은 발행사 디다노니아의 공식 게임방법 문서를 따릅니다. 이 글에 필요한 만큼만 옮기면 이렇습니다.
- 타일은 숫자 1~15 가 구름·별·달·해 네 문양으로 각각 존재해 60장 입니다. 5인 게임에서 전부 사용하고, 한 사람이 12장 을 받습니다
- 숫자 서열은 3 < 4 < … < 15 < 1 < 2, 문양 서열은 구름 < 별 < 달 < 해 입니다. 그래서 가장 약한 타일이 구름 3, 가장 강한 타일이 해 2 입니다
- 정산은 라운드가 끝났을 때 남은 장수만큼 물어내되, 손에 쥔 2 한 장마다 그 금액이 두 배 가 됩니다
1. 두 CPU 는 어떻게 다른가#
1.1 easy 봇 — 규칙 세 줄짜리 CPU#
easy 봇은 판을 읽지 않습니다. 지금 낼 수 있는 수만 보고, 고정된 규칙으로 고릅니다.
flowchart TD
A["내 차례"] --> B["낼 수 있는 수를<br/>전부 나열"]
B --> C{"지금 테이블에<br/>패가 깔려 있나?"}
C -- "깔려 있다 (받아치는 상황)" --> D{"받을 수 있는<br/>수가 있나?"}
D -- "없다" --> E["패스"]
D -- "있다" --> F["가장 약한 패로 받는다<br/>(최소한으로 받기)"]
C -- "비어 있다 (내가 리드)" --> G["손에서 가장 약한 타일이<br/>들어가는 수만 남긴다"]
G --> H["그중 장수가 가장 많은 수<br/>(같으면 더 약한 쪽)"]
H --> I["제출"]
F --> I
style A fill:#FFD700,color:#000000
style E fill:#FFB6C1,color:#000000
style I fill:#90EE90,color:#000000
두 줄로 줄이면 이렇습니다.
- 받을 때: 이길 수 있는 가장 싼 패로 받습니다. 못 받으면 패스합니다
- 리드할 때: 손에서 제일 약한 타일을 반드시 끼워서, 최대한 여러 장을 텁니다
나쁘지 않은 원칙입니다. 약한 타일부터 처리하고 손패를 빨리 줄입니다. 다만 앞을 내다보지 않습니다. 지금 이 수를 내면 남은 손패로 몇 번에 끝낼 수 있는지, 상대가 받을 수 있는지, 2 를 언제 털어야 하는지를 전혀 고려하지 않습니다.
1.2 hard 봇 — 점수를 매겨 고르는 CPU#
hard 봇은 낼 수 있는 모든 수에 점수를 매기고 가장 높은 것을 냅니다. 패스도 하나의 후보로 놓고 같이 점수를 매깁니다.
flowchart TD
A["내 차례"] --> B["판 상태 정리<br/>· 아직 아무도 못 본 타일이 무엇인지<br/>· 각 상대가 몇 장 남았는지<br/>· 라운드가 얼마나 급한지"]
B --> C["내 손패를 최소 몇 번에<br/>다 낼 수 있는지 계산"]
C --> D["낼 수 있는 수를 전부 나열"]
D --> E["각 수마다 계산:<br/>이 수를 상대 중 누군가<br/>받을 수 있을 확률"]
E --> F["수마다 점수 매기기"]
F --> G["<b>좋은 점</b><br/>· 손패를 많이 턴다<br/>· 최소 횟수 계획을 안 깬다<br/>· 라운드 막바지에 많이 턴다<br/>· 라운드 막바지에 상대가 못 받는 패"]
F --> H["<b>나쁜 점</b><br/>· 아무도 못 이기는 귀한 타일을 쓴다<br/>· 내고 나서도 손에 많이 남는다"]
G --> I["합산 점수"]
H --> I
I --> J{"테이블에 패가<br/>깔려 있나?"}
J -- "깔려 있다" --> K["패스도 후보에 넣는다<br/>(작은 기본 점수만 받음)"]
J -- "비어 있다" --> L["패스는 불가"]
K --> M["가장 점수가 높은 수"]
L --> M
M --> N{"동점인가?"}
N -- "아니오" --> O["제출"]
N -- "예" --> P["동점 규칙:<br/>1. 제출이 패스를 이긴다<br/>2. 장수가 적은 쪽<br/>3. <b>더 약한 패</b><br/>(강한 건 손에 남긴다)"]
P --> O
M -.-> Q["0.1초를 넘기거나 계산에 실패하면<br/>easy 가 대신 한 수 둔다"]
style A fill:#FFD700,color:#000000
style G fill:#90EE90,color:#000000
style H fill:#FFB6C1,color:#000000
style O fill:#90EE90,color:#000000
style Q fill:#DDA0DD,color:#000000
핵심 차이는 셋입니다.
- 미공개 타일을 셉니다. 렉시오는 60장을 다섯 명이 전부 나눠 갖습니다. 그래서 “내가 못 본 타일"은 반드시 누군가의 손에 있습니다.
hard는 이걸 이용해 “이 수를 상대가 받을 수 있을 확률"을 계산합니다. - 손패 계획을 세웁니다. 남은 타일을 최소 몇 번에 다 낼 수 있는지 계산해 두고, 그 계획을 깨는 수에 벌점을 줍니다. 해 문양 플러시 재료 다섯 장 중 한 장을 페어에 끼워 쓰면 플러시가 무너지는데,
easy는 그걸 보지 않습니다. - 라운드가 얼마나 급한지 압니다. 누군가 손패를 거의 비웠으면 지금이 마지막 기회일 수 있으므로 기준이 달라집니다.
2. 어떻게 겨루게 했는가#
2.1 문제: 렉시오는 운이 크다#
두 CPU 중 뭐가 나은지 재는 것은 생각보다 까다롭습니다. 좋은 패를 받으면 아무렇게나 둬도 이기기 때문입니다. 몇 판 돌려보고 “이쪽이 더 잘 두는 것 같다"고 말하는 것은 거의 의미가 없습니다.
2.2 해결: 같은 패를 양쪽에 똑같이 나눠준다#
브리지 대회에서 쓰는 방식을 그대로 썼습니다. 똑같은 딜을 여러 번 재사용합니다.
flowchart LR
A["딜 하나<br/>(60장을 섞어<br/>5명에게 12장씩)"] --> B["배치 1<br/>hard 1명 + easy 4명<br/>hard 가 1번 자리"]
A --> C["배치 2·3·4·5<br/>hard 가 2·3·4·5번 자리"]
A --> D["배치 6<br/>easy 1명 + hard 4명<br/>easy 가 1번 자리"]
A --> E["배치 7·8·9·10<br/>easy 가 2·3·4·5번 자리"]
B --> F["같은 딜로<br/>10경기"]
C --> F
D --> F
E --> F
style A fill:#FFD700,color:#000000
style F fill:#90EE90,color:#000000
같은 패를 두 CPU 가 모두 잡아보게 하면 운이 상쇄됩니다. 좋은 패가 들어와서 이긴 것인지, 잘 둬서 이긴 것인지 구분할 수 있게 됩니다.
자리를 다섯 군데로 돌리는 것에도 이유가 있습니다. 렉시오에서는 순서에 따라 유불리가 있고, 특히 “혼자만 다른 CPU"라는 자리 자체가 유불리를 만듭니다. 그래서 양방향 으로 겨룹니다. hard 하나가 easy 넷과 겨루는 판, 그리고 easy 하나가 hard 넷과 겨루는 판을 모두 만듭니다.
2.3 몇 판을 돌렸나#
| 무엇 | 판 수 |
|---|---|
| 가중치 조정 과정 (148가지 설정을 각각 640판) | 약 9만 5천 판 |
| 최종 검증 | 3,840 판 |
| 진단·측정용 실행 | 약 1만 판 |
한 판이 3~10라운드이므로, 라운드로 세면 50만 라운드 가 넘습니다. 사람이 하루 100판씩 두어도 2년 반이 걸리는 양입니다.
2.4 조정용 패와 검증용 패를 갈랐다#
이것이 결과를 믿을 수 있게 만드는 핵심 장치입니다.
시험 문제로 공부하고 그 시험을 보면 점수가 잘 나오는 게 당연합니다. 가중치를 맞출 때 쓴 딜로 성능을 재면 “얼마나 잘 외웠는지"를 재는 것이지 실력을 재는 것이 아닙니다.
그래서 딜을 두 묶음으로 완전히 갈랐습니다.
- 조정용(64가지 딜): 가중치를 맞출 때만 씁니다
- 검증용(128가지 딜): 조정 과정에서 한 번도 보지 않습니다. 최종 성적표는 여기서만 냅니다
두 묶음이 절대 겹치지 않는다는 것은 자동 테스트로 고정해 두었습니다.
2.5 라운드 수를 나눠서 쟀다#
3라운드, 5라운드, 10라운드 판을 따로 재고 셋 다 통과해야 합격으로 봤습니다.
긴 판에서 크게 이기고 짧은 판에서 조금씩 지면 평균으로는 이기는 것처럼 보입니다. 그런데 라운드 수는 플레이어가 고릅니다. 짧은 판을 고른 사람에게 약한 CPU 가 붙으면 안 됩니다.
3. 결과#
아래 숫자는 전부 검증용 딜 에서 나온 것입니다. 조정 과정에서 한 번도 보지 않은 패입니다.
3.1 등수 분포#
다섯 명이 두므로, 아무렇게나 둬도 각 등수가 20% 씩 나와야 정상입니다.
5라운드 판 (1,280경기)
| 1등 | 2등 | 3등 | 4등 | 꼴찌 | |
|---|---|---|---|---|---|
easy |
16.0% | 19.1% | 20.6% | 21.1% | 23.2% |
hard |
26.8% | 21.8% | 19.2% | 17.8% | 14.4% |
10라운드 판 (1,280경기)
| 1등 | 2등 | 3등 | 4등 | 꼴찌 | |
|---|---|---|---|---|---|
easy |
13.1% | 18.1% | 20.6% | 21.7% | 26.5% |
hard |
27.9% | 22.6% | 18.9% | 18.1% | 12.4% |
hard 는 1등을 easy 의 두 배 넘게 합니다. 꼴찌는 절반 이하입니다. 그리고 판이 길어질수록 격차가 벌어집니다. 운이 상쇄될 시간이 길어지기 때문입니다.
3.2 맞대결#
“혼자만 다른 CPU” 한 명이 나머지 넷의 평균보다 잘했는지를 셌습니다.
| 판 길이 | hard 가 앞선 비율 |
|---|---|
| 5라운드 | 67.0% |
| 10라운드 | 74.5% |
10라운드 판이면 네 판 중 세 판 에서 hard 가 앞섭니다.
3.3 돈으로 환산하면#
렉시오 정산은 라운드가 끝났을 때 남은 장수 만큼 물어내되, 손에 쥔 2 한 장마다 그 금액이 두 배 가 됩니다. 3장을 남겼는데 그중 2 가 한 장이면 3 이 아니라 6 을 뭅니다. 두 장이면 12 입니다. 그래서 등수보다 “얼마나 깔끔하게 끝냈는가"가 훨씬 중요합니다.
라운드당 평균 정산은 hard 가 +1.2, easy 가 −1.2 입니다. 10라운드 한 판이면 약 12점 차이 가 납니다. 2 없이 3장을 남기고 끝나는 흔한 패배가 3점이니, 판당 네 번쯤 더 진 셈 입니다.
이 구조 때문에 hard 의 “2 를 쥔 채 끝나는 비율 1.3%”(easy 는 4.4%)가 특히 크게 작용합니다. 2 를 안고 끝나는 사고를 3분의 1 로 줄인 것이 격차의 상당 부분을 만듭니다.
3.4 왜 이겼는가#
아래는 경기 등수가 아니라 라운드 단위 지표입니다. 한 경기는 여러 라운드로 이루어지고, 정산은 라운드마다 일어납니다.
easy |
hard |
|
|---|---|---|
| 라운드에서 먼저 손패를 비우는 비율 | 20.6% | 19.4% |
| 라운드가 끝났을 때 남은 장수 | 3.44장 | 3.07장 |
| 2 를 손에 쥔 채 끝나는 비율 | 4.4% | 1.3% |
| 라운드당 제출 횟수 | 6.21회 | 5.25회 |
재미있는 것은 먼저 나가는 비율은 오히려 hard 가 약간 낮다 는 점입니다. hard 가 경기 등수에서 앞서는 이유는 라운드를 더 많이 이겨서가 아니라 못 이긴 라운드에서 덜 잃어서 입니다.
- 끝났을 때 손에 남은 장수가 0.4장 적습니다
- 2 를 쥔 채 끝나는 일이 3분의 1 이하입니다. 렉시오에서 2 를 안고 끝나면 벌점이 두 배가 되므로 이것이 가장 크게 작용합니다
- 제출 횟수가 적습니다. 한 번에 더 많이 턴다는 뜻입니다
한마디로 easy 는 자주 나가고 크게 지고, hard 는 비슷하게 나가고 적게 집니다.
4. 그래서 hard 가 더 똑똑하다고 단정할 수 있는가#
4.1 단정할 수 있는 것#
hard 는 easy 보다 확실히 강합니다. 이건 자신 있게 말할 수 있습니다.
- 검증용 딜 3,840경기에서 라운드 수 세 종류 모두 이겼습니다
- 통계적으로 우연이 아닙니다. 가장 보수적으로 잡아도 라운드당 최소 +1.08점 앞섭니다
- 조정에 쓴 딜에서 얻은 성적(+1.25)과 검증용 딜 성적(+1.21)이 거의 같습니다. 이건 “시험 문제만 외운 게 아니다"라는 뜻이라 특히 중요합니다
- 0.1초 판단 시간 제한을 한 번도 넘기지 않았습니다. 실제로 그 실력으로 둡니다
4.2 단정할 수 없는 것#
“똑똑하다"와 “easy 보다 낫다"는 다른 말입니다.
-
사람과 겨뤄본 적이 없습니다.
easy는 규칙 세 줄짜리 CPU 입니다. 그걸 이겼다고 사람 상대로 잘한다는 보장은 없습니다. 사람은 CPU 가 못 하는 것을 합니다. 예를 들면 상대의 버릇을 읽습니다. -
hard는 상대를 읽지 않습니다. 미공개 타일이 무엇인지는 세지만, 누가 무엇을 가졌을지는 추측하지 않습니다. “저 사람이 아까 해 문양을 안 냈으니 해가 부족할 것"이라는 추론을 전혀 하지 못합니다. 사람 상대로는 이것이 큰 약점이 될 수 있습니다. -
매번 똑같이 둡니다. 두 CPU 모두 같은 상황에서 항상 같은 수를 냅니다. 사람이 여러 판 하면 패턴을 외울 수 있고, 그러면 실제 체감 난이도는 측정치보다 훨씬 낮아집니다.
-
5인 경기, 한 가지 규칙에서만 쟀습니다. 다른 인원수나 다른 정산 방식은 재보지 않았습니다.
-
평가 함수가 옳다는 보장은 없습니다.
hard는 “손패를 많이 턴다”, “귀한 타일을 아낀다” 같은 항목에 점수를 매겨 고릅니다. 이 항목들이 렉시오의 실제 좋은 수와 얼마나 맞는지는easy를 이겼다는 사실만으로는 알 수 없습니다. -
아직 설명 못 한 것이 하나 있습니다. “2 를 털어라"는 항목을 꺼도 성적이 오히려 조금 좋아집니다. 그런데 그 항목을 끄면 2 를 쥔 채 끝나는 비율은 올라갑니다. 2 를 터는 일을 이 항목이 아니라 다른 항목이 하고 있고, 이 항목은 그 위에 덧대는 과잉일 가능성이 있습니다. 아직 안 봤습니다.
4.3 실제로 있었던 함정 하나#
이 결과에 이르기까지 있었던 일을 적어 두겠습니다. 숫자를 어디까지 믿어야 하는지에 대한 감각이 되기 때문입니다.
처음 측정했을 때 hard 가 easy 보다 라운드당 1.5점 지고 있었습니다. 여러 편에 걸쳐 설계한 전략이 규칙 세 줄짜리 CPU 에게 지고 있었던 것입니다.
원인을 찾는 데 가설을 세 번 틀렸습니다. 전부 “평가 항목 중 하나가 잘못 설계됐다"는 방향이었고, 셋 다 측정으로 기각됐습니다.
진짜 원인은 전략이 아니라 확률 계산 실수 였습니다. “이 수를 상대 중 누군가 받을 수 있는가"를 계산할 때, 상대 넷을 서로 독립적으로 취급하고 있었습니다. 그런데 렉시오는 60장을 전부 나눠 가지므로 상대 넷은 미공개 타일 전부 를 나눠 갖고 있습니다. 서로 독립일 수가 없습니다.
그래서 이런 일이 벌어졌습니다. 제가 달 2 를 내려고 하는데 그것을 이길 수 있는 해 2 가 아직 안 나왔다면, 해 2 는 반드시 누군가 갖고 있으므로 제 달 2 는 100% 받힙니다. 그런데 계산기는 68% 라고 답했습니다. 그 32% 가 평가에 “손에서 가장 센 타일을 내라"는 보상으로 들어갔고, CPU 는 별 3 리드를 달 2 로 받는 짓을 하고 있었습니다. 트릭은 이기고 라운드는 잃는 수입니다.
곱셈을 덧셈으로 바꾸는 한 줄을 고치자, 가중치를 하나도 건드리지 않았는데 −1.4 가 +0.7 이 되었습니다.
교훈은 이렇습니다. 평가 전략이 아무리 훌륭해도 거기 들어가는 숫자가 틀리면 소용없습니다. 그리고 그 차이는 코드를 읽어서는 잘 안 보입니다. 실제로 돌려서 재봐야 보입니다. 이 이야기의 전체 과정은 Part 6 에 있습니다.
5. 앞으로 해볼 만한 것#
우선순위 순입니다.
5.1 사람과 겨루게 하고 그 판을 기록한다#
가장 중요합니다. 지금 있는 근거는 전부 “CPU 대 CPU"입니다. 사람과의 판을 기록해 두면 CPU 가 어떤 국면에서 사람에게 지는지 볼 수 있습니다.
특히 볼 것은 이것입니다. 사람이 CPU 의 패턴을 몇 판 만에 읽어내는가.
5.2 상대 손패를 추측하게 한다#
지금 hard 는 “미공개 타일이 무엇인가"까지만 압니다. “누가 무엇을 갖고 있을 것 같은가"는 전혀 모릅니다.
렉시오에서 사람이 쓰는 가장 기본적인 추론이 여기 있습니다.
- 저 사람이 아까 낮은 싱글에 패스했다 → 낮은 싱글이 없다
- 저 사람이 해 문양 플러시를 안 냈다 → 해 타일이 부족하다
- 저 사람이 남은 두 장으로 페어를 노린다 → 리드를 뺏기면 안 된다
이걸 넣으면 easy 상대 성적보다 사람 상대 성적 이 크게 달라질 가능성이 있습니다.
5.3 무작위성을 조금 넣는다#
지금은 매번 똑같이 둡니다. 점수가 비슷한 후보 중에서 가끔 다른 것을 고르게 하면 패턴 읽기가 어려워집니다.
다만 강해지려고 넣는 게 아니라 덜 지루하려고 넣는 것 이므로, 얼마나 약해지는지를 재고 결정해야 합니다.
5.4 남은 이상 현상을 본다#
§4.2 의 여섯 번째 항목입니다. “2 를 털어라” 항목이 왜 도움이 안 되는지 밝힙니다. 다른 항목이 같은 일을 중복해서 하고 있다면 정리할 수 있습니다.
5.5 난이도를 나눈다#
지금 hard 는 “최대한 잘 두는 것"을 목표로 합니다. 그런데 혼자 하기 모드의 목적은 이기는 게 아니라 재미있는 판을 만드는 것 입니다.
판단 시간을 줄이거나, 계획 깊이를 얕게 하거나, 일부러 몇 % 는 두 번째로 좋은 수를 두게 하면 중간 난이도를 만들 수 있습니다. 지금 있는 도구로 각 설정이 얼마나 약해지는지 바로 잴 수 있습니다.
5.6 다른 인원수와 규칙에서 재본다#
5인 한 가지 조건에서만 쟀습니다. 인원수가 달라지면 미공개 타일 수가 달라지므로 확률 계산의 무게가 달라집니다. 옮겨가서도 성립하는지 확인이 필요합니다.
마치며#
세 줄로 줄이면 이렇습니다.
첫째,
easy는 이번 턴만 보고 가장 싼 수를 냅니다.hard는 아직 안 나온 타일을 세고, 손패를 몇 번에 비울지 계획하고, 라운드가 얼마나 급한지까지 보고 점수를 매겨 고릅니다.둘째, 같은 딜을 양쪽에 똑같이 나눠주고 자리를 돌려 운을 지운 뒤, 조정에 쓰지 않은 딜로만 성적을 냈습니다.
hard가 라운드당 약 1.2점 앞섭니다. 이긴 이유는 라운드를 더 많이 이겨서가 아니라 못 이긴 라운드에서 덜 잃어서 입니다.셋째, 그래도
hard가 사람보다 잘 둔다는 뜻은 아닙니다. 사람과 겨뤄본 적이 없고, 상대가 무엇을 들고 있을지 추측하지 않으며, 매번 똑같이 둡니다.
가장 오래 기억할 만한 것은 세 번째 줄입니다. “A 를 이겼다"와 “잘한다"는 다른 문장입니다. 무엇을 상대로 이겼는지를 함께 적지 않으면, 숫자는 실제보다 훨씬 큰 것을 증명하는 것처럼 보입니다.
더 깊이 들어가고 싶은 분을 위해 시리즈 전체의 지도를 남겨 둡니다.
| 편 | 다루는 것 |
|---|---|
| Part 1 | 타일 60장을 정수 하나로 압축하는 표현과 easy 봇 |
| Part 2 | hard 봇이 무엇을 아는가 — 미공개 타일 카운팅 |
| Part 3 | hard 봇이 자기 손패를 어떻게 보는가 — 분해 플랜 |
| Part 4 | hard 봇이 무엇을 결정하는가 — 수 평가와 정산 기대값 |
| Part 5 | 구현했더니 봇이 아무것도 하지 않았던 이야기 |
| Part 6 | 측정 도구를 만들고, 봇이 약한 이유를 네 번 만에 찾은 이야기 |
| Part 7 (이 글) | 코드 없이 보는 결론 |
References#
- 렉시오 공식 게임방법 (디다노니아)
- LEXIO (BoardGameGeek)
- Duplicate bridge (Wikipedia) — 같은 딜을 재사용해 운을 지우는 방식
- Training, validation, and test data sets (Wikipedia) — 조정용 패와 검증용 패를 가르는 이유
- 렉시오 CPU 플레이어 만들기 Part 1: 타일 하나를 정수 하나로
- 렉시오 CPU 플레이어 만들기 Part 2: 60비트 카운팅과 확실승수 판정
- 렉시오 CPU 플레이어 만들기 Part 3: 손패 분해 플랜
- 렉시오 CPU 플레이어 만들기 Part 4: 수 평가와 정산 기대값
- 렉시오 CPU 플레이어 만들기 Part 5: 봇이 아무것도 하지 않는 이유
- 렉시오 CPU 플레이어 만들기 Part 6: 봇이 약한지 어떻게 아는가
- 대부호부터 렉시오까지: 클라이밍 카드게임의 세계와 전략