game.lisp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435
  1. (in-package #:game)
  2. #|
  3. CHECKER PLAY
  4. The rules of backgammon are partly incorporated in the function POSSIBLE-MOVES.
  5. It returns a forest of possible moves as a list. The car of each element is
  6. the first move, the cdr is the tree of possible continuations.
  7. |#
  8. (deftype die-pips () '(integer 1 6))
  9. (deftype origin () '(or point (eql board:bar)))
  10. (defun destination (from pips player)
  11. "Return the place where PLAYER's checker would land starting from FROM and travelling PIPS. The second value is T if the move is shorter than DISTANCE (possible when bearing off)."
  12. (check-type from origin)
  13. (check-type pips die-pips)
  14. (check-type player player)
  15. (if (eql from board:bar)
  16. (board:point-id (- 25 pips) player)
  17. (let ((point-number (board:point-number from player)))
  18. (if (> point-number pips)
  19. (board:point-id (- point-number pips) player)
  20. board:off))))
  21. (defun openp (point player board)
  22. (check-type player player)
  23. (check-type point point)
  24. (check-type board board::board)
  25. (multiple-value-bind (checkers whose) (board:checkers-on-point point board)
  26. (not (and (eql whose (opponent player))
  27. (>= checkers 2)))))
  28. (defun can-move-p (from pips player board)
  29. (check-type from origin)
  30. (check-type pips die-pips)
  31. (check-type player player)
  32. (and
  33. ;; you must have checkers where you want to move them from
  34. (board:has-checkers-on-p from player board)
  35. ;; if you've got checkers on the bar, you must enter them first
  36. (or (not (board:has-checkers-on-p board:bar player board))
  37. (eql from board:bar))
  38. ;; now let's check the destination
  39. (let ((to (destination from pips player)))
  40. ;; either you go to an open point
  41. (or (and (board:pointp to) (openp to player board))
  42. ;; or else you want to bear off, but in this case
  43. (and (eql to board:off)
  44. (let ((highest (board:highest player board)))
  45. ;; all your checkers must be at home and
  46. (and (<= highest 6)
  47. ;; you either use up all the pips
  48. (or (= (board:point-number from player) pips)
  49. ;; or bear off from the highest point
  50. (eql (board:point-id highest player) from)))))))))
  51. (defmacro do-origins ((var &optional result) &body body)
  52. `(do ((,var board:bar (if (eql ,var board:bar)
  53. 24
  54. (1- ,var))))
  55. ((eql ,var 0) ,result)
  56. ,@body))
  57. (defparameter +number-of-checkers+ 15)
  58. (defstruct (checker-move (:type list))
  59. (pips 1 :type die-pips)
  60. (from 1 :type origin)
  61. (to 1)
  62. (end? nil :type boolean))
  63. (defun possible-single-moves (pips player board)
  64. (let ((moves '()))
  65. (do-origins (from moves)
  66. (when (can-move-p from pips player board)
  67. (let* ((destination (destination from pips player))
  68. (end? (and (eql destination board:off)
  69. (= (board:checkers-off player board)
  70. (1- +number-of-checkers+)))))
  71. (push (make-checker-move :pips pips
  72. :from from
  73. :to destination
  74. :end? end?)
  75. moves))))))
  76. (defun possible-moves* (dice player board)
  77. (cond ((endp dice) '())
  78. ((endp (rest dice)) (mapcar #'list (possible-single-moves (first dice) player board)))
  79. (t (loop with new-board
  80. for move in (possible-single-moves (first dice) player board)
  81. for from = (checker-move-from move)
  82. for to = (checker-move-to move)
  83. for end? = (checker-move-end? move)
  84. if end?
  85. collect (list move)
  86. else
  87. do (setf new-board (board:move-checker from to player board))
  88. collect (cons move (possible-moves* (rest dice) player new-board))))))
  89. (defun possible-moves-different-dice (die1 die2 player board)
  90. (or (append (possible-moves* (list die1 die2) player board)
  91. (possible-moves* (list die2 die1) player board))
  92. (possible-moves* (list die1) player board)
  93. (possible-moves* (list die2) player board)))
  94. (defun possible-moves-doublet (die player board)
  95. (loop for k from 4 downto 1
  96. for moves = (possible-moves* (make-list k :initial-element die) player board)
  97. when moves do (return moves)
  98. finally (return '())))
  99. (defun position-possible-moves (die1 die2 player board)
  100. (check-type die1 die-pips)
  101. (check-type die2 die-pips)
  102. (check-type player player)
  103. (check-type board board::board)
  104. (if (/= die1 die2)
  105. (possible-moves-different-dice die1 die2 player board)
  106. (possible-moves-doublet die1 player board)))
  107. (defun remaining-moves (moves possible-moves)
  108. (if (endp moves)
  109. possible-moves
  110. (remaining-moves (rest moves) (rest (assoc (first moves) possible-moves :test #'equal)))))
  111. (defun full-move-p (moves possible-moves)
  112. (cond ((endp moves) (endp possible-moves))
  113. ((endp possible-moves) t)
  114. (t (full-move-p (rest moves) (rest (assoc (first moves) possible-moves
  115. :test #'equal))))))
  116. (defun check-next-move (from pips possible-moves &optional partial-moves)
  117. (if (endp partial-moves)
  118. (flet ((this-move-p (move)
  119. (and (eql (checker-move-from move) from)
  120. (eql (checker-move-pips move) pips))))
  121. (find-if #'this-move-p (mapcar #'first possible-moves)))
  122. (check-next-move from
  123. pips
  124. (rest (assoc (first partial-moves) possible-moves :test #'equal))
  125. (rest partial-moves))))
  126. (defclass game ()
  127. ((initial-throws :reader initial-throws :initform '())
  128. (dice-no :reader dice-no :initform 0)
  129. (moves :reader moves :initform '())
  130. (partial-moves :reader partial-moves :initform '())
  131. (board :reader board :initform (board:make-initial-board))
  132. (partial-board :reader partial-board)
  133. (cube :reader cube :initarg :cube :initform nil)
  134. (cube-owner :reader cube-owner :initform nil)
  135. (dice :reader dice :initform '())
  136. (rest-dice :reader rest-dice :initform '())
  137. (turn :reader turn :initform nil)
  138. (is-doubling :reader is-doubling :initform '())
  139. (winner :reader winner :initform nil)
  140. (result :reader result :initform nil)
  141. (jacoby :reader jacoby :initarg :jacoby :initform t)
  142. (session :reader session :initarg :session :initform nil)
  143. (possible-moves% :reader possible-moves :initform '())))
  144. (defmethod initialize-instance :after ((game game) &key &allow-other-keys)
  145. (setf (slot-value game 'partial-board) (board game)))
  146. (defun random-dice ()
  147. (let ((dice (random 36)))
  148. (multiple-value-bind (d1 d2) (floor dice 6)
  149. (list (1+ d1) (1+ d2)))))
  150. (defun dices-rest-dice (dice)
  151. (if (= (first dice) (second dice))
  152. (append dice dice)
  153. dice))
  154. (defgeneric game:roll-dice (game))
  155. (defmethod game:roll-dice ((game game))
  156. (with-slots (dice rest-dice dice-no) game
  157. (setf dice (random-dice)
  158. rest-dice (dices-rest-dice dice))
  159. (incf dice-no))
  160. game)
  161. (defmethod roll-dice :after ((game game))
  162. (setf (slot-value game 'possible-moves%) (position-possible-moves (first (dice game))
  163. (second (dice game))
  164. (turn game)
  165. (board game))))
  166. (defgeneric set-turn (game))
  167. (defmethod set-turn ((game game))
  168. (with-slots (turn rest-dice dice-no) game
  169. (setf turn (if (> (first (dice game))
  170. (second (dice game)))
  171. :white
  172. :black)
  173. rest-dice (dice game)
  174. dice-no 1))
  175. game)
  176. (defmethod set-turn :after ((game game))
  177. (setf (slot-value game 'possible-moves%) (position-possible-moves (first (dice game))
  178. (second (dice game))
  179. (turn game)
  180. (board game))))
  181. (defgeneric move-checker (from pips game))
  182. ;;; Generally, we avoid enforcing any checks. Maybe this check-next-move should be removed as well? The caller should take care of that.
  183. (defmethod move-checker (from pips (game game))
  184. (let ((move (check-next-move from pips (possible-moves game) (partial-moves game))))
  185. (when (null move)
  186. (error "Cannot move from ~A by ~A." from pips))
  187. (with-slots (partial-moves partial-board rest-dice) game
  188. (setf partial-moves (append partial-moves (list move))
  189. partial-board (board:move-checker from (checker-move-to move) (turn game) partial-board)
  190. rest-dice (remove pips rest-dice :count 1)))
  191. game))
  192. (defgeneric set-winner (player game reason))
  193. (defmethod set-winner (player (game game) reason)
  194. (check-type player player)
  195. (with-slots (winner result) game
  196. (setf winner player
  197. result (ecase reason
  198. (:completed (ecase (board:loss (opponent player) (board game))
  199. (1 :single-game)
  200. (2 :gammon)
  201. (3 :backgammon)))
  202. (:dropped-double :dropped-double)))))
  203. (defun game-score (game)
  204. (let* ((cube (or (cube game) 1))
  205. (jacoby? (jacoby game))
  206. (value (ecase (result game)
  207. (:single-game cube)
  208. (:gammon (if (and jacoby? (= cube 1))
  209. 1
  210. (* cube 2)))
  211. (:backgammon (if (and jacoby? (= cube 1))
  212. 1
  213. (* cube 3)))
  214. (:dropped-double cube)
  215. ((nil) 0))))
  216. (cond ((zerop value) 0)
  217. ((player= (winner game) :white) value)
  218. (t (- value)))))
  219. (defun games-score (games)
  220. (loop for game in games
  221. for score = (game-score game)
  222. when (plusp score) sum score into white-score
  223. when (minusp score) sum (- score) into black-score
  224. finally (return (list white-score black-score))))
  225. (defun score (session)
  226. (games-score (games session)))
  227. (defgeneric finish-move (game))
  228. (defmethod finish-move ((game game))
  229. (let ((end? (some #'checker-move-end? (partial-moves game))))
  230. (with-slots (moves partial-moves board) game
  231. (push partial-moves moves)
  232. (setf partial-moves '()
  233. board (partial-board game)))
  234. (if end?
  235. (set-winner (turn game) game)))
  236. game)
  237. (defgeneric opponents-turn (game))
  238. (defmethod opponents-turn ((game game))
  239. (with-slots (turn dice) game
  240. (setf turn (opponent turn)
  241. dice '())
  242. game))
  243. (defgeneric undo-move (game))
  244. (defmethod undo-move ((game game))
  245. (unless (null (partial-moves game))
  246. (let ((player (turn game)))
  247. (with-slots (partial-moves partial-board rest-dice) game
  248. (setf partial-moves (butlast partial-moves)
  249. partial-board (board game)
  250. rest-dice (dices-rest-dice (dice game)))
  251. (dolist (move partial-moves)
  252. (setf partial-board (board:move-checker (checker-move-from move)
  253. (checker-move-to move)
  254. player
  255. partial-board))
  256. (setf rest-dice (remove (checker-move-pips move) rest-dice :count 1))))
  257. game)))
  258. (defgeneric offer-double (game))
  259. (defmethod offer-double ((game game))
  260. (with-slots (moves is-doubling) game
  261. (setf is-doubling (turn game))
  262. (push :double moves)))
  263. (defgeneric accept-double (game))
  264. (defmethod accept-double ((game game))
  265. (with-slots (moves cube cube-owner is-doubling) game
  266. (setf cube (* 2 cube)
  267. cube-owner (turn game)
  268. is-doubling nil)
  269. (push :accept moves)))
  270. (defgeneric refuse-double (game))
  271. (defmethod refuse-double ((game game))
  272. (with-slots (moves is-doubling) game
  273. (setf is-doubling nil)
  274. (push :drop moves)))
  275. (defparameter *default-game-class* 'game)
  276. (defparameter *default-match-class* 'match)
  277. (defparameter *default-money-session-class* 'money-session)
  278. (defclass session ()
  279. ((games :reader games :initform '())
  280. (jacobyp :reader jacobyp :initarg :jacoby)
  281. (game-class :reader game-class :initarg :game-class :initform *default-game-class*)
  282. (cube :reader cube :initarg :cube)))
  283. (defclass match (session)
  284. ((games :reader games :initform '())
  285. (limit :reader limit :initarg :limit)
  286. (jacobyp :reader jacobyp :initarg :jacoby :initform nil)
  287. (crawfordp :reader crawfordp :initarg :crawford :initform t)
  288. (crawford-game :reader crawford-game :initform nil)))
  289. (defclass money-session (session)
  290. ((jacobyp :reader jacobyp :initarg :jacoby :initform t)
  291. (continuation-query :reader continuation-query :initarg :continuation-query :initform (constantly t))
  292. (finished? :initform nil)))
  293. (defgeneric finished-p (session))
  294. (defmethod finished-p ((session money-session))
  295. (slot-value session 'finished?))
  296. (defmethod finished-p ((match match))
  297. (>= (apply #'max (score match)) (limit match)))
  298. (defun make-match (limit &key (class *default-match-class*) (game-class *default-game-class*))
  299. (make-instance class
  300. :limit limit
  301. :game-class game-class))
  302. (defun make-money-session (continuation-query &key (class *default-money-session-class*) (game-class *default-game-class*) )
  303. (make-instance class
  304. :continuation-query continuation-query
  305. :game-class game-class))
  306. (defun game (session)
  307. (first (games session)))
  308. (defgeneric opponent-can-double-p (session))
  309. (defmethod opponent-can-double-p ((game game))
  310. (and (not (null (cube game)))
  311. (not (player-equal (turn game)
  312. (cube-owner game)))))
  313. (defmethod opponent-can-double-p ((session money-session))
  314. (opponent-can-double-p (game session)))
  315. (defun crawford-game-p (game)
  316. (let ((session (session game)))
  317. (and (typep session 'match)
  318. (eql game (crawford-game session)))))
  319. (defun points (player session)
  320. (let ((score (score session)))
  321. (ecase player
  322. (:white (first score))
  323. (:black (second score)))))
  324. (defmethod opponent-can-double-p ((match match))
  325. (let ((game (game match)))
  326. (and (cube match)
  327. (null (winner game))
  328. (not (crawford-game-p game))
  329. (< (+ (points (opponent (turn game)) match)
  330. (cube game))
  331. (limit match))
  332. (opponent-can-double-p game))))
  333. (defgeneric start-new-game (session))
  334. (defmethod start-new-game ((session money-session))
  335. (let ((game (make-instance (game-class session)
  336. :session session
  337. :cube (and (cube session) 1)
  338. :jacoby (jacobyp session))))
  339. (push game (slot-value session 'games))))
  340. (defun next-game-crawford-p (match)
  341. (let ((games (games match))
  342. (limit (limit match)))
  343. (and (cube match)
  344. (>= (length (games match)) 1)
  345. (= (apply #'max (games-score games)) (1- limit))
  346. (< (apply #'max (games-score (rest games))) (1- limit)))))
  347. (defmethod start-new-game ((match match))
  348. (let ((crawford? (next-game-crawford-p match)))
  349. (let ((game (make-instance (game-class match)
  350. :session match
  351. :cube (and (cube match)
  352. (not crawford?)
  353. 1)
  354. :jacoby (jacobyp match))))
  355. (push game (slot-value match 'games))
  356. (when crawford?
  357. (setf (slot-value match 'crawford-game) game)))))
  358. (defmethod winner ((match match))
  359. (let ((score (score match))
  360. (limit (limit match)))
  361. (cond ((>= (first score) limit) :white)
  362. ((>= (second score) limit) :black)
  363. (t nil))))