event.lisp 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647
  1. (defpackage #:event (:use #:cl))
  2. (in-package #:event)
  3. #|
  4. The dynamics of the game is implemented in the event-driven style.
  5. In theory, one event queue can serve a few tables with simultaneous matches.
  6. |#
  7. (defvar *event-queue*)
  8. #|
  9. We use a thread safe implementation of the event queue based on safe-queue's mailboxes.
  10. |#
  11. (defclass safe-queue ()
  12. ((mailbox :initform (safe-queue:make-mailbox))))
  13. (defun make-event-queue ()
  14. (make-instance 'safe-queue))
  15. (defun put-event (event &optional (event-queue *event-queue*))
  16. (safe-queue:mailbox-send-message (slot-value event-queue 'mailbox) event))
  17. (defun get-event (&optional (event-queue *event-queue*))
  18. (safe-queue:mailbox-receive-message (slot-value event-queue 'mailbox)))
  19. #|
  20. GAME EVENTS drive a single game. For the most part, they are generated by the
  21. players, but sometimes, by the game itself.
  22. Player generated events:
  23. move-event user from pips
  24. undo-event user
  25. finish-move-event user
  26. TODO cube events
  27. Game generated events:
  28. first-move-event
  29. dice-event
  30. Each event includes the game itself as the *source* in case several games are
  31. simultaneously served by the same event loop.
  32. |#
  33. ;;; TODO either supply missing types, or remove them altogether
  34. (defstruct event
  35. source)
  36. (defstruct (player-event (:include event))
  37. (player :white :type game:player))
  38. (defstruct (move-event (:include player-event))
  39. (from 1 :type game::origin)
  40. (pips 1 :type game::die-pips))
  41. (defstruct (undo-event (:include player-event) :named))
  42. (defstruct (finish-move-event (:include player-event)))
  43. (defstruct (first-move-event (:include event)))
  44. (defstruct (dice-event (:include event)))
  45. (defstruct (offer-double-event (:include player-event))
  46. offered-p)
  47. (defstruct (accept-double-event (:include player-event))
  48. accepted-p)
  49. (defstruct (pause-event (:include event)))
  50. (defstruct (initial-roll-event (:include event))
  51. dice)
  52. ;(defvar *event*)
  53. #|
  54. Events trigger changes of game's states. Typically, to compute the next state,
  55. we need a few boolean properties of the event. It is useful to compute such
  56. properties simultaneously and to return them as multiple values in order not to
  57. duplicate calculations.
  58. |#
  59. #|
  60. When moving checkers, a player can make up to 4 moves (not counting undoes).
  61. Each of them triggers an event. For such an event we want to know whether it
  62. completes the full move or not and whether it concludes the game.
  63. |#
  64. (defun check-event-move (event)
  65. "If EVENT corresponds to a valid move in its source game, return the move."
  66. (if (not (move-event-p event))
  67. nil
  68. (let ((game (event-source event))
  69. (player (move-event-player event)))
  70. (if (not (eql player (game:turn game)))
  71. nil
  72. (game::check-next-move (move-event-from event)
  73. (move-event-pips event)
  74. (game::possible-moves game)
  75. (game::partial-moves game))))))
  76. ;; => admissible-partial-move? admissible-full-move? admissible-final-move?
  77. (defun move-event-properties (event)
  78. (let ((next-move (check-event-move event))
  79. (game (event-source event)))
  80. (if (null next-move)
  81. (values nil nil nil)
  82. (let ((full? (game::full-move-p (append (game::partial-moves game)
  83. (list next-move))
  84. (game::possible-moves game))))
  85. (values (not full?) full? (game::checker-move-end? next-move))))))
  86. #|
  87. Undo: 1. is it a legal undo? 2. once performed, is there anything else to undo?
  88. |#
  89. (defun undo-event-properties (event)
  90. (let* ((game (event-source event))
  91. (undo? (and (undo-event-p event)
  92. (game:player-equal (undo-event-player event) (game::turn game)))))
  93. (values undo? (and undo? (<= (length (game::partial-moves game)) 1)))))
  94. #|
  95. Is it legal for the player to finish the checker move?
  96. |#
  97. (defun admissible-finishing-p (event)
  98. (let ((game (event-source event)))
  99. (and (finish-move-event-p event)
  100. (game:player-equal (player-event-player event) (game::turn game))
  101. (game::full-move-p (game::partial-moves game) (game::possible-moves game)))))
  102. #|
  103. Is a double offered? Is it not offered? The latter refers to the action of
  104. not offering the double, so both questions can be answered `no' if offering a
  105. double is out of place.
  106. |#
  107. ;; double-offered? double-not-offered?
  108. (defun offer-double-event-properties (event)
  109. (if (and (offer-double-event-p event)
  110. (game:player-equal (player-event-player event)
  111. (game:turn (event-source event))))
  112. (let ((offered? (offer-double-event-offered-p event)))
  113. (values offered? (not offered?)))
  114. (values nil nil)))
  115. #|
  116. Check if the double is being accepted or not. As above, accepting a double may
  117. be out of place.
  118. |#
  119. ;; double-accepted? double-not-accepted?
  120. (defun accept-double-event-properties (event)
  121. (if (and (accept-double-event-p event)
  122. (game:player-equal (player-event-player event) (game:turn (event-source event))))
  123. (let ((accepted? (accept-double-event-accepted-p event)))
  124. (values accepted? (not accepted?)))
  125. (values nil nil)))
  126. #|
  127. The following event is used in a money session when asking the players if they
  128. would like to keep on playing.
  129. |#
  130. (defstruct (continue-reply-event (:include player-event))
  131. reply)
  132. #|
  133. When examining the dice, we would like to know if there are available moves or not.
  134. |#
  135. (defun dice-event-properties (event)
  136. (let ((game (event-source event)))
  137. (if (dice-event-p event)
  138. (let ((cannot-move? (null (game::possible-moves game))))
  139. (values (not cannot-move?) cannot-move?))
  140. (values nil nil))))
  141. #|
  142. After the dice have been rolled, we assume that the game enters the dice
  143. examination state (see below for a detailed explanation of the FSM). Then one
  144. more transition occurs, either to a checker move or the turn passes to the
  145. opponent. This second transition is not triggered by players. In a purely
  146. event-driven setting the game itself must emit the event.
  147. |#
  148. (defclass event-driven-game (game:game)
  149. ())
  150. (defmethod game:roll-dice :after ((game event-driven-game))
  151. (put-event (make-dice-event :source game)))
  152. ;;; TODO Urgent! The variables defined in game.lisp must be used instead.
  153. (defvar *game-class* 'event-driven-game)
  154. (defvar *match-class* 'game::match)
  155. (defvar *money-session-class* 'game::money-session)
  156. #|
  157. WELCOME TO THE MACHINE
  158. From the point of view of control, there are only so many states the game can
  159. be in:
  160. 1. Start (about to roll the dice & set the first turn).
  161. 2. One of the players is moving the checkers.
  162. 3. One of the players is deciding whether to double.
  163. 4. One of the players is deciding whether to accept a double.
  164. 5. The dice are examined in order to see if there are admissible moves.
  165. 6. End.
  166. Remark. We could do without state 5. In this case if the player rolled the
  167. dice and there were no admissible moves, he would still have to manually
  168. complete the move. State 5 allows to automatically pass the turn to the
  169. opponent. As of now, this is done instantly. A pause state should be added to
  170. provide a delay.
  171. Events trigger transitions between states. A sequence of accompanying actions
  172. is is calculated on the basis of the current state, event, and game state.
  173. We model the control by a Mealy machine. It incorporates nearly all the rules
  174. of backgammon and orchestrates the game actions. In particular, it ensures
  175. that an action only takes place when appropriate.
  176. A single machine is enough to take care of any game setting. For instance, if
  177. the game is played without doubles, the unavailability of the cube is a
  178. property of the game, and no event can bring it into a state when doubling is
  179. possible.
  180. |#
  181. (eval-when (:compile-toplevel :load-toplevel :execute)
  182. (defmethod fsm:state-bindings ((machine t) (state t) machine-arguments)
  183. (lambda (form)
  184. form))
  185. (defmethod fsm:state-bindings ((machine (eql 'backgammon-machine)) (state (eql 'checker-move)) arguments)
  186. (lambda (form)
  187. `(multiple-value-bind (admissible-partial-move? admissible-full-move? admissible-final-move?) (move-event-properties ,(first arguments))
  188. (multiple-value-bind (undo? last-undo?) (undo-event-properties ,(first arguments))
  189. (let* ((admissible-finishing? (admissible-finishing-p ,(first arguments)))
  190. ;; if *event* is an admissible finishing, its source MUST be a game
  191. (game (event-source ,(first arguments)))
  192. ;; doubling must be checked with respect to a session
  193. ;; but if by any chance the game is not a part of a session,
  194. ;; we check it with respect to the game itself
  195. (opponent-can-double? (game:opponent-can-double-p (or (game::session game) game))))
  196. ,form)))))
  197. (defmethod fsm:state-bindings ((machine (eql 'backgammon-machine)) (state (eql 'dice)) arguments)
  198. (lambda (form)
  199. `(multiple-value-bind (can-move? cannot-move?) (dice-event-properties ,(first arguments))
  200. ,form)))
  201. (defmethod fsm:state-bindings ((machine (eql 'backgammon-machine)) (state (eql 'cannot-move-pause)) arguments)
  202. (lambda (form)
  203. `(multiple-value-bind (opponent-can-double? opponent-cannot-double?)
  204. (if (pause-event-p ,(first arguments))
  205. (let ((opponent-can-double? (game:opponent-can-double-p (or (game::session game) game))))
  206. (values opponent-can-double? (not opponent-can-double?)))
  207. (values nil nil))
  208. ,form)))
  209. (defmethod fsm:state-bindings ((machine (eql 'backgammon-machine)) (state (eql 'offer-double?)) arguments)
  210. (lambda (form)
  211. `(multiple-value-bind (double-offered? double-not-offered?) (offer-double-event-properties ,(first arguments))
  212. ,form)))
  213. (defmethod fsm:state-bindings ((machine (eql 'backgammon-machine)) (state (eql 'accept-double?)) arguments)
  214. (lambda (form)
  215. `(multiple-value-bind (double-accepted? double-not-accepted?) (accept-double-event-properties ,(first arguments))
  216. ,form)))
  217. )
  218. (defparameter *delay* 2)
  219. ;; TODO reuse the scheduled function
  220. (defun schedule-pause (game &optional (event-queue *event-queue*))
  221. (portable-threads:schedule-function-relative
  222. (portable-threads:make-scheduled-function (lambda (f)
  223. (declare (ignore f))
  224. (put-event (make-pause-event :source game) event-queue)))
  225. *delay*))
  226. #|
  227. Here it comes. It's monolithic by design, since so are the rules of the game.
  228. All the logic in one place. Behold!
  229. Frankly, perhaps we could use some machine decomposition. But we haven't got it yet. So we'd better console ourselves that it would be more trouble than it's worth.
  230. |#
  231. (fsm:defmachine backgammon-machine (event game ui)
  232. (:initial-state first-roll)
  233. ;; TODO the start state needs polishing
  234. ;; TODO game API: something about initial dice; initial-throws are obsolete
  235. (:state first-roll
  236. (:arc
  237. :to first-roll-pause
  238. :when (initial-roll-event-p event)
  239. :action (progn
  240. (setf (slot-value game 'game::dice) (initial-roll-event-dice event))
  241. (schedule-pause game)
  242. (ui:refresh game ui))))
  243. (:state first-roll-pause
  244. (:arc
  245. :to checker-move
  246. :when (and (pause-event-p event)
  247. (/= (first (game::dice game))
  248. (second (game::dice game))))
  249. :action (progn
  250. (game:set-turn game)
  251. (ui:unlock-only '(:checkers) (game:turn game) ui)
  252. (ui:refresh game ui)))
  253. (:arc
  254. :to first-roll
  255. :when (and (pause-event-p event)
  256. (= (first (game::dice game))
  257. (second (game::dice game))))
  258. :action (put-event (make-initial-roll-event :source game :dice (game::random-dice)))))
  259. (:state checker-move
  260. (:arc
  261. :to checker-move
  262. :when (and admissible-partial-move? (not admissible-final-move?))
  263. :action (progn
  264. (game:move-checker (move-event-from event) (move-event-pips event) game)
  265. (ui:unlock-only '(:checkers :undo) (game:turn game) ui)
  266. (ui:refresh game ui)))
  267. (:arc
  268. :to checker-move
  269. :when (and admissible-full-move? (not admissible-final-move?))
  270. :action (progn
  271. (game:move-checker (move-event-from event) (move-event-pips event) game)
  272. (ui:unlock-only '(:checkers :undo :finish-move) (game:turn game) ui)
  273. (ui:refresh game ui)))
  274. (:arc
  275. :to end-game
  276. :when admissible-final-move?
  277. :action (progn
  278. (game:move-checker (move-event-from event) (move-event-pips event) game)
  279. (game:set-winner (game:turn game) game :completed)
  280. (ui:unlock-only '() (game:turn game) ui)
  281. (ui:refresh game ui)))
  282. (:arc
  283. :to checker-move
  284. :when (and undo? (not last-undo?))
  285. :action (progn
  286. (game:undo-move game)
  287. (ui:unlock-only '(:checkers :undo) (game:turn game) ui)
  288. (ui:refresh game ui)))
  289. (:arc
  290. :to checker-move
  291. :when last-undo?
  292. :action (progn
  293. (game:undo-move game)
  294. (ui:unlock-only '(:checkers) (game:turn game) ui)
  295. (ui:refresh game ui)))
  296. (:arc
  297. :to dice
  298. :when (and admissible-finishing? (not opponent-can-double?))
  299. :action (progn
  300. (game:finish-move game)
  301. (game:opponents-turn game)
  302. (ui:unlock-only '() (game:turn game) ui)
  303. (game:roll-dice game)
  304. (ui:refresh game ui)))
  305. (:arc
  306. :to offer-double?
  307. :when (and admissible-finishing? opponent-can-double?)
  308. :action (progn
  309. (game:finish-move game)
  310. (game:opponents-turn game)
  311. (ui:unlock-only '(:offer-double) (game:turn game) ui)
  312. (ui:refresh game ui))))
  313. (:state dice
  314. (:arc
  315. :to checker-move
  316. :when can-move?
  317. :action (progn
  318. (ui:unlock-only '(:checkers) (game:turn game) ui)
  319. (ui:refresh game ui)))
  320. (:arc
  321. :to cannot-move-pause
  322. :when cannot-move?
  323. :action (progn
  324. (ui:refresh game ui)
  325. (setf (ui:pausedp ui) t)
  326. (schedule-pause game))))
  327. (:state cannot-move-pause
  328. (:arc
  329. :to dice
  330. :when opponent-cannot-double?
  331. :action (progn
  332. (game:opponents-turn game)
  333. (game:roll-dice game)
  334. (ui:unlock-only '() (game:turn game) ui)
  335. (setf (ui:pausedp ui) nil)
  336. (ui:refresh game ui)))
  337. (:arc
  338. :to offer-double?
  339. :when opponent-can-double?
  340. :action (progn
  341. (game:opponents-turn game)
  342. (setf (ui:pausedp ui) nil)
  343. (ui:unlock-only '(:offer-double) (game:turn game) ui)
  344. (ui:refresh game ui))))
  345. (:state offer-double?
  346. (:arc
  347. :to dice
  348. :when double-not-offered?
  349. :action (progn
  350. (game:roll-dice game)
  351. (ui:unlock-only '() (game:turn game) ui)
  352. (ui:refresh game ui)))
  353. (:arc
  354. :to accept-double?
  355. :when double-offered?
  356. :action (progn
  357. (game:offer-double game)
  358. (game:opponents-turn game)
  359. (ui:unlock-only '(:accept-double) (game:turn game) ui)
  360. (ui:refresh game ui))))
  361. (:state accept-double?
  362. (:arc
  363. :to dice
  364. :when double-accepted?
  365. :action (progn
  366. (game:accept-double game)
  367. (game:opponents-turn game)
  368. (game:roll-dice game)
  369. (ui:unlock-only '() (game:turn game) ui)
  370. (ui:refresh game ui)))
  371. (:arc
  372. :to end-game
  373. :when double-not-accepted?
  374. :action (progn
  375. (game:refuse-double game)
  376. (game:set-winner (game:opponent (game:turn game)) game :dropped-double)
  377. (ui:unlock-only '() (game:turn game) ui)
  378. (ui:refresh game ui))))
  379. (:state end-game))
  380. #|
  381. Now we'll define handlers for money sessions and matches. A match continues as
  382. long as it continues. A money session goes on as long as the players are
  383. willing to play. After each game, we ask if they would like to continue.
  384. The handlers are considerably simpler than the game handler, so we don't use
  385. explicit machines here. Maybe later.
  386. |#
  387. ;; TODO I think now it's a part of game-info. Anyway, neither hunchensocket nor json belong here.
  388. (defun send-score (table)
  389. (let* ((score (game::score (session table)))
  390. (message (jonathan:to-json `("score" ,@score))))
  391. (dolist (user (hunchensocket:clients table))
  392. (hunchensocket:send-text-message user message))))
  393. #|
  394. TODO
  395. Move it to ui.lisp.
  396. The copy is adapted from ui:refresh. Perhaps the need of adaptation means that
  397. the tools are inconvenient. Rethink.
  398. |#
  399. (defun ui-continue-query (ui)
  400. (loop for connection in (ui::connections ui)
  401. for user = (ui::connection-user connection)
  402. for player = (flet ((user (player)
  403. (ui::player-user player ui)))
  404. (find user '(:white :black) :key #'user))
  405. when player
  406. do (ui::send-to-connection '("continue?") connection)))
  407. #|
  408. This is a factory of handlers for determining if the money session continues.
  409. |#
  410. (defun make-continue-query-handler ()
  411. (let ((agreed '()))
  412. (lambda (event)
  413. (if (continue-reply-event-p event)
  414. (if (continue-reply-event-reply event)
  415. (progn
  416. (pushnew (continue-reply-event-player event) agreed)
  417. (if (subsetp '(:white :black) agreed)
  418. 'yes
  419. 'undecided))
  420. (values 'no (continue-reply-event-player event)))
  421. 'undecided))))
  422. (defun make-money-handler (session ui)
  423. (let ((game-handler (backgammon-machine))
  424. continuation-query-handler)
  425. (lambda (event)
  426. ;; we only handle game events
  427. ;; we could also handle session-specific events
  428. ;; TODO mark end of session
  429. (cond ((and game-handler
  430. (eql (event-source event) (game:game session)))
  431. (funcall game-handler event (game:game session) ui)
  432. (when (game::winner (game::game session))
  433. (setf game-handler nil
  434. continuation-query-handler (make-continue-query-handler))
  435. (send-score (table ui)) ; todo is it needed?
  436. (ui-continue-query ui)))
  437. ((and (null game-handler)
  438. (eql (event-source event) session))
  439. (let ((query (funcall continuation-query-handler event)))
  440. (case query
  441. (undecided t)
  442. (yes
  443. (game::start-new-game session)
  444. (setf game-handler (backgammon-machine))
  445. (dolist (player '(:white black))
  446. (setf (ui:checkers-locked-p player ui) t)
  447. (setf (ui:offer-double-locked-p player ui) t)
  448. (setf (ui:accept-double-locked-p player ui) t)
  449. (setf (ui:finish-move-locked-p player ui) t)
  450. (setf (ui:undo-locked-p player ui) t))
  451. (put-event (make-initial-roll-event :source (game:game session) :dice (game::random-dice))))
  452. (no :stop))))))))
  453. #|
  454. The match handler is fairly simple, but perhaps would be clearer if represented as a machine.
  455. |#
  456. (defun make-match-handler (match ui)
  457. (let ((game-handler (backgammon-machine))
  458. (pause? nil))
  459. (lambda (event)
  460. (unless (game::winner match)
  461. (if pause?
  462. (when (pause-event-p event)
  463. (setf pause? nil)
  464. (game::start-new-game match)
  465. (setf game-handler (backgammon-machine))
  466. (dolist (player '(:white black))
  467. (setf (ui:checkers-locked-p player ui) t)
  468. (setf (ui:offer-double-locked-p player ui) t)
  469. (setf (ui:accept-double-locked-p player ui) t)
  470. (setf (ui:finish-move-locked-p player ui) t)
  471. (setf (ui:undo-locked-p player ui) t))
  472. (put-event (make-initial-roll-event :source (game:game match) :dice (game::random-dice))))
  473. (progn
  474. (funcall game-handler event (game:game match) ui)
  475. (when (game::winner (game:game match))
  476. (setf pause? t)
  477. (schedule-pause (game:game match)) ; perhaps can associate it with the session instead?
  478. (send-score (table ui)))))))))
  479. #|
  480. The following definitions actually belong to the client-server protocol and
  481. should be removed to another file.
  482. |#
  483. (defvar *event-codes* '())
  484. (defmacro define-event-code ((code &rest arguments) form)
  485. `(push (list ,code (lambda (encoded-event)
  486. (destructuring-bind ,arguments (rest encoded-event)
  487. ,form)))
  488. *event-codes*))
  489. (defvar *game*)
  490. (defvar *player*)
  491. (defvar *session*)
  492. (defvar *table*)
  493. (defvar *user*)
  494. (defun decode-event (event)
  495. (let ((decoder (second (assoc (first event) *event-codes* :test #'string=))))
  496. (if (null decoder)
  497. (error "Cannot decode event ~A." event)
  498. (funcall decoder event))))
  499. (define-event-code ("m" from pips) (make-move-event :source *game*
  500. :player *player*
  501. :from (if (equal from "bar")
  502. board:bar
  503. from)
  504. :pips pips))
  505. (define-event-code ("u") (make-undo-event :source *game*
  506. :player *player*))
  507. (define-event-code ("f") (make-finish-move-event :source *game*
  508. :player *player*))
  509. (define-event-code ("c" reply) (make-continue-reply-event :source *session* :player *player* :reply (equal reply "yes")))
  510. (define-event-code ("offer-double" yes-no) (make-offer-double-event :source *game* :player *player* :offered-p (string= yes-no "yes")))
  511. (define-event-code ("accept-double" yes-no) (make-accept-double-event :source *game* :player *player* :accepted-p (string= yes-no "yes")))
  512. (define-event-code ("preference" option value) (make-user-preference-event :source *table* :user *user* :option option :value value))