robotwar/src/robotwar/terminal.clj
Richard Harrington 9b46900f9b changed width and height to robot-range-x and robot-range-y and made
them constants

because technically, the robot ranges are the maximum ranges for the
centers of the robots' bodies,
so the actual extent of the arena when
displayed must be slightly larger
2013-08-20 12:10:58 -04:00

85 lines
3.9 KiB
Clojure

(ns robotwar.terminal
(:use [clojure.string :only [join]]
[clojure.pprint :only [pprint]]
[robotwar.constants])
(:require [clj-time.core :as time]
[clj-time.periodic :as periodic]))
(defn worlds-for-terminal
"takes worlds and a fast-forward factor, and
adds two fields to each world: idx and timestamp.
useful for our hacky display-in-terminal situation."
[worlds fast-forward]
(let [tick-duration (/ *GAME-SECONDS-PER-TICK* fast-forward)]
(map-indexed (fn [idx world]
(into world
{:idx idx
:timestamp (int (* idx tick-duration 1000))}))
worlds)))
(defn near-point [[pos-x pos-y] [x y]]
(and (= (int pos-x) x)
(= (int pos-y) y)))
(defn arena-text-grid
"outputs the arena, with borders"
[{robots :robots} print-robot-range-x print-robot-range-y]
(let [horiz-border-char "-"
vert-border-char "+"
header-footer (apply str (repeat (+ (* print-robot-range-x 3) 2) horiz-border-char))
scale-x #(* % (/ print-robot-range-x ROBOT-RANGE-X))
scale-y #(* % (/ print-robot-range-y ROBOT-RANGE-Y))
field (for [y (range print-robot-range-y), x (range print-robot-range-x)]
(or (some (fn [{:keys [idx pos-x pos-y]}]
(when (near-point [(scale-x pos-x) (scale-y pos-y)] [x y])
(str "(" idx ")")))
robots)
" "))]
(str header-footer
"\n"
(join "\n" (map #(join (apply str %) (repeat 2 vert-border-char))
(partition print-robot-range-x field)))
"\n"
header-footer)))
(defn display-robots-info [{idx :idx :as world} time-since-start fps]
(doseq [robot-idx (range (count (:robots world)))]
(println (apply format
"%d: x %.1f, y %.1f, v-x %.1f, v-y %.1f, desired-v-x %.1f, desired-v-y %.1f"
(map #(get-in world [:robots robot-idx %])
[:idx :pos-x :pos-y :v-x :v-y :desired-v-x :desired-v-y]))))
(println (format "Animation frame rate: %.1f frames per second", fps))
(println "Round number:" idx)
(println (format "Seconds elapsed in the game world: %d", (int (* idx *GAME-SECONDS-PER-TICK*))))
(println (format "Seconds elapsed in the real world: %d", (time/in-secs time-since-start)))
(println))
(defn animate
"animates a sequence of worlds in the terminal"
[initial-worlds print-robot-range-x print-robot-range-y fps]
(let [frame-period (time/millis (* (/ 1 fps) 1000))
starting-instant (time/now)]
(loop [[world :as worlds] initial-worlds
frame-start starting-instant]
(println (arena-text-grid world print-robot-range-x print-robot-range-y))
(display-robots-info world (time/interval starting-instant frame-start) fps)
(let [desired-next-frame-calc-start (time/plus frame-start frame-period)
this-instant (time/now)
next-frame-calc-start (if (time/after? this-instant desired-next-frame-calc-start)
this-instant
(do
(-> (time/interval
this-instant
desired-next-frame-calc-start)
(time/in-msecs)
(Thread/sleep))
desired-next-frame-calc-start))
animation-timestamp (time/in-msecs (time/interval
starting-instant
next-frame-calc-start))]
(recur (drop-while #(< (:timestamp %) animation-timestamp)
worlds)
next-frame-calc-start)))))