내가 작은 반복 게임 포커 분석 도구를 만든 이유
요약
반복되는 포커 게임 상황에서 플레이어의 고정된 전략(commitment) 가치를 분석하기 위한 Python 기반 실험적 툴킷인 `repeated-poker-analysis`를 소개합니다. 단일 핸드 분석을 넘어 반복 게임의 특성을 정밀하게 측정하는 것을 목표로 합니다.
핵심 포인트
- 반복 게임 내 고정된 전략의 가치를 정밀하게 분석하는 도구 개발
- Python 기반의 실험적 MVP 모델 구현
- Villain의 최적 대응 진단 및 경제적 적응 마감 기한 측정 기능 포함
- 완전한 솔버가 아닌 작은 장난감 게임(toy games) 분석에 집중
대부분의 포커 솔버(solver)는 한 가지 질문에 매우 잘 답합니다. 단일 핸드와 단일 결정 트리(decision tree)가 주어졌을 때, 균형 전략(equilibrium strategy)은 무엇인가? (네, 서브게임 솔빙(subgame solving), 노드 락킹(node locking) 등이 존재하지만, 기본 프레임은 여전히 하나의 핸드, 하나의 균형입니다.)
저는 다른 질문에 계속 막혀 있었습니다. 만약 '동일한 종류'의 상황이 반복해서 나타나고, 플레이어가 이러한 반복 과정에서 고정된 전략을 약속(commit)할 수 있다면 어떻게 될까요? 몇 가지 장난감 게임(toy games)을 통해, 고정된 전략을 약속하는 것이 단판 승부(one-shot)의 관점과 비교했을 때 그 가치를 변화시킬 수 있다는 직감을 얻었고, 이를 수기로 계산해 보았습니다. 저는 그 약속의 가치를 정밀하게 측정할 수 있는, 즉 단순히 믿는 것이 아니라 실제로 '분석'할 수 있는 도구를 원했습니다. (이 중 어떤 것이 반복 게임 균형(repeated-game equilibrium)에 도달하는지는 훨씬 더 강력한 주장이며, 저는 여기서 의도적으로 그 주장을 하지 않겠습니다.)
저는 여전히 소프트웨어 엔지니어링을 배우는 중이라, 최근까지 이를 구현할 수 없었습니다. 종이 위에서 장난감 게임을 추론하는 데 머물러 있었죠. AI 툴링(tooling) 덕분에 분석이 가능해졌고, 마침내 repeated-poker-analysis를 만들기 시작했습니다.
이것은 작은 연구 프로젝트입니다. 하나의 좁은 모델을 작성하고, 작은 예시들을 실행하며, 모델이 무엇을 정당화하고 무엇을 정당화하지 못하는지를 기록합니다.
repeated-poker-analysis란 무엇인가
이것은 작은 추상적 포커 게임을 위한 실험적인 Python 툴킷입니다. 현재의 MVP(Minimum Viable Product)는 다음을 다룹니다:
- 고정된 Hero 약속 후보군 (fixed Hero commitment candidates),
- 작은 유한 트리(finite trees)에서의 정확한 Villain 최적 대응 진단 (exact Villain best-response diagnostics),
- 후보 생성 및 필터링 (candidate generation and filtering),
T_deadline, 경제적 적응 마감 기한 (an economic adaptation deadline),- 로컬
T_detect, 관측 가능한 분포 민감도 추정치 (an observable-distribution sensitivity estimate), - 분석 보고서 및 Markdown 요약.
이 프로젝트는 의도적으로 작게 만들어졌습니다. 완전한 솔버(solver)가 아니며, 실제 솔버의 레인지(ranges)와 연결되어 있지도 않습니다. 손으로 검사하고 테스트할 수 있을 만큼 아주 작은 하나의 장난감 게임인 리버 상황(river spot)에서 시작합니다.
그 장난감 같은 상황은 쇼다운(showdown) 시 항상 칩을 나누어 갖게(chop) 되지만, 레이크(rake)는 여전히 발생하여 손실을 주는 상황입니다. 단일 핸드 관점에서는 레이크가 발생하는 팟(pot)에 더 많은 돈을 넣는 것이 국지적으로 매력적이지 않을 수 있습니다. 하지만 동일한 상황이 반복된다면 다음과 같은 커밋먼트(commitment, 약속/전념) 문제가 발생합니다. 만약 한 플레이어가 고정된 패턴으로 폴드(fold)를 거부한다면, 상대방은 어떻게 대응해야 하며, 그 커밋먼트가 더 이상 가치가 없어지지 않으려면 그 대응이 얼마나 빨리 이루어져야 하는가?
이것이 바로 제가 도구를 통해 정밀하게 확인하고 싶었던 질문입니다. 새로운 균형(equilibrium)이 존재한다는 주장을 하려는 것이 아닙니다.
반복되는 포커가 매력적인 이유 — 그리고 함정은 어디에 있는가
반복 게임(repeated games)은 평판(reputation), 처벌(punishment), 그리고 적응(adaptation)이 일어나기에 자연스러운 환경처럼 들리며, 포커 또한 명백한 반복 구조를 가지고 있습니다. 유사한 리버 상황(river spots), 유사한 블라인드 대 블라인드(blind-vs-blind) 상황, 유사한 베팅 사이즈(sizings), 유사한 풀(pools) 등이 그것입니다.
여기서 제가 주의해야 했던 함정이 있습니다. 만약 반복 횟수가 알려져 있고, 게임이 완전히 관찰되며(fully observed), 각 상황이 독립적이고, 두 플레이어가 완벽하게 합리적이라면, 유한 반복 게임(finite repeated game)은 역진 귀납법(backward induction)에 의해 종종 단판 게임 균형(one-shot equilibrium)으로 회귀합니다. "이 상황이 다섯 번 발생한다"는 사실 자체만으로는 평판 균형(reputation equilibrium)이 존재한다고 주장하기에 충분하지 않습니다. 이것이 표준 게임 이론(game-theory)의 결과이며, 이 프로젝트가 하위 레이어들을 분리하여 유지하는 이유입니다.
따라서 이 프로젝트는 혼동하기 쉬운 몇 가지 개념을 분리하여 유지합니다:
- 단일 핸드 기준 전략 (a one-hand baseline strategy)
- 고정된 히어로(Hero)의 커밋먼트 후보 (a fixed Hero commitment candidate)
- 해당 고정된 히어로 전략에 대한 빌런(Villain)의 정확한 최적 대응 (Villain's exact best response to that fixed Hero strategy)
- 적응을 위한 경제적 데드라인 (an economic deadline for adaptation)
- 변화가 얼마나 눈에 띄는지에 대한 국지적 추정치 (a local estimate of how visible the change is)
- 그리고 훨씬 더 강력한 주장인 반복 게임 균형 (a repeated-game equilibrium)
MVP(최소 기능 제품)는 주로 커밋먼트 분석 레이어(commitment-analysis layer)에 집중되어 있습니다. 즉, 제공된 트리(tree) 내에서 히어로가 특정 후보 전략을 고수할 때, 빌런의 정확한 최적 대응은 무엇인지, 그리고 보수적인 타이(tie, 비김) 처리 하에서 히어로의 EV(기댓값)에 어떤 일이 발생하는지를 다룹니다.
MVP가 할 수 있는 것
(이 부분은 작성 시점의 main 브랜치에 있는 MVP를 설명합니다. 저는 여전히 내용을 수정 중이므로 세부 사항은 변경될 수 있습니다.)
이 도구는 작은 추상 게임(abstract game)에 대해 엔드 투 엔드(end-to-end) 후보 분석 파이프라인을 실행합니다:
- 레이크(rake)가 포함된 작은 유한 2인 게임 트리(finite two-player game trees) 구축
- 고정된 Hero 및 Villain의 혼합 전략(mixed strategies) 평가
- 작은 트리에서 Villain의 정확한 최적 대응(best responses) 열거
- 최악 및 최선의 Villain 최적 대응 타이 규칙(tie rules) 하에서의 Hero EV(기댓값) 보고
- 베이스라인(baseline)으로부터 단순한 Hero 후보 생성
- 비교 전 후보 필터링
- 베이스라인 프로필과 후보 가치 비교
T_deadline및 로컬T_detect계산- Markdown 요약 렌더링
쉽게 말해, 분석 루프는 다음과 같습니다:
- 베이스라인 프로필에서 시작: 제공된 트리 상의 고정된 Hero 전략과 고정된 Villain 전략입니다. 이는 각 정보 집합(information set)에서의 행동 확률(action probabilities)을 의미하며, 핸드 레인지(hand ranges)가 아닙니다. 이 도구는 실제 솔버(solver)의 레인지를 모델링하거나 가져오지 않습니다.
- 단일 Hero 정보 집합에서 두 행동 사이의 확률을 이동시켜 Hero 후보를 생성합니다 (이는 Villain을 괴롭히기 위한 탐색이 아니라, 작은 변화를 무작위적이고 체계적으로 열거하는 방식입니다).
- 각 후보에 대해 Hero를 해당 전략으로 고정하고, Villain의 정확한 최적 대응(best response)을 계산합니다. 이를 통해 Villain이 적응한 후의 Hero의 최악 상황 EV를 도출합니다.
- 대응 후의 최악 상황 Hero EV가 베이스라인 프로필에서의 Hero EV보다 엄격하게 높을 때만 해당 후보를
robustly_profitable로 표시합니다. 핵심은 단순히 "양(+)의 EV"가 아니라, "상대방이 최적 대응을 한 후에도 일회성(one-shot) 베이스라인보다 여전히 더 나은가"입니다. - 그 후, 살아남은 후보들에 반복 게임(repeated-game) 타이밍인
T_deadline/T_detect를 추가합니다.
주요 진입점(entry point)은 run_candidate_analysis_pipeline입니다.
python scripts/check_mvp.py
단순화된 워크플로우:
from nuts_chop_river import build_nuts_chop_river, default_hero_strategy
from candidate_library import baseline_villain_strategy
...
출력값은 당신이 제공한 모델에 대한 진단 보고서이며, 포커 추천이 아닙니다. 다음은 nuts-chop river toy game에 대한 examples/analysis_pipeline.py의 실제 출력물 발췌본입니다 (Configurations 블록과 일부 열을 생략했습니다; 8개의 후보(candidates)가 생성되었고, 필터에 의해 6개가 제외되었으며, 2개가 비교되었습니다):
generated=8 kept=2 excluded=6
compared=2
...
이 실행에서의 baseline Hero EV는 +0.45입니다. 중요한 열은 robustly_profitable입니다. 이 값은 post_response_hero_ev_worst가 해당 baseline을 초과할 때만 yes가 됩니다. 여기서는 두 후보 모두 no입니다 (-0.85와 -0.75는 +0.45보다 낮습니다). baseline을 통과하는 후보는 드물며 인위적으로 구성된 사례에서만 존재할 수 있습니다. 도구의 역할은 후보들을 탐색하여 그러한 경우가 발생했을 때 찾아내는 것입니다. 다음 섹션은 이를 만족하는 사례를 직접 구축한 지점(spot)입니다.
commitment가 baseline을 이기는 toy game
이 메커니즘이 의도한 대로 작동함을 명확히 보여줄 최소 하나 이상의 예시가 필요했습니다. 즉, 상대방이 최선의 대응(best-response)을 한 후에도, 고정된 전략을 commitment(전념)하는 것이 일회성 baseline보다 Hero에게 더 유리한 것으로 알려진 지점 말입니다. 이 nuts-chop steal이 바로 그 예시이며, 이를 위해 전용 테스트(tests/test_nuts_chop_steal_commitment.py)를 작성했습니다. 이것을 도구가 해당 효과를 감지할 수 있는지 확인하는 용도로 취급해 주세요. 최종 목표나 실제 게임에 대한 주장으로 받아들이지는 마십시오. 이 인위적으로 구성된 지점 외에 어떤 상황이 commitment를 하기에 수익성이 있는지(혹은 존재하는지)는 알지 못합니다.
해당 지점: 보드가 이미 nuts(최강의 족보)이므로 모든 showdown(쇼다운)이 chop(분할)되는 리버(river) 상황입니다. 밸류 베팅(value betting)은 불가능합니다. 베팅(shove)을 하는 유일한 이유는 fold equity(폴드 에퀴티) 때문입니다. 레이크(Rake)는 캡(cap) 미만이며, 따라서 콜(called)된 팟은 단순히 하우스에 칩을 떼어주는 꼴이 됩니다. 작은 시작 팟과 큰 shove가 있는 경우, 단일 핸드는 다음과 같습니다:
initial commitment = 1, initial pot = 2, bet = 98, rake = 5%, cap = 4
| Line | Hero/IP EV | Villain/OOP EV |
...
한 핸드에서 콜러(caller)가 폴드(fold)한다면: -1.00 (fold)이 -2.00 (call)을 이깁니다. 따라서 단판 서브게임(one-shot subgame)의 해답은 OOP 베팅 / IP 폴드입니다. 보드가 찹(chop) 상황이고 베팅에 따른 가치(value)가 없으므로, 이는 순수한 스틸(steal)입니다.
이제 IP를 *항상 콜(always call)*하도록 고정하고, 도구에게 OOP의 정확한 최적 대응(best response)을 묻습니다. 스틸의 유일한 수익원(폴드 에퀴티 (fold equity))이 사라졌으므로, 콜이 발생한 팟(pot)은 OOP에게 -2.00이 됩니다. 따라서 OOP의 정확한 최적 대응은 **체크(check)**로 뒤집히며, 체크-체크(check-check)는 양측 모두에게 -0.05가 됩니다. 테스트는 정확히 이 점을 확인합니다: solve_exact_response는 Hero가 콜로 고정되면 {"OOP_river": "check"}를 반환합니다.
그리고 결정적으로, 이것은 기준점(baseline)을 명확히 해줍니다: OOP가 적응한 후 Hero의 EV는 -1.00(단판 스틸 기준점)에서 -0.05로 변합니다. 여전히 음수이지만, 기준점보다는 엄격하게 더 나은 수치이며, 이것이 바로 robustly_profitable 조건입니다. 이것이 하나의 예시로 명시된 이 프로젝트의 핵심 요지입니다: 단판 서브게임의 해답(bet/fold)은 내가 테스트하고자 했던 고정된 약속(fixed commitment, check/check) 하에서의 해답이 아닙니다. 콜을 하겠다는 약속은 상대방의 유일한 베팅 동기를 제거합니다. (이것이 반복 게임의 *균형 (equilibrium)*을 구성하는지에 대해서는 더 강력한 주장이 필요하며, 저는 의도적으로 그 주장을 하지 않습니다. 이것은 균형 증명이 아니라 약속 분석(commitment-analysis) 결과입니다.)
또한 이 도구는 그 약속이 얼마나 오랫동안 가치가 있는지를 수치화합니다. 기준 Hero EV = -1.00 (스틸), 적응 전 = -2.00 (OOP가 여전히 베팅할 때 콜로 고정됨), 적응 후 = -0.05 (OOP가 체크로 전환함)일 때, T_deadline은 floor(1 + 19N/39)로 산출됩니다:
| N (horizon) | T_deadline |
|------------:|-----------:|
| 10 | 5 |
...
솔직한 주의사항을 덧붙이자면: 이것은 아주 작게 수동으로 만든 트리이며, EV는 제가 직접 손으로 확인할 수 있는 값들입니다. 바로 그렇기 때문에 저는 저장소(repo)의 다른 어떤 것보다 이 결과를 더 신뢰합니다. 이것은 실제 게임에 대한 증거가 아닙니다. 효과를 검증하기 위해 구축된 하나의 구성된 예시에서 모델과 코드가 일치한다는 증거입니다.
내 컴퓨터에서의 검증:
python -m pytest tests/test_nuts_chop_steal_commitment.py -v→ 15 passedpython -m pytest -q→ 500 passedpython scripts/check_mvp.py→ passesgit diff --check→ clean
AI와 협업한 방식
나는 알고리즘과 포커 모델을 제공했습니다. Codex는 구현 지침을 작성하고 결과를 검토했으며, Claude Code가 코드를 작성했습니다. 나는 Codex의 프롬프트(prompt)를 확인하고 잘못된 전제를 수정했지만, 코드를 한 줄씩 검토하지는 않았습니다. 대신 Codex/Claude의 검토 루프(review loop)와 테스트 스위트(test suite, 현재 500개의 통과된 테스트)에 의존했습니다.
그 과정에서 기록할 만한 두 가지 사항이 있습니다:
- 어시스턴트(assistant)가 계속해서 일반적인 케이스(general case)로 흐르려는 경향이 있었습니다. 내가 원했던 커밋먼트 분석(commitment analysis)은 Hero를 완전히 고정하고 Villain의 정확한 최적 대응(best response)만을 계산하는 것이었으나, 모델은 반복적으로 CFR을 설정하려고 시도했습니다. Hero가 고정된 상태에서 이는 불필요한 메커니즘(machinery)입니다. 이를 중단시키자 내가 고려하지 못했던 부차적인 질문이 생겼습니다. 한쪽이 고정된 CFR은 고정된 환경에서의 학습 문제(fixed-environment learning problem)처럼 보인다는 점입니다. 이는 결과가 아닌 하나의 질문으로서 기록해 둡니다.
- 이 토이 게임(toy game)을 모델에게 설명하는 것이 사람에게 설명하는 것보다 더 어려웠습니다. 모델은 과도하게 일반화하고 적용되지 않는 사항들을 가정했습니다 (예: Villain이 이미 너츠(nuts)인 보드에서 밸류 벳(value-bet)을 하는 경우). 결국 나는 채팅을 통해 각 사양(spec)을 먼저 다듬은 다음, 정제된 버전을 코딩 에이전트(coding agent)에게 전달했습니다.
코드가 별도로 구분하여 관리하는 용어에 대한 참고 사항: T_deadline은 경제적 관점(잠긴 정책이 여전히 베이스라인(baseline)을 이기는 동안 Villain이 얼마나 늦게 적응할 수 있는지)이며, T_detect는 가시성(candidate의 행동 분포가 베이스라인과 구별 가능해 보이기까지 필요한 로컬 관측(local observations)의 횟수)입니다. 이들은 서로 다른 질문입니다.
내가 배운 것
최적 대응의 동률(Best-response ties)은 중요합니다. 만약 Villain(빌런)에게 동일한 Villain EV를 가진 여러 개의 최적 대응(best responses)이 있다면, Hero(히어로)의 EV는 이들 사이에서도 여전히 달라질 수 있습니다. 임의의 대응 하나만 반환한다면 그러한 리스크를 숨기게 되므로, MVP는 동률 집합(tie set) 전체에 걸쳐 ev_h_worst와 ev_h_best를 모두 보고합니다. (검증 완료: BestResponseResult는 두 값 모두와 최적 순수 전략(optimal pure strategies) 간의 액션 변동성을 노출합니다.)
작은 예시는 약점이 아닙니다. nuts-chop river 벤치마크는 의도적으로 아주 작게 설계되었습니다. 이는 직접 확인하기 더 쉽고, 실제 돈을 거는 권장 사항으로 오해할 위험을 줄이기 위함입니다.
현재의 한계점
가장 큰 한계는 코드가 독립적인 인간의 코드 리뷰(code review)를 거치지 않았다는 점입니다. 테스트는 통과하지만, 제가 구현 내용을 한 줄씩 읽어보지도 않았고 다른 누구도 읽어보지 않았습니다. 코드를 읽는 것에 의존하기보다는 외부에서 검증할 계획입니다. 즉, 가능한 한 철저하게 검증을 설계하고, 다양한 설정에서 시뮬레이션을 실행하며, 결과가 일관되게 유지되는지 확인할 것입니다. 정적 검사(static checking)나 속성 기반 검사(property-based checking)가 그러한 커버리지를 제공할 수 있을지는 여전히 고민 중인 부분입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기