<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Stojan Kojo</title>
    <description>The latest articles on DEV Community by Stojan Kojo (@stojankojo).</description>
    <link>https://dev.to/stojankojo</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1735594%2F45bd924b-1316-4a3f-bd9b-c19a35db1527.png</url>
      <title>DEV Community: Stojan Kojo</title>
      <link>https://dev.to/stojankojo</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/stojankojo"/>
    <language>en</language>
    <item>
      <title>This article provides a definitive guide for anyone interested in the complexities of building or launching online poker software. It addresses the common misconception that there is a simple "tutorial" for creating a real-money poker platform.</title>
      <dc:creator>Stojan Kojo</dc:creator>
      <pubDate>Wed, 10 Jun 2026 15:43:26 +0000</pubDate>
      <link>https://dev.to/stojankojo/this-article-provides-a-definitive-guide-for-anyone-interested-in-the-complexities-of-building-or-1b0</link>
      <guid>https://dev.to/stojankojo/this-article-provides-a-definitive-guide-for-anyone-interested-in-the-complexities-of-building-or-1b0</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/stojankojo/any-good-and-easy-tutorial-on-how-to-build-a-poker-software-40m7" class="crayons-story__hidden-navigation-link"&gt;Any good and easy tutorial on how to build a poker software?&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/stojankojo" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1735594%2F45bd924b-1316-4a3f-bd9b-c19a35db1527.png" alt="stojankojo profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/stojankojo" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Stojan Kojo
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Stojan Kojo
                
              
              &lt;div id="story-author-preview-content-3866900" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/stojankojo" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1735594%2F45bd924b-1316-4a3f-bd9b-c19a35db1527.png" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Stojan Kojo&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/stojankojo/any-good-and-easy-tutorial-on-how-to-build-a-poker-software-40m7" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Jun 10&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/stojankojo/any-good-and-easy-tutorial-on-how-to-build-a-poker-software-40m7" id="article-link-3866900"&gt;
          Any good and easy tutorial on how to build a poker software?
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/architecture"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;architecture&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/gamedev"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;gamedev&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/softwaredevelopment"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;softwaredevelopment&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/tutorial"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;tutorial&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/stojankojo/any-good-and-easy-tutorial-on-how-to-build-a-poker-software-40m7" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;1&lt;span class="hidden s:inline"&gt;&amp;nbsp;reaction&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/stojankojo/any-good-and-easy-tutorial-on-how-to-build-a-poker-software-40m7#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              &lt;span class="hidden s:inline"&gt;Add&amp;nbsp;Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            9 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
    </item>
    <item>
      <title>Any good and easy tutorial on how to build a poker software?</title>
      <dc:creator>Stojan Kojo</dc:creator>
      <pubDate>Wed, 10 Jun 2026 15:42:20 +0000</pubDate>
      <link>https://dev.to/stojankojo/any-good-and-easy-tutorial-on-how-to-build-a-poker-software-40m7</link>
      <guid>https://dev.to/stojankojo/any-good-and-easy-tutorial-on-how-to-build-a-poker-software-40m7</guid>
      <description>&lt;p&gt;Building a poker software platform from scratch is one of the most complex challenges in the gaming industry. There is no single "easy" tutorial that will take you from zero to a fully licensed, secure, and scalable real-money poker room. The gap between a simple coding exercise and a production-ready gaming platform is vast, involving not just game logic, but also regulatory compliance, financial security, and high-concurrency architecture.&lt;/p&gt;

&lt;p&gt;However, if your goal is to understand the process, the components, and the strategic decisions required to build or launch poker software, we can break down the entire lifecycle. This guide serves as a comprehensive roadmap, replacing the need for a simple "how-to" video with a deep dive into the reality of poker platform development.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Reality of "Building" Poker Software
&lt;/h3&gt;

&lt;p&gt;Before writing a single line of code, it is crucial to understand what "building" means in this context. You are not just creating a game; you are building a bank, a security firm, a customer support center, and a regulated financial institution, all wrapped in a game interface.&lt;/p&gt;

&lt;p&gt;A true poker software platform consists of three distinct layers:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;The Game Engine:&lt;/strong&gt; The logic that deals cards, calculates pot odds, determines winners, and manages game state.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;The Platform/Backend:&lt;/strong&gt; The user management, wallet systems, payment processing, and database architecture.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;The Frontend:&lt;/strong&gt; The client applications (Web, iOS, Android, Desktop) that players interact with.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;While you can find open-source projects like &lt;em&gt;PokerTH&lt;/em&gt; or &lt;em&gt;Holdem Manager&lt;/em&gt; tutorials on GitHub, these are rarely suitable for a commercial, real-money operation. They lack the security, anti-fraud, and compliance features required by gaming commissions.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Introduction: The Scope of the Challenge
&lt;/h3&gt;

&lt;p&gt;Why is there no "easy" tutorial for building poker software? Because the stakes are incredibly high. If you build a shopping cart incorrectly, a customer might not buy a shirt. If you build a poker room incorrectly, you risk losing player funds, getting hacked, or facing severe legal penalties.&lt;/p&gt;

&lt;p&gt;The poker software industry is dominated by a few key players who have spent decades refining their code. New entrants usually do not build from scratch; they license &lt;strong&gt;White-Label solutions&lt;/strong&gt; or use &lt;strong&gt;turnkey platforms&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This article is designed for founders, operators, and technical leads who want to understand the mechanics of poker software. Whether you are planning to code it yourself (the "Custom Build" route) or evaluate a vendor (the "White-Label" route), you need to know what lies under the hood. We will explore the architecture, the legal hurdles, the security requirements, and the operational realities of running a poker platform.&lt;/p&gt;

&lt;p&gt;By the end of this guide, you will have a clear understanding of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  The core components of a poker network.&lt;/li&gt;
&lt;li&gt;  The difference between building from scratch vs. licensing.&lt;/li&gt;
&lt;li&gt;  The critical security measures required to prevent bots and collusion.&lt;/li&gt;
&lt;li&gt;  The financial and legal infrastructure needed for real-money gaming.&lt;/li&gt;
&lt;li&gt;  How to scale a platform from 10 to 10,000 concurrent players.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Core Concept: The Anatomy of a Poker Room
&lt;/h3&gt;

&lt;p&gt;At its heart, poker software is a &lt;strong&gt;real-time, stateful, distributed system&lt;/strong&gt;. Unlike a standard web app where a user loads a page and submits a form, a poker room is a continuous connection where thousands of players are interacting simultaneously.&lt;/p&gt;

&lt;h4&gt;
  
  
  The Game Engine
&lt;/h4&gt;

&lt;p&gt;The game engine is the brain. It is responsible for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Shuffling and Dealing:&lt;/strong&gt; Using a certified Random Number Generator (RNG) to ensure fairness.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Hand Evaluation:&lt;/strong&gt; Determining the winner of a pot based on complex hand rankings (e.g., Royal Flush vs. Full House).&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;State Management:&lt;/strong&gt; Tracking the current phase of the hand (Pre-flop, Flop, Turn, River, Showdown) and the actions of every player.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Timer Management:&lt;/strong&gt; Enforcing shot clocks (time limits for actions) to keep games moving.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  The Network Architecture
&lt;/h4&gt;

&lt;p&gt;Modern poker rooms are rarely standalone. They are often part of a &lt;strong&gt;Poker Network&lt;/strong&gt;, where multiple brands share the same player pool.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Client-Server Model:&lt;/strong&gt; The client (the player's app) sends actions (Fold, Call, Raise) to the server. The server validates the action, updates the game state, and broadcasts the new state to all players at the table.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;WebSocket Protocol:&lt;/strong&gt; To achieve the low latency required for poker, servers use WebSockets. This keeps a persistent connection open, allowing instant data transfer without the overhead of HTTP requests.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  The Wallet System
&lt;/h4&gt;

&lt;p&gt;This is the financial engine. It must handle:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Deposits and withdrawals.&lt;/li&gt;
&lt;li&gt;  Table stakes (buying in).&lt;/li&gt;
&lt;li&gt;  Rake calculation (the fee taken by the house).&lt;/li&gt;
&lt;li&gt;  Bonus tracking and promotion credits.&lt;/li&gt;
&lt;li&gt;  Multi-currency support.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Technical Breakdown: Architecture and Implementation
&lt;/h3&gt;

&lt;p&gt;Building a poker platform requires a sophisticated stack. Let's break down the technical components required for a production-ready system.&lt;/p&gt;

&lt;h4&gt;
  
  
  The Backend: High Concurrency and Low Latency
&lt;/h4&gt;

&lt;p&gt;Poker is I/O intensive. You need to handle thousands of concurrent connections with microsecond latency.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Languages:&lt;/strong&gt; Go (Golang), C++, Rust, or Java are preferred for the core game engine due to their performance and concurrency models. Node.js or Python might be used for peripheral services (user profiles, marketing), but rarely for the core hand logic.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Database:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Relational (PostgreSQL/MySQL):&lt;/strong&gt; Used for transactional data like user accounts, financial ledgers, and KYC documents. ACID compliance is non-negotiable here.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;NoSQL (Redis/Cassandra):&lt;/strong&gt; Used for game state. Redis is ideal for storing the current state of thousands of tables in memory, allowing for instant access and updates.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Message Queues:&lt;/strong&gt; Systems like Apache Kafka or RabbitMQ are used to decouple services. For example, when a hand ends, the game engine publishes an event to the queue, which is then consumed by the analytics engine, the loyalty system, and the hand-history logger.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  The Frontend: Cross-Platform Compatibility
&lt;/h4&gt;

&lt;p&gt;Players expect to play anywhere.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Web:&lt;/strong&gt; HTML5/Canvas/WebGL for browser-based play.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Mobile:&lt;/strong&gt; Native apps (Swift for iOS, Kotlin for Android) or cross-platform frameworks like Flutter or React Native. &lt;em&gt;Note: Native is often preferred for performance and access to device features.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Desktop:&lt;/strong&gt; Often built using Electron or as a native wrapper, though the market is shifting heavily to mobile.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  The RNG (Random Number Generator)
&lt;/h4&gt;

&lt;p&gt;This is the most critical component for fairness.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Not Just Math.random():&lt;/strong&gt; Standard pseudo-random generators are predictable and insecure.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Certified RNGs:&lt;/strong&gt; You must use a Cryptographically Secure Pseudo-Random Number Generator (CSPRNG).&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Auditing:&lt;/strong&gt; The RNG must be tested and certified by independent labs like eCOGRA, GLI (Gaming Laboratories International), or iTech Labs. This certification is required for almost all gaming licenses.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Security Architecture
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Encryption:&lt;/strong&gt; All data in transit must be encrypted via TLS 1.3. Data at rest (database) must be encrypted.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Anti-Collusion:&lt;/strong&gt; Algorithms that detect if players at the same table are sharing information or folding to each other intentionally.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Anti-Bot:&lt;/strong&gt; Behavioral analysis to detect automated play (e.g., perfect timing, identical action patterns).&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;DDoS Protection:&lt;/strong&gt; Poker rooms are frequent targets. You need enterprise-grade DDoS mitigation (e.g., Cloudflare, AWS Shield).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Business Impact: Cost, Profitability, and Operations
&lt;/h3&gt;

&lt;p&gt;Choosing between building from scratch and buying a white-label solution is the most significant business decision you will make.&lt;/p&gt;

&lt;h4&gt;
  
  
  Option A: Custom Build (From Scratch)
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Pros:&lt;/strong&gt; Complete ownership of IP, no recurring licensing fees, full customization, no revenue sharing with a provider.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Cons:&lt;/strong&gt; Extremely high upfront cost ($500k - $2M+), long development time (12-24 months), high risk of failure, requires a large team of senior engineers.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Best For:&lt;/strong&gt; Large gaming groups, well-funded startups with a unique tech angle, or specific niche markets.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Option B: White-Label / Turnkey Solution
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Pros:&lt;/strong&gt; Launch in 4-8 weeks, lower upfront cost ($50k - $200k), proven technology, built-in compliance and payment integrations, shared liquidity (if on a network).&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Cons:&lt;/strong&gt; Monthly licensing fees, revenue share (often 20-40% of GGR), limited customization, dependency on the provider.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Best For:&lt;/strong&gt; Most new operators, affiliates expanding into operations, and brands wanting to test the market quickly.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Revenue Models
&lt;/h4&gt;

&lt;p&gt;How does a poker room make money?&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Rake:&lt;/strong&gt; A small percentage (usually 2.5% to 5%) of the pot, capped at a certain amount.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Time Collection:&lt;/strong&gt; Charging a fixed fee every 30 minutes in high-stakes cash games.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Tournament Fees:&lt;/strong&gt; The entry fee includes a portion that goes to the prize pool and a portion kept as the "rake" or "fee."&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Affiliate Revenue:&lt;/strong&gt; Earning money from players brought in by affiliates (though this is a cost, not revenue).&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  Operational Costs
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Licensing Fees:&lt;/strong&gt; Obtaining a gaming license (e.g., Malta, Curacao, Isle of Man) can cost $50k to $500k+ annually.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Payment Processing:&lt;/strong&gt; Transaction fees (2-5%) and potential chargeback reserves.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Customer Support:&lt;/strong&gt; 24/7 support teams are mandatory.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Marketing:&lt;/strong&gt; The biggest cost. Acquiring a player (CPA) can range from $100 to $500+ depending on the market.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  5. Common Mistakes
&lt;/h3&gt;

&lt;p&gt;Even experienced teams make critical errors when entering the poker software space.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Underestimating Security:&lt;/strong&gt; A single breach of the wallet system or a successful bot attack can destroy a brand's reputation instantly. Security must be a priority from day one, not an afterthought.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Ignoring Liquidity:&lt;/strong&gt; A poker room with no players is useless. Launching without a strategy for player acquisition or without joining a network for shared liquidity is a common fatal error.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Poor Mobile Experience:&lt;/strong&gt; Over 70% of poker traffic is mobile. If your mobile app is clunky or slow, you will lose players to competitors.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Inadequate RNG Certification:&lt;/strong&gt; Using an uncertified RNG can lead to immediate license revocation and legal action.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Over-Engineering:&lt;/strong&gt; Trying to build the "perfect" custom engine before launching. It is better to launch a robust white-label solution and iterate later.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Regulatory Blindness:&lt;/strong&gt; Failing to understand the specific laws of the target market (e.g., KYC/AML requirements) can result in massive fines.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  6. Best Practices
&lt;/h3&gt;

&lt;p&gt;To succeed in the competitive poker software market, adhere to these industry standards:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Start with White-Label:&lt;/strong&gt; Unless you have millions in funding and a unique tech advantage, start with a white-label solution to get to market fast.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Focus on Player Experience (UX):&lt;/strong&gt; The interface should be intuitive. Complex tables with too many buttons confuse players.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Implement Robust KYC/AML:&lt;/strong&gt; Know Your Customer (KYC) and Anti-Money Laundering (AML) checks are mandatory. Automate these processes to avoid bottlenecks.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Use Multi-Brand Networks:&lt;/strong&gt; If possible, join a network that allows your brand to share players with other brands. This ensures tables fill up quickly.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Invest in Bonuses and Loyalty:&lt;/strong&gt; A well-structured VIP program and rakeback system are essential for retention.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;24/7 Support:&lt;/strong&gt; Have a dedicated team ready to handle disputes, technical issues, and payment queries instantly.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Data Analytics:&lt;/strong&gt; Use data to track player behavior, detect fraud, and optimize bonus offers.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  7. Real-World Example: The Launch of "PokerPro"
&lt;/h3&gt;

&lt;p&gt;Let's look at a hypothetical case study of "PokerPro," a new operator targeting the European market.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Challenge:&lt;/strong&gt;&lt;br&gt;
PokerPro wanted to launch a real-money poker room within 90 days with a budget of $200,000. They had a strong marketing team but no technical expertise.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Solution:&lt;/strong&gt;&lt;br&gt;
Instead of building from scratch, they opted for a &lt;strong&gt;White-Label solution&lt;/strong&gt; from a provider based in Curacao.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Implementation Steps:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Licensing:&lt;/strong&gt; They secured a sub-license under the Curacao eGaming authority ($15k setup).&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Platform Integration:&lt;/strong&gt; The provider supplied the game engine, wallet, and 24/7 support infrastructure.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Branding:&lt;/strong&gt; PokerPro customized the skin (logo, colors, UI) to match their brand identity.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Payment Integration:&lt;/strong&gt; The provider integrated their existing payment gateways (Visa, Crypto, Skrill).&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Marketing:&lt;/strong&gt; They launched an affiliate program offering 30% revenue share to drive traffic.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;The Outcome:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Launch:&lt;/strong&gt; Live in 8 weeks.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Liquidity:&lt;/strong&gt; Immediate access to the provider's network of 5,000+ daily players.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Cost:&lt;/strong&gt; $150k upfront + 25% of GGR (Gross Gaming Revenue) to the provider.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Result:&lt;/strong&gt; Within 6 months, PokerPro was generating $50k/month in net revenue. They used the profits to build their own custom features later.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Key Takeaway:&lt;/strong&gt; By not trying to "build" the software, they focused on what they did best: marketing and operations.&lt;/p&gt;

&lt;h3&gt;
  
  
  8. Comparison: Custom Build vs. White-Label
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Custom Build (From Scratch)&lt;/th&gt;
&lt;th&gt;White-Label / Turnkey&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Development Time&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;12 - 24 Months&lt;/td&gt;
&lt;td&gt;4 - 8 Weeks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Upfront Cost&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;$500,000 - $2,000,000+&lt;/td&gt;
&lt;td&gt;$50,000 - $200,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Ongoing Cost&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Maintenance, Server, Team&lt;/td&gt;
&lt;td&gt;Monthly Fee + Revenue Share&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Flexibility&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;100% Customizable&lt;/td&gt;
&lt;td&gt;Limited to Provider's Options&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Liquidity&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Must build own player pool&lt;/td&gt;
&lt;td&gt;Shared network liquidity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Risk&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;High (Technical/Market failure)&lt;/td&gt;
&lt;td&gt;Low (Proven tech)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Best For&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Large Enterprises, Unique IP&lt;/td&gt;
&lt;td&gt;Startups, Affiliates, Niche Brands&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  9. Future Trends
&lt;/h3&gt;

&lt;p&gt;The poker software landscape is evolving rapidly. Here are the trends shaping the future:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;AI and Personalization:&lt;/strong&gt; Using AI to tailor bonus offers, detect bot behavior in real-time, and provide personalized coaching to players.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Blockchain and Crypto:&lt;/strong&gt; Integration of cryptocurrencies for faster, anonymous (where legal) transactions. Some platforms are exploring "provably fair" systems using blockchain.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;VR and AR Poker:&lt;/strong&gt; Immersive poker rooms using Virtual Reality to replicate the physical casino experience.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Social Poker:&lt;/strong&gt; Hybrid models where players can play for fun with social features, then seamlessly switch to real-money modes.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Mobile-First Design:&lt;/strong&gt; As mobile usage surpasses desktop, platforms are optimizing specifically for small screens and touch interfaces.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Streamlined KYC:&lt;/strong&gt; Using biometric verification and AI to speed up the onboarding process while maintaining compliance.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  10. Conclusion
&lt;/h3&gt;

&lt;p&gt;There is no "easy" tutorial to build poker software that results in a successful, compliant, and profitable business. The complexity of game logic, security, financial compliance, and high-concurrency architecture makes it a formidable challenge.&lt;/p&gt;

&lt;p&gt;For most entrepreneurs, the path to success is not through writing code from scratch, but through &lt;strong&gt;strategic partnerships&lt;/strong&gt;. Leveraging white-label solutions allows you to focus on the business side: marketing, player acquisition, and brand building. If you do choose to build from scratch, be prepared for a multi-year, multi-million dollar journey with a high risk of technical and regulatory hurdles.&lt;/p&gt;

&lt;p&gt;Whether you build or buy, the key to success lies in &lt;strong&gt;trust&lt;/strong&gt;. Players need to trust that the game is fair, their money is safe, and their data is protected. By prioritizing security, compliance, and user experience, you can build a poker platform that stands the test of time.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>gamedev</category>
      <category>softwaredevelopment</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Building Poker Software: From Zero to Hero</title>
      <dc:creator>Stojan Kojo</dc:creator>
      <pubDate>Tue, 09 Jun 2026 16:11:01 +0000</pubDate>
      <link>https://dev.to/stojankojo/building-poker-software-from-zero-to-hero-3ad7</link>
      <guid>https://dev.to/stojankojo/building-poker-software-from-zero-to-hero-3ad7</guid>
      <description>&lt;p&gt;Building poker software from "zero to hero" requires transitioning from a simple game loop to a &lt;strong&gt;distributed, event-driven, real-time multiplayer architecture&lt;/strong&gt;. The journey involves mastering deterministic state machines, cryptographic RNG, low-latency networking, and rigorous security compliance.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Phase 1: The Core Engine (Determinism &amp;amp; Logic)
&lt;/h3&gt;

&lt;p&gt;The foundation is a &lt;strong&gt;pure, deterministic game engine&lt;/strong&gt;. No networking, no UI, just logic.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Architecture&lt;/strong&gt;: Use a &lt;strong&gt;Functional Core, Imperative Shell&lt;/strong&gt; pattern. The core logic (rules, hand evaluation, state transitions) must be pure functions with no side effects.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;State Machine&lt;/strong&gt;: Implement a &lt;strong&gt;Finite State Machine (FSM)&lt;/strong&gt; for betting rounds. Every state (Pre-flop, Flop, Turn, River) and transition (Check, Call, Raise) must be mathematically exhaustive.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Hand Evaluator&lt;/strong&gt;: Integrate a &lt;strong&gt;Lookup Table (LUT)&lt;/strong&gt; based evaluator (e.g., Cactus Kev) for $O(1)$ performance. Never use runtime logic for hand ranking.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;RNG&lt;/strong&gt;: Implement a &lt;strong&gt;Cryptographically Secure Pseudo-Random Number Generator (CSPRNG)&lt;/strong&gt; (e.g., &lt;code&gt;crypto.randomBytes&lt;/code&gt; in Node, &lt;code&gt;rand_chacha&lt;/code&gt; in Rust). &lt;em&gt;Crucial&lt;/em&gt;: The seed must be derived from high-entropy sources (OS entropy, hardware RNG).&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Testing&lt;/strong&gt;: Write &lt;strong&gt;Property-Based Tests&lt;/strong&gt; (e.g., using QuickCheck) to verify that for any sequence of actions, the final pot distribution is mathematically correct.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Phase 2: Real-Time Networking (The "Live" Layer)
&lt;/h3&gt;

&lt;p&gt;This phase introduces latency and concurrency challenges.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Protocol&lt;/strong&gt;: Use &lt;strong&gt;WebSockets&lt;/strong&gt; (raw &lt;code&gt;ws&lt;/code&gt; or &lt;code&gt;uWebSockets.js&lt;/code&gt; for C++ speed) or &lt;strong&gt;gRPC-Web&lt;/strong&gt; for low-latency bi-directional communication. Avoid HTTP polling.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Message Format&lt;/strong&gt;: Use &lt;strong&gt;Protocol Buffers (Protobuf)&lt;/strong&gt; or &lt;strong&gt;MessagePack&lt;/strong&gt; for binary serialization. JSON is too verbose and slow for high-frequency state updates.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Server Topology&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Game Servers&lt;/strong&gt;: Stateless, in-memory state machines. Each server handles ~500-2,000 concurrent tables depending on the language (Go/C++ &amp;gt; Node/Java).&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Lobby/Matchmaking&lt;/strong&gt;: A separate service that assigns players to Game Servers based on region, stakes, and skill.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;State Sync&lt;/strong&gt;: The server is the &lt;strong&gt;Source of Truth&lt;/strong&gt;. Clients send &lt;em&gt;intent&lt;/em&gt; (e.g., &lt;code&gt;Action: Raise(100)&lt;/code&gt;), not state updates. The server validates, updates state, and broadcasts the new &lt;code&gt;StateSnapshot&lt;/code&gt; to all clients in the room.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Latency Handling&lt;/strong&gt;: Implement &lt;strong&gt;Client-Side Prediction&lt;/strong&gt; for UI responsiveness (e.g., show the card flip immediately) but &lt;strong&gt;Server-Side Reconciliation&lt;/strong&gt; to correct any discrepancies.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Phase 3: Data Persistence &amp;amp; Event Sourcing
&lt;/h3&gt;

&lt;p&gt;Do not store the "current state" as the primary database record. Use &lt;strong&gt;Event Sourcing&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Pattern&lt;/strong&gt;: Every action (Deal, Bet, Fold, Showdown) is an immutable event appended to a log.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Storage&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Hot Path (Redis)&lt;/strong&gt;: Store the current &lt;code&gt;GameState&lt;/code&gt; and active &lt;code&gt;Event Log&lt;/code&gt; for fast access and replay. Use Redis Streams for high-throughput event logging.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Cold Path (PostgreSQL)&lt;/strong&gt;: Store finalized &lt;code&gt;HandHistories&lt;/code&gt;, &lt;code&gt;PlayerStats&lt;/code&gt;, and &lt;code&gt;Transactions&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Replayability&lt;/strong&gt;: If a server crashes, the new instance loads the last &lt;code&gt;Snapshot&lt;/code&gt; and replays the &lt;code&gt;Event Log&lt;/code&gt; from Redis to reconstruct the exact state. This is critical for dispute resolution.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Phase 4: Security, Compliance &amp;amp; Anti-Fraud
&lt;/h3&gt;

&lt;p&gt;This is where "social" poker becomes "real-money" poker.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;RNG Certification&lt;/strong&gt;: The RNG algorithm and its implementation must be audited by a third party (e.g., eCOGRA, GLI-11). You cannot just "use &lt;code&gt;Math.random()&lt;/code&gt;".&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Anti-Collusion&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Graph Analysis&lt;/strong&gt;: Run background jobs that analyze player interaction graphs. If Player A and B always fold when Player C raises, flag them.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;IP/Device Fingerprinting&lt;/strong&gt;: Detect multiple accounts from the same IP/MAC address.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Chip Transfer Monitoring&lt;/strong&gt;: Detect abnormal chip flow between accounts (e.g., "soft play" where players avoid raising against each other).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Wallet Integration&lt;/strong&gt;: Use a &lt;strong&gt;Double-Entry Ledger&lt;/strong&gt; system. Every chip movement is a debit/credit pair. Never trust the balance field; calculate it as &lt;code&gt;Sum(Credits) - Sum(Debits)&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  5. Phase 5: Scalability &amp;amp; Infrastructure
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Microservices&lt;/strong&gt;: Decouple the Lobby, Wallet, Game Logic, and Analytics.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Orchestration&lt;/strong&gt;: Use &lt;strong&gt;Kubernetes (K8s)&lt;/strong&gt; with &lt;strong&gt;Horizontal Pod Autoscaling (HPA)&lt;/strong&gt;. Scale Game Servers based on CPU/Memory usage or custom metrics (e.g., "Tables per Node").&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Sharding&lt;/strong&gt;: Shard the database by &lt;code&gt;TableID&lt;/code&gt; or &lt;code&gt;Region&lt;/code&gt; to distribute load.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Monitoring&lt;/strong&gt;: Implement &lt;strong&gt;Distributed Tracing&lt;/strong&gt; (Jaeger/Zipkin) to track a hand across services. Monitor &lt;strong&gt;P99 Latency&lt;/strong&gt; for WebSocket messages.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Architecture Diagram (High Level)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Client (React/Flutter)] 
       | (WebSocket/Protobuf)
       v
[API Gateway / Load Balancer]
       |
       +---&amp;gt; [Lobby Service] (Matchmaking, Player Profiles)
       |
       +---&amp;gt; [Game Server Cluster] (State Machines, FSM, RNG) &amp;lt;---+
       |                         |                                |
       |                         v                                |
       |                   [Redis Cluster] (State &amp;amp; Events)       |
       |                         |                                |
       +---&amp;gt; [Wallet Service] &amp;lt;---+ (Double-Entry Ledger)         |
       |                         |                                |
       +---&amp;gt; [Analytics Service] (Event Stream Processing)        |
       |                         |                                |
       +---&amp;gt; [Fraud Detection] (Background Job, Graph Analysis)   |
       |
       v
[PostgreSQL] (Hand Histories, Audit Logs)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Real-World Implementation Example
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Scenario&lt;/strong&gt;: A 6-max Hold'em table with 100k concurrent users.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Join&lt;/strong&gt;: Player connects to &lt;code&gt;Lobby Service&lt;/code&gt;, gets assigned to &lt;code&gt;GameServer-42&lt;/code&gt; via consistent hashing.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Hand Start&lt;/strong&gt;: &lt;code&gt;GameServer-42&lt;/code&gt; generates a seed, draws cards (CSPRNG), emits &lt;code&gt;HandStarted&lt;/code&gt; event.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Betting&lt;/strong&gt;: Player clicks "Raise". Client sends &lt;code&gt;Raise(100)&lt;/code&gt;. Server validates against FSM. If valid, updates state, emits &lt;code&gt;ActionApplied&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Side Pot&lt;/strong&gt;: If an all-in occurs, the server calculates side pots using the multi-pass algorithm and emits &lt;code&gt;SidePotCreated&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Showdown&lt;/strong&gt;: Server evaluates hands (LUT), distributes pots, emits &lt;code&gt;HandEnded&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Persistence&lt;/strong&gt;: Event log is flushed to Redis, then asynchronously to PostgreSQL.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Audit&lt;/strong&gt;: Fraud service consumes the event stream, updates player risk scores.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Trade-offs &amp;amp; Decisions
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Language&lt;/strong&gt;: &lt;strong&gt;Go&lt;/strong&gt; or &lt;strong&gt;Rust&lt;/strong&gt; for Game Servers (high concurrency, low GC pressure). &lt;strong&gt;Node.js&lt;/strong&gt; or &lt;strong&gt;Python&lt;/strong&gt; for Lobby/Analytics (rapid development, rich ecosystem).&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Database&lt;/strong&gt;: &lt;strong&gt;Redis&lt;/strong&gt; for speed (state), &lt;strong&gt;PostgreSQL&lt;/strong&gt; for integrity (wallets/histories). Avoid NoSQL for financial ledgers.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Consistency&lt;/strong&gt;: &lt;strong&gt;Strong Consistency&lt;/strong&gt; for wallet transactions (ACID). &lt;strong&gt;Eventual Consistency&lt;/strong&gt; for player stats/follower counts.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  FAQs
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Q1: Why not use a monolithic architecture for a poker site?&lt;/strong&gt;&lt;br&gt;
Monoliths are easier to start with but become unmanageable at scale. A crash in the "Lobby" service could take down the "Game" logic, freezing all active hands. Microservices allow independent scaling (e.g., adding more Game Servers during peak hours without touching the Wallet service) and isolation of failures.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q2: How do you handle the "All-In" logic if the server crashes mid-hand?&lt;/strong&gt;&lt;br&gt;
The server state is stored in Redis (in-memory) with persistence enabled (AOF/RDB). If the process crashes, the Kubernetes pod restarts. The new instance loads the last snapshot from Redis, replays the event log to reconstruct the exact state, and resumes the hand. This is why Event Sourcing is mandatory for real-money poker.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q3: Can I use a standard web server (Nginx/Apache) for the game logic?&lt;/strong&gt;&lt;br&gt;
No. Nginx is for routing and static content. Poker requires persistent, full-duplex connections (WebSockets) and in-memory state management. You need a dedicated application server (e.g., Go, Node.js, C++) that maintains the connection and state for each table.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q4: How is the RNG certified?&lt;/strong&gt;&lt;br&gt;
You cannot just "write good code." You must submit your RNG algorithm (and its implementation) to an independent testing lab (e.g., eCOGRA, GLI, BMM Testlabs). They run statistical tests (Chi-square, Kolmogorov-Smirnov) on billions of generated numbers to prove uniformity and unpredictability.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q5: What is the biggest security risk in poker software?&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Race Conditions&lt;/strong&gt; in the wallet system. If two requests try to deduct chips simultaneously, a poorly designed system might deduct twice. This is solved using &lt;strong&gt;Optimistic Locking&lt;/strong&gt; (version numbers) or &lt;strong&gt;Pessimistic Locking&lt;/strong&gt; (database row locks) in the wallet service, ensuring only one transaction can modify a balance at a time.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>node</category>
      <category>react</category>
    </item>
    <item>
      <title>What is the most efficient way to evaluate poker hands at scale?</title>
      <dc:creator>Stojan Kojo</dc:creator>
      <pubDate>Sun, 07 Jun 2026 08:21:35 +0000</pubDate>
      <link>https://dev.to/stojankojo/what-is-the-most-efficient-way-to-evaluate-poker-hands-at-scale-4dpn</link>
      <guid>https://dev.to/stojankojo/what-is-the-most-efficient-way-to-evaluate-poker-hands-at-scale-4dpn</guid>
      <description>&lt;p&gt;The most efficient way to evaluate poker hands at scale is &lt;strong&gt;not&lt;/strong&gt; to write logic that checks for straights and flushes at runtime. Instead, the industry standard for high-frequency trading (HFT) and real-money poker is &lt;strong&gt;pre-computed Lookup Tables (LUTs)&lt;/strong&gt; combined with &lt;strong&gt;bitwise operations&lt;/strong&gt; and &lt;strong&gt;SIMD (Single Instruction, Multiple Data)&lt;/strong&gt; vectorization.&lt;/p&gt;

&lt;p&gt;The goal is to reduce hand evaluation to a &lt;strong&gt;single CPU instruction&lt;/strong&gt; or a few array lookups, achieving nanosecond-level latency. This allows the engine to evaluate millions of hands per second for Monte Carlo simulations, AI training, or real-time fairness checks.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. The Core Architecture: Lookup Tables (LUTs)
&lt;/h3&gt;

&lt;p&gt;The fundamental insight is that there are a finite number of 5-card combinations from a 52-card deck: $\binom{52}{5} = 2,598,960$.&lt;br&gt;
For 7-card games (Hold'em/Omaha), it's $\binom{52}{7} = 133,784,560$.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Strategy:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Pre-computation&lt;/strong&gt;: Before the server starts, generate a massive array where every possible 5-card (or 7-card) combination maps to a unique integer "strength" value.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Runtime&lt;/strong&gt;: Convert the player's hand into a unique integer key (hash).&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Lookup&lt;/strong&gt;: Access the array at that index. The value returned is the hand's strength.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Memory Trade-off:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;5-Card LUT&lt;/strong&gt;: ~2.6 million entries. If each entry is a 4-byte integer, this is ~10 MB. Fits easily in L3 CPU cache.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;7-Card LUT&lt;/strong&gt;: ~133 million entries. ~534 MB. Fits in RAM, but may spill out of cache on large servers.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Optimization&lt;/strong&gt;: Most engines use a &lt;strong&gt;5-card LUT&lt;/strong&gt; and a &lt;strong&gt;7-to-5 reduction algorithm&lt;/strong&gt;. They generate all $\binom{7}{5} = 21$ combinations, look them up, and pick the best.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  2. The Bitwise Implementation (The "Cactus Kev" / "TwoPlusTwo" Method)
&lt;/h3&gt;

&lt;p&gt;The most famous high-performance implementation is the &lt;strong&gt;Cactus Kev&lt;/strong&gt; algorithm (popularized by &lt;code&gt;poker-eval&lt;/code&gt; in C/C++). It maps cards to prime numbers or bitmasks to perform arithmetic operations that instantly detect flushes and straights.&lt;/p&gt;
&lt;h4&gt;
  
  
  Data Representation
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Cards&lt;/strong&gt;: Represented as integers.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Ranks&lt;/strong&gt;: 2-14 (2-Ace).&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Suits&lt;/strong&gt;: 4 bits (1 for Spade, 2 for Heart, etc.).&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Bitmasks&lt;/strong&gt;: A 52-bit integer where the $i$-th bit is 1 if the $i$-th card is present.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;
  
  
  The Evaluation Function (Pseudocode)
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Pre-computed table: maps a 5-card hash to a strength score (0-7462)&lt;/span&gt;
&lt;span class="c1"&gt;// Lower score = better hand (or higher, depending on convention)&lt;/span&gt;
&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;lookup_table&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2598960&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt; 

&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;evaluate_hand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;hand_mask&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// 1. Check for Flush: Sum of suit bits. If any suit sum &amp;gt;= 4 (5 cards), it's a flush.&lt;/span&gt;
    &lt;span class="c1"&gt;// This is done via bitwise AND with suit masks (e.g., 0xF0F0F0F0F0F0F0F0)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;is_flush&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hand_mask&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;lookup_flush&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;hand_mask&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt; &lt;span class="c1"&gt;// Pre-computed flush strength&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// 2. Check for Straight: Use a "rank bitboard".&lt;/span&gt;
    &lt;span class="c1"&gt;// Shift bits to detect sequences of 5 consecutive bits.&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;is_straight&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hand_mask&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;lookup_straight&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;hand_mask&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// 3. Standard Evaluation (Pairs, Trips, etc.)&lt;/span&gt;
    &lt;span class="c1"&gt;// Use the "Cactus Kev" prime multiplication method.&lt;/span&gt;
    &lt;span class="c1"&gt;// Each rank is a prime: 2=2, 3=3, ..., A=41.&lt;/span&gt;
    &lt;span class="c1"&gt;// Multiply the primes of the 5 cards.&lt;/span&gt;
    &lt;span class="c1"&gt;// The product is unique for every hand type (e.g., A-A-A-K-K always yields X).&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;product&lt;/span&gt; &lt;span class="o"&gt;*=&lt;/span&gt; &lt;span class="n"&gt;RANK_PRIMES&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;get_rank&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hand_mask&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)];&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;lookup_table&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Why this is fast:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;No Loops&lt;/strong&gt;: The logic uses bitwise shifts and masks, which are single-cycle CPU instructions.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Cache Friendly&lt;/strong&gt;: The lookup table is small enough to stay in the CPU's L1/L2 cache.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Branch Prediction&lt;/strong&gt;: The &lt;code&gt;if&lt;/code&gt; statements are highly predictable (most hands are high-card, few are straights), minimizing pipeline stalls.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  3. Scaling to Millions of Hands: SIMD &amp;amp; Vectorization
&lt;/h3&gt;

&lt;p&gt;For Monte Carlo simulations (e.g., "What is my equity if I run this hand 100,000 times?"), single-threaded evaluation is too slow. You must use &lt;strong&gt;SIMD&lt;/strong&gt; (AVX2, AVX-512 on x86; NEON on ARM).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Approach:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Load 8 or 16 hands into a single 256-bit or 512-bit register.&lt;/li&gt;
&lt;li&gt;  Perform the bitwise operations (flush check, straight check) on all 8/16 hands &lt;strong&gt;simultaneously&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;  Use lookup tables that are vectorized (or use &lt;code&gt;shuffle&lt;/code&gt; instructions to map multiple keys to multiple values).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Library Example: &lt;code&gt;HandEvaluator&lt;/code&gt; in Rust/C++ with AVX2&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Conceptual SIMD evaluation (using a crate like `hand-eval-simd`)&lt;/span&gt;
&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;evaluate_batch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hands&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Hand&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;u32&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Load 8 hands into a 256-bit register&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;hand_vec&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;load_simd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hands&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Parallel Flush Check&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;flush_mask&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;check_flush_simd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hand_vec&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Parallel Straight Check&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;straight_mask&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;check_straight_simd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hand_vec&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Parallel Lookup (requires a specialized vectorized LUT or scatter/gather)&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;scores&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;vectorized_lookup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hand_vec&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;flush_mask&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;straight_mask&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nf"&gt;store_simd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;scores&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This can achieve &lt;strong&gt;100M+ hands per second&lt;/strong&gt; per core on modern CPUs.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Architecture for Production Systems
&lt;/h3&gt;

&lt;h4&gt;
  
  
  The "Evaluator Service" Microservice
&lt;/h4&gt;

&lt;p&gt;In a distributed architecture, do not embed the heavy evaluation logic in the main game loop if you need to run simulations. Offload it to a dedicated &lt;strong&gt;Evaluator Service&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Protocol&lt;/strong&gt;: gRPC or UDP (for low latency).&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Language&lt;/strong&gt;: C++ or Rust for the evaluator core (maximum performance).&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Interface&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;  &lt;code&gt;EvaluateHandsRequest&lt;/code&gt;: &lt;code&gt;{ hand_1: [c1, c2...], hand_2: [...] }&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;EvaluateHandsResponse&lt;/code&gt;: &lt;code&gt;{ score_1: 1234, score_2: 5678 }&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;  &lt;strong&gt;Scaling&lt;/strong&gt;: Run the service as a stateless pod in Kubernetes. Use Horizontal Pod Autoscaling (HPA) based on CPU utilization.&lt;/li&gt;

&lt;/ul&gt;

&lt;h4&gt;
  
  
  Database &amp;amp; Caching
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Redis&lt;/strong&gt;: Cache the results of common board textures (e.g., &lt;code&gt;A-K-Q-J-T&lt;/code&gt; on board) if the same board appears frequently in simulations.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;PostgreSQL&lt;/strong&gt;: Store the final hand history, including the &lt;code&gt;hand_strength_score&lt;/code&gt; and &lt;code&gt;winner_ids&lt;/code&gt;. Do not store the raw bitmasks unless needed for debugging.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  5. Security &amp;amp; Compliance
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Determinism&lt;/strong&gt;: The evaluator must be &lt;strong&gt;pure&lt;/strong&gt;. No random numbers, no external state. If the same 7 cards are passed in, the same score must be returned. This is non-negotiable for regulatory compliance.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Certification&lt;/strong&gt;: The LUT generation algorithm must be certified by an independent lab (e.g., eCOGRA, GLI). The source code for the LUT generator is often audited, not just the runtime binary.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Anti-Cheating&lt;/strong&gt;: Never expose the evaluator logic to the client. The client sends cards &lt;em&gt;only&lt;/em&gt; when allowed; the server evaluates.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  6. Real-World Implementation Example
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Scenario&lt;/strong&gt;: A 6-max No-Limit Hold'em table.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Pre-flop&lt;/strong&gt;: Player A (AA) vs Player B (KK).&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Simulation&lt;/strong&gt;: The engine runs 100,000 Monte Carlo simulations to determine equity for the "All-In" button.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Execution&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;  The "Simulator" service generates random boards (5 cards).&lt;/li&gt;
&lt;li&gt;  It calls the &lt;strong&gt;Evaluator&lt;/strong&gt; 200,000 times (2 hands x 100k simulations).&lt;/li&gt;
&lt;li&gt;  The Evaluator uses the &lt;strong&gt;Cactus Kev LUT&lt;/strong&gt; + &lt;strong&gt;AVX2&lt;/strong&gt; to process 8 hands in parallel.&lt;/li&gt;
&lt;li&gt;  Total time: ~50ms.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Result&lt;/strong&gt;: Player A has 82% equity. The server updates the UI.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Trade-offs&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Memory vs. Speed&lt;/strong&gt;: A full 7-card LUT is fast but memory-heavy (500MB+). A 5-card LUT + 21 combinations is slower (21x lookups) but uses only 10MB.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Solution&lt;/strong&gt;: Hybrid approach. Use a 5-card LUT. If the board is a flush/straight, use the optimized path. Otherwise, use the standard lookup.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  7. Code Snippet: The "Fast" Evaluator (Node.js with N-API)
&lt;/h3&gt;

&lt;p&gt;Since Node.js is single-threaded and slow for raw math, use &lt;strong&gt;N-API&lt;/strong&gt; to bind a C++ evaluator.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// evaluator.cc (C++ N-API Module)&lt;/span&gt;
&lt;span class="cp"&gt;#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;node_api.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;"poker_eval.h"&lt;/span&gt;&lt;span class="c1"&gt; // The C++ LUT engine&lt;/span&gt;&lt;span class="cp"&gt;
&lt;/span&gt;
 &lt;span class="n"&gt;napi_value&lt;/span&gt; &lt;span class="nf"&gt;EvaluateHand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;napi_env&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;napi_callback_info&lt;/span&gt; &lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="c1"&gt;// 1. Extract card array from JS&lt;/span&gt;
   &lt;span class="c1"&gt;// 2. Convert to bitboard (uint64_t)&lt;/span&gt;
   &lt;span class="c1"&gt;// 3. Call C++ evaluate(bitboard) -&amp;gt; int&lt;/span&gt;
   &lt;span class="c1"&gt;// 4. Return int to JS&lt;/span&gt;
   &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;napi_create_int32&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// evaluator.js (Node.js)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;evalModule&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./build/Release/evaluator.node&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getHandStrength&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;holeCards&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;communityCards&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Hole: [0, 1], Board: [2, 3, 4, 5, 6]&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;allCards&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;holeCards&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;communityCards&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;evalModule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;evaluate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;allCards&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  FAQs
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Q1: Why not just write a simple &lt;code&gt;if (isFlush) return ...&lt;/code&gt; function in Python/JavaScript?&lt;/strong&gt;&lt;br&gt;
Because a standard &lt;code&gt;if-else&lt;/code&gt; approach involves loops to check suits, loops to check ranks, and sorting. This is $O(N)$ or $O(N \log N)$ and involves many conditional branches. In a high-load environment (10k hands/sec), this creates a bottleneck and high CPU usage. A LUT approach is $O(1)$ and uses bitwise operations, which are orders of magnitude faster.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q2: How do you handle 7-card hands (Omaha/Hold'em) with a 5-card LUT?&lt;/strong&gt;&lt;br&gt;
You generate all $\binom{7}{5} = 21$ possible 5-card combinations from the 7 available cards. You run the 5-card LUT on each of the 21 combinations and pick the highest score. This is still extremely fast because the LUT lookup is instant. Advanced engines optimize this by pre-computing the "best 5 of 7" directly into a 7-card LUT, but the 21-lookup method is often sufficient and uses less memory.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q3: Can I use this for Web3/Blockchain poker?&lt;/strong&gt;&lt;br&gt;
Yes, but with a caveat. On-chain evaluation (e.g., on Ethereum) is too expensive for full LUTs due to gas costs. The standard pattern is &lt;strong&gt;Off-chain Evaluation with On-chain Verification&lt;/strong&gt;. The server (or a trusted oracle) evaluates the hand off-chain using the LUT, signs the result, and the smart contract verifies the signature. The contract never runs the evaluator.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q4: What is the "Cactus Kev" method?&lt;/strong&gt;&lt;br&gt;
It is a specific algorithm developed by Cactus Kev (Eric Persson) that maps each card rank to a prime number. The product of the primes of 5 cards is unique for every hand type (e.g., a Full House of Aces over Kings has a unique product). This allows the engine to determine the hand type and strength using a single multiplication and a lookup table, avoiding complex conditional logic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q5: How do I ensure the LUT is correct and not buggy?&lt;/strong&gt;&lt;br&gt;
You must &lt;strong&gt;verify&lt;/strong&gt; the LUT against a known reference implementation (like the PokerStars or WSOP logic) or a brute-force generator.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Generate all 2.6M 5-card hands.&lt;/li&gt;
&lt;li&gt; Evaluate them with a slow, correct (but inefficient) reference algorithm.&lt;/li&gt;
&lt;li&gt; Evaluate them with your fast LUT.&lt;/li&gt;
&lt;li&gt; Assert that the results match for every single hand.
This verification process is part of your CI/CD pipeline.&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How do you handle side pots correctly when multiple all-ins occur?</title>
      <dc:creator>Stojan Kojo</dc:creator>
      <pubDate>Sun, 07 Jun 2026 08:19:19 +0000</pubDate>
      <link>https://dev.to/stojankojo/how-do-you-handle-side-pots-correctly-when-multiple-all-ins-occur-52m3</link>
      <guid>https://dev.to/stojankojo/how-do-you-handle-side-pots-correctly-when-multiple-all-ins-occur-52m3</guid>
      <description>&lt;p&gt;Handling side pots correctly is one of the most critical logic challenges in a poker engine, as errors here directly result in financial loss and regulatory failure. The solution requires a &lt;strong&gt;deterministic, multi-pass evaluation algorithm&lt;/strong&gt; that decouples &lt;strong&gt;pot creation&lt;/strong&gt; from &lt;strong&gt;hand evaluation&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The core principle is: &lt;strong&gt;A player can only win a pot they contributed to.&lt;/strong&gt; The engine must logically segregate the total chips on the table into distinct "Pots" (Main, Side 1, Side 2, etc.) based on the &lt;em&gt;minimum&lt;/em&gt; effective stack at each stage of the betting.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. The Data Model: Pot Segregation
&lt;/h3&gt;

&lt;p&gt;Do not store a single &lt;code&gt;totalPot&lt;/code&gt; integer. Instead, maintain a list of &lt;code&gt;Pot&lt;/code&gt; objects, each tracking:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;code&gt;id&lt;/code&gt;: Unique identifier.&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;eligiblePlayers&lt;/code&gt;: A &lt;code&gt;Set&lt;/code&gt; of player IDs allowed to compete for this pot.&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;amount&lt;/code&gt;: Total chips in the pot.&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;contributions&lt;/code&gt;: A map of &lt;code&gt;{ playerId: amount }&lt;/code&gt; to track exactly who put in what (crucial for refunds if a player is eliminated early or for audit trails).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;State Transition Logic:&lt;/strong&gt;&lt;br&gt;
When a player goes All-In, the engine must immediately check if this All-In amount is less than the current &lt;code&gt;highestBet&lt;/code&gt;. If so, it triggers a &lt;strong&gt;Side Pot Creation&lt;/strong&gt; event.&lt;/p&gt;
&lt;h3&gt;
  
  
  2. The Algorithm: Multi-Pass Side Pot Calculation
&lt;/h3&gt;

&lt;p&gt;The most robust implementation uses a &lt;strong&gt;two-pass algorithm&lt;/strong&gt; (or a single pass with deferred distribution) to ensure mathematical accuracy.&lt;/p&gt;
&lt;h4&gt;
  
  
  Pass 1: Determine Pot Boundaries (The "Stack Normalization" Phase)
&lt;/h4&gt;

&lt;p&gt;Before dealing cards or evaluating hands, the engine must calculate exactly how many pots exist and their sizes.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Sort Players by Stack&lt;/strong&gt;: Get all active players and sort them by their &lt;code&gt;currentStack&lt;/code&gt; (chips remaining before the current betting round).&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Identify Effective Stacks&lt;/strong&gt;: Iterate through the sorted players to find "breakpoints."

&lt;ul&gt;
&lt;li&gt;  &lt;em&gt;Example&lt;/em&gt;: Player A (100), Player B (100), Player C (50), Player D (200).&lt;/li&gt;
&lt;li&gt;  The first "layer" is limited by the smallest stack (Player C: 50). Everyone contributes 50 to the &lt;strong&gt;Main Pot&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;  Player C is now "All-In" for the Main Pot layer.&lt;/li&gt;
&lt;li&gt;  Remaining stacks: A (50), B (50), D (150).&lt;/li&gt;
&lt;li&gt;  The next "layer" is limited by the next smallest (A/B: 50). A and B contribute 50 to &lt;strong&gt;Side Pot 1&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;  Player D contributes 50 to &lt;strong&gt;Side Pot 1&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;  Remaining stacks: D (100).&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Side Pot 2&lt;/strong&gt;: D contributes the remaining 100. Only D is eligible? No, D is the only one left, but usually, this implies D is raising against someone else.&lt;/li&gt;
&lt;li&gt;  &lt;em&gt;Correction&lt;/em&gt;: The logic is:

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Main Pot&lt;/strong&gt;: Everyone contributes &lt;code&gt;min(stack, current_bet)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Side Pot 1&lt;/strong&gt;: Players with &lt;code&gt;stack &amp;gt; min_bet&lt;/code&gt; contribute the difference up to the next lowest stack.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Side Pot N&lt;/strong&gt;: Repeat until all chips are allocated.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Pseudocode for Pot Calculation:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;calculateSidePots&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;players&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Player&lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="nx"&gt;currentHighestBet&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;Pot&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// 1. Filter active players (not folded)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;activePlayers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;players&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;active&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;all_in&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// 2. Sort by remaining stack (chips they can still put in)&lt;/span&gt;
  &lt;span class="nx"&gt;activePlayers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;remainingStack&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;remainingStack&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pots&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Pot&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;currentLayerLimit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;layerIndex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// 3. Iterate through stack layers&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;activePlayers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;player&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;activePlayers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;amountToContribute&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;remainingStack&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;currentHighestBet&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;currentLayerLimit&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// If the player has no more chips to contribute to this layer, skip&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;amountToContribute&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Create a new pot layer if this is a new stack tier&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;remainingStack&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;currentLayerLimit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;newPot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Pot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;layerIndex&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="c1"&gt;// Add all players who have enough stack for this layer&lt;/span&gt;
      &lt;span class="nx"&gt;activePlayers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;remainingStack&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nx"&gt;currentLayerLimit&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;amountToContribute&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;contribution&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;remainingStack&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;currentLayerLimit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;amountToContribute&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="nx"&gt;newPot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addContribution&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;contribution&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;remainingStack&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="nx"&gt;contribution&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="nx"&gt;currentLayerLimit&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;amountToContribute&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;pots&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newPot&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;pots&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Pass 2: Hand Evaluation &amp;amp; Distribution
&lt;/h4&gt;

&lt;p&gt;Once pots are defined, the engine evaluates hands &lt;strong&gt;independently for each pot&lt;/strong&gt;.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Iterate Pots&lt;/strong&gt;: Start with the Main Pot, then Side 1, etc.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Filter Eligible Players&lt;/strong&gt;: For &lt;code&gt;Pot X&lt;/code&gt;, only players in &lt;code&gt;Pot X.eligiblePlayers&lt;/code&gt; are considered.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Evaluate&lt;/strong&gt;: Run the &lt;code&gt;HandEvaluator&lt;/code&gt; for eligible players using their hole cards and community cards.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Distribute&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;  If multiple players tie for the best hand in &lt;code&gt;Pot X&lt;/code&gt;, split the pot.&lt;/li&gt;
&lt;li&gt;  If a player went all-in and lost, they are removed from the &lt;code&gt;eligiblePlayers&lt;/code&gt; set for &lt;em&gt;subsequent&lt;/em&gt; pots.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Critical Edge Case: The "Unmatched" All-In&lt;/strong&gt;&lt;br&gt;
If Player A (100) calls Player B (150) All-In, and Player B raises to 200:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Player A puts in 100.&lt;/li&gt;
&lt;li&gt;  Player B puts in 100 (matched) + 50 (side pot).&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Main Pot&lt;/strong&gt;: 200 (100 each).&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Side Pot&lt;/strong&gt;: 0 (Player A cannot contribute).&lt;/li&gt;
&lt;li&gt;  Player B wins the Side Pot automatically (no one else can contest it), but the engine must still run the logic to confirm the Main Pot winner.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  3. Architecture &amp;amp; Data Flow
&lt;/h3&gt;
&lt;h4&gt;
  
  
  The "Pot Manager" Microservice
&lt;/h4&gt;

&lt;p&gt;In a large-scale architecture, the pot logic should be isolated in a dedicated service or a pure function within the Game Engine.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Input&lt;/strong&gt;: &lt;code&gt;List&amp;lt;Player&amp;gt;&lt;/code&gt;, &lt;code&gt;CurrentHighestBet&lt;/code&gt;, &lt;code&gt;BettingHistory&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Output&lt;/strong&gt;: &lt;code&gt;List&amp;lt;Pot&amp;gt;&lt;/code&gt; with winners and amounts.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;State&lt;/strong&gt;: The &lt;code&gt;Pot&lt;/code&gt; objects are immutable snapshots stored in the event log.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;
  
  
  Database Schema (PostgreSQL)
&lt;/h4&gt;

&lt;p&gt;Store pot structures as JSONB or normalized tables for auditability.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;game_pots&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="n"&gt;UUID&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;game_id&lt;/span&gt; &lt;span class="n"&gt;UUID&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;pot_type&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;-- 'MAIN', 'SIDE_1', 'SIDE_2'&lt;/span&gt;
    &lt;span class="n"&gt;total_amount&lt;/span&gt; &lt;span class="nb"&gt;BIGINT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;eligible_players&lt;/span&gt; &lt;span class="n"&gt;JSONB&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;-- ["player_id_1", "player_id_2"]&lt;/span&gt;
    &lt;span class="n"&gt;contributions&lt;/span&gt; &lt;span class="n"&gt;JSONB&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;    &lt;span class="c1"&gt;-- {"player_id_1": 100, "player_id_2": 100}&lt;/span&gt;
    &lt;span class="n"&gt;winner_ids&lt;/span&gt; &lt;span class="n"&gt;JSONB&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                &lt;span class="c1"&gt;-- ["player_id_1"]&lt;/span&gt;
    &lt;span class="n"&gt;amount_per_winner&lt;/span&gt; &lt;span class="n"&gt;JSONB&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;         &lt;span class="c1"&gt;-- {"player_id_1": 200}&lt;/span&gt;
    &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Scalability &amp;amp; Performance
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Algorithmic Complexity&lt;/strong&gt;: The sorting and layering algorithm is $O(N \log N)$ where $N$ is the number of players. Since $N \le 10$ (usually 9 or 10 max), this is effectively $O(1)$ and executes in microseconds.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Concurrency&lt;/strong&gt;: Pot calculation is a read-heavy operation (triggered at showdown) but must be consistent. Since it's a pure function of the game state, it can be executed on a separate worker thread without locking the main game loop.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Memory&lt;/strong&gt;: Do not create new objects for every calculation if possible. Reuse object pools for &lt;code&gt;Pot&lt;/code&gt; and &lt;code&gt;Contribution&lt;/code&gt; objects to reduce GC pressure in high-frequency environments (e.g., 100k hands/hour).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  5. Security &amp;amp; Compliance
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Audit Trail&lt;/strong&gt;: Every side pot creation must be logged as an event: &lt;code&gt;SidePotCreated(potId, amount, eligiblePlayers)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Determinism&lt;/strong&gt;: The algorithm must produce the exact same result on any server. No floating-point math; use integer arithmetic (cents/smallest currency unit) exclusively.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Refund Logic&lt;/strong&gt;: If a player folds &lt;em&gt;after&lt;/em&gt; a side pot is created but &lt;em&gt;before&lt;/em&gt; the showdown, they are simply removed from the &lt;code&gt;eligiblePlayers&lt;/code&gt; set. Their contribution remains in the pot. If a player goes all-in and folds &lt;em&gt;before&lt;/em&gt; the showdown (impossible in poker, but hypothetically), the logic must handle refunds correctly. (In poker, once all-in, you are in the pot regardless of folding).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Real-World Example: Three-Way All-In
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Player A&lt;/strong&gt;: 500 chips&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Player B&lt;/strong&gt;: 300 chips&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Player C&lt;/strong&gt;: 150 chips&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Current Bet&lt;/strong&gt;: 100 (everyone calls 100).&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Action&lt;/strong&gt;:

&lt;ol&gt;
&lt;li&gt; C goes All-In (150).&lt;/li&gt;
&lt;li&gt; A and B call (150 each).&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Main Pot&lt;/strong&gt;: 450 (150 each). Eligible: A, B, C.&lt;/li&gt;
&lt;li&gt; Remaining: A (350), B (150).&lt;/li&gt;
&lt;li&gt; B goes All-In (150 more).&lt;/li&gt;
&lt;li&gt; A calls (150).&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Side Pot 1&lt;/strong&gt;: 300 (150 from A, 150 from B). Eligible: A, B. (C is ineligible).&lt;/li&gt;
&lt;li&gt; Remaining: A (200).&lt;/li&gt;
&lt;li&gt; A goes All-In (200).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Side Pot 2&lt;/strong&gt;: 200 (200 from A). Eligible: A only? No, A is the only one left to contribute, but B and C are already all-in.&lt;/li&gt;
&lt;li&gt;  &lt;em&gt;Correction&lt;/em&gt;: If A has 200 left and B/C are all-in, A cannot raise against them. A just calls.&lt;/li&gt;
&lt;li&gt;  &lt;em&gt;Scenario Change&lt;/em&gt;: A raises to 500. B calls 300 (all in). C calls 150 (all in).&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Main&lt;/strong&gt;: 450 (150 each).&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Side 1&lt;/strong&gt;: 300 (150 from A, 150 from B).&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Side 2&lt;/strong&gt;: 200 (200 from A).&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Winners&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;  Main Pot: Best hand among A, B, C.&lt;/li&gt;
&lt;li&gt;  Side 1: Best hand among A, B.&lt;/li&gt;
&lt;li&gt;  Side 2: A (automatic win, no one else eligible).&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ol&gt;

&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  Implementation Checklist
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Normalize Stacks&lt;/strong&gt;: Always work with remaining stacks, not total bets.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Layering&lt;/strong&gt;: Create pots from smallest stack to largest.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Eligibility&lt;/strong&gt;: Strictly enforce that a player cannot win a pot they didn't contribute to.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Tie-Breaking&lt;/strong&gt;: Handle split pots within each layer independently.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Logging&lt;/strong&gt;: Record every pot distribution event.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  FAQs
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Q1: Can a player win a side pot if they are the only one who contributed to it?&lt;/strong&gt;&lt;br&gt;
Yes. If Player A raises, Player B folds, and Player C goes all-in for less than the raise, Player A and C play for the Main Pot. If Player A has enough chips to raise again and Player B is out, any further chips A puts in form a "Side Pot" where A is the only eligible player. A wins this pot automatically. The engine must still create the pot object for audit purposes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q2: What happens if two players tie for the best hand in a side pot?&lt;/strong&gt;&lt;br&gt;
The side pot is split equally between the tied players. Any odd chip (if the amount is odd) is typically awarded to the player with the highest card in the hand (or by position rules defined in the specific game variant), or simply given to the player to the left of the dealer button if tie-breakers aren't specified. The engine must implement a "split pot" function that handles integer division and remainder distribution.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q3: How does the engine handle "All-In" players who fold?&lt;/strong&gt;&lt;br&gt;
In poker, a player who goes All-In &lt;strong&gt;cannot fold&lt;/strong&gt;. They remain in the hand until the showdown for any pot they contributed to. The FSM state machine must prevent a "Fold" action for any player with &lt;code&gt;status == 'all_in'&lt;/code&gt;. The only way they leave the active betting is if they lose the pot or the hand ends.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q4: Is it possible to have a side pot with only one player?&lt;/strong&gt;&lt;br&gt;
Yes, but only if that player is the &lt;em&gt;only&lt;/em&gt; one eligible. This usually happens if one player raises, everyone else folds, and the raiser has chips left over (which is impossible in a standard hand unless it's a specific tournament rule or a multi-way all-in scenario where the side pot is created by the remaining stack of the aggressor against no one else). In a standard 3-way all-in, Side Pot 2 might only have Player A if B and C are all-in and A raises further, and B/C cannot call. Wait, if B and C are all-in, they can't call. So A's extra chips go into a pot where only A is eligible. A wins it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q5: How do you handle the "Dead Button" or special tournament rules with side pots?&lt;/strong&gt;&lt;br&gt;
The side pot logic is independent of button position. The button determines the &lt;em&gt;order of action&lt;/em&gt;, but the side pot calculation is purely mathematical based on stack sizes. The "Dead Button" (where a player sits out but chips remain) is handled by ensuring the &lt;code&gt;eligiblePlayers&lt;/code&gt; set only includes players with &lt;code&gt;status != 'folded'&lt;/code&gt; and &lt;code&gt;status != 'sitting_out'&lt;/code&gt; (if sitting out means they can't win). The engine must respect the specific tournament rules regarding "sitting out" players in the &lt;code&gt;eligiblePlayers&lt;/code&gt; filter.&lt;/p&gt;

</description>
      <category>poker</category>
      <category>programming</category>
      <category>aws</category>
      <category>ai</category>
    </item>
    <item>
      <title>How should betting rounds be modeled using finite state machines?</title>
      <dc:creator>Stojan Kojo</dc:creator>
      <pubDate>Sun, 07 Jun 2026 08:13:44 +0000</pubDate>
      <link>https://dev.to/stojankojo/how-should-betting-rounds-be-modeled-using-finite-state-machines-3nj8</link>
      <guid>https://dev.to/stojankojo/how-should-betting-rounds-be-modeled-using-finite-state-machines-3nj8</guid>
      <description>&lt;p&gt;Modeling betting rounds using a &lt;strong&gt;Finite State Machine (FSM)&lt;/strong&gt; is the industry standard for ensuring deterministic, bug-free poker logic. The core concept is to treat every possible game situation as a distinct &lt;strong&gt;State&lt;/strong&gt; and every player action as a &lt;strong&gt;Transition&lt;/strong&gt; that moves the system to a new state. This eliminates "if-else" hell and guarantees that invalid actions (like raising before the flop in a specific variant) are mathematically impossible to execute.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. The Core FSM Architecture
&lt;/h3&gt;

&lt;p&gt;In a poker engine, the betting round is not a loop; it is a state machine where the &lt;strong&gt;current state&lt;/strong&gt; dictates &lt;strong&gt;valid transitions&lt;/strong&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  State Definition
&lt;/h4&gt;

&lt;p&gt;The state must capture the &lt;em&gt;exact&lt;/em&gt; context of the round, not just the current pot size. A robust state tuple looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;BettingState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;street&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;preflop&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;flop&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;turn&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;river&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;currentActorId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;highestBet&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;      &lt;span class="c1"&gt;// The current best bet in the round&lt;/span&gt;
  &lt;span class="nl"&gt;minRaise&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;        &lt;span class="c1"&gt;// Minimum legal raise amount&lt;/span&gt;
  &lt;span class="nl"&gt;activePlayers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Set&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Players still in the hand&lt;/span&gt;
  &lt;span class="nl"&gt;lastAggressorId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Who made the last raise?&lt;/span&gt;
  &lt;span class="nl"&gt;actionCount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;     &lt;span class="c1"&gt;// Used to detect round completion (everyone acted)&lt;/span&gt;
  &lt;span class="nl"&gt;roundStatus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;active&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;completed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;all_in&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Transition Logic
&lt;/h4&gt;

&lt;p&gt;Transitions are triggered by &lt;code&gt;PlayerAction&lt;/code&gt; events. The FSM validates the action against the current state before applying it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Valid Transitions (Example: No-Limit Hold'em):&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Idle/Start&lt;/strong&gt;: Transition to &lt;code&gt;WaitingForBlinds&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Blind Posting&lt;/strong&gt;: Transition to &lt;code&gt;PreFlopActive&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Check&lt;/strong&gt;: Valid only if &lt;code&gt;highestBet == 0&lt;/code&gt;. Moves actor to next player.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Call&lt;/strong&gt;: Valid if &lt;code&gt;betAmount == highestBet&lt;/code&gt;. Moves actor to next player.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Raise&lt;/strong&gt;: Valid if &lt;code&gt;betAmount &amp;gt; highestBet&lt;/code&gt; and &lt;code&gt;betAmount &amp;gt;= minRaise&lt;/code&gt;. Resets the "round completion" counter. Moves actor to next player.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Fold&lt;/strong&gt;: Removes player from &lt;code&gt;activePlayers&lt;/code&gt;. If only 1 player remains, transition to &lt;code&gt;Showdown&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;All-In&lt;/strong&gt;: Special transition. Player remains active but cannot act further. If &lt;code&gt;highestBet&lt;/code&gt; remains unmet, the round continues; otherwise, it closes.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  2. Implementation Details: The State Machine Pattern
&lt;/h3&gt;

&lt;p&gt;Do not use simple boolean flags (e.g., &lt;code&gt;isRoundOver&lt;/code&gt;). Use a formal State Machine library or a strict class structure.&lt;/p&gt;

&lt;h4&gt;
  
  
  The State Machine Class (TypeScript Example)
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;enum&lt;/span&gt; &lt;span class="nx"&gt;BettingRoundState&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;OPENING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;ACTIVE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;ALL_IN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;COMPLETED&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BettingRoundFSM&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;BettingRoundState&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;BettingStateContext&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;BettingStateContext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;BettingRoundState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;OPENING&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// The core entry point for all actions&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;processAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Action&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;validTransitions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getValidTransitions&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;validTransitions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;InvalidActionError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Action &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; invalid in state &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Execute state transition logic&lt;/span&gt;
    &lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nx"&gt;BettingRoundState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;OPENING&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;handleOpening&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nx"&gt;BettingRoundState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ACTIVE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;handleActive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nx"&gt;BettingRoundState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ALL_IN&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;handleAllIn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Check for round termination&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isRoundComplete&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;BettingRoundState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;COMPLETED&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;triggerNextStreet&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nf"&gt;handleActive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Action&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Update highest bet&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;amount&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;highestBet&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;highestBet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lastAggressorId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;playerId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roundStatus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;reset&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Reset counter, everyone must act again&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Handle player elimination&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;FOLD&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;activePlayers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;playerId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Move turn to next active player&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentActorId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getNextActivePlayer&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nf"&gt;isRoundComplete&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Logic: Everyone active has matched the highest bet&lt;/span&gt;
    &lt;span class="c1"&gt;// AND the last aggressor has acted (or no one raised)&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;allMatched&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;activePlayers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;every&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; 
      &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentBet&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;highestBet&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isAllIn&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;everyoneActed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;actionCount&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;activePlayers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;allMatched&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lastAggressorId&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lastAggressorId&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentActorId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Handling Edge Cases &amp;amp; Complex Logic
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Side Pots &amp;amp; All-Ins
&lt;/h4&gt;

&lt;p&gt;The FSM must handle "All-In" as a special state modifier, not a standard action.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;State Split&lt;/strong&gt;: When a player goes all-in, the FSM creates a &lt;strong&gt;Side Pot&lt;/strong&gt; context.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Transition&lt;/strong&gt;: The player is removed from the "Active Betting" set but remains in the "Showdown Eligible" set.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Logic&lt;/strong&gt;: The main round continues with remaining players. If the all-in player was the aggressor, the round only ends when everyone else has either folded or called the full amount (if possible).&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Heads-Up vs. Multi-Player
&lt;/h4&gt;

&lt;p&gt;The "Round Complete" condition differs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Multi-Player&lt;/strong&gt;: All active players must have acted, and no raises are pending.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Heads-Up&lt;/strong&gt;: The Big Blind (or aggressor) gets the last action. The state machine must track &lt;code&gt;lastAggressorId&lt;/code&gt; specifically to ensure the correct player gets the final decision.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Rebuy &amp;amp; Add-On
&lt;/h4&gt;

&lt;p&gt;In tournaments, the FSM must handle "Rebuy" events during specific windows. This is a &lt;strong&gt;side-transition&lt;/strong&gt; that doesn't affect the current betting round but updates the player's stack in the context object.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Data Flow &amp;amp; Architecture Integration
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Event Sourcing
&lt;/h4&gt;

&lt;p&gt;Every state transition should be an immutable event stored in an append-only log.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Event&lt;/strong&gt;: &lt;code&gt;BettingRoundStarted&lt;/code&gt;, &lt;code&gt;PlayerChecked&lt;/code&gt;, &lt;code&gt;PlayerRaised&lt;/code&gt;, &lt;code&gt;RoundCompleted&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Replay&lt;/strong&gt;: If the server crashes, you can reconstruct the exact state by replaying these events from the last checkpoint. This is critical for dispute resolution and "Game History" features.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Client-Server Sync
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Server&lt;/strong&gt;: The FSM is the source of truth.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Client&lt;/strong&gt;: Receives &lt;code&gt;StateSnapshot&lt;/code&gt; after every transition.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Protocol&lt;/strong&gt;:

&lt;ol&gt;
&lt;li&gt; Client sends &lt;code&gt;Raise(amount)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt; Server validates against FSM.&lt;/li&gt;
&lt;li&gt; Server updates state, emits &lt;code&gt;StateUpdate&lt;/code&gt; with new &lt;code&gt;highestBet&lt;/code&gt;, &lt;code&gt;currentActorId&lt;/code&gt;, and &lt;code&gt;potSize&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt; Client UI animates to the new state.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  5. Scalability &amp;amp; Performance
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Memory&lt;/strong&gt;: Keep the FSM state in memory (Redis or in-process memory for Node.js/Go). Do not query the database for every action.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Concurrency&lt;/strong&gt;: Use &lt;strong&gt;Optimistic Locking&lt;/strong&gt; or &lt;strong&gt;Distributed Locks&lt;/strong&gt; (Redis &lt;code&gt;SETNX&lt;/code&gt;) if the game server is sharded. Only one thread should process actions for a specific &lt;code&gt;table_id&lt;/code&gt; at a time to prevent race conditions (e.g., two players raising simultaneously).&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Latency&lt;/strong&gt;: The FSM logic is $O(1)$ or $O(N)$ where $N$ is the number of players. It must execute in &amp;lt; 5ms to maintain real-time feel.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  6. Security Considerations
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Determinism&lt;/strong&gt;: The FSM must be purely functional regarding logic. No random numbers or external API calls during the transition. This ensures that if two servers run the same input, they produce the same output (crucial for backup servers).&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Input Validation&lt;/strong&gt;: The FSM acts as the primary firewall. If a client sends a "Raise" when the state is &lt;code&gt;Folded&lt;/code&gt;, the FSM rejects it immediately.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Audit Trail&lt;/strong&gt;: Every state transition is logged with a timestamp and signature. This is required for regulatory compliance (e.g., UKGC, MGA).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Real-World Example: Omaha vs. Hold'em
&lt;/h3&gt;

&lt;p&gt;The FSM structure is identical, but the &lt;strong&gt;Context&lt;/strong&gt; differs.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Hold'em&lt;/strong&gt;: &lt;code&gt;maxHoleCards = 2&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Omaha&lt;/strong&gt;: &lt;code&gt;maxHoleCards = 4&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Validation&lt;/strong&gt;: The &lt;code&gt;validateAction&lt;/code&gt; function checks &lt;code&gt;context.variantRules&lt;/code&gt; to ensure the player isn't using 3 cards from their hand to make a hand (which is illegal in Omaha). The FSM state machine doesn't care &lt;em&gt;which&lt;/em&gt; variant it is; it just enforces the rules defined in the injected &lt;code&gt;VariantStrategy&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  FAQs
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Q1: How does the FSM handle a player timing out?&lt;/strong&gt;&lt;br&gt;
The FSM includes a &lt;code&gt;Timer&lt;/code&gt; state or a timeout check in the &lt;code&gt;processAction&lt;/code&gt; loop. If the &lt;code&gt;currentActorId&lt;/code&gt; does not act within $X$ seconds, a &lt;code&gt;TimeoutAction&lt;/code&gt; event is triggered, automatically transitioning the state to &lt;code&gt;Fold&lt;/code&gt; (or &lt;code&gt;Check&lt;/code&gt; in some variations) and moving to the next player. This is usually handled by a background job or a Redis key expiration listener.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q2: What happens if the server crashes mid-betting round?&lt;/strong&gt;&lt;br&gt;
Because the FSM is deterministic and all actions are logged as events (Event Sourcing), a backup server can restart, load the last snapshot of the game state, and replay the event log up to the crash point. The system regenerates the exact same state, ensuring no money or hands are lost.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q3: Can the same FSM be used for Tournaments and Cash Games?&lt;/strong&gt;&lt;br&gt;
Yes, but the &lt;strong&gt;Context&lt;/strong&gt; object differs. In Tournaments, the context includes &lt;code&gt;blindLevel&lt;/code&gt;, &lt;code&gt;ante&lt;/code&gt;, and &lt;code&gt;playerStack&lt;/code&gt; (which affects rebuys). In Cash Games, the context includes &lt;code&gt;tableLimits&lt;/code&gt; (min/max buy-in) and &lt;code&gt;cashOut&lt;/code&gt; logic. The FSM logic (transitions) remains the same; only the rules injected into the context change.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q4: How do you prevent race conditions when two players click "Raise" at the exact same millisecond?&lt;/strong&gt;&lt;br&gt;
The server processes requests sequentially per &lt;code&gt;table_id&lt;/code&gt;. If using a distributed system, a distributed lock (e.g., Redis Redlock) is acquired for the table before processing any action. If a second request arrives while the lock is held, it is queued. The lock ensures the FSM state is updated atomically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q5: How is the "Last Aggressor" tracked for Heads-Up play?&lt;/strong&gt;&lt;br&gt;
The FSM explicitly stores &lt;code&gt;lastAggressorId&lt;/code&gt; in the state. In a standard round, if Player A raises, &lt;code&gt;lastAggressorId&lt;/code&gt; becomes A. The round only ends when Player B calls (or folds) AND Player A has had a chance to act again (if B raised). In Heads-Up, the logic is simplified: the round ends when the Big Blind (or current aggressor) gets the final action after all raises are matched. The FSM tracks this via the &lt;code&gt;actionCount&lt;/code&gt; and &lt;code&gt;lastAggressorId&lt;/code&gt; comparison.&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>poker</category>
      <category>pokergamedevelopmemt</category>
      <category>pokerai</category>
    </item>
    <item>
      <title>How do you design a poker game engine that supports multiple poker variants without code duplication?</title>
      <dc:creator>Stojan Kojo</dc:creator>
      <pubDate>Sun, 07 Jun 2026 08:08:15 +0000</pubDate>
      <link>https://dev.to/stojankojo/how-do-you-design-a-poker-game-engine-that-supports-multiple-poker-variants-without-code-5b99</link>
      <guid>https://dev.to/stojankojo/how-do-you-design-a-poker-game-engine-that-supports-multiple-poker-variants-without-code-5b99</guid>
      <description>&lt;p&gt;To design a poker game engine that supports multiple variants (Texas Hold'em, Omaha, Short Deck, OFC) without code duplication, you must adopt a &lt;strong&gt;Component-Based Architecture&lt;/strong&gt; driven by a &lt;strong&gt;Strategy Pattern&lt;/strong&gt; for variant-specific logic. The core engine handles universal game flow (deal, betting rounds, pot management, side pots), while variant-specific rules are injected as pluggable modules.&lt;/p&gt;

&lt;h3&gt;
  
  
  Core Architecture: The "Universal Engine" Pattern
&lt;/h3&gt;

&lt;p&gt;The fundamental challenge is separating &lt;em&gt;game state&lt;/em&gt; from &lt;em&gt;game rules&lt;/em&gt;. The engine should treat a "Hand" as a generic sequence of actions and cards, while a "Variant Controller" interprets those actions based on specific rules.&lt;/p&gt;

&lt;h4&gt;
  
  
  1. The Domain Model (Shared Layer)
&lt;/h4&gt;

&lt;p&gt;This layer contains entities common to all poker variants. These are immutable data structures or state machines that never change regardless of the variant.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;&lt;code&gt;Card&lt;/code&gt;&lt;/strong&gt;: Rank, Suit.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;&lt;code&gt;Player&lt;/code&gt;&lt;/strong&gt;: ID, Stack, Seat position, CurrentBet.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;&lt;code&gt;Pot&lt;/code&gt;&lt;/strong&gt;: Main pot, side pots, contribution map.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;&lt;code&gt;GameRound&lt;/code&gt;&lt;/strong&gt;: Pre-flop, Flop, Turn, River, Showdown.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;&lt;code&gt;Action&lt;/code&gt;&lt;/strong&gt;: Type (Check, Call, Raise, Fold, All-in), Amount, Timestamp.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  2. The Strategy Pattern (Variant Abstraction)
&lt;/h4&gt;

&lt;p&gt;Define abstract interfaces that every poker variant must implement. The engine calls these interfaces without knowing the specific variant.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Interface definition&lt;/span&gt;
&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;IPokerVariant&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;getInitialDealCount&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;getCommunityCardDistribution&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt; &lt;span class="c1"&gt;// e.g., [3, 1, 1] for Hold'em&lt;/span&gt;
  &lt;span class="nf"&gt;validateHand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;playerCards&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Card&lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="nx"&gt;communityCards&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Card&lt;/span&gt;&lt;span class="p"&gt;[]):&lt;/span&gt; &lt;span class="nx"&gt;HandEvaluation&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;getValidActions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;gameState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;GameState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;player&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Player&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;Action&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="nf"&gt;calculateWinners&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pot&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Pot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;hands&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;HandEvaluation&lt;/span&gt;&lt;span class="p"&gt;[]):&lt;/span&gt; &lt;span class="nx"&gt;Winner&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  3. Implementation Workflow
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;A. State Machine &amp;amp; Event Sourcing&lt;/strong&gt;&lt;br&gt;
The core engine runs a deterministic state machine. It does not contain &lt;code&gt;if (variant == 'Holdem')&lt;/code&gt; logic. Instead, it emits events based on the current state.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Initialization&lt;/strong&gt;: Engine loads &lt;code&gt;IPokerVariant&lt;/code&gt; implementation (e.g., &lt;code&gt;HoldemVariant&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Deal Phase&lt;/strong&gt;: Engine calls &lt;code&gt;variant.getInitialDealCount()&lt;/code&gt;. It deals that many cards to players.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Community Dealing&lt;/strong&gt;: Engine iterates through &lt;code&gt;variant.getCommunityCardDistribution()&lt;/code&gt;. It deals 3 cards (Flop), pauses, then 1 (Turn), etc.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Betting Logic&lt;/strong&gt;: The engine manages the "Acting Player" and "Bet Amount" but delegates &lt;em&gt;validity&lt;/em&gt; to the variant.

&lt;ul&gt;
&lt;li&gt;  &lt;em&gt;Engine&lt;/em&gt;: "Player X wants to raise."&lt;/li&gt;
&lt;li&gt;  &lt;em&gt;Variant&lt;/em&gt;: &lt;code&gt;validateActions()&lt;/code&gt; checks if the raise meets the minimum (e.g., 2x big blind in Hold'em vs. specific rules in OFC).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Showdown&lt;/strong&gt;: Engine calls &lt;code&gt;variant.calculateWinners()&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;B. Hand Evaluation Engine&lt;/strong&gt;&lt;br&gt;
This is the most complex part. Do not hardcode logic for every variant.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Hold'em/Omaha&lt;/strong&gt;: Use a pre-calculated lookup table or a SIMD-optimized evaluator (e.g., &lt;code&gt;replay-js&lt;/code&gt; or &lt;code&gt;poker-eval&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;OFC (Open Face Chinese)&lt;/strong&gt;: The logic is completely different (scoring rows). This is treated as a separate evaluator module.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Short Deck&lt;/strong&gt;: Adjust the rank mapping (remove 2-5) and flush/straight rules before passing to the standard evaluator.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Pseudocode for the Engine Loop:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PokerEngine&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;variant&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;IPokerVariant&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;roomConfig&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;variant&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;variant&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;GameState&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;processAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;playerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Action&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// 1. Validate generic constraints (time, turn order)&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isTurn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;playerId&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Not your turn&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// 2. Delegate variant-specific validation&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;validActions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;variant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getValidActions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getPlayer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;playerId&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;validActions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Invalid move for this variant&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// 3. Apply action to state (Universal logic)&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;applyAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;action_applied&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// 4. Check for round completion&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isBettingRoundComplete&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dealNextStreet&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;dealNextStreet&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;communityCount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;variant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getCommunityCardDistribution&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;streetIndex&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;newCards&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;deck&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;communityCount&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addCommunityCards&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newCards&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Variant might have special rules here (e.g., burn card)&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;variant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;needsBurnCard&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;deck&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;discard&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resetBettingRound&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;resolveShowdown&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hands&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;players&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;player&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;evaluation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;variant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;evaluate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cards&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;communityCards&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}));&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;winners&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;variant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;calculateWinners&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;hands&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;distributePot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;winners&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Technical Deep Dive: Avoiding Duplication
&lt;/h3&gt;

&lt;h4&gt;
  
  
  1. Rule Configuration via JSON/Schema
&lt;/h4&gt;

&lt;p&gt;Instead of hardcoding logic, define variant rules in a schema.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Deck Composition&lt;/strong&gt;: &lt;code&gt;["A", "K", "Q", ...]&lt;/code&gt; (Standard) vs &lt;code&gt;["A", "K", "Q", "J", "T", "9", "8", "7", "6", "A"]&lt;/code&gt; (Short Deck).&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Hand Rankings&lt;/strong&gt;: Weighted scores for pairs, straights, etc.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Betting Structures&lt;/strong&gt;: No-limit, Pot-limit, Fixed-limit logic.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This allows you to add a new variant (e.g., "6+ Hold'em") by loading a config file and a small evaluator override, rather than rewriting the engine.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. The Evaluator Pattern
&lt;/h4&gt;

&lt;p&gt;For high-performance hand evaluation, use a &lt;strong&gt;Lookup Table&lt;/strong&gt; approach.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Generate all possible hand combinations for the specific variant.&lt;/li&gt;
&lt;li&gt;  Hash the 7 cards (2 hole + 5 board) into an integer.&lt;/li&gt;
&lt;li&gt;  Look up the hand strength in a pre-computed array.&lt;/li&gt;
&lt;li&gt;  &lt;em&gt;Optimization&lt;/em&gt;: For variants like OFC, the evaluation is not just "best 5 cards" but a scoring algorithm. Implement this as a separate service injected into the engine.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  3. Networking &amp;amp; Real-Time Sync
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Protocol&lt;/strong&gt;: Use WebSockets (Socket.IO or raw &lt;code&gt;ws&lt;/code&gt; in Node.js) or gRPC-Web for low latency.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;State Synchronization&lt;/strong&gt;: The server is the source of truth. Clients send &lt;em&gt;intent&lt;/em&gt; (e.g., "Raise 100"), not state updates.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Determinism&lt;/strong&gt;: If the server crashes, the game state can be reconstructed by replaying the event log (Event Sourcing) using the same &lt;code&gt;IPokerVariant&lt;/code&gt; logic. This is crucial for compliance and dispute resolution.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Scalability &amp;amp; Performance Considerations
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Memory Management&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;  Game state objects should be lightweight.&lt;/li&gt;
&lt;li&gt;  Use &lt;strong&gt;Object Pooling&lt;/strong&gt; for &lt;code&gt;Card&lt;/code&gt; and &lt;code&gt;Action&lt;/code&gt; objects to prevent Garbage Collection (GC) pauses in Node.js or Go, which can cause lag in real-time games.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Concurrency&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;  In Node.js, use &lt;code&gt;worker_threads&lt;/code&gt; for hand evaluation if the CPU is the bottleneck (e.g., evaluating thousands of hands for fairness audits or AI training).&lt;/li&gt;
&lt;li&gt;  In Go, leverage goroutines for each table; the engine logic is CPU-bound but I/O is minimal per action.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Database Design&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Hot Path (Redis)&lt;/strong&gt;: Store current game state, player actions, and active pots. Use Redis Streams for action logging.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Cold Path (PostgreSQL)&lt;/strong&gt;: Store finalized hand histories, player statistics, and transaction logs.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Sharding&lt;/strong&gt;: Shard by &lt;code&gt;table_id&lt;/code&gt; or &lt;code&gt;region&lt;/code&gt; to distribute load.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Security &amp;amp; Anti-Collusion
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;RNG Certification&lt;/strong&gt;: The RNG (Random Number Generator) must be cryptographically secure (e.g., &lt;code&gt;crypto.randomBytes&lt;/code&gt; in Node.js, &lt;code&gt;math/rand/v2&lt;/code&gt; with hardware seed in Go). It must be independently certified (GLI-11, eCOGRA).&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Card Distribution&lt;/strong&gt;: Never send hole cards to other players. The server sends &lt;em&gt;only&lt;/em&gt; the cards relevant to the client.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Collusion Detection&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;  Track "Seat History": If Player A and Player B always fold when Player C raises, flag it.&lt;/li&gt;
&lt;li&gt;  Analyze "Chip Transfer": Detect abnormal chip flow between accounts.&lt;/li&gt;
&lt;li&gt;  Implement a background analytics service that consumes the event stream and runs anomaly detection algorithms (e.g., clustering analysis).&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  Example: Adding "Short Deck" (6+ Hold'em)
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Define Deck&lt;/strong&gt;: Remove 2, 3, 4, 5.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Override Rules&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;  Flush beats Full House.&lt;/li&gt;
&lt;li&gt;  A-6-7-8-9 is a straight (lowest).&lt;/li&gt;
&lt;li&gt;  Ante is usually mandatory.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Inject&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;  Create &lt;code&gt;ShortDeckVariant&lt;/code&gt; class implementing &lt;code&gt;IPokerVariant&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;  Update the evaluator to use the new hand hierarchy.&lt;/li&gt;
&lt;li&gt;  No changes to the core &lt;code&gt;PokerEngine&lt;/code&gt; class.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This architecture ensures that the &lt;strong&gt;core logic remains stable&lt;/strong&gt; while &lt;strong&gt;variant rules are modular&lt;/strong&gt;, allowing rapid iteration and deployment of new poker games without risking the stability of the entire platform.&lt;/p&gt;

</description>
      <category>poker</category>
      <category>react</category>
      <category>ai</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
