making progress on refactoring, especially in brain (formerly robot). made new file called robot to represent external robot state

This commit is contained in:
Richard Harrington 2013-08-02 01:36:17 -04:00
parent 6e04c076a6
commit 9112c82dc6
4 changed files with 135 additions and 116 deletions

100
src/robotwar/brain.clj Normal file
View File

@ -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))))

View File

@ -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

View File

@ -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)]
))

View File

@ -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"