Yu-Gi-Oh! Deck Building and Card Inventory Management web interface written in Common Lisp, utilizing HTMX.
Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

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)))