Yu-Gi-Oh! Deck Building and Card Inventory Management web interface written in Common Lisp, utilizing HTMX.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

259 lines
10KB

  1. #|
  2. src/models/ygoprodeck-2.lisp
  3. Yu-Gi-Oh! Pro Deck Database Interface v2
  4. TODO Documentation
  5. |#
  6. (in-package #:cl-deck-builder2.models.ygoprodeck.methods)
  7. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  8. (defmacro ygo-card (&body body)
  9. `(with-includes
  10. 'ygo-card
  11. (mito:includes
  12. 'ygo-card-archetype
  13. 'ygo-card-attribute
  14. 'ygo-card-frame-type
  15. 'ygo-card-race
  16. 'ygo-card-type)
  17. ,@body))
  18. (defmacro ygo-set (&body body)
  19. `(with-includes 'ygo-set
  20. (mito:includes
  21. 'ygo-card
  22. 'ygo-set-code
  23. 'ygo-set-edition
  24. 'ygo-set-name
  25. 'ygo-set-rarity
  26. 'ygo-set-rarity-code)
  27. ;; (mito:includes
  28. ;; 'ygo-card-name)
  29. ,@body))
  30. (defun ygo-set-by-id (id)
  31. (first (ygo-set (sxql:where (:= :id id)))))
  32. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  33. (defmethod split-code ((set-code ygo-set-code))
  34. (cl-ppcre:split "-" (name-of set-code)))
  35. (defmethod ygo-card-sets ((obj ygo-card))
  36. (ygo-card-sets (ygo-passcode-of obj)))
  37. (defmethod ygo-card-sets ((passcode integer))
  38. ;; If alternate IDs exist, just mush them in
  39. (let ((alt-id (has-alternative-artwork passcode)))
  40. (when alt-id
  41. ;; XXX Why the long accessor name here? How can we get this accessor? Maybe I have to specify it manually.
  42. (with-slots ((alternate-id cl-deck-builder2.models.ygoprodeck.classes::alternate-id)) alt-id
  43. (setf passcode alternate-id)))
  44. (let ((sets
  45. (ygo-set
  46. ;; (sxql:left-join :ygo_card_name :on (:= :ygo-card.name-id :ygo-card-name.id))
  47. (sxql:where (:= :passcode-id passcode))
  48. (sxql:order-by :price :desc))))
  49. sets)))
  50. ;; Only showing cards with inventory
  51. ;; (with-connection (db)
  52. ;; (mito:select-dao 'cl-deck-builder2.models.ygoprodeck.classes::ygo-set-item
  53. ;; (sxql:where (:and (:in :item_id
  54. ;; (sxql:select :id
  55. ;; (sxql:from :ygo_set)
  56. ;; (sxql:where (:= :passcode-id 89631139))))
  57. ;; (:> :qty 0)))))
  58. ;; (mapcar (alexandria:compose #'cl-deck-builder2.models.ygoprodeck2::ygo-card-sets #'deck-passcode-of)
  59. ;; (retrieve-dao 'deck-item :deck-id 5))
  60. ;; (reduce #'+ (mapcar (alexandria:compose #'read-from-string #'cl-deck-builder2.models.ygoprodeck2::ygo-price-of)
  61. ;; (cl-deck-builder2.models.ygoprodeck2::ygo-card-sets (deck-passcode-of (find-dao 'deck-item)))))
  62. ;; price of a deck by id using #'first of the ygo-card-sets
  63. ;;
  64. ;; (reduce '+ (mapcar (alexandria:compose #'read-from-string #'cl-deck-builder2.models.ygoprodeck.classes::ygo-price-of)
  65. ;; (mapcar #'first
  66. ;; (mapcar (alexandria:compose #'cl-deck-builder2.models.ygoprodeck.methods::ygo-card-sets #'deck-passcode-of)
  67. ;; (retrieve-dao 'deck-item :deck-id 308)))))
  68. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  69. (defun ygo-card-by-name (name &optional (test :=))
  70. (let ((clauses (case test
  71. (:= (sxql:where (:= :name name)))
  72. (:like (sxql:where (:like :name (format nil "%~a%" name)))))))
  73. (ygo-card clauses)))
  74. (defun ygo-card-by-passcode (passcode)
  75. (first (ygo-card (sxql:where (:= :id passcode)))))
  76. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  77. (defun ygo-set-by-code (name &optional (test :=))
  78. (let ((clauses (case test
  79. (:= (sxql:where (:= :name name)))
  80. (:like (sxql:where (:like :name (format nil "%~a%" name)))))))
  81. (ygo-set
  82. (sxql:where (:in :code-id
  83. (sxql:select :id
  84. (sxql:from (sxql:make-sql-symbol
  85. (mito.class.table:table-name
  86. (find-class 'ygo-set-code))))
  87. clauses))))))
  88. (defun ygo-set-by-name (name &optional (test :=))
  89. (let ((clauses (case test
  90. (:= (sxql:where (:= :set_name name)))
  91. (:like (sxql:where (:like :set_name (format nil "%~a%" name)))))))
  92. (ygo-set
  93. (sxql:where (:in :code-id
  94. (sxql:select :id
  95. (sxql:from (sxql:make-sql-symbol
  96. (mito.class.table:table-name
  97. (find-class 'ygo-set-name))))
  98. clauses))))))
  99. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  100. (defun ygo-select-set-item (item-id variant-id)
  101. "Helper function. Select a single YGO-SET-ITEM by ITEM-ID and VARIANT-ID."
  102. (mito:select-dao 'ygo-set-item
  103. (mito:includes 'ygo-set 'variant-condition)
  104. (sxql:where (:and (:= :item-id item-id)
  105. (:= :variant-id variant-id)))))
  106. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  107. ;; https://lispcookbook.github.io/cl-cookbook/clos.html#pretty-printing
  108. ;;
  109. ;; (defmethod print-object ((obj person) stream)
  110. ;; (print-unreadable-object (obj stream :type t)
  111. ;; (with-accessors ((name name)
  112. ;; (lisper lisper))
  113. ;; obj
  114. ;; (format stream "~a, lisper: ~a" name lisper))))
  115. ;;
  116. ;; (defmethod print-object ((obj person) stream)
  117. ;; (print-unreadable-object (obj stream :type t)
  118. ;; (format stream "~a, lisper: ~a" (name obj) (lisper obj))))
  119. ;;
  120. ;; (defmethod print-object ((obj person) stream)
  121. ;; (print-unreadable-object (obj stream :type t :identity t)))
  122. ;;
  123. ;; Caution: trying to access a slot that is not bound by default will lead to an error. Use slot-boundp.
  124. (defmethod print-object ((obj ygo-card) stream)
  125. (print-unreadable-object (obj stream :type t)
  126. (let ((args))
  127. (when (ygo-passcode-of obj)
  128. (push (ygo-passcode-of obj) args))
  129. (ignore-errors
  130. (with-accessors ((name name-of))
  131. obj
  132. (when (and (slot-boundp obj 'name)
  133. (slot-value obj 'name))
  134. (push (name-of name) args))))
  135. (when args
  136. (format stream "~{~a~^ - ~}" (reverse args))))))
  137. (defmethod print-object ((obj ygo-set) stream)
  138. (print-unreadable-object (obj stream :type t)
  139. (let ((args))
  140. (when (mito:object-id obj)
  141. (push (mito:object-id obj) args))
  142. (ignore-errors
  143. (with-accessors ((passcode ygo-passcode-of)
  144. (name name-of)
  145. (code ygo-code-of)
  146. (rarity ygo-rarity-of)
  147. (edition ygo-edition-of))
  148. obj
  149. (when (and (slot-boundp obj 'passcode)
  150. (slot-value obj 'passcode))
  151. (push (ygo-passcode-of passcode) args))
  152. (when (and (slot-boundp obj 'code)
  153. (slot-value obj 'code))
  154. (push (ygo-set-code-of code) args))
  155. (when (and (slot-boundp obj 'rarity)
  156. (slot-value obj 'rarity))
  157. (push (ygo-rarity-of rarity) args))
  158. (when (and (slot-boundp obj 'edition)
  159. (slot-value obj 'edition))
  160. (push (ygo-edition-of edition) args))
  161. (when (and (slot-boundp obj 'name)
  162. (slot-value obj 'name))
  163. (push (name-of name) args))))
  164. (when args
  165. (format stream "~{~a~^ - ~}" (reverse args))))))
  166. (defun ygo-card-names-from-list (&rest args)
  167. (with-connection (db)
  168. (with-transaction
  169. (mito:retrieve-by-sql
  170. (sxql:select (:ygo_card.id :name)
  171. (sxql:from :ygo_card)
  172. (sxql:left-join :ygo_card_name :on (:= :ygo_card.name_id :ygo_card_name.id))
  173. (sxql:where (:in :ygo_card_name.id
  174. (sxql:select :name_id
  175. (sxql:from :ygo_card)
  176. (sxql:where (:in :id args))))))))))
  177. (defun ygo-card-names-by-deck-id (id)
  178. (with-connection (db)
  179. (with-transaction
  180. (mito:retrieve-by-sql
  181. (sxql:select (:ygo_card.id :name)
  182. (sxql:from :ygo_card)
  183. (sxql:left-join :ygo_card_name :on (:= :ygo_card.name_id :ygo_card_name.id))
  184. (sxql:where (:in :ygo_card_name.id
  185. (sxql:select :name_id
  186. (sxql:from :ygo_card)
  187. (sxql:where (:in :id
  188. (sxql:select :passcode
  189. (sxql:from :deck_item)
  190. (sxql:where (:= :deck_id id)))))))))))))
  191. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  192. (defun count-all-dao ()
  193. (let ((class-list (append (registered-classes (registry) :cl-deck-builder2.models.ygoprodeck.fields)
  194. (registered-classes (registry) :cl-deck-builder2.models.ygoprodeck.classes)))
  195. (lst '()))
  196. (with-connection (db)
  197. (with-transaction
  198. (dolist (class class-list lst)
  199. (push (list class (mito:count-dao class)) lst))))))
  200. ;; If you have altnerative artwork and you are the main card, youll get a list with length > 1
  201. ;; If you have alternative artwork and you are an alternative artwork card, you'll get the main list with length 1
  202. (defmethod has-alternative-artwork ((id integer))
  203. (find-dao 'ygo-alternative-artwork :passcode-id id))
  204. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  205. ;; So in the CSV importer I had a function PRE-SEED-DB-ITEMS that
  206. ;; would copy CC-ITEM x5 VARIANT-CONDITION to seed something like 250k
  207. ;; items in the db. We're just going to duplicate the logic here.
  208. ;;
  209. ;; INPUT: (select-dao 'ygo-set)
  210. (defun pre-seed-db-items (ygo-sets)
  211. (let ((variants (select-dao 'variant-condition)))
  212. (with-connection (db)
  213. (with-transaction
  214. (dolist (itm ygo-sets)
  215. (dolist (variant variants)
  216. (mito:create-dao 'ygo-set-item :item-id (mito:object-id itm)
  217. :variant-id (mito:object-id variant))))
  218. (mito:count-dao 'ygo-set-item)))))
  219. (defun set-ygo-set-item-qty (amt)
  220. "Helper function. Set the QTY of every YGO-SET-ITEM to AMT."
  221. (with-connection (db)
  222. (with-transaction
  223. (mito:execute-sql
  224. (sxql:update (sxql:make-sql-symbol
  225. (mito.class.table:table-name
  226. (find-class 'ygo-set-item)))
  227. (sxql:set= :qty amt))))))