Skip to content

Instantly share code, notes, and snippets.

@devstopfix
Created December 14, 2016 22:44
Show Gist options
  • Save devstopfix/480eaadcf3a1af42f310a96018553a66 to your computer and use it in GitHub Desktop.
Save devstopfix/480eaadcf3a1af42f310a96018553a66 to your computer and use it in GitHub Desktop.
Zuhlke UK Coding Night - Scrabble word score in ClojureScript
#!/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