Created
April 3, 2012 01:17
-
-
Save mikebridge/2288537 to your computer and use it in GitHub Desktop.
Codelesson Clojure Assignment 5
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
(ns codelesson.assignment-5 | |
(:require [clojure.string :as str])) | |
;; util | |
(defn log [s] | |
(println s)) | |
;; delay a function for a random number of milliseconds | |
(defn wait-and-call [f] | |
{:pre [(fn? f)]} | |
(fn [& args] | |
{:pre [(fn? f)]} | |
(let [interval (rand-int 2000)] | |
(Thread/sleep (rand-int interval)) | |
(apply f args)))) | |
(def uniq-id (ref 1)) | |
(defn new-uniq-id [] | |
(dosync (alter uniq-id inc))) | |
;; plateau | |
;; the plateau is a map from a position [x y] to a rover id | |
(def plateau-map (ref {})) | |
(defn plateau-clear [plateau-ref] | |
(dosync (ref-set plateau-ref {}))) | |
;; return nil if can't move to new position | |
(defn plateau-move [plateau-ref id old-coords new-coords] | |
{:pre [(vector? old-coords) (vector? new-coords)]} | |
(dosync | |
(if (and (plateau-ref new-coords) | |
(not= id (plateau-ref new-coords))) | |
(do | |
(log (str "** COLLISION at " new-coords | |
" between #" id " and #" (plateau-ref new-coords))) | |
old-coords ;; collision: need to signal it somehow | |
) | |
(do | |
(alter plateau-ref dissoc old-coords) | |
(alter plateau-ref assoc new-coords id) | |
new-coords)))) | |
;; move | |
(defn rotate-left [current-dir] | |
{:pre [(char? current-dir)]} | |
((apply hash-map (seq "NWWSSEEN")) current-dir)) | |
(defn rotate-right [current-dir] | |
{:pre [(char? current-dir)]} | |
((apply hash-map (seq "NEESSWWN")) current-dir)) | |
(defn rotate [rover f] | |
(assoc rover :orientation (f (:orientation rover)))) | |
(defn delta-xy [direction] | |
{:pre [(char? direction)]} | |
({\N [0 1] \E [1 0] \S [0 -1] \W [-1 0]} direction)) | |
(defn new-point [& v] | |
(vec (apply map + v))) | |
(defn move [rover] | |
{:pre [(map? rover) (char? (:orientation rover))]} | |
(assoc rover :position | |
(new-point (:position rover) (delta-xy (:orientation rover))))) | |
(defn command [cmd] | |
{:pre [(char? cmd)]} | |
(cond | |
(= cmd \M) (fn [rover] (move rover)) | |
(= cmd \R) (fn [rover] (rotate rover rotate-right)) | |
(= cmd \L) (fn [rover] (rotate rover rotate-left)))) | |
(defn movement-functions [movement-commands] | |
(map #(command %1) movement-commands)) | |
;; collisions return a rover in an identical position | |
(defn apply-movement [rover plateau-map plateau-size move-fn] | |
{:pre [(map? rover) (vector? plateau-size) (fn? move-fn)] | |
:post [(map? %)]} | |
(let [requested-new-rover (move-fn rover) | |
maxx (first plateau-size) | |
maxy (second plateau-size) | |
x (first (:position requested-new-rover)) | |
y (second (:position requested-new-rover))] | |
(log (str "#" (:id rover) | |
" F:" (:position rover) | |
"/" (:orientation rover) | |
" T:" (:position requested-new-rover) | |
"/" (:orientation requested-new-rover))) | |
(if (or (< x 0) | |
(> x maxx) | |
(< y 0) | |
(> y maxy)) | |
;; off plateau | |
(do | |
(log (str "#" (:id rover) " hit the edge of the plateau")) | |
rover | |
) | |
;; try to move to new position | |
(assoc rover :position | |
(plateau-move plateau-map | |
(:id rover) | |
(:position rover) | |
(:position requested-new-rover)))))) | |
(defn rover-hash [position-coords orientation] | |
{:pre [(char? orientation)]} | |
{:id (new-uniq-id) :position (vec position-coords) :orientation orientation}) | |
;; return future reference which is executing a sequence of | |
;; commands for this rover. | |
(defn spawn-rover | |
[plateau-size plateau-map start-pos commands] | |
{:pre [(vector? plateau-size) (vector? start-pos) (string? commands)] | |
:post [(future? %)]} | |
(let [move-fns (movement-functions commands) | |
rover (rover-hash (take 2 start-pos) (start-pos 2)) | |
fns-with-delay (map wait-and-call move-fns)] | |
;;(spawn-movements plateau-size rover fns-with-delay))) | |
(log (str "spawning #" (:id rover) " at " (:position rover))) | |
(future (reduce | |
#(apply-movement %1 plateau-map plateau-size %2) | |
(cons rover fns-with-delay))))) | |
;; read data | |
(defn str-to-vec [s] | |
(read-string (str "[" s "]"))) | |
(defn char-if-sym [s] | |
(if (symbol? s) | |
(first (str s)) | |
s)) | |
(defn convert-position [pos] | |
{:pre [(string? pos)]} | |
(vec (map char-if-sym (str-to-vec pos)))) | |
(defn rover-to-string [rover] | |
(str (first (:position rover)) " " (second (:position rover)) " " (:orientation rover))) | |
(defn positions-to-string [position-list] | |
(clojure.string/join "\n" (map rover-to-string position-list))) | |
(defn rove [in] | |
{:pre [(string? in)]} | |
(positions-to-string | |
(let [lines (clojure.string/split-lines in) | |
plateau-size (str-to-vec (first lines)) | |
pos-and-command-pairs (partition 2 (rest lines)) | |
futures (map #(spawn-rover plateau-size | |
plateau-map | |
(convert-position (first %)) | |
(first (rest %))) pos-and-command-pairs) | |
] | |
;; not sure why I have to sort these backwards to launch all threads? | |
(map deref (reverse futures))))) | |
Note: the "T:" represents a requested move, not an actual move.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
"4 0 W\n3 0 E\n5 3 E\n1 5 N"