Source

typkov / src / typkov / views / core.clj

(ns typkov.views.core
  (:require [typkov.templates :as t])
  (:require [noir.validation :as valid])
  (:require [clojure.string :as string])
  (:use [noir.core :only [render defpage]]))


; Utils -----------------------------------------------------------------------
(defn random-choice [coll]
  (nth coll (rand-int (count coll))))

(defn random-choice-weighted [choices]
  (let [n (apply + (vals choices))
        i (inc (rand-int n))]
    (loop [i i
           choices (seq choices)]
      (let [[word weight] (first choices)
            new-i (- i weight)]
        (if (<= new-i 0)
          word
          (recur new-i (rest choices)))))))

(def human-indices (partial iterate inc 1))

(def join-s (partial string/join " "))

(def join-n (partial string/join "\n"))

(def join-nn (partial string/join "\n\n"))


; Gtypist ---------------------------------------------------------------------
(defn to-gtypist-single [lesson n]
  (let [lines (map join-s lesson)]
    (str "*:S_LESSON" n "\n"
         "B:Lesson " n "\n"
         (apply str (map #(str %2 ":" % "\n")
                         lines
                         (concat ["D"] (cycle [" "]))))
         "G:MENU\n")))

(defn to-gtypist [lessons]
  (str "G:MENU\n\n"
       (join-n
         (map to-gtypist-single
              lessons
              (human-indices)))
       "\n*:MENU\n"
       "M: \"Lesson generated by Typkov\"\n"
       (apply str (map #(str " :S_LESSON" % " \"Lesson " % "\"\n")
                       (take (count lessons) (human-indices))))))


; Ktouch ----------------------------------------------------------------------
(defn to-ktouch-single [lesson n]
  (let [lines (map join-s lesson)]
    (str "Lesson " n "\n"
         (join-n lines))))

(defn to-ktouch [lessons]
  (str "# Lesson generated by Typkov\n\n"
       (join-nn (map to-ktouch-single
                     lessons
                     (human-indices)))))


; Markov ----------------------------------------------------------------------
(defn markov-dict
  ([words]
   (markov-dict {} words))
  ([dict words]
   (if (not (second words))
     dict
     (let [w (first words)
           n (second words)]
       (recur (assoc-in dict [w n]
                        (inc (get-in dict [w n] 0)))
              (rest words))))))

(defn markov-get [dict word]
  (if (dict word)
    (random-choice-weighted (dict word))
    (random-choice (keys dict))))


; Lesson generation -----------------------------------------------------------
(defn sanitize [s]
  (-> s
    (string/replace \’ \')
    (string/replace \” \")
    (string/replace \“ \")))

(defn get-line [dict]
  (let [initial-word (random-choice (keys dict))]
    (take 10 (iterate (partial markov-get dict) initial-word))))

(defn get-single [dict]
  (repeatedly 4 (partial get-line dict)))


(defn get-lesson [text format n]
  (let [words (take 20000 (filter (comp not empty?)
                                  (string/split (sanitize text)
                                                #"\s+")))
        dict (markov-dict words)
        lessons (repeatedly n (partial get-single dict))]
    (case format
      "gtypist" (to-gtypist lessons)
      "ktouch" (to-ktouch lessons))))


; Home page -------------------------------------------------------------------
(defn form-valid? [{:keys [text]}]
  (valid/rule (valid/min-length? text 24)
              [:text "You must enter a decent amount of text!"])
  (not (valid/errors? :text)))


(defpage "/" {:as data}
  (t/home (:text data)
          (Integer/parseInt (get data :num "10"))
          (:format data)
          nil))

(defpage [:post "/"] {:as data}
  (if (or (:restart data)
          (not (form-valid? data)))
    (render "/" data)
    (t/home (:text data)
            (Integer/parseInt (:num data))
            (:format data)
            (get-lesson (:text data)
                        (:format data)
                        (Integer/parseInt (:num data))))))