Created
December 14, 2016 22:44
-
-
Save devstopfix/480eaadcf3a1af42f310a96018553a66 to your computer and use it in GitHub Desktop.
Zuhlke UK Coding Night - Scrabble word score in ClojureScript
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env planck | |
; | |
; Coding night challenge - Scrabble | |
; Generate a rack of tiles and find highest scoring word | |
; | |
; Written in ClojureScript running on Planck | |
; http://planck-repl.org/guide-all.html | |
; | |
; To run on OS X: | |
; | |
; brew install planck | |
; cd ~/Downloads | |
; wget -O scrabble.cljs https://gist.github.com/devstopfix/... | |
; chmod +x scrabble.cljs | |
; | |
; ./scrabble.cljs | |
; | |
(ns zuhlke.codingnight.scrabble | |
(:require [planck.core :refer [slurp]] | |
[clojure.string :refer [split-lines upper-case join]] | |
[goog.string :as gstring] | |
[goog.string.format])) | |
(def number-of-tiles-in-rack 9) | |
; https://en.wikipedia.org/wiki/Scrabble_letter_distributions#English | |
(def wikipedia-Scrabble-letter-distributions | |
"1 point: E ×12, A ×9, I ×9, O ×8, N ×6, R ×6, T ×6, L ×4, S ×4, U ×4 | |
2 points: D ×4, G ×3 | |
3 points: B ×2, C ×2, M ×2, P ×2 | |
4 points: F ×2, H ×2, V ×2, W ×2, Y ×2 | |
5 points: K ×1 | |
8 points: J ×1, X ×1 | |
10 points: Q ×1, Z ×1") | |
(def letter-frequencies | |
"Map of letter to the number of tiles in the game" | |
(->> wikipedia-Scrabble-letter-distributions | |
(re-seq #"([A-Z]) ×(\d+)") | |
(reduce (fn [res [_ l f]] (assoc res l (js/parseInt f))) {}))) | |
(def letter-tiles-in-game | |
"Seq of all letter tiles in a game | |
[B B C C D D D D ...]" | |
(flatten (for [[l n] letter-frequencies] (repeat n l)))) | |
(defn gen-rack | |
"Generate a rack of n tiles" | |
([] (gen-rack number-of-tiles-in-rack)) | |
([n] (->> letter-tiles-in-game | |
(shuffle) | |
(take n)))) | |
(def letter-scores | |
"Map of Letter to it's points score {Z 10, A 1 ...})" | |
(->> wikipedia-Scrabble-letter-distributions | |
(re-seq #"(\d+) point[s]?: (.*)") | |
(reduce | |
(fn [res [_ points line]] | |
(->> (re-seq #"[A-Z]" line) | |
(reduce (fn [res l] (assoc res l (js/parseInt points))) {}) | |
(merge res))) | |
{}))) | |
(def words | |
(->> "/usr/share/dict/words" | |
(slurp) | |
(split-lines) | |
(map upper-case) | |
(distinct))) | |
(defn single-letter? [w] | |
(= 1 (.-length w))) | |
(defn long-word? [w] | |
(> (.-length w) number-of-tiles-in-rack)) | |
(def scrabble-words | |
"All words that can be played in Scrabble (length 2..7)" | |
(->> words | |
(remove single-letter?) | |
(remove long-word?))) | |
(defn letter-frequencies-of-word [w] | |
(-> w | |
(seq) | |
(frequencies))) | |
(defn enough-letters-in-rack? [rack l n] | |
"Return true iff we have n or more letter l in our rack" | |
(>= (get rack l 0) n)) | |
(defn word-in-rack? [rack-freq word] | |
"Return true iff the word can be formed from the letters in the rack" | |
(let [word-freq (letter-frequencies-of-word word)] | |
(every? | |
(fn [[letter freq]] (enough-letters-in-rack? rack-freq letter freq)) | |
word-freq))) | |
(defn candidate-words [rack] | |
"Return a seq of words that can be formed from the given rack. | |
Rack is a seq of letters" | |
(let [rack-freq (frequencies rack)] | |
(filter #(word-in-rack? rack-freq %) scrabble-words))) | |
(defn score-word [word] | |
(->> word | |
(seq) | |
(map #(get letter-scores %)) | |
(reduce +))) | |
(defn compare-scores [w1 w2] | |
"Compare scores (higher comes first)" | |
(compare (score-word w2) (score-word w1))) | |
(defn compare-length [w1 w2] | |
"Compare words by number of letters (longest first)" | |
(compare (count w2) (count w1))) | |
(defn play [] | |
(let [rack (gen-rack) | |
words (candidate-words rack) | |
top-word (first (sort compare-scores words)) | |
longest-word (first (sort compare-length words))] | |
(gstring/format | |
"[%s] %s scores %d points, longest %s %s" | |
(join " " rack) | |
top-word | |
(score-word top-word) | |
(if (= top-word longest-word) "is also" "is") | |
longest-word))) | |
(doseq [n (range number-of-tiles-in-rack)] | |
(-> | |
(play) | |
(println))) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment