]> code.delx.au - gnu-emacs-elpa/blob - packages/poker/poker.el
Fix some quoting problems in doc strings
[gnu-emacs-elpa] / packages / poker / poker.el
1 ;;; poker.el --- Texas hold 'em poker
2
3 ;; Copyright (C) 2014 Free Software Foundation, Inc.
4
5 ;; Author: Mario Lang <mlang@delysid.org>
6 ;; Maintainer: Mario Lang <mlang@delysid.org>
7 ;; Version: 0.1
8 ;; Keywords: games
9
10 ;; This program is free software; you can redistribute it and/or modify
11 ;; it under the terms of the GNU General Public License as published by
12 ;; the Free Software Foundation, either version 3 of the License, or
13 ;; (at your option) any later version.
14
15 ;; This program is distributed in the hope that it will be useful,
16 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 ;; GNU General Public License for more details.
19
20 ;; You should have received a copy of the GNU General Public License
21 ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
22
23 ;;; Commentary:
24
25 ;; poker.el provides Texas hold 'em poker gameplay for Emacs.
26
27 ;;; Requires:
28
29 (require 'cl-lib)
30 (require 'cookie1)
31 (require 'ert)
32
33 ;;; Compatibility:
34
35 (eval-and-compile
36 (unless (fboundp 'cookie-shuffle-vector)
37 (defalias 'cookie-shuffle-vector 'shuffle-vector)))
38
39 ;;; Constants:
40
41 (defconst poker-ranks '(2 3 4 5 6 7 8 9 10 jack queen king ace))
42 (defconst poker-suits '(clubs diamonds hearts spades))
43 (defconst poker-deck (cl-loop for card from 0 to 51 collect card))
44 (defconst poker-unicode-cards
45 (let ((unicode-suit '((clubs . #xD0) (diamonds . #XC0)
46 (hearts . #XB0) (spades . #XA0))))
47 (apply #'vector
48 (cl-loop for suit in poker-suits
49 nconc
50 (cl-loop for rank in poker-ranks
51 collect
52 (logior #x1f000
53 (cdr (assq suit unicode-suit))
54 (cond
55 ((eq rank 'ace) #x1)
56 ((eq rank 'jack) #xB)
57 ((eq rank 'queen) #xD)
58 ((eq rank 'king) #XE)
59 (t rank))))))))
60
61 ;;; Code:
62
63 (defsubst poker-make-card (rank suit)
64 "Make a poker card from RANK and SUIT.
65 RANK is one of `poker-ranks' and SUIT is one of `poker-suits'."
66 (cl-assert (memq rank poker-ranks))
67 (cl-assert (memq suit poker-suits))
68 (+ (* (cl-position suit poker-suits) 13) (cl-position rank poker-ranks)))
69
70 (defsubst poker-card-rank (card)
71 "The rank (a integer from 0 to 12) of a poker CARD."
72 (cl-check-type card (integer 0 51))
73 (% card 13))
74
75 (defsubst poker-card-suit (card)
76 "The suit (an integer from 0 to 3) of a poker CARD."
77 (cl-check-type card (integer 0 51))
78 (/ card 13))
79
80 (defsubst poker-card-name (card)
81 "The name of a poker CARD (a string of two characters)."
82 (cl-check-type card (integer 0 51))
83 (concat (aref ["2" "3" "4" "5" "6" "7" "8" "9" "T" "J" "Q" "K" "A"]
84 (poker-card-rank card))
85 (aref ["c" "d" "h" "s"] (poker-card-suit card))))
86
87 (defun poker-card-unicode (card)
88 "The Unicode character for a poker CARD."
89 (aref poker-unicode-cards card))
90
91 (defun poker-hand-value (hand)
92 "Calculate the value of a given 5 card poker HAND.
93 The result is a 24 bit integer where the leftmost 4 bits (0-8) indicate the type
94 of hand, and the remaining nibbles are rank values of decisive cards.
95 The highest possible value is therefore #x8CBA98 and the lowest is #x053210."
96 (let* ((ranks (mapcar #'poker-card-rank hand))
97 (rank-counts (sort (mapcar (lambda (rank) (cons (cl-count rank ranks) rank))
98 (cl-remove-duplicates ranks))
99 (lambda (lhs rhs) (or (> (car lhs) (car rhs))
100 (and (= (car lhs) (car rhs))
101 (> (cdr lhs) (cdr rhs)))))))
102 (ranks-length nil))
103 (setq ranks (mapcar #'cdr rank-counts)
104 rank-counts (mapcar #'car rank-counts)
105 ranks-length (length ranks))
106 (logior (ash (cond
107 ((equal rank-counts '(2 1 1 1)) 1)
108 ((eq ranks-length 5)
109 (let ((straight (or (when (and (eq (nth 0 ranks) 12)
110 (eq (nth 1 ranks) 3))
111 (setq ranks '(3 2 1 0 0)))
112 (eq (- (nth 0 ranks) (nth 4 ranks)) 4)))
113 (flush (eq (length (cl-delete-duplicates
114 (mapcar #'poker-card-suit hand))) 1)))
115 (cond ((and straight flush) 8) (flush 5) (straight 4) (t 0))))
116 ((equal rank-counts '(2 2 1)) 2)
117 ((equal rank-counts '(3 1 1)) 3)
118 ((equal rank-counts '(3 2)) 6)
119 ((equal rank-counts '(4 1)) 7))
120 20)
121 (ash (nth 0 ranks) 16)
122 (ash (nth 1 ranks) 12)
123 (if (> ranks-length 2) (ash (nth 2 ranks) 8) 0)
124 (if (> ranks-length 3) (ash (nth 3 ranks) 4) 0)
125 (if (> ranks-length 4) (nth 4 ranks) 0))))
126
127 (defun poker-hand-> (hand1 hand2)
128 "Return non-nil if HAND1 is better than HAND2."
129 (> (poker-hand-value hand1) (poker-hand-value hand2)))
130
131 (defun poker-sort-hands (hands)
132 "Sort HANDS (a list of list of cards) according to the value of the individual hands."
133 (mapcar #'cdr
134 (cl-sort (mapcar (lambda (hand) (cons (poker-hand-value hand) hand)) hands)
135 #'> :key #'car)))
136
137 (defun poker-combinations (n list)
138 "A list of all unique ways of taking N different elements from LIST."
139 (when list
140 (let ((length (length list)))
141 (nconc (if (eq n 1)
142 (list (if (cdr list) (list (car list)) list))
143 (if (eq n length)
144 (list list)
145 (mapcar (lambda (rest) (cons (car list) rest))
146 (poker-combinations (1- n) (cdr list)))))
147 (when (> length n) (poker-combinations n (cdr list)))))))
148
149 (defun poker-possible-hands (cards)
150 "Generate a list of possible 5 card poker hands from CARDS.
151 CARDS is a list of 5 to 7 poker cards."
152 (cl-check-type (length cards) (integer 5 7))
153 (cond
154 ;; While this could certainly be made generic,
155 ;; the performance of this hand-crafted implementation is unmatched.
156 ((eq 7 (length cards))
157 (let ((car (car cards))
158 (cdr (cdr cards)))
159 (let ((cadr (car cdr))
160 (cddr (cdr cdr)))
161 (let ((caddr (car cddr))
162 (cdddr (cdr cddr)))
163 (let ((cadddr (car cdddr))
164 (cddddr (cdr cdddr)))
165 (let ((caddddr (car cddddr))
166 (cdddddr (cdr cddddr)))
167 (let ((cadddddr (car cdddddr))
168 (cddddddr (cdr cdddddr)))
169 (list (list car cadr caddr cadddr caddddr)
170 (list car cadr caddr cadddr cadddddr)
171 (cons car (cons cadr (cons caddr (cons cadddr cddddddr))))
172 (list car cadr caddr caddddr cadddddr)
173 (cons car (cons cadr (cons caddr (cons caddddr cddddddr))))
174 (cons car (cons cadr (cons caddr cdddddr)))
175 (cons car (cons cadr (butlast cdddr)))
176 (cons car (cons cadr (cons cadddr (cons caddddr cddddddr))))
177 (cons car (cons cadr (cons cadddr cdddddr)))
178 (cons car (cons cadr cddddr))
179 (cons car (butlast cddr))
180 (cons car (cons caddr (cons cadddr (cons caddddr cddddddr))))
181 (cons car (cons caddr (cons cadddr cdddddr)))
182 (cons car (cons caddr cddddr))
183 (cons car cdddr)
184 (butlast cdr)
185 (cons cadr (cons caddr (cons cadddr (cons caddddr cddddddr))))
186 (cons cadr (cons caddr (cons cadddr cdddddr)))
187 (cons cadr (cons caddr cddddr))
188 (cons cadr cdddr)
189 cddr))))))))
190 (t (poker-combinations 5 cards))))
191
192 (defun poker-best-hand (cards)
193 "Find the best hand for a number of CARDS (usually a list of 6 or 7 elements)."
194 (let ((max 0) (best-hand nil))
195 (dolist (hand (poker-possible-hands cards) best-hand)
196 (let ((value (poker-hand-value hand)))
197 (when (> value max) (setq max value best-hand hand))))))
198
199 (defun poker-rank-to-string (rank)
200 "The english name of poker card RANK."
201 (aref ["2" "3" "4" "5" "6" "7" "8" "9" "10" "jack" "queen" "king" "ace"] rank))
202
203 (defun poker-rank-to-plural-string (rank)
204 "The plural english name of poker card RANK."
205 (concat (poker-rank-to-string rank) "s"))
206
207 (defun poker-describe-hand (hand)
208 "Return a string description of the value of the given poker HAND.
209 HAND is a list of 5 poker cards."
210 (cl-assert (eq (length hand) 5))
211 (pcase (let ((value (poker-hand-value hand)))
212 (cl-loop for i from 5 downto 0 collect (logand (ash value (- (* i 4))) #xf)))
213 (`(8 ,high ,_ ,_ ,_ ,_) (pcase high
214 (12 "royal flush")
215 (_ (format "%s high straight flush"
216 (poker-rank-to-string high)))))
217 (`(7 ,four ,high 0 0 0) (format "four %s, %s high"
218 (poker-rank-to-plural-string four)
219 (poker-rank-to-string high)))
220 (`(6 ,three ,two 0 0 0) (format "full house of %s and %s"
221 (poker-rank-to-plural-string three)
222 (poker-rank-to-plural-string two)))
223 (`(5 ,high ,k1 ,k2 ,k3 ,k4) (format "%s high flush, %s %s %s and %s kickers"
224 (poker-rank-to-string high)
225 (poker-rank-to-string k1)
226 (poker-rank-to-string k2)
227 (poker-rank-to-string k3)
228 (poker-rank-to-string k4)))
229 (`(4 ,high ,_ ,_ ,_ ,_) (pcase high
230 (3 "5 high straight (steel wheel)")
231 (_ (format "%s high straight"
232 (poker-rank-to-string high)))))
233 (`(3 ,three ,high ,kicker 0 0) (format "three %s, %s high, %s kicker"
234 (poker-rank-to-plural-string three)
235 (poker-rank-to-string high)
236 (poker-rank-to-string kicker)))
237 (`(2 ,two1 ,two2 ,high 0 0) (format "wwo pairs of %s and %s, %s high"
238 (poker-rank-to-plural-string two1)
239 (poker-rank-to-plural-string two2)
240 (poker-rank-to-string high)))
241 (`(1 ,two ,high ,k1 ,k2 0) (format "a pair of %s, %s high, %s and %s kickers"
242 (poker-rank-to-plural-string two)
243 (poker-rank-to-string high)
244 (poker-rank-to-string k1)
245 (poker-rank-to-string k2)))
246 (`(0 ,high ,k1 ,k2 ,k3 ,k4) (format "high card %s, %s %s %s and %s kickers"
247 (poker-rank-to-string high)
248 (poker-rank-to-string k1)
249 (poker-rank-to-string k2)
250 (poker-rank-to-string k3)
251 (poker-rank-to-string k4)))))
252
253 (defun poker-random-deck ()
254 "Return a shuffled deck of 52 poker cards."
255 (append (cookie-shuffle-vector (apply 'vector poker-deck)) nil))
256
257 (defun poker-strength (pocket &optional community opponents)
258 "Estimate the strength of POCKET and COMMUNITY cards against number of OPPONENTS.
259 The optional number of OPPONENTS defaults to 2."
260 (let ((wins 0) (iterations 100))
261 (dotimes (i iterations)
262 (let ((deck (poker-random-deck))
263 (players (make-vector (or opponents 1) nil)))
264 (dolist (card pocket) (setq deck (delete card deck)))
265 (dolist (card community) (setq deck (delete card deck)))
266 (dotimes (cards 2)
267 (dotimes (player (or opponents 1))
268 (push (pop deck) (aref players player))))
269 (let ((board (append community nil)))
270 (dotimes (_ (- 5 (length community)))
271 (push (pop deck) board))
272 (setq wins (+ wins (caar (cl-sort
273 (mapcar (lambda (info)
274 (setcdr info (poker-best-hand
275 (append (cdr info) board)))
276 info)
277 (nconc (list (cons 1 pocket))
278 (mapcar (lambda (cards)
279 (cons 0 cards))
280 players)))
281 #'poker-hand-> :key #'cdr)))))))
282 (/ (float wins) iterations)))
283
284 (defun poker-pre-flop-starting-hands (opponents)
285 (let ((rank-name (vector "2" "3" "4" "5" "6" "7" "8" "9" "T" "J" "Q" "K" "A"))
286 (hands ()))
287 (dolist (rank1 poker-ranks)
288 (dolist (rank2 poker-ranks)
289 (if (eq rank1 rank2)
290 (push (cons (poker-strength (list (poker-make-card rank1 'clubs)
291 (poker-make-card rank2 'hearts))
292 nil opponents)
293 (if (memq rank1 '(2 3 4 5 6 7 8 9))
294 (+ (* rank1 10) rank1)
295 (intern (format "%s%s"
296 (aref rank-name (cl-position rank1 poker-ranks))
297 (aref rank-name (cl-position rank2 poker-ranks))))))
298 hands)
299 (when (< (cl-position rank1 poker-ranks) (cl-position rank2 poker-ranks))
300 (let ((tmp rank1))
301 (setq tmp rank1
302 rank1 rank2
303 rank2 tmp)))
304 (dolist (suited '(nil t))
305 (let ((code (if (and (memq rank1 '(2 3 4 5 6 7 8 9))
306 (memq rank2 '(2 3 4 5 6 7 8 9))
307 (not suited))
308 (+ (* rank1 10) rank2)
309 (intern
310 (format "%s%s%s"
311 (aref rank-name (cl-position rank1 poker-ranks))
312 (aref rank-name (cl-position rank2 poker-ranks))
313 (if suited "s" ""))))))
314 (unless (rassq code hands)
315 (accept-process-output)
316 (message "%S" code)
317 (push (cons (poker-strength
318 (list (poker-make-card rank1 'clubs)
319 (poker-make-card rank2 (if suited 'clubs 'hearts)))
320 nil opponents) code) hands)))))))
321 (cl-sort hands #'> :key #'car)))
322
323 (defun poker-pot-odds (bet pot)
324 "Return the odds when BET is added to POT."
325 (/ (float bet) (+ pot bet)))
326
327 (defun poker-random-fold-call-raise (fold% call% raise%)
328 "Randomly choose between FOLD%, CALL% and RAISE%."
329 (cl-assert (= (+ fold% call% raise%) 100))
330 (let ((value (random 100)))
331 (cond
332 ((< value fold%) 'fold)
333 ((< value (+ fold% call%)) 'call)
334 ((< value (+ fold% call% raise%)) 'raise)
335 (t (error "Random FCR Error")))))
336
337 (defun poker-make-player (name fcr-fn)
338 "Create a new poker player with NAME and FCR-FN.
339 FCR-FN specifies a function to use when a fold-call-raise decision is required."
340 (list (cons 'name name)
341 (cons 'stack 0)
342 (cons 'wagered 0)
343 (cons 'pocket nil)
344 (cons 'fcr-fn fcr-fn)))
345
346 (defun poker-player-name (player)
347 "Return the name of poker PLAYER."
348 (cdr (assq 'name player)))
349
350 (defun poker-player-stack (player)
351 "Return the remaining stack of poker PLAYER."
352 (cdr (assq 'stack player)))
353
354 (defun poker-player-bet (player amount)
355 "Make PLAYER bet AMOUNT of chips."
356 (let ((actual (min (poker-player-stack player) amount)))
357 (when (zerop actual) (message "WARNING: Actual is 0."))
358 (unless (zerop actual)
359 (cl-decf (cdr (assq 'stack player)) actual)
360 (cl-incf (cdr (assq 'wagered player)) actual))
361 actual))
362
363 (defun poker-player-payout (player amount)
364 "Give PLAYER AMOUNT of chips."
365 (cl-incf (cdr (assq 'stack player)) amount)
366 amount)
367
368 (defun poker-player-wagered (player)
369 "Return the amount of chips currently wagered by poker PLAYER."
370 (cdr (assq 'wagered player)))
371
372 (defun poker-player-pocket (player)
373 "Return the current pocket (hole) cards of PLAYER."
374 (cdr (assq 'pocket player)))
375
376 (defun poker-player-fold (player)
377 "Make PLAYER fold and forget about their cards."
378 (setcdr (assq 'pocket player) nil))
379
380 (defun poker-player-active-p (player)
381 (and (poker-player-pocket player) (> (poker-player-wagered player) 0)))
382
383 (defun poker-player-all-in-p (player)
384 (and (poker-player-active-p player) (zerop (poker-player-stack player))))
385
386 (defun poker-player-can-bet-p (player)
387 (and (poker-player-pocket player) (> (poker-player-stack player) 0)))
388
389 (defun poker-player-best-hand (player community)
390 (cl-assert (>= (length (poker-player-pocket player)) 2))
391 (cl-assert (>= (length community) 3))
392 (poker-best-hand (append (poker-player-pocket player) community)))
393
394 (defun poker-player-give-card (player card)
395 (cl-check-type card (integer 0 51))
396 (push card (cdr (assq 'pocket player))))
397
398 (defun poker-player-fcr-fn (player)
399 (cdr (assq 'fcr-fn player)))
400
401 (defun poker-player-fcr (player pot amount-to-call max-raise board opponents)
402 (funcall (poker-player-fcr-fn player)
403 player pot amount-to-call max-raise board opponents))
404
405 (defun poker-read-fold-call-raise (pot to-call max-raise &optional prompt)
406 (let ((cursor-in-echo-area t)
407 (map (let ((map (make-sparse-keymap)))
408 (define-key map [?c] 'call)
409 (define-key map [?f] 'fold)
410 (when (> max-raise 0) (define-key map [?r] 'raise))
411 (define-key map [?q] 'quit)
412 map))
413 (action nil))
414 (while (not action)
415 (message (format "%s%d in pot, %d to call: (f)old%s: "
416 (or prompt "") pot to-call
417 (if (> max-raise 0)
418 (if (zerop to-call)
419 ", (c)heck or (r)aise"
420 ", (c)all or (r)aise")
421 (if (zerop to-call)
422 " or (c)heck"
423 " or (c)all"))))
424 (setq action (lookup-key map (vector (read-event)))))
425 (cond
426 ((eq action 'fold) nil)
427 ((eq action 'call) to-call)
428 ((eq action 'raise) (+ to-call (let ((raise (1+ max-raise)))
429 (while (> raise max-raise)
430 (setq raise
431 (read-number (format "Raise by (max %d): "
432 max-raise))))
433 (cl-check-type raise integer)
434 raise))))))
435
436 (defun poker-interactive-fcr (player pot due max-raise board opponents)
437 (poker-read-fold-call-raise
438 pot due max-raise (format "%s%s, %d stack, "
439 (mapconcat #'poker-card-name (poker-player-pocket player) ", ")
440 (if board
441 (concat "(" (mapconcat #'poker-card-name board " ") ")")
442 "")
443 (poker-player-stack player))))
444
445 (defun poker-automatic-fcr (player pot due max-raise board &optional opponents)
446 (let* ((strength (poker-strength (poker-player-pocket player) board opponents))
447 (pot-odds (poker-pot-odds due pot))
448 (rate-of-return (/ strength pot-odds))
449 (action (cond
450 ((< rate-of-return 0.8) (poker-random-fold-call-raise 95 1 4))
451 ((< rate-of-return 1.0) (poker-random-fold-call-raise 80 15 5))
452 ((< rate-of-return 1.3) (poker-random-fold-call-raise 0 60 40))
453 (t (poker-random-fold-call-raise 0 25 75)))))
454 (when (and (memq action '(call raise))
455 (< (- (poker-player-stack player) due) 200) (< strength 0.5))
456 (setq action 'fold))
457 (when (and (eq action 'raise) (< strength 0.1))
458 (setq action 'call))
459 (when (and (zerop due) (eq action 'fold))
460 (setq action 'call))
461 (cond
462 ((eq action 'fold) nil)
463 ((eq action 'call) due)
464 ((eq action 'raise) (+ due (min 100 max-raise))))))
465
466 (defun poker-rotate-to-first (player players)
467 "Make PLAYER the first element of PLAYERS."
468 (let ((position (cl-position player players)))
469 (when position
470 (let ((shift (- (length players) position)))
471 (append (last players shift) (butlast players shift))))))
472
473 (defun poker-next-players (player players)
474 (cdr (poker-rotate-to-first player players)))
475
476 (defun poker-next-player (player players)
477 (car (poker-next-players player players)))
478
479 (defun poker-pot (players)
480 "Return the amount of chips in the pot, the total wagered by all PLAYERS."
481 (apply #'+ (mapcar #'poker-player-wagered players)))
482
483 (defun poker-current-wager (players)
484 "Determine the maximum amount of chips wagered by any of PLAYERS."
485 (apply #'max (mapcar #'poker-player-wagered players)))
486
487 (defun poker-collect-wager (amount players)
488 "Collect AMOUNT of wager from PLAYERS."
489 (let ((total 0))
490 (dolist (player players total)
491 (let ((wagered (assq 'wagered player)))
492 (if (> amount (cdr wagered))
493 (progn
494 (setq total (+ total (cdr wagered)))
495 (setcdr wagered 0))
496 (setq total (+ total amount))
497 (setcdr wagered (- (cdr wagered) amount)))))))
498
499 (defun poker-distribute-winnings (winners players)
500 "Distribute chips to WINNERS from PLAYERS accounting for split-pot rules."
501 (cl-assert (not (null winners)))
502 (cl-assert (> (length players) 1))
503 (if (= (length winners) 1)
504 (poker-player-payout (car winners)
505 (poker-collect-wager (poker-player-wagered (car winners))
506 players))
507 (let* ((lowest (apply #'min (mapcar #'poker-player-wagered winners)))
508 (total (poker-collect-wager lowest players))
509 (each (/ total (length winners)))
510 (leftover (- total (* each (length winners)))))
511 (poker-player-payout (car winners) (+ each leftover))
512 (dolist (player (cdr winners)) (poker-player-payout player each))
513 total)))
514
515 (defun poker-player-max-raise (player players)
516 "Determine the maximum amount allowed to raise for PLAYER considering PLAYERS stacks."
517 (let ((other-stacks (mapcar #'poker-player-stack
518 (cl-remove
519 player
520 (cl-remove-if-not #'poker-player-can-bet-p players)))))
521 (min (poker-player-stack player) (if other-stacks (apply #'max other-stacks) 0))))
522
523 (defun poker-interactive-p (players)
524 (cl-find #'poker-interactive-fcr players :key #'poker-player-fcr-fn))
525
526 (defun poker-dealer-ask-player (player players board allow-raise)
527 "Ask PLAYER for next action."
528 (let ((pot (poker-pot players))
529 (max-raise (if allow-raise (poker-player-max-raise player players) 0))
530 (amount-to-call (- (poker-current-wager players)
531 (poker-player-wagered player)))
532 (opponents (1- (length (cl-remove-if-not #'poker-player-pocket players)))))
533 (cl-assert (> opponents 0))
534 (let ((decision (poker-player-fcr player pot amount-to-call max-raise
535 board opponents)))
536 (cl-assert (or (null decision)
537 (and (integerp decision)
538 (<= (- decision amount-to-call) max-raise))))
539 (cond
540 ((null decision)
541 (message (format "%s folds." (poker-player-name player)))
542 (poker-player-fold player))
543 ((zerop decision)
544 (message "%s checks." (poker-player-name player)))
545 ((integerp decision)
546 (if (= decision amount-to-call)
547 (message "%s calls %d." (poker-player-name player) decision)
548 (cl-assert (>= decision amount-to-call))
549 (message "%s raises by %d."
550 (poker-player-name player) (- decision amount-to-call)))
551 (poker-player-bet player decision))))))
552
553 (defun poker-dealer (min-bet deck board players)
554 "Deal a round of texas holdem poker with MIN-BET for PLAYERS."
555 (cl-assert (> (length players) 1))
556 (cond
557 ;; pre-flop
558 ((and (null board) (zerop (poker-pot players)))
559 (let ((blinds players))
560 (message "Collecting blinds.")
561 (message "%s posts %d small blind." (poker-player-name (car blinds)) (/ min-bet 2))
562 (poker-player-bet (car blinds) (/ min-bet 2))
563 (message "%s posts %d big blind." (poker-player-name (cadr blinds)) min-bet)
564 (poker-player-bet (cadr blinds) min-bet)
565 (message "Dealing cards to players.")
566 (dotimes (_ 2)
567 (dolist (player players) (poker-player-give-card player (pop deck))))
568
569 (message "Initial betting round.")
570
571 (dolist (player (poker-next-players (cadr blinds) players))
572
573 (unless (zerop (poker-player-stack player))
574 (poker-dealer-ask-player player players board t)))
575
576 (when (and (not (zerop (poker-player-stack (cadr blinds))))
577 (or (> (length (cl-remove-if-not #'poker-player-can-bet-p players)) 1)
578 (< (poker-player-wagered (cadr blinds))
579 (poker-current-wager players))))
580 (poker-dealer-ask-player (cadr blinds) players board t))
581
582 (poker-dealer min-bet deck board players)))
583
584 ;; All but one have folded
585 ((and (not (zerop (poker-pot players)))
586 (= (length (cl-remove-if-not #'poker-player-active-p players)) 1))
587 (let ((winners (cl-remove-if-not #'poker-player-active-p players)))
588 (message "%s silently wins %d."
589 (poker-player-name (car winners))
590 (poker-distribute-winnings winners players))
591 winners))
592
593 ;; pre-flop, second round of bets, no raises allowed
594 ((and (null board) (cl-remove-if
595 (lambda (player)
596 (or (zerop (poker-player-wagered player))
597 (not (poker-player-pocket player))
598 (poker-player-all-in-p player)
599 (= (poker-player-wagered player)
600 (poker-current-wager players))))
601 (poker-rotate-to-first (cadr players) players)))
602
603 (message "Pre flop, second round of bets.")
604
605 (dolist (player (cl-remove-if
606 (lambda (player)
607 (or (zerop (poker-player-wagered player))
608 (not (poker-player-pocket player))
609 (poker-player-all-in-p player)
610 (= (poker-player-wagered player)
611 (poker-current-wager players))))
612 (poker-rotate-to-first (cadr players) players)))
613 (when (or (> (length (cl-remove-if-not #'poker-player-can-bet-p players)) 1)
614 (< (poker-player-wagered player) (poker-current-wager players)))
615 (poker-dealer-ask-player player players board nil)))
616
617 (poker-dealer min-bet deck board players))
618
619 ;; flop
620 ((null board)
621 (dotimes (_ 3) (push (pop deck) board))
622
623 (message "The flop: %s" (mapconcat #'poker-card-name board " "))
624
625 (dolist (player (cl-remove-if-not #'poker-player-can-bet-p players))
626 (when (or (> (length (cl-remove-if-not #'poker-player-can-bet-p players)) 1)
627 (< (poker-player-wagered player) (poker-current-wager players)))
628 (poker-dealer-ask-player player players board t)))
629
630 (poker-dealer min-bet deck board players))
631
632 ;; flop, second round of bets, no raises allowed
633 ((and (= (length board) 3) (cl-remove-if
634 (lambda (player)
635 (or (not (poker-player-can-bet-p player))
636 (= (poker-player-wagered player)
637 (poker-current-wager players))))
638 players))
639 (message "The flop, second round of bets.")
640 (dolist (player (cl-remove-if
641 (lambda (player)
642 (or (not (poker-player-can-bet-p player))
643 (= (poker-player-wagered player)
644 (poker-current-wager players))))
645 players))
646 (poker-dealer-ask-player player players board nil))
647
648 (poker-dealer min-bet deck board players))
649
650 ;; turn
651 ((= (length board) 3)
652 (push (pop deck) board)
653
654 (message "The turn: %s" (mapconcat #'poker-card-name board " "))
655
656 (setq min-bet (* min-bet 2))
657
658 (dolist (player (cl-remove-if-not #'poker-player-can-bet-p players))
659 (when (or (> (length (cl-remove-if-not #'poker-player-can-bet-p players)) 1)
660 (< (poker-player-wagered player) (poker-current-wager players)))
661 (poker-dealer-ask-player player players board t)))
662
663 (poker-dealer min-bet deck board players))
664
665 ;; turn, second round of bets, no raises allowed
666 ((and (= (length board) 4) (cl-remove-if
667 (lambda (player)
668 (or (not (poker-player-can-bet-p player))
669 (= (poker-player-wagered player)
670 (poker-current-wager players))))
671 players))
672 (message "The turn, second round of bets.")
673 (dolist (player (cl-remove-if
674 (lambda (player)
675 (or (not (poker-player-can-bet-p player))
676 (= (poker-player-wagered player)
677 (poker-current-wager players))))
678 players))
679 (poker-dealer-ask-player player players board nil))
680
681 (poker-dealer min-bet deck board players))
682
683 ;; river
684 ((= (length board) 4)
685 (push (pop deck) board)
686 (message "The river: %s" (mapconcat #'poker-card-name board " "))
687
688 (dolist (player (cl-remove-if-not #'poker-player-can-bet-p players))
689 (when (or (> (length (cl-remove-if-not #'poker-player-can-bet-p players)) 1)
690 (< (poker-player-wagered player) (poker-current-wager players)))
691 (poker-dealer-ask-player player players board t)))
692
693 (poker-dealer min-bet deck board players))
694
695 ;; river, second round of bets, no raises allowed
696 ((and (= (length board) 5) (cl-remove-if
697 (lambda (player)
698 (or (not (poker-player-can-bet-p player))
699 (= (poker-player-wagered player)
700 (poker-current-wager players))))
701 players))
702 (message "Last betting round.")
703 (dolist (player (cl-remove-if
704 (lambda (player)
705 (or (not (poker-player-can-bet-p player))
706 (= (poker-player-wagered player)
707 (poker-current-wager players))))
708 players))
709 (poker-dealer-ask-player player players board nil))
710
711 (poker-dealer min-bet deck board players))
712
713 ;; showdown
714 ((= (length board) 5)
715 (cl-assert (not (zerop (poker-pot players))))
716 (let ((in-play (cl-remove-if-not #'poker-player-active-p players))
717 (groups ())
718 (game-interactive-p (poker-interactive-p players)))
719 (unless (> (length in-play) 1)
720 (error "In-play to small: %S %S" in-play players))
721 (while in-play
722 (if (= (length in-play) 1)
723 (progn
724 (message "%s wins %d."
725 (poker-player-name (car in-play))
726 (poker-distribute-winnings in-play players))
727 (when game-interactive-p (sit-for 2))
728 (push in-play groups)
729 (setq in-play nil))
730 (let* ((best-hand-value (poker-hand-value
731 (car
732 (poker-sort-hands
733 (mapcar (lambda (player)
734 (poker-player-best-hand player board))
735 in-play)))))
736 (winners (cl-remove-if (lambda (player)
737 (< (poker-hand-value
738 (poker-player-best-hand player board))
739 best-hand-value))
740 in-play)))
741 (dolist (player in-play)
742 (message "%s shows %s, %s."
743 (poker-player-name player)
744 (mapconcat #'poker-card-name (poker-player-pocket player) " ")
745 (poker-describe-hand (poker-player-best-hand player board)))
746 (when game-interactive-p (sit-for 2)))
747 (message "%s wins %d."
748 (mapconcat #'poker-player-name winners ", ")
749 (poker-distribute-winnings winners players))
750 (when game-interactive-p (sit-for 2))
751 (push winners groups))
752 (setq in-play (cl-remove-if-not #'poker-player-active-p players))))
753
754 (cons board (nreverse groups))))
755
756 (t (list 'error min-bet deck board players))))
757
758 ;;;###autoload
759 (defun poker (initial-stack min-bet players)
760 "Play a game of texas hold 'em poker."
761 (interactive (list (read-number "Initial stack: " 1000)
762 (read-number "Minimum bet: " 50)
763 (list (poker-make-player "Angela" #'poker-automatic-fcr)
764 (poker-make-player "Bettina" #'poker-automatic-fcr)
765 (poker-make-player "Christina" #'poker-automatic-fcr)
766 (poker-make-player "Daniela" #'poker-automatic-fcr)
767 (poker-make-player "Emil" #'poker-automatic-fcr)
768 (poker-make-player "Frank" #'poker-automatic-fcr)
769 (poker-make-player "Günther" #'poker-automatic-fcr)
770 (poker-make-player "Harald" #'poker-automatic-fcr)
771 (poker-make-player "Ingrid" #'poker-automatic-fcr)
772 (poker-make-player (user-full-name) #'poker-interactive-fcr))))
773 (cl-assert (> (length players) 1))
774 (dolist (player players)
775 (message "%s receives %d chips." (poker-player-name player) initial-stack)
776 (setcdr (assq 'stack player) initial-stack))
777 (let ((game-interactive-p (poker-interactive-p players))
778 (button-player (nth (random (length players)) players))
779 (rounds ())
780 (losers ()))
781 (setq players (poker-rotate-to-first button-player players))
782 (while (and button-player
783 (or (not game-interactive-p)
784 (poker-interactive-p players)))
785 (message "Round %d, %d players." (1+ (length rounds)) (length players))
786
787 (push (poker-dealer min-bet (poker-random-deck) () players)
788 rounds)
789
790 (mapc #'poker-player-fold players)
791 (setq button-player
792 (car-safe (cdr (cl-remove-if (lambda (player)
793 (zerop (poker-player-stack player)))
794 (poker-rotate-to-first button-player players)))))
795 (let ((lost (cl-remove-if-not (lambda (player) (zerop (poker-player-stack player)))
796 players)))
797 (when lost
798 (setq players (cl-remove-if
799 (lambda (player)
800 (when (member player lost)
801 (message "%s drops out." (poker-player-name player))
802 t))
803 players))
804 (setq losers (nconc losers lost))))
805 (message "Remaining players: %s"
806 (mapconcat (lambda (player) (format "%s (%d)"
807 (poker-player-name player)
808 (poker-player-stack player)))
809 (cl-sort (append players nil)
810 #'> :key #'poker-player-stack)
811 " "))
812 (when button-player
813 (cl-assert (member button-player players))
814 (let ((count (length players)))
815 (setq players (poker-rotate-to-first button-player players))
816 (cl-assert (= count (length players)))))
817
818 (accept-process-output)
819
820 (when (and game-interactive-p (not (poker-interactive-p players)))
821 (message "You drop out in %s place."
822 (let ((rank (1+ (length players))))
823 (pcase rank
824 (2 "2nd")
825 (3 "3rd")
826 (n (format "%dth" n)))))))
827
828 (when (and game-interactive-p (poker-interactive-p players))
829 (message "You are the winner."))
830
831 (cons players rounds)))
832
833
834 ;;;###autoload
835 (define-key menu-bar-games-menu
836 [poker] '(menu-item "Texas hold 'em poker" poker
837 :help "Play Texas hold 'em poker"))
838
839 ;;; Tests:
840
841 (ert-deftest poker-combinations ()
842 (should (equal 21 (length (poker-combinations 5 (last poker-deck 7)))))
843 (should (equal 1326 (length (poker-combinations 2 poker-deck)))))
844
845 (ert-deftest poker-possible-hands ()
846 (should (equal (poker-possible-hands '(1 2 3 4 5 6 7))
847 (poker-combinations 5 '(1 2 3 4 5 6 7))))
848 (should (equal (poker-possible-hands '(1 2 3 4 5 6))
849 (poker-combinations 5 '(1 2 3 4 5 6)))))
850
851 (ert-deftest poker ()
852 (let ((players (list (poker-make-player "Angela" #'poker-automatic-fcr)
853 (poker-make-player "Bettina" #'poker-automatic-fcr)
854 (poker-make-player "Christoph" #'poker-automatic-fcr)
855 (poker-make-player "Daniela" #'poker-automatic-fcr)
856 (poker-make-player "Emilia" #'poker-automatic-fcr)
857 (poker-make-player "Franz" #'poker-automatic-fcr)
858 (poker-make-player "Günter" #'poker-automatic-fcr)
859 (poker-make-player "Harald" #'poker-automatic-fcr)
860 (poker-make-player "Isabella" #'poker-automatic-fcr)
861 (poker-make-player "Jakob" #'poker-automatic-fcr))))
862 (while (> (length players) 1)
863 (should (equal (poker-player-stack (caar (poker 1000 100 players)))
864 (* 1000 (length players))))
865 (setq players (cdr players)))))
866
867 (provide 'poker)
868 ;;; poker.el ends here