diff --git a/src/robotwar/brain.clj b/src/robotwar/brain.clj new file mode 100644 index 0000000..65cddfa --- /dev/null +++ b/src/robotwar/brain.clj @@ -0,0 +1,100 @@ +(ns robotwar.brain + (:use [clojure.string :only [join]] + [robotwar.kernel-lexicon])) + +(def op-map (zipmap op-commands + (map (fn [op] + (case op + "/" #(int (Math/round (float (/ %1 %2)))) + "#" not= + (-> op read-string eval))) + op-commands))) + +(defn init-register [reg-name read write data] + {reg-name {:read read + :write write + :data data}}) + +(defn default-read [data world] + data) + +(defn default-write [robot data] + (assoc-in robot [:brain :registers reg-name] data)) + +(def default-data 0) + +(defn default-register [reg-name] + (init-register reg-name default-read default-write default-data)) + +(defn init-brain + "initialize the brain (meaning all the internal state variables that go along + with the robot program when it's running). + (Optionally, also pass in a hash-map of register names and values, + which will override the defaults)." + [program reg-names & [registers]] + {:acc 0 + :instr-ptr 0 + :call-stack [] + :program program + :registers (into {} (concat (map default-register reg-names) registers))}) + +(defn read-register + "wrapper for the read function in each register. takes a register + and also takes world state, because + some of those functions may need access to the world state. + returns a numeric value." + [{:keys [read data] :as register} world] + (read data world)) + +(defn write-register + "wrapper for the write function in each register. + takes a robot, a register, and the data to write. + returns a full robot (brain and external properties as well). + TODO: implement extra flag to indicate if we've fired a shot." + [robot {write :write :as register} data] + (write robot data)) + +(defn resolve-arg [{arg-val :val arg-type :type} registers labels world] + "resolves an instruction argument to a numeric value + (either an arithmetic or logical comparison operand, or an instruction pointer)." + (case arg-type + :label (labels arg-val) + :number arg-val + :register (read-register (registers arg-val) world) + nil)) + +(defn tick-brain + "takes a full robot. Only the internal state (the brain) will be + different when we pass it back, for all of the operations except 'TO', + which may alter the external state of the robot as well. (And for the time + being, shots fired will be indicated with a flag in the robot.) + + Also takes a 'world' parameter, which may contain information that some of the + registers' read functions may need. Will not be passed out in the return value. + + TODO: Figure out a way to have this function not know about the external robot stuff, + like that the name of the field leading to the brain is :brain." + + [robot world] + (let [brain (:brain robot) + {:keys [acc instr-ptr call-stack registers program]} brain + [{command :val} arg] ((:instrs program) instr-ptr) + resolve #(resolve-arg % registers (:labels program) world) + return-robot #(assoc robot :brain (into brain %))] + (case command + "GOTO" (return-robot {:instr-ptr (resolve arg)}) + "GOSUB" (return-robot {:instr-ptr (resolve arg) + :call-stack (conj call-stack (inc instr-ptr))}) + "ENDSUB" (return-robot {:instr-ptr (peek call-stack) + :call-stack (pop call-stack)}) + ("IF", ",") (return-robot {:instr-ptr (inc instr-ptr) + :acc (resolve arg)}) + ("+" "-" "*" "/") (return-robot {:instr-ptr (inc instr-ptr) + :acc ((op-map command) acc (resolve arg))}) + ("=" ">" "<" "#") (if ((op-map command) acc (resolve arg)) + (return-robot {:instr-ptr (inc instr-ptr)}) + (return-robot {:instr-ptr (+ instr-ptr 2)})) + "TO" (write-register (return-robot {:instr-ptr (inc instr-ptr)}) + (registers (:val arg)) + acc)))) + diff --git a/src/robotwar/core.clj b/src/robotwar/core.clj index b9fd74b..dc8907e 100644 --- a/src/robotwar/core.clj +++ b/src/robotwar/core.clj @@ -1,6 +1,6 @@ (ns robotwar.core (:use [clojure.pprint] - (robotwar foundry robot world game-lexicon))) + (robotwar foundry brain robot world game-lexicon))) (def src-code1 " START 0 TO A diff --git a/src/robotwar/robot.clj b/src/robotwar/robot.clj index 6bce686..993cb4f 100644 --- a/src/robotwar/robot.clj +++ b/src/robotwar/robot.clj @@ -1,112 +1,26 @@ (ns robotwar.robot (:use [clojure.string :only [join]] - [clojure.pprint :only [pprint]] - (robotwar kernel-lexicon game-lexicon))) + (robotwar brain game-lexicon))) -; TODO: remove the game-lexicon dependency above, when it's no longer needed -; (i.e. when we've moved the resolve-register logic out of this module) - -(def op-map (zipmap op-commands - (map (fn [op] - (case op - "/" #(int (Math/round (float (/ %1 %2)))) - "#" not= - (-> op read-string eval))) - op-commands))) - -(defn init-internal-state - "initialize all the internal state variables that go along - with the robot program when it's running. - (Optionally, also pass in a hash-map of register names and values, - which will override the defaults)." - [program reg-names & [registers]] - (pprint program) - (pprint reg-names) - (pprint registers) - (let [identity-with-throwaway-args (fn [x & args] x)] - {:registers (into {} (concat - (for [reg-name reg-names] - ; default values for :read, :write and :data. - ; (TODO: move these into a higher-level module) - ; NOTE: the default version of the :read function - ; does not need the world-state parameter. - [reg-name {:read identity-with-throwaway-args - :write (fn [robot data] - (assoc-in - robot - [:internal-state :registers reg-name] - data)) - :data 0}]) - registers)) - :program program - :acc 0 - :instr-ptr 0 - :call-stack []})) - -(defn read-register - "returns a numeric value" - [{:keys [read data] :as register} world] - (read data world)) - -(defn write-register - "returns a full robot state, including its external state and its internal brain state. - TODO: implement extra flag to indicate if we've fired a shot." - [robot {write :write :as register} data] - (write robot data)) - -;(defn resolve-register [registers reg] -; (case reg -; "RANDOM" (rand-int (registers reg)) -; "DATA" (registers (reg-names (registers "INDEX"))) -; (registers reg))) - -(defn resolve-arg [{arg-val :val arg-type :type} registers labels world] - "resolves an instruction argument to a numeric value - (either an arithmetic or logical comparison operand, or an instruction pointer). - If it's reading from a register, uses that register's :read function." - (case arg-type - :label (labels arg-val) - :number arg-val - :register (read-register (registers arg-val) world) - nil)) +; TODO: Fill out this module. +; Probably it will consist mostly of +; 0) An init function, to initialize all the fields containing +; the external robot information. I think this should be +; SEPARATE FROM THE REGISTERS, even the ones that are similar. +; 1) Specialty read and write functions for the registers +; 2) Code to deal with the flag when the robot fires a shot (probably this +; will just involve passing the flag up to the world) +; 3) Something else I can't remember. Maybe put some of the init-register +; and default-register and register-handling-in-general code in this +; module, instead of in brain. Something to think about. +; 4) GENERAL NOTES: A) CHANGE STRINGS TO KEYWORDS EARLY ON. +; B) CHANGE SOME OF THESE MODULE LOADINGS FROM +; "USE" TO "REFER", SO THAT THEY HAVE TO USE +; FULLY-QUALIFIED NAMES. THAT MIGHT MAKE THINGS +; A BIT CLEARER. THE NAMES CAN BE SHORTENED QUITE A BIT, +; WHEN LOADED INTO THE MODULES. (defn tick-robot - "takes as input a data structure representing all that the robot's brain - needs to know about the world: - - 1) The robot program, consisting of a vector of two-part instructions - (a command, followed by an argument or nil) as well as a map of labels to - instruction numbers - 2) The instruction pointer (an index number for the instruction vector) - 3) The value of the accumulator, or nil - 4) The call stack (a vector of instruction pointers to lines following - GOSUB calls) - 5) The contents of all the registers - - After executing one instruction, tick-robot returns the updated verion of all of the above, - plus an optional :action field, to notify the world if the SHOT, SPEEDX, SPEEDY or RADAR - registers have been pushed to." - [robot world] - (let [internal-state (:internal-state robot) - {:keys [acc instr-ptr call-stack registers program]} internal-state - [{command :val} arg] ((program :instrs) instr-ptr) - resolve #(resolve-arg % registers (program :labels) world) - return-robot #(assoc robot :internal-state (into internal-state %))] - (case command - "GOTO" (return-robot {:instr-ptr (resolve arg)}) - "GOSUB" (return-robot {:instr-ptr (resolve arg) - :call-stack (conj call-stack (inc instr-ptr))}) - "ENDSUB" (return-robot {:instr-ptr (peek call-stack) - :call-stack (pop call-stack)}) - ("IF", ",") (return-robot {:instr-ptr (inc instr-ptr) - :acc (resolve arg)}) - ("+" "-" "*" "/") (return-robot {:instr-ptr (inc instr-ptr) - :acc ((op-map command) acc (resolve arg))}) - ("=" ">" "<" "#") (if ((op-map command) acc (resolve arg)) - (return-robot {:instr-ptr (inc instr-ptr)}) - (return-robot {:instr-ptr (+ instr-ptr 2)})) - "TO" (write-register (return-robot {:instr-ptr (inc instr-ptr)}) - (registers (:val arg)) - acc)))) - + (let [ticked (tick-brain robot world)] + )) diff --git a/src/robotwar/world.clj b/src/robotwar/world.clj index 64773bb..7f0fe7f 100644 --- a/src/robotwar/world.clj +++ b/src/robotwar/world.clj @@ -1,9 +1,6 @@ (ns robotwar.world (:use [clojure.string :only [join]] - (robotwar foundry robot game-lexicon))) - -; TODO: Write init-register function, in robot probably, and init those -; X and Y registers down below. + (robotwar foundry brain robot game-lexicon))) (defn init-world "initialize all the variables for a robot world" @@ -12,11 +9,19 @@ :height height :shells [] :robots (vec (map-indexed (fn [idx program] - {:internal-state (init-internal-state program reg-names - {"X" (rand-int width) - "Y" (rand-int height)}) - :icon (str idx)}) - programs))}) + {:brain (init-brain + program + reg-names + {(init-register "X" + default-read + default-write + (rand-int width)) + (init-register "Y" + default-read + default-write + (rand-int height))}) + :icon (str idx)}) + programs))}) (defn tick-world "TODO"