어려운 방법으로 블랙잭 시뮬레이션

발행: (2026년 2월 17일 오전 10:16 GMT+9)
8 분 소요
원문: Dev.to

Source: Dev.to

위 링크에 있는 전체 텍스트를 제공해 주시면, 해당 내용을 한국어로 번역해 드리겠습니다. (코드 블록, URL 및 마크다운 형식은 그대로 유지됩니다.)

가려움

이것은 언제나 그렇듯 시작되었다: 나는 숫자를 이해하고 싶었다. 온라인에서 보는 “하우스가 0.5 %의 이점을 가지고 있다”는 막연한 말이 아니라 실제 메커니즘을 말이다. 딜러가 소프트 17을 히트할 때 기본 전략은 어떻게 바뀌는가? 카드 카운팅이 실제로 무엇을 가져다 주는가? 한 덱이 진정으로 무작위가 되기까지 몇 번의 리플 셔플이 필요한가?

나는 책을 읽을 수도 있었다. 대신 시뮬레이터를 만들었다. 그리고 다시 고쳤다. 또 한 번 더 고쳤다.

시뮬레이션을 올바르게 만들기

The first rule I set for myself was 지름길 금지. If you’re simulating blackjack to understand blackjack, you can’t approximate the parts you find inconvenient. Every card comes from a shoe. Every shuffle follows real physics. Every hand resolves by the actual casino rules: splits, doubles, surrender, insurance, the whole mess.

The shuffle simulation is where things got interesting. A perfect Fisher‑Yates shuffle is trivial to implement, and it produces a uniformly random deck. But casino dealers don’t perform perfect shuffles. They do riffle shuffles, and the mathematics of imperfect riffles are well‑studied. It takes about seven riffle shuffles to adequately randomize a deck. Fewer than that and there’s exploitable structure left in the card order. I implemented three shuffle types with configurable fidelity so I could study exactly how much information survives an imperfect shuffle.

Card counting was the other rabbit hole. The basic Hi‑Lo system is straightforward: low cards add one, high cards subtract one, divide by decks remaining. But professional‑play deviations are where it gets complicated. The correct play changes based on the true count. Sometimes you should hit a 16 against a dealer 10. Sometimes you shouldn’t. The threshold depends on how deep you are into the shoe.

I implemented all of this because I wanted the numbers to be right.

아키텍처 집착

시뮬레이션 코드는 모놀리식 형태로 잘 동작했습니다. 수천 번의 핸드를 실행해 통계적으로 유의미한 결과를 얻을 수 있었죠. 하지만 코드를 볼 때마다 고치고 싶은 부분이 눈에 들어왔습니다.

게임 로직이 I/O와 뒤섞여 있었고, 전략 결정이 핸드 해결과 결합돼 있었습니다. 상태는 가변적이며 여섯 개 정도 되는 객체에 흩어져 있었습니다. 작동은 했지만, 새로운 기능을 추가하려면 다른 모든 기능을 이해해야 하는 불편한 코드였습니다.

그래서 저는 네 단계에 걸친 리팩터링을 진행했습니다:

  1. 이벤트‑드리븐 아키텍처
  2. 불변 상태와 frozen dataclass 사용
  3. 플랫폼 어댑터를 도입해 엔진을 특정 인터페이스와 분리
  4. 전역 비동기 지원

핵심 통찰은 모든 상태 변화를 이벤트로 취급하는 것이었습니다. 카드가 배분 → 이벤트. 플레이어가 히트 → 이벤트. 딜러가 히든 카드를 공개 → 이벤트. 이렇게 하면 게임 엔진은 순수 상태 머신이 됩니다: 행동을 넣으면 새로운 상태를 반환합니다. 부작용도 없고, 숨겨진 변형도 없으며, 테스트와 검증이 쉬워집니다.

또한 전체 게임을 이벤트 시퀀스로 기록하고 나중에 재생할 수 있다는 장점도 있습니다. 시뮬레이션 결과가 이상하게 보일 때, 모든 결정을 단계별로 살펴보면서 수학적 계산이 기대와 어디서 달라졌는지 정확히 확인할 수 있었습니다. 보통은 수학이 맞고 기대가 잘못된 경우가 많았습니다.

초당 350,000핸드가 알려주는 것

현재 시뮬레이터는 초당 약 350,000게임을 실행할 수 있습니다. 이는 거의 모든 질문에 대해 통계적으로 의미 있는 결과를 얻기에 충분합니다.

확인된 기대

  • 기본 전략이 작동한다.
  • 카드 카운팅이 거의 작동한다, 이상적인 조건 하에서.
  • 마팅게일 베팅 시스템은 서서히 파산하는 신뢰할 수 있는 방법이다.

놀라운 발견

  • 블랙잭의 변동성은 가혹하다. 완벽한 기본 전략을 사용해도 몇 시간 동안 질 수 있다.
  • 수학적으로는 수천 손에 걸쳐 약간 이득을 볼 수 있지만, “약간”이라는 표현이 그 문장에서 큰 의미를 차지한다.
  • 이제 나는 카드 카운팅이 큰 자본과 강인한 정신을 모두 필요로 한다는 직관을 훨씬 더 잘 이해하게 되었다.

왜 이것을 만들었는가

사람들은 다양한 이유로 사이드 프로젝트를 만듭니다. 어떤 사람은 제품을 출시하고 싶어하고, 어떤 사람은 기술을 배우고 싶어합니다. 저는 시스템을 이해하고 싶었고, 시뮬레이션을 만드는 것이 제가 알기로 가장 철저한 방법이었습니다.

아키텍처 작업 자체가 큰 보상이었습니다. 이벤트‑드리븐 블랙잭 엔진에 불변 상태 관리를 적용해야 할 필요가 있지는 않지만, 그 패턴이 전이되기 때문입니다. 카드 게임을 테스트 가능하게 만드는 동일한 관심사 분리가 분산 시스템을 디버깅 가능하게 만들고, 손쉽게 블랙잭 한 판을 재생할 수 있게 하는 이벤트‑드리븐 접근 방식이 실제 운영 사고를 재현하는 방식과 동일합니다.

코드는 GitHub에서 확인할 수 있습니다. 참고: 이것은 계속해서 규모가 커진 사이드 프로젝트이며, 카드 게임을 위해 28,000줄이 넘는 파이썬 코드가 포함되어 있습니다. 어떤 프로젝트는 바로 그런 식으로 성장합니다.

0 조회
Back to Blog

관련 글

더 보기 »