robotwar/src/robotwar/register.clj

183 lines
7.3 KiB
Clojure

(ns robotwar.register
(:use [robotwar.constants])
(:require [robotwar.shell :as shell]))
(def reg-names [ "DATA"
"A" "B" "C" "D" "E" "F" "G" "H" "I" "J" "K" "L" "M"
"N" "O" "P" "Q" "R" "S" "T" "U" "V" "W" "X" "Y" "Z"
"AIM" "SHOT" "RADAR" "DAMAGE" "SPEEDX" "SPEEDY" "RANDOM" "INDEX" ])
(defn path-to-robot [robot-idx]
[:robots robot-idx])
(defn path-to-robot-field [robot-idx robot-field]
[:robots robot-idx robot-field])
(defn path-to-registers [robot-idx]
[:robots robot-idx :brain :registers])
(defn path-to-val [robot-idx reg-name]
[:robots robot-idx :brain :registers reg-name :val])
(defprotocol IReadRegister
"returns the value of a register"
(read-register [this world]))
(defprotocol IWriteRegister
"returns a world"
(write-register [this world data]))
(def register-field-read-mixin
; returns :val field of register
(fn [{val :val} world]
val))
(def register-field-write-mixin
; returns a world with :val field of register altered
(fn [{:keys [robot-idx reg-name]} world data]
(assoc-in world
(path-to-val robot-idx reg-name)
data)))
(def robot-field-read-mixin
; returns the value of a field in the robot hash-map,
; rounded to an integer
(fn [{:keys [robot-idx field-name multiplier]} world]
(Math/round (/ (get-in
world
(path-to-robot-field robot-idx field-name))
multiplier))))
(def robot-field-write-mixin
; returns a world with the value of a field in the robot hash map altered
; (with the number being cast to double before being pushed)
(fn [{:keys [robot-idx field-name multiplier]} world data]
(assoc-in
world
(path-to-robot-field robot-idx field-name)
(double (* data multiplier)))))
(defrecord StorageRegister [robot-idx reg-name val])
(extend StorageRegister
IReadRegister
{:read-register register-field-read-mixin}
IWriteRegister
{:write-register register-field-write-mixin})
(defrecord ReadWriteRobotFieldRegister [robot-idx field-name multiplier])
(extend ReadWriteRobotFieldRegister
IReadRegister
{:read-register robot-field-read-mixin}
IWriteRegister
{:write-register robot-field-write-mixin})
(defrecord ReadOnlyRobotFieldRegister [robot-idx field-name multiplier])
(extend ReadOnlyRobotFieldRegister
IReadRegister
{:read-register robot-field-read-mixin}
IWriteRegister
; returns a world with nothing changed
{:write-register (fn [this world data]
world)})
(defrecord RandomRegister [robot-idx reg-name val])
(extend RandomRegister
IReadRegister
; returns a random number. maximum value is the :val field of the register
{:read-register (fn [{val :val} world]
(rand-int val))}
IWriteRegister
{:write-register register-field-write-mixin})
(defrecord AimRegister [robot-idx field-name multiplier])
(extend AimRegister
IReadRegister
{:read-register robot-field-read-mixin}
IWriteRegister
; sets the angle of the gun
{:write-register
(fn [{:keys [robot-idx field-name multiplier]} world data]
(assoc-in
world
(path-to-robot-field robot-idx field-name)
(mod (double (* data multiplier)) 360)))})
(defrecord ShotRegister [robot-idx field-name multiplier])
(extend ShotRegister
IReadRegister
{:read-register robot-field-read-mixin}
IWriteRegister
; adds a shell to the list of shells.
; It's a no-op if the shot clock hasn't reached zero yet.
{:write-register
(fn [{:keys [robot-idx field-name]}
{:keys [shells next-shell-id] :as world}
data]
(let [{:keys [pos-x pos-y aim shot-timer] :as robot}
(get-in world (path-to-robot robot-idx))]
(if (> shot-timer 0)
world
(let [world-with-new-shot-timer (assoc-in
world
(path-to-robot-field robot-idx :shot-timer)
GAME-SECONDS-PER-SHOT)]
(assoc
world-with-new-shot-timer
:shells (merge shells (shell/init-shell pos-x pos-y aim next-shell-id data))
:next-shell-id (inc next-shell-id))))))})
(defn get-target-register
"helper function for DataRegister record"
[world robot-idx index-reg-name]
(let [registers (get-in world (path-to-registers robot-idx))]
(registers (reg-names (read-register (registers index-reg-name) world)))))
(defrecord DataRegister [robot-idx index-reg-name]
IReadRegister
(read-register
; returns the number stored in whatever register
; is pointed to by the index-reg-name register
[this world]
(read-register (get-target-register world robot-idx index-reg-name) world))
IWriteRegister
(write-register
; returns a world with the number in the register pointed to
; by the index-reg-name register updated with the data argument to write-register
[this world data]
(write-register (get-target-register world robot-idx index-reg-name) world data)))
; TODO: (defrecord ShotRegister [robot-idx reg-name])
; TODO: (defrecord RadarRegister [robot-idx reg-name])
(defn init-registers
"AIM, INDEX, SPEEDX and SPEEDY.
AIM and INDEX's specialized behaviors are only when they're used by
SHOT and DATA, respectively. In themselves, they're only storage registers.
Likewise, SPEEDX and SPEEDY are used later in tick-robot to determine
the appropriate acceleration, which may have to applied over several ticks."
[robot-idx]
(let [storage-registers (for [reg-name [ "A" "B" "C" "D" "E" "F" "G" "H" "I" "J" "K" "L"
"M" "N" "O" "P" "Q" "R" "S" "T" "U" "V" "W" "Z"]]
{reg-name (->StorageRegister robot-idx reg-name 0)})
read-only-registers (for [[reg-name robot-field mult] [["X" :pos-x 1.0]
["Y" :pos-y 1.0]
["DAMAGE" :damage 1.0]]]
{reg-name (->ReadOnlyRobotFieldRegister robot-idx robot-field mult)})
; TODO: change reading from these registers into an error, instead of just a wasted
; processor cyle for the robot.
read-write-registers (for [[reg-name robot-field mult] [["AIM" :aim 1.0]
["SPEEDX" :desired-v-x 0.1]
["SPEEDY" :desired-v-y 0.1]]]
{reg-name (->ReadWriteRobotFieldRegister robot-idx robot-field mult)})]
(into {} (concat storage-registers
read-only-registers
read-write-registers
[{"INDEX" (->StorageRegister robot-idx "INDEX" 0)}
{"DATA" (->DataRegister robot-idx "INDEX")}
{"RANDOM" (->RandomRegister robot-idx "RANDOM" 0)}
{"AIM" (->AimRegister robot-idx :aim 1.0)}
{"SHOT" (->ShotRegister robot-idx :shot-timer *GAME-SECONDS-PER-TICK*)}
; TODO: {"RADAR" (->RadarRegister robot-idx "RADAR")}
]))))