diff options
author | Jason Katz-Brown <jason@airbnb.com> | 2013-08-25 02:17:13 -0700 |
---|---|---|
committer | Jason Katz-Brown <jason@airbnb.com> | 2013-08-25 02:17:13 -0700 |
commit | 9306cb60c32082c5403931de0823a9fd5daa196c (patch) | |
tree | ca1b6eb695fdf3f0c2294e92416b272164bae642 /lisp/ouat/scrabble-structs.lisp | |
parent | 8fb2c681cecc01b46b0f4ba02d5cc177c4747b1c (diff) |
Initial git commit.
Diffstat (limited to 'lisp/ouat/scrabble-structs.lisp')
-rw-r--r-- | lisp/ouat/scrabble-structs.lisp | 1150 |
1 files changed, 1150 insertions, 0 deletions
diff --git a/lisp/ouat/scrabble-structs.lisp b/lisp/ouat/scrabble-structs.lisp new file mode 100644 index 0000000..8a8c337 --- /dev/null +++ b/lisp/ouat/scrabble-structs.lisp @@ -0,0 +1,1150 @@ +(in-package :ouat) + +(defstruct layout + height + width + center-row + center-col + letter-muls + word-muls) + +(defun new-symmetric-square-layout (eighth) + (let* ((center (1- (length eighth))) + (size (+ 1 center center)) + (letter-muls (make-array `(,size ,size) :initial-element 1)) + (word-muls (make-array `(,size ,size) :initial-element 1))) + (loop + for row-list in eighth + for row from 0 + do (loop + with squares = (string-tokens row-list) + for square in squares + for col from 0 + for mul = (parse-integer square :junk-allowed t) + for array = (case (elt square 1) + (#\L letter-muls) + (#\W word-muls)) + when (and mul array) + do (setf (aref array row col) mul + (aref array col row) mul + (aref array (- size row 1) col) mul + (aref array row (- size col 1)) mul + (aref array (- size row 1) (- size col 1)) mul + (aref array (- size col 1) (- size row 1)) mul + (aref array (- size col 1) row) mul + (aref array col (- size row 1)) mul))) + (make-layout :height size + :width size + :center-row center + :center-col center + :letter-muls letter-muls + :word-muls word-muls))) + +(defstruct tile + text + blank-text + quantity + score + type + name) + +(defun text-tile (text tiles) + (loop + for index fixnum from 1 + for tile across (subseq tiles 1) do + (when (string= text (tile-text tile)) + (return-from text-tile index)))) + +(defun tiles-blank (tiles) + (loop + for index fixnum from 1 + for tile across (subseq tiles 1) do + (when (eq :BLANK (tile-type tile)) + (return-from tiles-blank index)))) + +(defstruct board + layout + (empty-p t) + blank-ps + letters + sq-infos + nodes) + +(defstruct sq-info + h + v) + +(defstruct h/v + cross + score + nodes + scores + positions) + +(defun new-sq-info () + (make-sq-info :h (make-h/v) :v (make-h/v))) + +(defun new-board (layout) + (labels ((new-board-array (type initial-element) + (make-array (list (layout-height layout) + (layout-width layout)) + :element-type type + :initial-element initial-element))) + (let ((board + (make-board :layout layout + :blank-ps (new-board-array 'boolean nil) + :letters (new-board-array 'fixnum +empty+) + :sq-infos (new-board-array t nil)))) + (dotimes (row (layout-height layout)) + (dotimes (col (layout-height layout)) + (setf (aref (board-sq-infos board) row col) (new-sq-info)))) + board))) + +(defun mul-text (letter-mul word-mul letter-mul-texts word-mul-texts) + (if (i< 1 letter-mul) + (aref letter-mul-texts letter-mul) + (aref word-mul-texts word-mul))) + +(defun board-row-pretty-squares (board row tiles + letter-mul-texts word-mul-texts) + (loop + with layout = (board-layout board) + for col below (layout-width layout) + for letter = (aref (board-letters board) row col) + collect + (if (iplusp letter) + (let ((letter-tile (aref tiles letter))) + (if (aref (board-blank-ps board) row col) + (tile-blank-text letter-tile) + (tile-text letter-tile))) + (mul-text (aref (layout-letter-muls layout) row col) + (aref (layout-word-muls layout) row col) + letter-mul-texts word-mul-texts)))) + +(defun board-pretty-squares (board tiles letter-mul-texts + word-mul-texts) + (loop + with layout = (board-layout board) + for row below (layout-height layout) collect + (board-row-pretty-squares board row tiles letter-mul-texts + word-mul-texts))) + +(defun place-move (board move) + (when (eq (move-action move) :place) + (setf (board-empty-p board) nil) + (loop + with row = (move-start-row move) + with col = (move-start-col move) + for letter across (move-word move) + for blank-p across (move-blank-ps move) + for already-p across (move-already-on-board-ps move) + unless already-p do + (setf (aref (board-letters board) row col) letter + (aref (board-blank-ps board) row col) blank-p) + do (if (eq (move-direction move) :horizontal) + (iincf col) + (iincf row))))) + +(defstruct rack + capacity + (tiles (make-array '(30) :element-type 'fixnum) + :type (simple-array fixnum))) + +(defun rack-texts (rack tiles) + (loop + for tile being the elements of (rack-tiles rack) collect + (tile-text (aref tiles tile)))) + +(defun rack-sort (rack) + (make-rack :capacity (rack-capacity rack) + :tiles (sort (copy-seq (rack-tiles rack)) '<))) + +(defun rack-size (rack) + (loop + with num-tiles = 0 + for tile across (rack-tiles rack) + when (iplusp tile) + do (iincf num-tiles) + finally (return (the fixnum num-tiles)))) + +(defstruct move + action ; (:place :exchange :pass :deadwood :time-penalty) + challenged-phony-p + bingo-p + score + static-equity + simmed-equity + win% + + direction ; (:horizontal :vertical) + start-row + start-col + word + blank-ps + tiles + already-on-board-ps) + +(defun move-string (move tiles) + (when (eq (move-action move) :place) + (let* ((row (row2uv-row (move-start-row move))) + (col (col2uv-col (move-start-col move))) + (pos (if (eq :vertical (move-direction move)) + (format nil "~a~a" col row) + (format nil "~a~a" row col))) + (word (apply #'concatenate + (cons + 'string + (loop + for tile-id across (move-word move) + for tile = (aref tiles tile-id) + for blank-p across (move-blank-ps move) + collect (if blank-p + (tile-blank-text tile) + (tile-text tile))))))) + (format nil "~a ~a" pos word)))) + +(defstruct dawg + nodes + nth-child-letter-table + letter2child-table) + +(defstruct filldawg + nodes) + +(defun new-dawg (nodes) + (let ((nth-child-table (make-array (* 32 (length nodes)) + :element-type '(unsigned-byte 8))) + (letter2child-table (make-array (* 32 (length nodes)) + :element-type '(unsigned-byte 32)))) + (loop + for node-idx from 0 + for node across nodes do + (labels ((node-has-child (child-letter) + (logbitp child-letter node))) + (loop + with n = 0 + for letter below 26 + when (node-has-child letter) do + (setf (aref letter2child-table (i+ (i* 32 node-idx) (i1+ letter))) + (i+ node-idx (node-letter-offset node letter))) + (setf (aref nth-child-table (i+ (i* 32 node-idx) n)) (i1+ letter)) + (iincf n)))) + (make-dawg :nodes nodes + :nth-child-letter-table nth-child-table + :letter2child-table letter2child-table))) + +(defstruct gaddag + nodes) + +(defstruct lexicon + dawg + filldawg) + +(defstruct filldawg-node + (data 0) + (children nil)) + +(defun filldawg-node-letter (node) + (logand (filldawg-node-data node) #.(i1- (iash 1 5)))) + +(defun set-filldawg-node-letter (node letter) + (assert (typep letter '(integer 0 #.(i1- (iash 1 5))))) + (setf (filldawg-node-data node) + (logior (logand (filldawg-node-data node) + #.(ilognot (i1- (iash 1 5)))) + letter))) + +(defun filldawg-node-terminal-p (node) + (logbitp 5 (filldawg-node-data node))) + +(defun set-filldawg-node-terminal-p (node terminal-p) + (setf (logbitp 5 (filldawg-node-data node)) terminal-p)) + +(defun filldawg-node-idx (node) + (ash (filldawg-node-data node) -6)) + +(defun set-filldawg-node-idx (node idx) + (setf (filldawg-node-data node) + (logior (logand (filldawg-node-data node) + #.(i1- (iash 1 6))) + (ash idx 6)))) + +(defun filldawg-node-child-which-has-letter (node letter) + (find-if #'(lambda (child) + (i= letter (filldawg-node-letter child))) + (filldawg-node-children node))) + +(defun filldawg-node-child-which-has-letter! (node letter) + (or (filldawg-node-child-which-has-letter node letter) + (first (push (make-filldawg-node :data letter) + (filldawg-node-children node))))) + +(defun filldawg-push-suffix (suffix node) + (set-filldawg-node-idx node 0) + (let* ((letter (first suffix)) + (new-suffix (rest suffix))) + (if suffix + (filldawg-push-suffix + new-suffix + (filldawg-node-child-which-has-letter! node letter)) + (set-filldawg-node-terminal-p node t)))) + +(defun filldawg-push-word (word trie) + (filldawg-push-suffix (coerce word 'list) trie)) + +(defun filldawg-sort-children (node) + (setf (filldawg-node-children node) + (sort (filldawg-node-children node) + '< :key 'filldawg-node-letter)) + (dolist (child (filldawg-node-children node)) + (filldawg-sort-children child))) + +(defun filldawg-node-child-bits (node) + (let ((child-bits 0)) + (dolist (child (filldawg-node-children node)) + (setf (logbitp (filldawg-node-letter child) child-bits) t)) + child-bits)) + +(defun filldawg-node-bits (node) + (let ((bits (filldawg-node-idx node))) + (if (filldawg-node-terminal-p node) + (logior bits (ash 1 28)) + bits))) + +(defun filldawg-node-whole-word-p (node) + (logbitp 28 node)) + +(defun filldawg-number-trie (node) + (let ((index 0)) + (labels ((number-subtrie (node) + (if (filldawg-node-children node) + (progn + (set-filldawg-node-idx node index) + (incf index) + (incf index (length (filldawg-node-children node))) + (dolist (child (filldawg-node-children node)) + (number-subtrie child))) + (set-filldawg-node-idx node 0)))) + (number-subtrie node)) + index)) + +(defun suffix-completes-a-word-p (suffix dawg-idx lexicon) + (declare (optimize (safety 3) (debug 3))) + (destructuring-bind (letter . new-suffix) + suffix + (let* ((dawg (lexicon-dawg lexicon)) + (nodes (dawg-nodes dawg)) + (letter2child-table (dawg-letter2child-table dawg))) + (labels ((fast-child-idx (child-letter) + (aref letter2child-table + (i+ (i* 32 dawg-idx) child-letter)))) + (let ((child-idx (fast-child-idx (i1+ letter)))) + (when (iplusp child-idx) + (let* ((child (aref nodes child-idx)) + (new-dawg-idx (child-pointer child)) + (whole-word-p (child-terminates child))) + (if new-suffix + (and (iplusp new-dawg-idx) + (suffix-completes-a-word-p + new-suffix new-dawg-idx lexicon)) + whole-word-p)))))))) + +;; Stupid and temporary anglocentric hack. +(defconstant +thru-delimiter+ 26) +(defconstant +segment-delimiter+ 27) + +(defun filldawg-node-children-string (filldawg-idx lexicon tiles) + (let* ((filldawg (lexicon-filldawg lexicon)) + (nodes (filldawg-nodes filldawg)) + (node (aref nodes filldawg-idx))) + (apply #'concatenate + (cons 'string + (append + (loop + for letter fixnum below 26 + for tile = (aref tiles (i1+ letter)) + when (logbitp letter node) + collect (tile-text tile)) + (if (logbitp #.+thru-delimiter+ node) '("-") nil) + (if (logbitp #.+segment-delimiter+ node) '("/") nil)))))) + +(defun filldawg-prefix-node-idx (prefix-suffix filldawg-idx lexicon) + (when (null prefix-suffix) + (return-from filldawg-prefix-node-idx filldawg-idx)) + #+nil + (format t "prefix-suffix: ~a filldawg-idx: ~a~%" + (pattern2string prefix-suffix) filldawg-idx) + (let* ((filldawg (lexicon-filldawg lexicon)) + (nodes (filldawg-nodes filldawg)) + (node (aref nodes filldawg-idx))) + (labels ((node-has-child (datum) + (logbitp datum node)) + (node-datum-offset (datum) + (let ((mask (1- (iash 1 datum)))) + (1+ (logcount (logand node mask))))) + (find-child-idx (datum) + (when (node-has-child datum) + (i+ filldawg-idx + (node-datum-offset datum))))) + (destructuring-bind (datum . new-prefix-suffix) + prefix-suffix + #+nil + (format t "datum: ~a filldawg-idx: ~a node: ~a~%" + datum filldawg-idx node) + (let ((child-idx (find-child-idx datum))) + (when child-idx + (let* ((child (aref nodes child-idx)) + (new-filldawg-idx (child-pointer child))) + (filldawg-prefix-node-idx new-prefix-suffix + new-filldawg-idx + lexicon)))))))) + +(defun filldawg-string-extensions (string lexicon) + (let ((prefix (filldawg-string2list string))) + (mapcar #'pattern2string + (filldawg-extensions prefix + (filldawg-prefix-node-idx prefix + 0 *twl*) + lexicon)))) + +(defun filldawg-extensions (prefix filldawg-idx lexicon) + (declare (optimize debug)) + (when (null filldawg-idx) + (return-from filldawg-extensions nil)) + #+nil + (format t "prefix: ~a filldawg-idx:~a~%" + (pattern2string prefix) filldawg-idx) + (let* ((filldawg (lexicon-filldawg lexicon)) + (nodes (filldawg-nodes filldawg)) + (node (aref nodes filldawg-idx))) + (labels ((node-has-child (datum) + (logbitp datum node)) + (num-children () + (logcount node)) + (nth-child-datum (n) + (loop + with found = 0 + for datum below 28 + when (node-has-child datum) + do (if (i= found n) + (return-from nth-child-datum datum) + (iincf found))))) + (apply #'append + (loop + for n below (num-children) + for datum = (nth-child-datum n) + for child-idx = (+ 1 n filldawg-idx) + for child-node = (aref nodes child-idx) + for new-filldawg-idx = (logand child-node + (1- (ash 1 28))) + for new-prefix = (append prefix (list datum)) + when (filldawg-node-whole-word-p child-node) + collect (list new-prefix) + when (iplusp new-filldawg-idx) + collect (filldawg-extensions new-prefix + new-filldawg-idx + lexicon)))))) + +(defun list-is-word-p (word &optional (lexicon *twl*)) + (suffix-completes-a-word-p word 0 lexicon)) + +(defun filldawg-patterns (word max-new-tiles max-thrus allow-phony-thrus-p) + ;(declare (optimize (speed 3) (safety 0) (space 1) (debug 0))) + (let ((possible-splits nil)) + (labels ((push-partitions (prefix-groups suffix) + (push (append prefix-groups (list suffix)) possible-splits) + (loop + for first-length from 1 to (i1- (length suffix)) + for first = (subseq (the list suffix) 0 first-length) + for rest = (subseq (the list suffix) first-length) + do (push-partitions (append prefix-groups (list first)) rest))) + (all-valid (thru-strings) + (when (i> (length thru-strings) max-thrus) + (return-from all-valid nil)) + (let* ((num-thru-tiles (apply '+ (mapcar #'length thru-strings))) + (num-gap-tiles (i- (length word) num-thru-tiles))) + (when (or (i> num-gap-tiles max-new-tiles) + (izerop num-gap-tiles)) + (return-from all-valid nil)) + (when allow-phony-thrus-p + (return-from all-valid t)) + (loop + for string in thru-strings + always (or (null (cdr string)) + (list-is-word-p string))))) + (collect-thru/gap-starting (split thru-p) + (loop + for thru in split + for idx fixnum from 0 + when (i= (ilogand idx 1) (if thru-p 0 1)) + collect thru)) + (codify-split (thrus gaps) + (append (apply #'append + (loop + for idx from 0 + for thru in thrus + collect (append thru + (if (i< idx (i1- (length thrus))) + (list #.+thru-delimiter+) + nil)))) + (list #.+segment-delimiter+) + (loop + for gap in (if (i< (length thrus) (length gaps)) + (butlast gaps) + gaps) + collect (length gap)) + (list #.+segment-delimiter+) + (apply #'append gaps)))) + (push-partitions nil word) + (loop + for split in possible-splits + for thru-starting-thrus = (collect-thru/gap-starting split t) + for gap-starting-thrus = (collect-thru/gap-starting split nil) + when (all-valid thru-starting-thrus) + collect (codify-split thru-starting-thrus + (cons nil gap-starting-thrus)) + when (all-valid gap-starting-thrus) + collect (codify-split gap-starting-thrus thru-starting-thrus))))) + +(defun pattern2string (pattern) + (coerce (loop + with segment = 0 + for datum in pattern collect + (cond ((= datum +thru-delimiter+) + #\-) + ((= datum +segment-delimiter+) + (progn (incf segment) #\/)) + ((= segment 1) ;; gap lengths + (code-char (+ (char-code #\0) datum))) + (t ;; thrus or gaps + (letter2char datum)))) + 'string)) + +(defun filldawg-string2list (string) + (loop + for letter across string + collect (cond ((and (>= (char-code letter) (char-code #\A)) + (<= (char-code letter) (char-code #\Z))) + (- (char-code letter) (char-code #\A))) + ((and (>= (char-code letter) (char-code #\a)) + (<= (char-code letter) (char-code #\z))) + (- (char-code letter) (char-code #\a))) + ((eq letter #\-) +thru-delimiter+) + ((eq letter #\/) +segment-delimiter+) + (t (- (char-code letter) (char-code #\0)))))) + +(defun filldawg-trie-from-file (&optional filename (max-newly-placed 7) + (max-thrus MOST-POSITIVE-FIXNUM)) + (setf filename (string-append *data-directory* (or filename "twl.txt"))) + (let ((trie (make-filldawg-node))) + (do-file-lines (word filename) + (let* ((word-list (word2list word)) + (patterns (filldawg-patterns word-list + max-newly-placed + max-thrus + nil))) + (dolist (pattern patterns) + (filldawg-push-word pattern trie)))) + trie)) + +(defun count-filldawg-patterns (&optional filename (max-newly-placed 7) + (max-thrus MOST-POSITIVE-FIXNUM)) + + (setf filename (string-append *data-directory* (or filename "twl.txt"))) + (let ((num-patterns 0)) + (do-file-lines (word filename) + (let* ((word-list (word2list word)) + (patterns (filldawg-patterns word-list + max-newly-placed + max-thrus + t))) + (incf num-patterns (length patterns)))) + num-patterns)) + +(defun filldawg-from-wordlist (wordlist filldawg + &optional (max-newly-placed 7) + (max-thrus MOST-POSITIVE-FIXNUM)) + (let* ((trie (filldawg-trie-from-file wordlist + max-newly-placed + max-thrus)) + (filldawg (string-append *data-directory* filldawg)) + (size (progn + (filldawg-sort-children trie) + (filldawg-number-trie trie)))) + (with-open-file (stream + filldawg + :direction :output + :element-type '(unsigned-byte 32) + :if-does-not-exist :create) + (labels ((write-node (node) + (when (filldawg-node-children node) + (write-byte (filldawg-node-child-bits node) stream) + (dolist (child (filldawg-node-children node)) + (write-byte (filldawg-node-bits child) stream)) + (dolist (child (filldawg-node-children node)) + (write-node child))))) + (write-byte size stream) + (write-node trie))))) + +;; http://en.wikipedia.org/wiki/Scrabble_variants +(defstruct rules + clabbers-p ; implemented in the dawg + woadwage-p ; lexicon = words + concatenations of two words + escalating-p ; bingo player's rack grows by one + recycle-p ; swap on-board blank with on-rack blank representation + if-only-p ; may turn one natural tile into a blank + toroidal-p + bingo-bonus + max-scoreless-turns + challenge-rule ; (:single :double :penalty-per-word :penalty-per-move) + challenge-penalty + overdraw-rule ; (:naspa :wespa) + time-limit + time-penalty-duration + time-penalty) + +(defun list2text (list &optional (tiles *english*)) + (apply #'concatenate + (cons 'string + (mapcar (lambda (letter) + (tile-text (aref tiles letter))) + list)))) + +(defun fit-between (before after lexicon) + (declare (optimize (debug 3))) + (unless (or before after) + (return-from fit-between +all-letters+)) + (let* ((pattern + (cond ((null before) (append after '(#.+segment-delimiter+ 1 + #.+segment-delimiter+))) + ((null after) (append before '(#.+segment-delimiter+ 0 + #.+segment-delimiter+))) + (t (append before '(#.+thru-delimiter+) after + '(#.+segment-delimiter+ 0 1 + #.+segment-delimiter+))))) + (filldawg (lexicon-filldawg lexicon)) + (nodes (filldawg-nodes filldawg)) + (node-idx (filldawg-prefix-node-idx pattern 0 lexicon)) + (node (and node-idx (aref nodes node-idx)))) + (when (null node) + (return-from fit-between 0)) + (labels ((node-has-child (letter) + (logbitp letter node)) + (num-children () + (logcount node)) + (nth-child-letter (n) + (loop + with found = 0 + for letter below 26 + when (node-has-child letter) + do (if (i= found n) + (return-from nth-child-letter letter) + (iincf found))))) + (let ((bits 0)) + (loop + for n below (num-children) + for letter = (nth-child-letter n) + for child-idx = (+ 1 n node-idx) + for child-node = (aref nodes child-idx) + when (filldawg-node-whole-word-p child-node) do + (setf (logbitp letter bits) t)) + bits)))) + +(defun zero-aligned-bit-letters (bits tiles) + (apply #'concatenate + (cons 'string + (loop + for letter fixnum below 26 + for tile = (aref tiles (1+ letter)) + when (logbitp letter bits) + collect (tile-text tile))))) + +(defun string-fit-between (before after + &optional (lexicon *twl*) (tiles *english*)) + (zero-aligned-bit-letters (fit-between (word2list before) + (word2list after) + lexicon) + tiles)) + +(defun update-sq-infos (board lexicon tiles + &optional (debug-p t) (rack-size 7) last-move) + (declare (optimize (debug 3))) + (declare (ignore last-move)) + (let* ((layout (board-layout board)) + (height (layout-height layout)) + (width (layout-width layout)) + (letters (board-letters board)) + (blank-ps (board-blank-ps board)) + (sq-infos (board-sq-infos board)) + (nodes (board-nodes board)) + (blank (tiles-blank tiles))) + (macrolet ((do-squares ((row col dir sq-info h/v) &body body) + `(dotimes (,row height) + (dotimes (,col width) + (dolist (,dir '(:horizontal :vertical)) + (let* ((,sq-info (aref sq-infos row col)) + (,h/v (if (eq ,dir :horizontal) + (sq-info-v ,sq-info) + (sq-info-h ,sq-info)))) + ,@body)))))) + (labels ((on-board-p (r c) + (and (<= 0 r (1- height)) + (<= 0 c (1- width)))) + (next-sq (r c l-to-r-p dir) + (if (eq dir :horizontal) + (if l-to-r-p + (values r (i1+ c)) + (values r (i1- c))) + (if l-to-r-p + (values (i1+ r) c) + (values (i1- r) c)))) + (get-word (r c l-to-r-p dir) + (multiple-value-bind (next-r next-c) + (next-sq r c l-to-r-p dir) + (when (on-board-p next-r next-c) + (let ((letter (aref letters next-r next-c))) + (unless (i= letter +empty+) + (append (list (1- (aref letters next-r next-c))) + (get-word next-r next-c l-to-r-p dir))))))) + (get-score (r c l-to-r-p dir) + (multiple-value-bind (next-r next-c) + (next-sq r c l-to-r-p dir) + (if (on-board-p next-r next-c) + (let ((letter (aref letters next-r next-c))) + (if (i= letter +empty+) + 0 + (let* ((blank-p (aref blank-ps next-r next-c)) + (tile-idx (if blank-p blank letter)) + (tile (aref tiles tile-idx)) + (score (tile-score tile))) + (i+ score + (get-score next-r next-c l-to-r-p dir))))) + 0))) + (thru-words (row col dir) + #+nil + (format t "(thru-words ~a ~a) dir: ~a~%" + row col dir) + (multiple-value-bind (prev-r prev-c) + (next-sq row col nil dir) + (when (and (on-board-p prev-r prev-c) + (iplusp (aref letters prev-r prev-c))) + (return-from thru-words nil))) + (let ((words nil) + (gaps (list 0)) + (last-tile :gap) + (num-tiles 0) + (r row) + (c col)) + (loop + (unless (on-board-p r c) + (return-from thru-words + (values (reverse (mapcar #'reverse words)) + (if (eq last-tile :gap) + (reverse (rest gaps)) + (reverse gaps))))) + (let ((letter (aref letters r c))) + #+nil + (format t "r: ~a c: ~a letter: ~a words: ~a gaps: ~a ~ + last: ~a num-tiles: ~a~%" + r c letter words gaps last-tile num-tiles) + (if (izerop letter) + (progn + (if (eq last-tile :gap) + (iincf (first gaps)) + (progn + (push 1 gaps) + (setf last-tile :gap))) + (incf num-tiles) + (when (i> num-tiles rack-size) + (return-from thru-words + (values (reverse (mapcar #'reverse words)) + (reverse (rest gaps)))))) + (if (eq last-tile :gap) + (progn + (push (list letter) words) + (setf last-tile :letter)) + (push letter (first words)))) + (multiple-value-setq (r c) + (next-sq r c t dir)))))) + (pattern (thrus gaps) + (when (null thrus) (return-from pattern nil)) + (apply #'append + (loop + for (thru . more) on thrus + collect (mapcar #'1- thru) + when more + collect (list +thru-delimiter+) + unless more + collect (append (list +segment-delimiter+) + gaps + (list +segment-delimiter+))))) + (cross-at (r c dir) + (let* ((sq-info (aref sq-infos r c)) + (h/v (if (eq dir :horizontal) + (sq-info-h sq-info) + (sq-info-v sq-info)))) + (h/v-cross h/v))) + (score-at (r c) + (let ((letter (aref letters r c))) + (if (i= letter +empty+) + 0 + (let* ((blank-p (aref blank-ps r c)) + (tile-idx (if blank-p blank letter)) + (tile (aref tiles tile-idx))) + (tile-score tile))))) + (patterns (thrus gaps row col dir) + (let ((patterns (make-array `(,rack-size))) + (scores (make-array `(,rack-size))) + (positions (make-array `(,rack-size)))) + (loop + ;; Really need to clean this up + with min-tiles = 0 + with score = 0 + with r = row + with c = col + for num-used from 0 to (length thrus) + for used-thrus = (subseq thrus 0 num-used) + for used-gaps = (subseq gaps 0 num-used) + for next-thru-size = (if thrus + (length (first (last used-thrus))) + 0) + for next-gap = (if (i= num-used (length thrus)) + rack-size + (nth num-used gaps)) + for max-tiles = (if (i< num-used (length thrus)) + (if used-thrus + (if (and (izerop (first gaps)) + (i= num-used 1)) + (i+ min-tiles next-gap -2) + (i+ min-tiles next-gap -1)) + (i+ min-tiles next-gap -2)) + (i1- rack-size)) + for ahead-r = r + for ahead-c = c + do + (loop + named :find-nonempty-square do + (when (or (not (on-board-p ahead-r ahead-c)) + (iplusp (aref letters ahead-r ahead-c))) + (return-from :find-nonempty-square)) + (multiple-value-setq (ahead-r ahead-c) + (next-sq ahead-r ahead-c t dir))) + (loop + repeat next-thru-size do + (when debug-p + (format t "ahead-r: ~a ahead-c: ~a~%" ahead-r ahead-c)) + (when (on-board-p ahead-r ahead-c) + (when debug-p + (format t "(score-at ahead-r ahead-c): ~a~%" + (score-at ahead-r ahead-c))) + (iincf score (score-at ahead-r ahead-c))) + (multiple-value-setq (ahead-r ahead-c) + (next-sq ahead-r ahead-c t dir))) + (when debug-p + (format t "min-tiles: ~a num-used: ~a max-tiles: ~a ~ + used-thrus: ~a~%~ + used-gaps: ~a next-gap: ~a next-thru-size: ~a~%" + min-tiles num-used max-tiles used-thrus used-gaps + next-gap next-thru-size)) + (when (i<= 0 max-tiles) + (loop + with hooked-p = nil + for num-tiles from min-tiles to max-tiles do + (when (or (iplusp num-tiles) + (and (first gaps) + (izerop (first gaps)) + (izerop num-tiles))) + (loop + named :advance-to-next-empty-square do + (multiple-value-setq (r c) + (next-sq r c t dir)) + (when (or (not (on-board-p r c)) + (i= +empty+ (aref letters r c))) + (return-from :advance-to-next-empty-square)))) + (when debug-p + (format t "r: ~a c: ~a~%" r c)) + (if (on-board-p r c) + (let ((cross (cross-at r c dir))) + (setf hooked-p (or hooked-p (iplusp cross)) + (aref scores num-tiles) score + (aref positions num-tiles) + (if (eq dir :horizontal) + (i- c col) + (i- r row)) + (aref patterns num-tiles) + (if used-thrus + (pattern used-thrus used-gaps) + (if hooked-p + '(#.+segment-delimiter+ + #.+segment-delimiter+) + nil))) + (when debug-p + (format t " r: ~a c: ~a dir: ~a cross: ~a ~ + score: ~a hooked-p: ~a used-thrus: ~a ~ + used-gaps: ~a~%" + r c dir cross score hooked-p + used-thrus used-gaps))) + (setf (aref patterns num-tiles) nil))) + (setf min-tiles (i1+ max-tiles)))) + (values patterns scores positions)))) + + (do-squares (row col dir sq-info h/v) + (let ((letter (aref letters row col))) + (when (i= +empty+ letter) + (let* ((before (reverse (get-word row col nil dir))) + (after (get-word row col t dir)) + (between (fit-between before after lexicon)) + (before-score (get-score row col nil dir)) + (after-score (get-score row col t dir))) + (setf (h/v-cross h/v) between + (h/v-score h/v) (i+ before-score after-score)))) + #+nil + (when (i/= +empty+ letter) + (format t "row: ~a col: ~a dir: ~a~%" row col dir)))) + + (do-squares (row col dir sq-info h/v) + (multiple-value-bind (prev-r prev-c) + (next-sq row col nil dir) + (unless (and (on-board-p prev-r prev-c) + (iplusp (aref letters prev-r prev-c))) + (multiple-value-bind (thrus gaps) + (thru-words row col dir) + (when debug-p + (format t "row: ~a col: ~a dir :~a thrus: ~a gaps: ~a~%" + row col dir (mapcar #'list2text thrus) gaps)) + (multiple-value-bind (thru-patterns scores positions) + (patterns thrus gaps row col dir) + (when debug-p + (format t "scores: ~a~%" scores)) + (setf (h/v-nodes h/v) (map 'vector + (lambda (pattern) + (or (filldawg-prefix-node-idx + pattern 0 lexicon) + 0)) + thru-patterns) + (h/v-scores h/v) scores + (h/v-positions h/v) positions) + #+nil + (loop + for pattern across thru-patterns + for num-tiles fixnum from 1 + for node = (filldawg-prefix-node-idx pattern 0 lexicon) + for example-lists = (and pattern debug-p + (not (equal node 35168598)) + (filldawg-extensions + nil node lexicon)) + for of-correct-length = (loop + for list in example-lists + when (i= (length list) num-tiles) + collect list) + for example-words = + (mapcar #'pattern2string + (subseq + of-correct-length 0 + (min 10 (length of-correct-length)))) + do + (when debug-p + (format t "pattern: ~a num: ~a examples: ~a~%" + (pattern2string pattern) + num-tiles + example-words)))))))))))) + +(defstruct perm-elt + change-pos + pos-value + next) + +(defstruct perm-seq + num-items + elts) + +(defvar *base-perm-seq-cache* (make-hash-table)) +(defvar *unique-perm-seq-cache* (make-hash-table :test 'equal)) + +(defun new-perm-seq (n &optional (memoize-p t)) + (when memoize-p + (return-from new-perm-seq + (or (gethash n *base-perm-seq-cache*) + (setf (gethash n *base-perm-seq-cache*) + (new-perm-seq n nil))))) + + (let ((elts nil) + (old-indices (make-array (list n) + :element-type 'fixnum + :initial-element -1)) + (indices (make-array (list n)))) + (dotimes (index n) (setf (aref indices index) index)) + (labels ((reverse-from (start) + (loop + for reverse1 from start below n + for reverse2 from (i1- n) downto start + while (i< reverse1 reverse2) + do (rotatef (aref indices reverse1) + (aref indices reverse2)))) + + ;; ported from C++ STL <algorithm> + (set-next-perm () + (when (i> 2 n) + (return-from set-next-perm nil)) + (loop + with i = (i1- n) + for j = i + do + (idecf i) + (when (i< (aref indices i) (aref indices j)) + (let ((k (i1- n))) + (loop + while (i>= (aref indices i) + (aref indices k)) + do (idecf k)) + (rotatef (aref indices i) (aref indices k)) + (reverse-from j) + (return-from set-next-perm t))) + (when (izerop i) + (reverse-from 0) + (return-from set-next-perm nil))))) + + (loop :named changes do + (loop + with change-p = nil + for i below n do + (when (or change-p (i/= (aref indices i) + (aref old-indices i))) + (let ((e (make-perm-elt + :change-pos i + :pos-value (aref indices i)))) + (push e elts) + (setf change-p t)))) + (dotimes (i n) (setf (aref old-indices i) (aref indices i))) + (unless (set-next-perm) + (return-from changes))) + + (dotimes (i n) (setf (aref indices i) (length elts))) + + (setf elts (make-array (list (length elts)) + :initial-contents (nreverse elts))) + + (loop + with size = (length elts) + for r from (i1- size) downto 0 + for elt = (aref elts r) + do + (setf (perm-elt-next elt) + (aref indices (perm-elt-change-pos elt))) + (loop + for q from (perm-elt-change-pos elt) below n do + (setf (aref indices q) r))) + + (make-perm-seq :num-items n :elts elts)))) + +(defun get-perm-constraints (rack) + (let* ((size (rack-size rack)) + (constraints (make-array size)) + (constraint-p nil) + (same-indices 1)) + (dotimes (i size) (setf (aref constraints i) (cons 0 0))) + (loop + for i from 1 below size do + (when (i/= (aref (rack-tiles rack) i) + (aref (rack-tiles rack) (i1- i))) + (setf same-indices 0)) + (setf (car (aref constraints i)) same-indices) + (when (iplusp same-indices) + (setf constraint-p t)) + (set-bitf same-indices i)) + (values constraint-p constraints))) + +(defun constraint-key (rack) + (multiple-value-bind (constraint-p constraints) + (get-perm-constraints rack) + (declare (ignore constraint-p)) + (loop for (a . b) across constraints collecting a))) + +(defun test-perm-constraints (constraints pos index) + (let ((preceding-indices (if (izerop pos) + 0 + (cdr (aref constraints (i1- pos))))) + (required-preceding-indices (car (aref constraints index)))) + (when (i/= (ilogand required-preceding-indices preceding-indices) + required-preceding-indices) + (return-from test-perm-constraints nil)) + (setf (cdr (aref constraints pos)) + (ilogior preceding-indices (1bit index))))) + +(defun unique-rack-perms (rack &optional (memoize-p t)) + (when memoize-p + (let ((key (constraint-key rack))) + (return-from unique-rack-perms + (or (gethash key *unique-perm-seq-cache*) + (setf (gethash key *unique-perm-seq-cache*) + (unique-rack-perms rack nil)))))) + + (let* ((base (new-perm-seq (rack-size rack) memoize-p))) + (multiple-value-bind (constraint-p constraints) + (get-perm-constraints rack) + + (when (null constraint-p) + (return-from unique-rack-perms base)) + + (let* ((seq (make-perm-seq :elts (copy-seq + (perm-seq-elts base)))) + (orig-size (length (perm-seq-elts seq))) + (orig2filtered (make-array (list (i1+ orig-size)))) + (j 0)) + + (loop :named test-constraints + with i = 0 + for elt = (aref (perm-seq-elts seq) i) do + (if (test-perm-constraints constraints + (perm-elt-change-pos elt) + (perm-elt-pos-value elt)) + (progn + (setf (aref orig2filtered i) j + (aref (perm-seq-elts seq) j) elt) + (iincf j) + (iincf i) + (when (i= i (length (perm-seq-elts seq))) + (return-from test-constraints))) + (progn + (setf (aref orig2filtered i) MOST-POSITIVE-FIXNUM) + (setf i (perm-elt-next elt)) + (when (i= i (length (perm-seq-elts seq))) + (return-from test-constraints))))) + + (setf (perm-seq-elts seq) + (adjust-array (perm-seq-elts seq) (list j))) + + (setf (aref orig2filtered orig-size) j) + + (loop + for i from (i1- orig-size) downto 0 + for f = (aref orig2filtered i) do + (when (i= MOST-POSITIVE-FIXNUM f) + (let* ((elt (aref (perm-seq-elts base) i)) + (next (perm-elt-next elt))) + (setf f (aref orig2filtered next)) + (setf (aref orig2filtered i) f) + (assert (i>= next i)) + (assert (i<= f j))))) + + (loop + for i below j + for elt = (aref (perm-seq-elts seq) i) do + (assert (i<= (perm-elt-next elt) orig-size)) + (setf (perm-elt-next elt) + (aref orig2filtered (perm-elt-next elt))) + (assert (<= (perm-elt-next elt) j)) + #+nil + (format t "(perm-elt-next elt): ~a j: ~a perm-elt-change-pos-blah: ~a~%" + (perm-elt-next elt) j + (when (i/= (perm-elt-next elt) j) + (perm-elt-change-pos + (aref (perm-seq-elts seq) + (perm-elt-next elt))))) + (assert (or (i= (perm-elt-next elt) j) + (>= (perm-elt-change-pos elt) + (perm-elt-change-pos + (aref (perm-seq-elts seq) + (perm-elt-next elt))))))) + + seq)))) |