Big state machines

Joel Reymont <>
Fri Apr 15 12:41:02 CEST 2005


> 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