game: kuhn-poker
label: Kuhn poker
players: "2 players"
mode: competitive
num_players: 2
demonstrates: "hidden information · executable"
summary: >
  The smallest poker game: three cards (J, Q, K), one face-down card per player and one betting round.
  Small enough to solve exactly, which makes it the standard test for imperfect information:
  the game value for player 1 under optimal play is exactly −1/18.
scope: "Rules as in Kuhn (1950) and OpenSpiel: ante 1, bet 1, actions pass and bet."

constants:
  RANK: {J: 1, Q: 2, K: 3}

utility: {min: -2, max: 2}   # win or loss of 1 or 2 chips

state:
  entity_types:
    card: {attrs: {rank: "enum[J, Q, K]"}}
  zones:
    deck: {kind: stack, owner: global, visibility: hidden, shuffled: true, holds: card, init_x: "['J', 'Q', 'K']"}
    hand: {kind: slot, owner: player, visibility: owner, holds: card, note: "only the owner sees their card"}
  vars:
    pot: {scope: player, type: int, init: 1, note: "chips in per player; ante 1"}
    bets: {scope: global, type: list, init_x: "[]", note: "p = pass, b = bet"}
    current: {scope: global, type: ref, init: 0}
  derived:
    folder:
      text: "who passed on a bet (the last to act)"
      x: "(len(bets) - 1) % 2"

flow:
  id: game
  label: Game
  kind: sequence
  children:
    - id: deal
      label: "Deal: one face-down card per player"
      kind: step
      do: deal
    - id: betting
      label: Betting round
      kind: loop
      over: players
      over_x: players
      as: current
      order: fixed
      note: "ends through the end conditions: showdown or a pass on a bet"
      children:
        - id: act
          label: "Pass or bet (after a bet: pass = fold, bet = call)"
          kind: step
          actor: current
          actor_x: current
          do: [pass, bet]

actions:
  deal:
    effect:
      - {op: draw, from: deck, to: "hand[0]", n: 1}
      - {op: draw, from: deck, to: "hand[1]", n: 1}
  pass:
    effect:
      - {op: set, var: bets, value_x: "bets + ['p']"}
  bet:
    effect:
      - {op: add, var: "pot[current]", amount: 1}
      - {op: set, var: bets, value_x: "bets + ['b']"}

end:
  - id: fold
    check: immediate
    if: "pass on a bet: the other player wins the folder's stake"
    if_x: "bets in (['p', 'b', 'p'], ['b', 'p'])"
    returns_x: "[(-pot[p] if p == folder else pot[folder]) for p in players]"
    result: score
  - id: showdown
    check: immediate
    if: "both passed or both bet: the higher card wins the other's stake"
    if_x: "bets in (['p', 'p'], ['b', 'b'], ['p', 'b', 'b'])"
    returns_x: "[(pot[1 - p] if RANK[hand[p][0]] > RANK[hand[1 - p][0]] else -pot[p]) for p in players]"
    result: score
