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.

592 lines
24KB

  1. #|
  2. src/models/ydk/classes.lisp
  3. # YDK
  4. Yu-Gi-Oh! Pro Deck YDK Importer And Models
  5. TODO This can be a stand-alone package CL-YDK + the CL-YDK+MITO mixin probably.
  6. |#
  7. (in-package #:cl-deck-builder2.models.ydk)
  8. (defclass deck-item ()
  9. (;; 0 => Main Deck
  10. ;; 1 => Extra Deck
  11. ;; 2 => Side Deck
  12. (deck-id :accessor deck-id-of :col-type :integer)
  13. (kind :accessor deck-kind-of :col-type :integer)
  14. (passcode :accessor deck-passcode-of :col-type :integer))
  15. (:metaclass registered-table-class)
  16. (:documentation "DECK-LIST is a representation of a single card in a YDK deck in
  17. SQL. Since SQL doesn't have a LIST type, we use a bunch of
  18. these (60-90 rows generally make up a deck). The ID field supplied by
  19. MITO:DAO-TABLE-CLASS is the \"Deck ID\" in the builder system."))
  20. (defclass ydk-deck ()
  21. (;; (id :accessor ydk-id-of :col-type :integer :primary-key t)
  22. (category :accessor ydk-category-of :col-type (or category :null))
  23. (created-by :accessor ydk-created-by :col-type :text :initarg :created-by)
  24. (name :accessor ydk-name-of :col-type :text :initarg :name))
  25. (:unique-keys name)
  26. (:metaclass registered-table-class)
  27. (:documentation "A YDK-DECK is an in-database representation of a deck. It's really actually just an index to keep track of metadata, with the ID field used to key into DECK-ITEM.
  28. SQL doesn't allow us to store anything as a list. We take advantage
  29. of Mito's INSERT-DAO, SELECT-DAO, etc. being methods which we can
  30. define :BEFORE :AFTER and :AROUND methods for. The YDK-DECK class is
  31. an intermediate representation of a deck. Bookeeping for the \"Deck
  32. ID\", CREATED-BY, and NAME."))
  33. (defclass ydk ()
  34. ((;; I think this is supposed to be the MITO:OBJECT-ID to keep tabs
  35. ;; on the SYNC status... Looks like I never wrote it down, but I'm
  36. ;; pretty sure that was my intention with this SLOT. It's been a
  37. ;; pain point previously (PPP).
  38. deck-id :accessor ydk-id-of :initarg :deck-id)
  39. (created-by :accessor ydk-created-by :initarg :created-by :initform nil)
  40. (name :accessor ydk-name-of :initarg :name :initform nil)
  41. (main-deck :accessor ydk-main-deck-of :initarg :main-deck :initform '())
  42. (extra-deck :accessor ydk-extra-deck-of :initarg :extra-deck :initform '())
  43. (side-deck :accessor ydk-side-deck-of :initarg :side-deck :initform '()))
  44. (:documentation "Intermediate YDK representation.
  45. This is what's stored in-memory during program operation, and then gets synced to the database.
  46. Notice this class does not draw from MITO:DAO-TABLE-CLASS."))
  47. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  48. ;; Helper functions
  49. ;;
  50. ;; TODO maybe I don't have to duplicate this data?
  51. ;;
  52. ;; I tried using defparameter but defmacro complained when I tried to
  53. ;; load it.
  54. ;;
  55. ;; Aha, The Original Variant.
  56. ;; This code needs to be incorporated into the VARIANT code.
  57. (defmacro %sql-to-keyword (type)
  58. `(ecase ,type
  59. (0 :main-deck)
  60. (1 :extra-deck)
  61. (2 :side-deck)))
  62. (defmacro %keyword-to-sql (type)
  63. `(ecase ,type
  64. (:main-deck 0)
  65. (:extra-deck 1)
  66. (:side-deck 2)))
  67. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  68. ;; TODO I think I'm going to erase all these DEFUNs since they're kinda
  69. ;; outdated now with the changes I made in db.lisp
  70. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  71. ;; Delete
  72. (defun ydk-deck-delete-from (&rest clauses)
  73. (apply #'delete-from 'ydk-deck clauses))
  74. (defun ydk-deck-item-delete-from (&rest clauses)
  75. (apply #'delete-from 'deck-item clauses))
  76. (defun ydk-deck-delete-by-id (id)
  77. "Remove all matching ID from YDK-DECK and DECK-ITEM, effectively erasing the deck from the database."
  78. (ydk-deck-delete-from
  79. (sxql:where (:= :id id)))
  80. (ydk-deck-item-delete-from
  81. (sxql:where (:= :deck_id id))))
  82. (defun ydk-deck-delete-by-name (name)
  83. (let ((found (ydk-deck-by-name name)))
  84. (when found
  85. ;; Delete from both the DECK-ITEM and the YDK-DECK reference
  86. ;; table. The :deck_item table doesn't have a :NAME field, it
  87. ;; only has the :DECK_ID field. So we need to use
  88. ;; SELECT-DECK-BY-NAME anyway.
  89. (ydk-deck-delete-by-id (mito:object-id found)))))
  90. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  91. ;; Select
  92. (defmacro ydk-table-select (&body body)
  93. `(select-dao 'ydk-deck ,@body))
  94. (defun ydk-table-select-count (&optional fields-and-values)
  95. (apply #'count-dao 'ydk-deck fields-and-values))
  96. (defun ydk-deck-by-id (id &rest args)
  97. (apply #'find-dao 'ydk-deck :id id args))
  98. (defun ydk-deck-by-name (name &rest args)
  99. (apply #'find-dao 'ydk-deck :name name args))
  100. ;; Very cool to have figured this out. To get info on a deck, select
  101. ;; the deck_item where the deck_id is the deck you want. Then INNER
  102. ;; JOIN on :card_info. It just works. One db trip. Aweseome!
  103. ;;
  104. ;; TODO how can we do this with SELECT-DAO?
  105. ;;
  106. ;; For now since this is only getting used in one place, RENDER-YDK,
  107. ;; which is albeit, a pretty core component, ... I'll use
  108. ;; datafly. Until I figure out how to relate these columns.
  109. ;;
  110. ;; I think this is a duplicate of YDK-QUERY... Okay it occured to
  111. ;; me. I used to use YDK-QUERY before I figured this one out and I
  112. ;; think I know what's going on.
  113. ;;
  114. ;; So YDK-DECK-INFO-BY-ID uses SQL to do an INNER JOIN to get the data
  115. ;; from the deck. YDK-QUERY works on decks that aren't in the database
  116. ;; and only returns partial information. I have to use that info to
  117. ;; re-construct the data. The original purpose of YDK-QUERY was to use
  118. ;; it like a look-up table, based on that sorting dev.to article.
  119. ;;
  120. ;; But then I realized, it's a lot more efficient to do it at the
  121. ;; database level. so that's where YDK-DECK-INFO-BY-ID was born
  122. ;; from. It works on existing decks in the database.
  123. (defgeneric ydk-query (id))
  124. (defmethod ydk-query ((id integer))
  125. "Query the database for information from YGO_INFO about the DECK ID."
  126. (with-connection (db)
  127. (with-transaction
  128. (mito:retrieve-by-sql
  129. (sxql:select :*
  130. (sxql:from :ygo_info)
  131. (sxql:inner-join :deck_item
  132. :on (:= :deck_item.passcode :ygo_info.passcode))
  133. (sxql:order-by :asc :deck_item.id)
  134. (sxql:where (:= :deck_id id)))))))
  135. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  136. (defmethod ydk-query ((ydk-deck ydk-deck))
  137. "Query the YDK-DECK by its MITO:OBJECT-ID"
  138. (ydk-query (mito:object-id ydk-deck)))
  139. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  140. (defmethod ydk-query ((ydk ydk))
  141. "Select entries from :YGOPRODECK-DATA corresponding to the unique list
  142. of cards in DECK. This can be used as a look up table to construct a
  143. deck description.
  144. It looks like I updated YDK-QUERY to work with the new YDK-ALL output. Neat!"
  145. (let* ((all (ydk-all ydk))
  146. (cards (ydk-concatenate ydk))
  147. (info (with-datafly-connection (db)
  148. (with-datafly-transaction (db)
  149. (datafly:retrieve-all
  150. (sxql:select :*
  151. (sxql:from :ygo_info)
  152. (sxql:where (:in :passcode cards))))))))
  153. (labels ((query-deck (kind)
  154. (loop for passcode in (getf all kind)
  155. collect
  156. (find passcode info
  157. :key (lambda (plist) (getf plist :passcode))))))
  158. (list
  159. :main-deck (query-deck :main-deck)
  160. :extra-deck (query-deck :extra-deck)
  161. :side-deck (query-deck :side-deck)))))
  162. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  163. ;; Update
  164. (defmethod ydk-rename-deck ((ydk-deck ydk-deck) new-name)
  165. (setf (ydk-name-of ydk-deck) new-name)
  166. (save-dao ydk-deck))
  167. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  168. (defun normalize-content (content)
  169. "Strip UTF-8 BOM, \"\r\n\" sequences, then split on #\Newline into a list."
  170. (etypecase content
  171. ;; It's a LIST - pass it on
  172. (list content)
  173. ;; It's a PATHNAME - UIOP:READ-FILE-STRING and then treat it like
  174. ;; a STRING.
  175. (pathname
  176. (split-sequence:split-sequence
  177. #\Newline
  178. (normalize-newlines
  179. (strip-bom (uiop:read-file-string content)))))
  180. ;; It's a STRING: strip BOM and normalize newlines.
  181. (string
  182. (split-sequence:split-sequence
  183. #\Newline
  184. (normalize-newlines
  185. (strip-bom content))))))
  186. (defmethod initialize-with-content ((ydk ydk) content)
  187. (let ((created-by nil)
  188. (main-deck '())
  189. (extra-deck '())
  190. (side-deck '())
  191. (state :in-created-by))
  192. (dolist (itm (normalize-content content))
  193. ;; A bunch of :END2 checks to prevent overrun.
  194. (unless
  195. ;; Detect state change
  196. (cond ((string-equal "#main" itm :end2 (min (length itm) 5))
  197. (setf state :in-main)
  198. t)
  199. ((string-equal "#extra" itm :end2 (min (length itm) 6))
  200. (setf state :in-extra)
  201. t)
  202. ((string-equal "!side" itm :end2 (min (length itm) 5))
  203. (setf state :in-side)
  204. t)
  205. ((equal "" itm)
  206. t)) ; Skip blank lines!
  207. ;; Otherwise, we assume it's a valid PASSCODE.
  208. (ccase state
  209. (:in-created-by (setf created-by itm))
  210. (:in-main (push (parse-integer itm) main-deck))
  211. (:in-side (push (parse-integer itm) side-deck))
  212. (:in-extra (push (parse-integer itm) extra-deck)))))
  213. (unless (and (slot-boundp ydk 'created-by)
  214. (slot-value ydk 'created-by))
  215. (setf (ydk-created-by ydk) created-by))
  216. ;; Currently we don't read the NAME of the YDK from anywhere
  217. ;; *inside* the file contents, it is provided externally,
  218. ;; e.g. file name.
  219. ;;
  220. ;; (unless (and (slot-boundp ydk 'name)
  221. ;; (slot-value ydk 'name))
  222. ;; (setf (ydk-name-of ydk) name))
  223. (unless (and (slot-boundp ydk 'main-deck)
  224. (slot-value ydk 'main-deck))
  225. (setf (ydk-main-deck-of ydk)
  226. (reverse main-deck)))
  227. (unless (and (slot-boundp ydk 'extra-deck)
  228. (slot-value ydk 'extra-deck))
  229. (setf (ydk-extra-deck-of ydk)
  230. (reverse extra-deck)))
  231. (unless (and (slot-boundp ydk 'side-deck)
  232. (slot-value ydk 'side-deck))
  233. (setf (ydk-side-deck-of ydk)
  234. (reverse side-deck)))))
  235. (defmethod initialize-instance :after ((ydk ydk) &rest initargs
  236. &key content &allow-other-keys)
  237. (declare (ignore initargs))
  238. (when content
  239. (initialize-with-content ydk content)))
  240. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  241. (defmethod ydk-add ((ydk ydk) target-deck passcode)
  242. (let ((deck
  243. (ecase target-deck
  244. (:main (ydk-main-deck-of ydk))
  245. (:extra (ydk-extra-deck-of ydk))
  246. (:side (ydk-side-deck-of ydk)))))
  247. (ccase target-deck
  248. (:main
  249. (setf (ydk-main-deck-of ydk) (append deck (list passcode))))
  250. (:extra
  251. (setf (ydk-extra-deck-of ydk) (append deck (list passcode))))
  252. (:side
  253. (setf (ydk-side-deck-of ydk) (append deck (list passcode)))))))
  254. ;; TODO Deprecated. Remove this code. YDK-DELETE-INDEX is
  255. ;; superior. How are we going to tackle moving cards?
  256. (defmethod ydk-delete ((ydk ydk) kind passcode &optional (count 1))
  257. "Sloppily remove PASSCODE from YDK KIND deck. KIND is one of :MAIN
  258. :EXTRA or :SIDE. Optionally specify how many to remove with COUNT,
  259. default of 1."
  260. (labels ((remove-fn (array)
  261. (remove-if (lambda (lst) (eq lst passcode)) array :count count)))
  262. (with-slots (main-deck extra-deck side-deck) ydk
  263. (ccase kind
  264. (:main (setf main-deck (remove-fn main-deck)))
  265. (:side (setf side-deck (remove-fn side-deck)))
  266. (:extra (setf extra-deck (remove-fn extra-deck)))))))
  267. (defmethod ydk-delete-index ((ydk ydk) kind index &optional (count 1))
  268. "Delete COUNT sequential cards at INDEX from the KIND of deck. KIND
  269. is one of :MAIN :EXTRA or :SIDE."
  270. (with-slots (main-deck extra-deck side-deck) ydk
  271. (ccase kind
  272. (:main (setf main-deck
  273. (append
  274. (subseq main-deck 0 index)
  275. (subseq main-deck (+ count index)))))
  276. (:extra (setf extra-deck
  277. (append
  278. (subseq extra-deck 0 index)
  279. (subseq extra-deck (+ count index)))))
  280. (:side (setf side-deck
  281. (append
  282. (subseq side-deck 0 index)
  283. (subseq side-deck (+ count index))))))))
  284. (defmethod ydk-clear ((ydk ydk) &optional target-deck)
  285. "Clear out the YDK."
  286. (case target-deck
  287. (:main (setf (ydk-main-deck-of ydk) nil))
  288. (:side (setf (ydk-side-deck-of ydk) nil))
  289. (:extra (setf (ydk-extra-deck-of ydk) nil))
  290. (t (progn
  291. (ydk-clear ydk :main)
  292. (ydk-clear ydk :extra)
  293. (ydk-clear ydk :side)))))
  294. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  295. (defmethod ydk-sync ((ydk ydk))
  296. "Synchronize this YDK intermediate deck to the database. If there's
  297. already a deck by this name, we \"update\" it by erasing all the
  298. entries matching that DECK ID using YDK-DECK-ITEM-DELETE-FROM. That is, YDK => SQL."
  299. (let ((deck-list '())
  300. (deck (ydk-deck-by-name (ydk-name-of ydk))))
  301. ;; Create the new deck if it does not exist.
  302. (unless deck
  303. (setf deck
  304. (create-dao 'ydk-deck
  305. :name (ydk-name-of ydk)
  306. :created-by (ydk-created-by ydk))))
  307. ;; If A deck with this name already exists: clear out the old
  308. ;; list. This no longer erases the YDK-DECK entry, so DECK-ID
  309. ;; should stay consistent now.
  310. (ydk-deck-item-delete-from
  311. (sxql:where
  312. (:= :deck_item.deck_id (mito:object-id deck))))
  313. ;; Insert new deck_items into the database. How do we do
  314. ;; that? We create a bunch of objects then MITO:INSERT-DAO them.
  315. (loop for (kind lst) on (ydk-all ydk) by #'cddr do
  316. (dolist (passcode lst)
  317. (push (make-instance 'deck-item
  318. :deck-id (mito:object-id deck)
  319. :kind (%keyword-to-sql kind)
  320. :passcode passcode)
  321. deck-list)))
  322. (if (do-grouped-insert deck-list)
  323. deck)))
  324. (defmethod ydk-sync ((ydk-deck ydk-deck))
  325. "Query the database deck with this YDK-DECK. That is, SQL => YDK.
  326. TODO Why not WITH-SLOTS?"
  327. (with-connection (db)
  328. (let* ((id (mito:object-id ydk-deck))
  329. (created-by (ydk-created-by ydk-deck))
  330. (name (ydk-name-of ydk-deck))
  331. (deck-list (mito:retrieve-dao 'deck-item :deck-id id))
  332. (main-deck '())
  333. (extra-deck '())
  334. (side-deck '()))
  335. (dolist (deck-item deck-list)
  336. (let ((passcode (deck-passcode-of deck-item)))
  337. ;; TODO There could be a macro (with-main-side-extra-decks)
  338. ;; or something to suplliment %keyword-to-sql and
  339. ;; %sql-to-keyword.
  340. (ccase (deck-kind-of deck-item)
  341. (0 (push passcode main-deck))
  342. (1 (push passcode extra-deck))
  343. (2 (push passcode side-deck)))))
  344. (make-instance 'ydk
  345. :deck-id id
  346. :name name
  347. :created-by created-by
  348. :main-deck main-deck
  349. :extra-deck extra-deck
  350. :side-deck side-deck))))
  351. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  352. (defmethod ydk-all ((ydk ydk))
  353. "Get the list of cards from a deck, duplicates included. Generally this order is retained from e.g. file upload or deck creation.
  354. SEE YDK-SORTED."
  355. (with-slots (main-deck extra-deck side-deck) ydk
  356. (list :main-deck main-deck
  357. :side-deck side-deck
  358. :extra-deck extra-deck)))
  359. (defmethod ydk-concatenate ((ydk ydk))
  360. "Get the list of cards from a deck, duplicates included. Generally this order is retained from e.g. file upload or deck creation.
  361. SEE YDK-SORTED."
  362. (with-slots (main-deck extra-deck side-deck) ydk
  363. (concatenate 'list main-deck extra-deck side-deck)))
  364. (defmethod ydk-unique ((ydk ydk))
  365. "Get the unique list of cards from a deck."
  366. (remove-duplicates (ydk-concatenate ydk)))
  367. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  368. ;; (with-datafly-connection (db)
  369. ;; (with-datafly-transaction
  370. ;; (datafly:retrieve-all
  371. ;; (select (:frame_type) (from :ygo_info) (group-by :frame_type)))))
  372. ;; ((:FRAME-TYPE "effect") (:FRAME-TYPE "effect_pendulum") (:FRAME-TYPE "fusion")
  373. ;; (:FRAME-TYPE "fusion_pendulum") (:FRAME-TYPE "link") (:FRAME-TYPE "normal")
  374. ;; (:FRAME-TYPE "normal_pendulum") (:FRAME-TYPE "ritual")
  375. ;; (:FRAME-TYPE "ritual_pendulum") (:FRAME-TYPE "skill") (:FRAME-TYPE "spell")
  376. ;; (:FRAME-TYPE "synchro") (:FRAME-TYPE "synchro_pendulum") (:FRAME-TYPE "token")
  377. ;; (:FRAME-TYPE "trap") (:FRAME-TYPE "xyz") (:FRAME-TYPE "xyz_pendulum"))
  378. (defmethod ydk-sorted ((ydk ydk))
  379. "Sort the YDK according to FRAME-TYPE-PRIO."
  380. (let ((deck-info (copy-list (ydk-query ydk))))
  381. (labels ((frame-type-prio (plist)
  382. (let ((kind (getf plist :frame-type)))
  383. (cond ((string= kind "normal") 0)
  384. ((string= kind "effect") 1)
  385. ((string= kind "ritual") 2)
  386. ((string= kind "fusion") 3)
  387. ((string= kind "link") 4)
  388. ((string= kind "skill") 5)
  389. ((string= kind "synchro") 6)
  390. ((string= kind "token") 7)
  391. ((string= kind "xyz") 8)
  392. ((string= kind "spell") 9)
  393. ((string= kind "trap") 10)
  394. ((string= kind "effect_pendulum") 11)
  395. ((string= kind "fusion_pendulum") 12)
  396. ((string= kind "normal_pendulum") 13)
  397. ((string= kind "ritual_pendulum") 14)
  398. ((string= kind "synchro_pendulum") 15)
  399. ((string= kind "xyz_pendulum") 16)
  400. (t 17))))
  401. (sort-and-extract (kind)
  402. (mapcar (lambda (plist) (getf plist :passcode))
  403. (sort
  404. (sort (copy-list (getf deck-info kind))
  405. #'string< :key (lambda (plist) (princ-to-string
  406. (getf plist :name))))
  407. #'< :key #'frame-type-prio))))
  408. (make-instance 'ydk
  409. :created-by (ydk-created-by ydk)
  410. ;; Sometimes unbound - on unsaved decks.
  411. :deck-id (ignore-errors (ydk-id-of ydk))
  412. :name (ydk-name-of ydk)
  413. :main-deck (sort-and-extract :main-deck)
  414. :extra-deck (sort-and-extract :extra-deck)
  415. :side-deck (sort-and-extract :side-deck)))))
  416. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  417. ;; Now where on earth does this belong? Probably with the YDK stuff maybe?
  418. ;;
  419. ;; Maybe this will go back to the toolkit This code might be useful for parsing CSVs or similar.
  420. ;;
  421. #+nil
  422. (defun parse-decklist (string)
  423. (let ((split-query
  424. (mapcar #'(lambda (s)
  425. (string-trim '(#\Newline #\Return #\Tab #\Space) s))
  426. (remove ""
  427. (split-sequence:split-sequence
  428. #\Newline
  429. (normalize-newlines string))
  430. :test #'equal)))
  431. (new-list '()))
  432. (dolist (line split-query)
  433. (multiple-value-bind (string substrings)
  434. (cl-ppcre:scan-to-strings "^(\\d+)x (.*)" line)
  435. (declare (ignore string))
  436. (cond (substrings
  437. (let ((card-name (svref substrings 1))
  438. (as-number (read-from-string (svref substrings 0))))
  439. ;; 10x => dotimes 10
  440. (dotimes (_ as-number)
  441. (push card-name new-list))))
  442. (t (push line new-list)))))
  443. (reverse new-list)))
  444. (defmethod ydk-filter-frame-type ((ydk ydk) kind)
  445. (let ((monster '("normal" "effect" "ritual" "fusion" "link" "synchro"
  446. "xyz" "effect_pendulum" "fusion_pendulum" "normal_pendulum"
  447. "ritual_pendulum" "synchro_pendulum" "xyz_pendulum"))
  448. (spell '("spell"))
  449. (trap '("trap"))
  450. (deck-info (ydk-query ydk)))
  451. (labels ((filter (plist)
  452. (remove-if (lambda (itm)
  453. (if (member (getf itm :frame-type)
  454. (case kind
  455. (:monster monster)
  456. (:spell spell)
  457. (:trap trap))
  458. :test #'equal)
  459. nil
  460. t))
  461. plist)))
  462. (filter (getf deck-info :main-deck)))))
  463. (defmethod ydk-monster-cards ((ydk ydk))
  464. (ydk-filter-frame-type ydk :monster))
  465. (defmethod ydk-spell-cards ((ydk ydk))
  466. (ydk-filter-frame-type ydk :spell))
  467. (defmethod ydk-trap-cards ((ydk ydk))
  468. (ydk-filter-frame-type ydk :trap))
  469. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  470. (defmethod ydk-length ((ydk ydk))
  471. (reduce #'+ (list (length (ydk-main-deck-of ydk))
  472. (length (ydk-extra-deck-of ydk))
  473. (length (ydk-side-deck-of ydk)))))
  474. (defmethod ydk-to-kde ((ydk ydk))
  475. "Transform the YDK according to KDE Team List."
  476. (let ((created-by (ydk-created-by ydk))
  477. (deck-info (ydk-query ydk))
  478. (monster-cards (ydk-monster-cards ydk))
  479. (spell-cards (ydk-spell-cards ydk))
  480. (trap-cards (ydk-trap-cards ydk)))
  481. (let ((main-deck (getf deck-info :main-deck))
  482. (extra-deck (getf deck-info :extra-deck))
  483. (side-deck (getf deck-info :side-deck)))
  484. (let ((main-deck-rle (rle-encode-plist main-deck))
  485. (extra-deck-rle (rle-encode-plist extra-deck))
  486. (side-deck-rle (rle-encode-plist side-deck))
  487. (monster-cards-rle (rle-encode-plist monster-cards))
  488. (spell-cards-rle (rle-encode-plist spell-cards))
  489. (trap-cards-rle (rle-encode-plist trap-cards)))
  490. (labels ((pad (lst)
  491. (let ((max 17))
  492. (append lst
  493. (loop for i from (length lst) upto max collect
  494. '("" :name ""))))))
  495. (list
  496. :name (ydk-name-of ydk)
  497. :set nil
  498. :length (ydk-length ydk)
  499. :created-by created-by
  500. :first-date nil
  501. :final-date nil
  502. :monster-cards monster-cards
  503. :spell-cards spell-cards
  504. :trap-cards trap-cards
  505. :main-deck main-deck
  506. :side-deck side-deck
  507. :extra-deck extra-deck
  508. :monster-cards-rle (pad monster-cards-rle)
  509. :spell-cards-rle (pad spell-cards-rle)
  510. :trap-cards-rle (pad trap-cards-rle)
  511. :main-deck-rle (pad main-deck-rle)
  512. :side-deck-rle (pad side-deck-rle)
  513. :extra-deck-rle (pad extra-deck-rle)))))))
  514. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  515. (defmethod %to-string ((ydk ydk))
  516. (with-slots (created-by main-deck extra-deck side-deck) ydk
  517. (with-output-to-string (s)
  518. (format s "~A~%"
  519. (if created-by
  520. created-by
  521. "created by..."))
  522. (format s "#main~%")
  523. (dolist (card-name (coerce main-deck 'list))
  524. (format s "~A~%" card-name))
  525. (format s "#extra~%")
  526. (dolist (card-name (coerce extra-deck 'list))
  527. (format s "~A~%" card-name))
  528. (format s "!side~%")
  529. (dolist (card-name (coerce side-deck 'list))
  530. (format s "~A~%" card-name))
  531. s)))