Add two-player Russian Bank (Crapette) vs the computer

* cg-crapette.el: new game on cg-core — eight foundations (up by suit,
two decks), eight shared houses (down, alternating colour), per-player
reserve/waste/hand; foundation priority; loading onto the opponent; a
greedy AI; text UI with cursor pick/drop, undo, and q-to-menu.
* card-games.el: require cg-crapette and add the chooser entry.
* Makefile (EL): add cg-crapette.el.
* test/card-games-tests.el: 5 ERT (deal, legality, win, AI turn, AI foundation).
This commit is contained in:
Corwin Brust 2026-07-01 03:09:27 -05:00
parent f54d2d3213
commit 0d8901041f
5 changed files with 690 additions and 3 deletions

View file

@ -675,6 +675,91 @@
(should (= 1 (length (cg-sol--col g 0))))
(should (= (1- rlen) (length (cg-get g :reserve)))))))
(ert-deftest cgt-sol-russian-bank-deal ()
(let ((g (cg-sol--deal (cg-russian-bank-game))))
(should (= 8 (oref g ncols)))
(should (= 13 (length (cg-get g :reserve))))
(dotimes (c 8) (should (= 1 (length (cg-sol--col g c)))))
(dotimes (i 4) (should (null (aref (cg-get g :found) i))))
(should (= 31 (length (cg-get g :stock)))) ; 52 - 13 reserve - 8 houses
;; Houses build down by alternating colour; foundations up by suit.
(should (cg-sol--place-p g '(0 . 6) '(2 . 5))) ; 7S <- 6D (down, alt)
(should-not (cg-sol--place-p g '(0 . 6) '(1 . 5))) ; 7S <- 6C (same colour)
(should (cg-sol--found-accepts g 0 '(3 . 0))) ; empty foundation takes an Ace
(should-not (cg-sol--found-accepts g 0 '(3 . 1))))) ; but not a Two
(ert-deftest cgt-sol-russian-bank-autofill ()
(let ((g (cg-sol--deal (cg-russian-bank-game))))
(let ((rlen (length (cg-get g :reserve))))
(aset (cg-get g :tableau) 0 nil) ; empty a house
(cg-sol--autofill g)
(should (= 1 (length (cg-sol--col g 0))))
(should (= (1- rlen) (length (cg-get g :reserve)))))))
;;;; Russian Bank duel (Crapette)
(ert-deftest cgt-crap-deal ()
(let ((g (cg-crap--deal (cg-crapette-game))))
(dotimes (p 2)
(should (= 13 (length (cg-crap--reserve g p))))
(should (= 35 (length (cg-crap--hand g p))))
(should (null (cg-crap--waste g p))))
(dotimes (i 8) (should (= 1 (length (cg-crap--house g i)))))
(dotimes (i 8) (should (null (cg-crap--found g i))))
(should (= 0 (cg-get g :turn)))))
(ert-deftest cgt-crap-legality ()
(let ((g (cg-crap--deal (cg-crapette-game))))
(aset (cg-get g :found) 0 nil)
(should (cg-crap--found-accepts g 0 '(3 . 0))) ; empty -> Ace
(should-not (cg-crap--found-accepts g 0 '(3 . 1))) ; not a Two
(aset (cg-get g :found) 0 (list '(3 . 0)))
(should (cg-crap--found-accepts g 0 '(3 . 1))) ; up in suit
(should-not (cg-crap--found-accepts g 0 '(2 . 1))) ; wrong suit
(aset (cg-get g :houses) 0 (list '(0 . 6))) ; 7 of spades (black)
(should (cg-crap--house-accepts g 0 '(2 . 5))) ; 6 of diamonds (red)
(should-not (cg-crap--house-accepts g 0 '(1 . 5))) ; 6 of clubs (black)
(should (cg-crap--load-accepts '(2 . 5) '(2 . 6))) ; same suit, one up
(should (cg-crap--load-accepts '(2 . 5) '(2 . 4))) ; same suit, one down
(should-not (cg-crap--load-accepts '(2 . 5) '(3 . 6))) ; wrong suit
(should-not (cg-crap--load-accepts '(2 . 5) '(2 . 7))))) ; not adjacent
(ert-deftest cgt-crap-win ()
(let ((g (cg-crap--deal (cg-crapette-game))))
(aset (cg-get g :reserve) 0 nil)
(aset (cg-get g :hand) 0 nil)
(aset (cg-get g :waste) 0 nil)
(should (cg-crap--won-p g 0))
(should-not (cg-crap--won-p g 1))))
(ert-deftest cgt-crap-ai-turn ()
(let ((g (cg-crap--deal (cg-crapette-game))))
(cl-flet ((total ()
(let ((n 0))
(dotimes (i 8)
(setq n (+ n (length (cg-crap--found g i))
(length (cg-crap--house g i)))))
(dotimes (p 2)
(setq n (+ n (length (cg-crap--reserve g p))
(length (cg-crap--waste g p))
(length (cg-crap--hand g p)))))
n)))
(should (= 104 (total)))
(cg-put g :turn 1)
(let ((cg-crap--recording nil)) (cg-crap--ai-play g))
(should (= 104 (total))) ; the AI turn conserves every card
(should-not (eq (cg-get g :winner) 0))))) ; the AI cannot make YOU win
(ert-deftest cgt-crap-ai-plays-foundation ()
(let ((g (cg-crap--deal (cg-crapette-game))))
(aset (cg-get g :reserve) 1 (list '(0 . 0))) ; AI reserve top = Ace of spades
(aset (cg-get g :waste) 1 nil)
(aset (cg-get g :hand) 1 nil)
(aset (cg-get g :found) 0 nil)
(cg-put g :turn 1)
(let ((cg-crap--recording nil)) (cg-crap--ai-play g))
(should (eq (cg-get g :winner) 1)))) ; plays its last card, wins
(ert-deftest cgt-pat-golf-deal ()
(let ((g (cg-pat--deal (cg-golf-game))))
(should (= 35 (length (cg-get g :cards))))