Big state machines
Ulf Wiger (AL/EAB)
ulf.wiger@REDACTED
Fri Apr 15 12:51:21 CEST 2005
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:joelr1@REDACTED]
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