;; Hackety hacks for the front-end dictionary (defparameter *words* (with-open-file (in "/home/inaimathi/projects/OWL2.js") (alist-hash-table (json:decode-json in) :test 'equalp))) (define-json-handler (bogglish/dictionary) () *words*)
*WORDS*
(defvar *words* nil) ;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Hackety hack continued (defun dict! () (get/json "/bogglish/dictionary" {} (lambda (data) (setf *words* data)))) (dict!) ;;;;;;;;;;;;;;;;;;;;;;;;;;
(defvar *dice* '(("R" "I" "F" "O" "B" "X") ("I" "F" "E" "H" "E" "Y") ("D" "E" "N" "O" "W" "S") ("U" "T" "O" "K" "N" "D") ("H" "M" "S" "R" "A" "O") ("L" "U" "P" "E" "T" "S") ("A" "C" "I" "T" "O" "A") ("Y" "L" "G" "K" "U" "E") ("Qu" "B" "M" "J" "O" "A") ("E" "H" "I" "S" "P" "N") ("V" "E" "T" "I" "G" "N") ("B" "A" "L" "I" "Y" "T") ("E" "Z" "A" "V" "N" "D") ("R" "A" "L" "E" "S" "C") ("U" "W" "I" "L" "R" "G") ("P" "A" "C" "E" "M" "D"))) (defun shuffle (lst) (sort lst (lambda (a b) (< a b)) :key (lambda (_) (random)))) (defun pick (lst) (aref lst (random (length lst)))) (defun roll (dice) (loop for d in (shuffle dice) collect (pick d)))
;; Basic front-end (defun >>letter (value) (who-ps-html (:span :class (+ "letter " (chain value (to-upper-case))) (if (= value "Qu") (who-ps-html (:span (:span :class "Q" "Q") (:span :class "u" "u"))) value)))) (defun >>grid (roll) (who-ps-html (:div :class "total-score") (:div :class "timer" "03:00") (:ul :class "letter-grid" (join (loop for (a b c d) on roll by (lambda (arr) (chain arr (slice 4))) collect (who-ps-html (:li (join (map >>letter (list a b c d)))))))) (:div :class "clobble-controls" (:button :class "claim-word" :onclick "claimWord()" "Claim") (:button :class "quit" "Quit") (:button :class "clear-word" :onclick "clearWord()" "Clear")) (:div :class "claimed-words"))) (defun render-grid! (elem roll) (dom-set elem (>>grid roll)) (add-listener! (by-selector elem ".quit") :click (lambda () (clear-delay! :clobble-countdown) (render-score! elem (chain *score* (concat (list (claimed-words))))))) (loop for l in (by-selector-all elem ".letter") do (add-listener! l :click (lambda () (letter-clicked l))))) (defun >>main-menu () (who-ps-html (:span :class "clobble-title" (join (map >>letter (list "Cl" "O" "B" "B" "L" "E")))) (:ul :class "clobble-menu" (:li :class "loader" "Loading Dictionary " (:p :class "spinner")) (:li (:button :class "single" :disabled "true" "Single Level")) (:li (:button :class "infinite" :disabled "true" "Zen Mode"))))) (defvar *spinner* (chain ". o O @ *" (split " "))) (defun render-menu! (elem) (dom-set elem (>>main-menu)) (add-listener! (by-selector elem ".infinite") :click (lambda () (infinite-mode! elem))) (add-listener! (by-selector elem ".single") :click (lambda () (single-level! elem))) (let ((enable! (lambda () (loop for b in (by-selector-all elem "button") do (setf (@ b disabled) false)) (hide! (by-selector elem ".loader")) (clear-delay! :clobble-loader))) (spun (reverse *spinner*))) (if *words* (enable!) (interval! :clobble-loader (lambda () (let ((next (chain spun (pop)))) (dom-set (by-selector elem ".spinner") (+ next next next))) (when (= (length spun) 0) (setf spun (reverse *spinner*))) (when *words* (enable!))) 150)))) (defun longest-word (wds) (@ (sort wds (lambda (a b) (>= a b)) :key (lambda (w) (length w))) 0)) (defun >>score (claimed-word-record) (let ((count (length claimed-word-record)) (total (loop for ws in claimed-word-record sum (score-of ws))) (word-total (loop for wds in claimed-word-record sum (length wds)))) (who-ps-html (:ul :class "score-summary" (:li :class "total" (:span :class "label" "Total Score:") total) (when (> count 1) (who-ps-html (:li :class "levels" (:span :class "label" "Levels Complete:") (- count 1)) (:li :class "average" (:span :class "label" "Average Score:") (/ total (- count 1))))) (:li :class "words-found" (:span :class "label" "Words Found:") word-total) (:li :class "longest" (:span :class "label" "Longest Word:") (longest-word (map longest-word claimed-word-record)))) (:div :class "clobble-controls" (:button :class "menu" "Main Menu"))))) (defun render-score! (elem claimed-word-record) (dom-set elem (>>score claimed-word-record)) (add-listener! (by-selector elem ".menu") :click (lambda () (render-menu! elem))))
(defun definition-of (string) (aref *words* (chain string (to-lower-case)))) (defun word? (string) (let ((opp (not (definition-of string)))) (not opp))) (defvar *word-in-progress* (list)) (defun letter-clicked (elem) (chain elem class-list (remove "partial")) (cond ((and (chain elem class-list (contains "selected")) (= elem (last *word-in-progress*))) (chain *word-in-progress* (pop)) (chain elem class-list (remove "selected"))) ((not (chain elem class-list (contains "selected"))) (chain *word-in-progress* (push elem)) (chain elem class-list (add "selected"))))) (defun clear-word () (loop for l in (by-selector-all ".letter.selected") do (chain l class-list (remove "selected"))) (setf *word-in-progress* (list)))
(defvar *claimed-words* nil) (defun reset-words! () (setf *claimed-words* (create :set (new (-set)) :vals (list)))) (reset-words!) (defun claimed-words () (@ *claimed-words* vals)) (defun fresh-word? (word) (not (chain *claimed-words* set (has word)))) (defun push-word! (word) (when (fresh-word? word) (chain *claimed-words* set (add word)) (chain *claimed-words* vals (push word)) (chain *claimed-words* vals (sort)) t)) (defun claim-word () (let ((word (chain (join (loop for l in *word-in-progress* collect (@ l inner-text))) (to-upper-case)))) (if (word? word) (progn (when (push-word! word) (dom-set (by-selector ".claimed-words") (who-ps-html (:ul (join (map (lambda (wd) (who-ps-html (:li :class (+ "word " wd) wd))) (claimed-words)))))) (update-score! (by-selector ".total-score"))) t) nil)))
(defvar *score* (list)) (defun update-score! (elem) (dom-set elem (+ (current-score) (loop for s in *score* sum (score-of s))))) (defun score-of (words-list) (loop for w in words-list sum (let ((l (length w))) (cond ((> 3 l) 0) ((> l 8) 6) (t (- l 2)))))) (defun current-score () (score-of (claimed-words))) (defun countdown! (elem min sec fn) (let ((time (+ sec (* min 60)))) (interval! :clobble-countdown (lambda () (if (= 0 time) (progn (clear-delay! :clobble-countdown) (fn)) (let ((min (floor (/ time 60))) (sec (mod time 60))) (dom-set elem (+ "0" min ":" (if (> 10 sec) (+ "0" sec) sec))) (decf time)))) 1000)))
(defun clobble-letter-keydown (raw) (when (= 1 (length raw)) (let* ((sel (+ ".letter-grid .letter." (if (= raw "Q") "QU" raw) ":not(.selected)")) (el (by-selector sel))) (if (= raw "Q") (chain el class-list (add "partial")) (let ((partial (by-selector ".letter-grid .letter.partial"))) (cond ((and (= raw "U") partial) (letter-clicked partial)) (partial nil) (t (when el (letter-clicked el))))))))) (defun clobble-keydown (ev) (cond ((= (@ ev key-code) 13) (when (claim-word) (clear-word))) ;;claims and clears ((= (@ ev key-code) 27) (clear-word)) ;; clears ((= (@ ev key-code) 8) (let ((partial (by-selector ".letter-grid .letter.partial"))) (if partial (chain partial class-list (remove "partial")) (let ((el (chain *word-in-progress* (pop)))) (when el (chain el class-list (remove "selected"))))))) (t (clobble-letter-keydown (chain ev key (to-upper-case))))))
(chain (by-selector "body") (add-event-listener :keydown #'clobble-keydown))
(defun main-menu! (elem)) (defun counter! (fn) (countdown! (by-selector ".timer") 3 0 fn)) (defun single-level! (elem) (clear-word) (reset-words!) (render-grid! elem (roll *dice*)) (counter! (lambda () (render-score! elem (list (claimed-words)))))) (defun infinite-mode! (elem) (clear-word) (reset-words!) (render-grid! elem (roll *dice*)) (counter! (lambda () (chain *score* (push (claimed-words))) (infinite-mode! elem))) (update-score! (by-selector ".total-score")) nil) (defun run-game! (elem) (render-menu! elem)) (book-ready (lambda (_) (run-game! (by-selector "#game"))))