Big state machines

Valentin Micic <>
Sat Apr 16 09:32:51 CEST 2005


If you were to consider a separate FSM for each player, and game as a common
context shared amongst group of player, I would opt to implement a "table"
as a gen_server that routes  messages (context of the game) between
players... I see only few states for a player's FSM. Am I missing a plot
completely?

Valentin.


----- Original Message -----
From: "Ulf Wiger (AL/EAB)" <>
To: "Joel Reymont" <>; "Erlang Users' List"
<>
Sent: Friday, April 15, 2005 12:51 PM
Subject: RE: Big state machines



Well, then. You may write your code the
same way in Erlang. Your dealer process
sends messages (cards) to the players,
and receives requests or cards (if a player
plays a card, it might as well be sent to
the dealer).

Again, with selective receive, you should be
able to translate your lisp program logic into
erlang with fairly little trouble. With gen_fsm,
I'd have to think for much more than the
two minutes I spent browsing your lisp code.

Given the small amount of thinking that went
into the writing of this post, I may have
missed something.


/Uffe

-----Original Message-----
From: Joel Reymont [mailto:]
Sent: den 15 april 2005 12:41
To: Ulf Wiger (AL/EAB); Erlang Users' List
Subject: Re: Big state machines


> Ulf Wiger (AL/EAB) wrote:
> [...]
>The remedy? Use plain Erlang state machines with
>selective receive. You will still have to think
>about whether you can break up your state machines
>into several smaller ones. The proof is in your
>code. If it simplifies the code - do it. Otherwise,
>don't.

Let me elaborate ...

I'm writing poker game logic for the Texas Hold'em variant.

First I wait for a few seconds to let people join, leave, sit out, come
back. I selectively ignore all other actions. When getting a timeout I
check if I have enough players and transition to collecting blinds.

Here I need to ask players to post a small blind and a big blind. Players
who join in these two states need to make up by posting the big blind and
I process this in a separate state after SB and BB are collected.

So I have small_blind, big_blind and make_up_blinds for states.

Have a separate small_blind state because after picking the player whose
turn it is to post the SB I need to see if they actually post it. If they
do not then I mark them as sitting out and need to ask the following
player. If I run out of players then I cancel the game and go to wait for
players.

If the the small blind is posted then I repeat the procedure for the big
blind and so on and so forth.

I'm not sure if I'm suffering from the syndrome that you described. I
just have a lot of possible things to handle. This was far easier in Lisp
with the continuation passing style, at least far more compact :D.

I'm including my Lisp implementation, nicely sequential. Note how compact
the makeup-blinds code is. With Erlang the state machine gets quite big
and I'm just trying to figure out how to modularize it.

I'm not trying to start a flame war or discuss the merits of Lisp vs.
Erlang. I picked Erlang after implementing a working poker engine in Lisp
and I'm simply trying to go back to FSM from continuations now.

    Thanks for your advice, Joel

P.S.

;;; Save game continuation

(defun/cc receive (game)
  (let/cc k
    ;; save continuation
    (setf (continuation game) k)
    ;; TODO: start a timer here
    ))

(defun/cc ask-for-blind (game amount context)
  (let ((posted nil)
(seat nil)
(active (car context))
(small-blind-p (= (small-blind$ game) amount)))
    (while (and (not posted) (car active))
      (setf seat (pop active))
      ;; skip people who are waiting
      ;; for the big blind if small blind
      ;; is being posted.
      (unless (and (waiting-for-bb-p seat)
   small-blind-p)
(setf (state seat) 'sitting-out)
(setf (current game) seat)
(send (player seat) 'blind amount)
(let* ((cmd (receive game))
       (action (first cmd))
       (bet (second cmd))
       (inplay$ (inplay$ (player seat))))
  (case action
    ('blind
     (when (or (= bet amount)
       (= bet inplay$))
       (setf (state seat) (if (= bet amount)
      'playing
      'all-in))
       (record-bet game seat bet)
       (setf posted t)
       (broadcast game 'notify 'blind (seat# seat) bet)))
    ('fold
     (broadcast game 'notify 'fold (seat# seat) 0))
    ))))
    (setf (car context) active)
    (if posted seat nil)))

(defun/cc assign-blinds (game)
  ;; the cons bit is a hack since ask-for-blind pops the list
  ;; and needs pass the updated list back to us.
  (let ((context (cons (copy-list (blind-active-seats game)) t))
(blind (blinds game))
(sb$ (small-blind$ game))
(bb$ (big-blind$ game))
(b nil)
(sb nil)
(bb nil))
    (with-slots (small big button) blind
(if (not (or small big))
    ;; first hand of this game
    (progn
      (setf b button)
      (setf sb (ask-for-blind game sb$ context))
      (setf bb (ask-for-blind game bb$ context)))
    (progn
      ;; get active players starting with the seat
      ;; to the left of the current small blind.
      (setf (car context)
    (copy-list (blind-active-seats game :start-seat small)))
      (setf b small)
      (if (bust-p big)
  ;; big blind is bust
  (progn
    (setf sb big)
    (setf bb (ask-for-blind game bb$ context))
    ;; special heads-up handling
    (unless (second (car context))
      (setf sb (pop (car context)))))
  ;; otherwise
  (progn
    (setf sb (ask-for-blind game sb$ context))
    (setf bb (ask-for-blind game bb$ context))))
      )))
    ;; heads-up play
    (when (not (car context))
      (setf b sb)) ; heads-up, button = sb
    (list b sb bb)
    ))

;;; We let people join once the blinds have passed them
;;; but they must make up for it by posting the big blind.

(defun/cc make-up-blinds (game active)
  (let ((new (active-seats game))
(amount (big-blind$ game)))
    (loop for seat in new
       unless (member seat active) do
(send (player seat) 'blind amount)
(let* ((cmd (receive game))
(action (first cmd))
(bet (second cmd))
(inplay$ (inplay$ (player seat))))
   (case action
     ('blind
      (when (or (= bet amount)
(= bet inplay$))
(setf (state seat) (if (= bet amount)
       'playing
       'all-in))
(record-bet game seat bet)
(broadcast game 'notify 'blind (seat# seat) bet)))
     ('wait-for-bb
      (setf (state seat) 'waiting-for-bb)
      (broadcast game 'notify 'wait-for-bb (seat# seat) 0))
     (t
      ;; time out received
      (setf (state seat) 'sitting-out)
      (broadcast game 'notify 'sit-out (seat# seat) 0))
     )))))





--
http://wagerlabs.com/tech





More information about the erlang-questions mailing list