table.lisp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  1. (defpackage #:table
  2. (:use #:cl))
  3. (in-package #:event)
  4. (defclass connection (hunchensocket:websocket-client)
  5. ((web-session :accessor web-session :initarg web-session :initform nil)))
  6. (defmethod ui::connection-user ((connection connection))
  7. (hunchentoot:session-value :user (web-session connection)))
  8. (defclass table (hunchensocket:websocket-resource)
  9. ((id :initarg :id :initform (error "Name this room!") :reader table-id)
  10. (ui :reader ui)
  11. (session :reader session)
  12. (event-queue :accessor event-queue)
  13. (proposal :accessor proposal :initform nil)
  14. (proposals :accessor proposals :initform '()))
  15. (:default-initargs :client-class 'connection))
  16. (defclass hunchensocket-ui (ui::multiple-connections-ui)
  17. ((table :accessor table :initarg :table)))
  18. (defmethod ui::connections ((ui hunchensocket-ui))
  19. (hunchensocket:clients (table ui)))
  20. (defmethod ui::send-to-connection (message (connection connection))
  21. (hunchensocket:send-text-message connection (jonathan:to-json message)))
  22. (defmethod initialize-instance :after ((table table) &key &allow-other-keys)
  23. (setf (slot-value table 'ui) (make-instance 'hunchensocket-ui :table table)))
  24. (defstruct (client-connected-event (:include event))
  25. connection)
  26. (defmethod hunchensocket:client-connected ((table table) (connection connection))
  27. (setf (web-session connection) hunchentoot:*session*)
  28. (put-event (make-client-connected-event :source table :connection connection) (event-queue table)))
  29. #+nil (defmethod hunchensocket:client-connected ((table table) (connection connection))
  30. (setf (web-session connection) hunchentoot:*session*)
  31. (let ((ui (ui table)))
  32. (unless (member (hunchentoot:session-value :user hunchentoot:*session*)
  33. (list (ui::player-user :white ui)
  34. (ui::player-user :black ui)))
  35. (dolist (player '(:white :black))
  36. (when (null (ui::player-user player ui))
  37. (setf (ui::player-user player ui) (hunchentoot:session-value :user hunchentoot:*session*))
  38. (hunchensocket:send-text-message connection (jonathan:to-json `("player" ,(string-downcase (string player)))))
  39. (return))))
  40. ;; ...
  41. (dolist (player '(:white :black))
  42. (when (ui::user= (ui::connection-user connection) (ui::player-user player ui))
  43. (hunchensocket:send-text-message connection (jonathan:to-json `("player" ,(string-downcase (string player)))))
  44. (return)))
  45. (when (and (ui::player-user :white ui)
  46. (ui::player-user :black ui)
  47. (null (game:turn (game:game (session table))))
  48. (null (game::dice (game:game (session table)))))
  49. (put-event (make-initial-roll-event :source (game:game (session table))
  50. :dice (game::random-dice))
  51. (event-queue table))))
  52. #+nil (destructuring-bind (white-points black-points) (game::score (session table))
  53. (hunchensocket:send-text-message user (jonathan:to-json `("score" ,white-points ,black-points))))
  54. (ui:refresh (game:game (session table)) (ui table)))
  55. ;; TODO formalize the protocol
  56. (defvar *connection*)
  57. (defmethod hunchensocket:text-message-received ((table table) connection message)
  58. ;; todo error handling
  59. (let ((e (ignore-errors (let ((*player* (find-if (lambda (player)
  60. (eql (ui::player-user player (ui table))
  61. (ui::connection-user connection)))
  62. '(:white :black)))
  63. (*game* (game:game (session table)))
  64. (*session* (session table))
  65. (*connection* connection)
  66. (*user* (ui::connection-user connection))
  67. (*table* table))
  68. (decode-event (jonathan:parse message))))))
  69. (when e
  70. (let ((*event-queue* (event-queue table)))
  71. (put-event e)))))
  72. (defmethod hunchensocket:client-disconnected ((table table) (user connection))
  73. )
  74. (defclass match-table (table)
  75. ())
  76. (defmethod initialize-instance :after ((table match-table) &key
  77. limit
  78. (game-class *game-class*)
  79. (match-class *match-class*)
  80. (cube t)
  81. &allow-other-keys)
  82. (check-type limit (integer 1 21))
  83. (setf (slot-value table 'session) (make-instance match-class
  84. :game-class game-class
  85. :cube cube
  86. :limit limit)))
  87. (defclass money-session-table (table)
  88. ())
  89. (defmethod initialize-instance :after ((table money-session-table) &key
  90. (game-class *game-class*)
  91. (match-class *money-session-class*)
  92. (cube t)
  93. (jacoby t)
  94. &allow-other-keys)
  95. (setf (slot-value table 'session) (make-instance match-class
  96. :game-class game-class
  97. :cube cube
  98. :jacoby jacoby)))
  99. (defstruct (user-preference-event (:include event))
  100. user
  101. option
  102. value)
  103. ;;; hmmm
  104. #+nil (defun user-preference-event-handler (event)
  105. (let ((user (user-preference-event-user event))
  106. (option (user-preference-event-option event))
  107. (value (user-preference-event-value event)))
  108. (setf (gethash option (user::preferences user)) value)))
  109. (defstruct (proposal-event (:include event))
  110. connection)
  111. (defstruct (proposal-decision-event (:include event))
  112. accept-p)
  113. (defstruct (proposal-cancel-event (:include event))
  114. user)
  115. ;; TODO ??? spontaneous doubling in proposals?
  116. ;; TOOD proposal-decision-event received twice
  117. ;; TODO error handling
  118. ;; TODO call a generic function on UI
  119. (defun show-proposal (table)
  120. (let* ((ui (ui table))
  121. (white (ui::player-user :white ui))
  122. (from (ui::connection-user (proposal table))))
  123. (dolist (connection (ui::connections ui))
  124. (when (eql (ui::connection-user connection) white)
  125. (hunchensocket:send-text-message connection (jojo:to-json `("proposal" ,(ui::name from))))))))
  126. (defun cancel-all-user-proposals (user table)
  127. (setf (slot-value table 'proposals)
  128. (loop for connection in (proposals table)
  129. if (eql (ui::connection-user connection) user) do (hunchensocket:close-connection connection)
  130. else collect connection)))
  131. (defun reject-all-user-proposals (user table)
  132. (setf (slot-value table 'proposals)
  133. (loop for connection in (proposals table)
  134. if (eql (ui::connection-user connection) user) do (ui::send-to-connection '("rejected") connection) (hunchensocket:close-connection connection)
  135. else collect connection)))
  136. (defun accept-proposal (table)
  137. (let ((black (ui::connection-user (proposal table))))
  138. (dolist (connection (ui::connections (ui table)))
  139. (ui::send-to-connection (if (eql (ui::connection-user connection) black)
  140. '("accepted")
  141. '("rejected"))
  142. connection)
  143. #+nil (hunchensocket:close-connection connection))
  144. ;; this shouldn't be here
  145. (dolist (connection (ui::connections (ui table)))
  146. (when (eql (ui::connection-user connection) black)
  147. (ui::send-to-connection '("player" "black") connection)))
  148. (setf (slot-value table 'proposals) nil)
  149. (setf (ui::player-user :black (ui table)) black)))
  150. (defun notify-canceled-proposal (table)
  151. (let* ((ui (ui table))
  152. (white (ui::player-user :white ui))
  153. (from (ui::connection-user (proposal table))))
  154. (dolist (connection (ui::connections ui))
  155. (when (eql (ui::connection-user connection) white)
  156. (hunchensocket:send-text-message connection (jojo:to-json `("cancelled" ,(ui::name from)))))))
  157. )
  158. (eval-when (:compile-toplevel :load-toplevel :execute)
  159. (defmethod fsm:state-bindings ((machine (eql 'proposal-machine)) (state (eql 'considering)) arguments)
  160. (destructuring-bind (event-v table-v) arguments
  161. (lambda (form)
  162. `(let ((proposal? (proposal-event-p ,event-v))
  163. (cancelled? (proposal-cancel-event-p ,event-v))
  164. (more-proposals? (flet ((different-user-p (connection)
  165. (not (eql (ui::connection-user connection)
  166. (ui::connection-user (proposal ,table-v))))))
  167. (some #'different-user-p (rest (proposals ,table-v)))))
  168. (holds? (member (ui::connection-user (proposal table)) (proposals table) :key #'ui::connection-user)))
  169. (multiple-value-bind (accepted? rejected?) (if (proposal-decision-event-p ,event-v)
  170. (if (proposal-decision-event-accept-p ,event-v)
  171. (values t nil)
  172. (values nil t))
  173. (values nil nil))
  174. ,form))))))
  175. ;;; todo white cancels table
  176. (fsm:defmachine proposal-machine (event table)
  177. (:initial-state waiting)
  178. (:state waiting
  179. (:arc
  180. :to considering
  181. :when (proposal-event-p event)
  182. :action (progn
  183. (setf (proposal table) (proposal-event-connection event)
  184. (proposals table) (list (proposal-event-connection event)))
  185. (show-proposal table))))
  186. (:state considering
  187. (:arc
  188. :to considering
  189. :when proposal?
  190. :action (nconc (proposals table) (list (proposal-event-connection event))))
  191. (:arc
  192. :to considering
  193. :when cancelled?
  194. :action (cancel-all-user-proposals (proposal-cancel-event-user event) table))
  195. (:arc
  196. :to considering
  197. :when (and rejected? more-proposals?)
  198. :action (progn
  199. (reject-all-user-proposals (ui::connection-user (proposal table)) table)
  200. (setf (proposal table) (first (proposals table)))
  201. (show-proposal table)))
  202. (:arc
  203. :to waiting
  204. :when (and rejected? (not more-proposals?))
  205. :action (progn
  206. (reject-all-user-proposals (ui::connection-user (proposal table)) table)
  207. (setf (proposal table) nil)))
  208. (:arc
  209. :to game
  210. :when (and accepted? holds?)
  211. :action (accept-proposal table))
  212. (:arc
  213. :to considering
  214. :when (and (and accepted? (not holds?))
  215. more-proposals?)
  216. :action (progn
  217. (notify-canceled-proposal table)
  218. (setf (proposal table) (first (proposals table)))
  219. (show-proposal table)))
  220. (:arc
  221. :to waiting
  222. :when (and (and accepted? (not holds?)) (not more-proposals?))
  223. :action (progn
  224. (notify-canceled-proposal table)
  225. (setf (proposal table) nil))))
  226. (:state game))
  227. (define-event-code ("proposal-decision" reply) (make-proposal-decision-event :source *table* :accept-p (equal reply "yes")))
  228. (define-event-code ("proposal") (make-proposal-event :source *table* :connection *connection*))
  229. (define-event-code ("proposal-cancel") (make-proposal-cancel-event :source *table* :user *user*))
  230. (defstruct (proposal-cancel-event (:include event))
  231. user)
  232. #+nil (defun make-table-handler (table)
  233. (let* ((session (session table))
  234. (ui (ui table))
  235. (session-handler (make-match-handler session ui)))
  236. (lambda (event)
  237. (let ((source (event-source event)))
  238. (cond ((member source (list session ui (game:game session)))
  239. (funcall session-handler event))
  240. ((and (eql source table)
  241. (user-preference-event-p event))
  242. (user-preference-event-handler event)))))))
  243. (defun make-table-handler (table)
  244. (let* ((session (session table))
  245. (ui (ui table))
  246. (session-handler (if (typep table 'match-table)
  247. (make-match-handler session ui)
  248. (make-money-handler session ui)))
  249. (proposal-handler (let ((m (proposal-machine)))
  250. (lambda (event)
  251. (funcall m event table))))
  252. (handler proposal-handler))
  253. (lambda (event)
  254. (if (client-connected-event-p event)
  255. (let ((connection (client-connected-event-connection event)))
  256. (cond ((null (ui::player-user :white ui))
  257. (setf (ui::player-user :white ui) (ui::connection-user connection))
  258. (ui::send-to-connection '("player" "white") connection)
  259. (ui:refresh (game:game (session table)) (ui table)))
  260. ((eql (ui::connection-user connection) (ui::player-user :white (ui table)))
  261. (ui::send-to-connection '("player" "white") connection)
  262. (ui:refresh (game:game (session table)) (ui table)))
  263. ((eql (ui::connection-user connection) (ui::player-user :black (ui table)))
  264. (ui::send-to-connection '("player" "black") connection)
  265. (ui:refresh (game:game (session table)) (ui table)))))
  266. (cond ((and (eql handler proposal-handler)
  267. (eql (funcall handler event) 'game))
  268. (setf handler session-handler)
  269. (put-event (make-initial-roll-event :source (game:game (session table))
  270. :dice (game::random-dice))
  271. (event-queue table))
  272. (ui:refresh (game:game session) ui))
  273. ((eql handler proposal-handler)
  274. ;; relax, we have already called it
  275. )
  276. (t (funcall handler event))))
  277. )))
  278. ;;; TODO: something about the clients' behaviour
  279. ;;; perhaps if everyone goes away, a special event could be generated
  280. ;;; todo I think this one processes an event with null source
  281. (defun table-loop (table)
  282. (let ((*event-queue* (event-queue table)))
  283. (let ((handler (make-table-handler table)))
  284. (loop until (game::finished-p (session table))
  285. do (funcall handler (get-event))
  286. finally (remhash (table-id table) *tables*)))))