207 lines
6.1 KiB
Fennel
207 lines
6.1 KiB
Fennel
(local { : view } (require :fennel))


(local { : merge : any } (require :lume))




(fn wraplongitude [x]


(if (<= x 180) (wraplongitude (+ x 360))


(< 180 x) (wraplongitude ( x 360))


x))




(fn atobstacle? [{: x : y : obstacles}]


(any obstacles


(fn [[ox oy]] (and (= ox x) (= oy y)))))




(fn lateral? [direction]


(. {:w true :e true} direction))




(fn trytodrive [{: x : y : direction} distance]


(let [distance


(if (lateral? direction)


(/ distance (math.cos (/ (* math.pi y) 180)))


distance)]


(match direction


:n {:y (+ y distance)}


:s {:y ( y distance)}


:w {:x (wraplongitude ( x distance))}


:e {:x (wraplongitude (+ x distance))})))




(fn drive [r distance]


(if r.stopped


r


(let [nextmove (merge r (trytodrive r distance))]


(if (atobstacle? nextmove)


(merge r {:stopped [nextmove.x nextmove.y]})


nextmove))))




(fn turnleft [{: x : y : direction}]


(if (= y 90)


{:x (wraplongitude ( x 90))}


{:direction


(> {


:n :w


:w :s


:s :e


:e :n


}


(. direction))}))




(fn turnright [{: x : y : direction}]


(if (= y 90)


{:x (wraplongitude (+ x 90))}


{:direction


(> {


:n :e


:e :s


:s :w


:w :n


}


(. direction))}))




(fn command [rover string]


(merge rover


(match string


:f (drive rover 1)


:b (drive rover 1)


:r (turnright rover)


:l (turnleft rover)


_ (assert false (. "unrecognised command " string)))))




(fn fudgeforpole [{: y &as r}]


(if (= y 90)


(merge r {:direction :s})


(= y 90)


(merge r {:direction :n})


r))




(fn execute [rover cmds]


(accumulate [rover rover


_ c (ipairs cmds)]


(merge rover (fudgeforpole (command rover c)))))




(fn rover [x y direction ?obstacles]


{: x


: y


: direction


:obstacles (or ?obstacles [])


})




;;;; TESTS




;; who needs a test framework when you have lisp macros?


(macro expect [text actual expected]


`(let [actual# ,actual]


(or (match actual#


,expected true)


(assert false (view {


:text ,text


:expected ,(view expected)


:actual actual#


})))))




(let [r (rover 7 15 :n)]


(expect


"rover knows (x,y) and the direction (N,S,E,W) it is facing"


r


{:x 7 :y 15 :direction :n}))




(let [r (rover 0 0 :n)]


(expect "The rover receives a character array of commands"


(execute r [:f :f :f])


{}))




(let [r (rover 0 0 :n)]


(expect "Moves north when pointing north and asked to move forward"


(execute r [:f]) {:x 0 :y 1 :direction :n}))




(let [r (rover 3 2 :s)]


(expect "Moves south when pointing south and asked to move forward"


(execute r [:f]) {:x 3 :y 1 :direction :s}))




(let [r (rover 0 0 :w)]


(expect "Moves west when pointing west and asked to move forward"


(execute r [:f]) {:x 1 :y 0 :direction :w}))




(let [r (rover 0 0 :e)]


(expect "Moves east when pointing east and asked to move forward"


(execute r [:f]) {:x 1 :y 0 :direction :e}))




(let [r (rover 1 1 :s)]


(expect "The rover acts on multiple commands"


(execute r [:f :f :f])


{:x 1 :y 2 :direction :s}))




(let [r (rover 3 2 :s)]


(expect "Moves north when pointing south and asked to move backward"


(execute r [:b]) {:x 3 :y 3 :direction :s}))




(let [r (rover 3 0 :e)]


(expect "Moves west when pointing east and asked to move backward"


(execute r [:b]) {:x 2 :y 0 :direction :e}))




(let [r (rover 2 4 :e)]


(expect "Rotates to south when pointing east and asked to turn right"


(execute r [:r]) {:x 2 :y 4 :direction :s}))




(let [r (rover 2 4 :s)]


(expect "Rotates to east when pointing south and asked to turn left"


(execute r [:l]) {:x 2 :y 4 :direction :e}))




(let [r (rover 2 4 :w)]


(expect "Rotates to north when pointing west and asked to turn right"


(execute r [:r]) {:x 2 :y 4 :direction :n}))




(let [r (rover 1 1 :s)]


(expect "Multiple commands are executed in the right order"


(execute r [:l :l :f :f :f :r :f :b])


{:x 1 :y 4 :direction :e}))




;; Circumference of mars is 3,376.2km (about half that of earth).


;; We choose the unit of distance to be equal to one degree at the equator


;; (about 60km), so if we travel the same linear distance at a higher


;; latitude, we will move be a larger number of degrees




(expect "At 60 degrees latitude, one unit of drive is two degrees"


(execute (rover 0 60 :e) [:f])


(where {:x x :y 60 :direction :e}


(< (math.abs ( x 2)) 0.000001)


))




(expect "At 45 degrees latitude, one unit of drive is (sqrt 2) degrees"


(execute (rover 0 45 :e) [:f])


(where {:x x } (< (math.abs ( x 1.4141)) 0.001)))




(expect "At 45 degrees latitude south, one unit of drive is (sqrt 2) degrees"


(execute (rover 0 45 :e) [:f])


(where {:x x :y 45} (< (math.abs ( x 1.4141)) 0.001)))




;; valid longitudes are 180 .. 180




(expect "Longitude wraps from positive to negative when travelling west"


(execute (rover 179 0 :w) [:f :f :f])


{:x 178 :y 0 :direction :w})




(expect "Longitude wraps from negative to positive when travelling east"


(execute (rover 179 0 :e) [:f :f :f])


{:x 178 :y 0 :direction :e})




(expect "At the North Pole we always point south"


(execute (rover 0 89 :n) [:f])


{:x 0 :y 90 :direction :s})




(expect "At the North Pole, turning left affects x not direction"


(execute (rover 0 89 :n) [:f :l])


{:x 90 :y 90 :direction :s})




(expect "At the North Pole, turning right affects x not direction"


(execute (rover 0 89 :n) [:f :r])


{:x 90 :y 90 :direction :s})




(expect "It does not move past obstacles"


(let [obstacles [[5 5] [7 5]]]


(execute (rover 5 3 :n obstacles) [:f :f :f :f :f :f]))


{:x 5 :y 4 :direction :n :stopped [5 5]})






;; "TODO: deal with the south pole special casing"




(print "OK")
