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 (ns robotwar.core
(:use [clojure.pprint] (:use [clojure.pprint]
(robotwar foundry robot world game-lexicon))) (robotwar foundry brain robot world game-lexicon)))
(def src-code1 " START (def src-code1 " START
0 TO A 0 TO A

View File

@ -1,112 +1,26 @@
(ns robotwar.robot (ns robotwar.robot
(:use [clojure.string :only [join]] (:use [clojure.string :only [join]]
[clojure.pprint :only [pprint]] (robotwar brain game-lexicon)))
(robotwar kernel-lexicon game-lexicon)))
; TODO: remove the game-lexicon dependency above, when it's no longer needed ; TODO: Fill out this module.
; (i.e. when we've moved the resolve-register logic out of this module) ; Probably it will consist mostly of
; 0) An init function, to initialize all the fields containing
(def op-map (zipmap op-commands ; the external robot information. I think this should be
(map (fn [op] ; SEPARATE FROM THE REGISTERS, even the ones that are similar.
(case op ; 1) Specialty read and write functions for the registers
"/" #(int (Math/round (float (/ %1 %2)))) ; 2) Code to deal with the flag when the robot fires a shot (probably this
"#" not= ; will just involve passing the flag up to the world)
(-> op read-string eval))) ; 3) Something else I can't remember. Maybe put some of the init-register
op-commands))) ; and default-register and register-handling-in-general code in this
; module, instead of in brain. Something to think about.
(defn init-internal-state ; 4) GENERAL NOTES: A) CHANGE STRINGS TO KEYWORDS EARLY ON.
"initialize all the internal state variables that go along ; B) CHANGE SOME OF THESE MODULE LOADINGS FROM
with the robot program when it's running. ; "USE" TO "REFER", SO THAT THEY HAVE TO USE
(Optionally, also pass in a hash-map of register names and values, ; FULLY-QUALIFIED NAMES. THAT MIGHT MAKE THINGS
which will override the defaults)." ; A BIT CLEARER. THE NAMES CAN BE SHORTENED QUITE A BIT,
[program reg-names & [registers]] ; WHEN LOADED INTO THE MODULES.
(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))
(defn tick-robot (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] [robot world]
(let [internal-state (:internal-state robot) (let [ticked (tick-brain robot world)]
{: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))))

View File

@ -1,9 +1,6 @@
(ns robotwar.world (ns robotwar.world
(:use [clojure.string :only [join]] (:use [clojure.string :only [join]]
(robotwar foundry robot game-lexicon))) (robotwar foundry brain robot game-lexicon)))
; TODO: Write init-register function, in robot probably, and init those
; X and Y registers down below.
(defn init-world (defn init-world
"initialize all the variables for a robot world" "initialize all the variables for a robot world"
@ -12,11 +9,19 @@
:height height :height height
:shells [] :shells []
:robots (vec (map-indexed (fn [idx program] :robots (vec (map-indexed (fn [idx program]
{:internal-state (init-internal-state program reg-names {:brain (init-brain
{"X" (rand-int width) program
"Y" (rand-int height)}) reg-names
:icon (str idx)}) {(init-register "X"
programs))}) default-read
default-write
(rand-int width))
(init-register "Y"
default-read
default-write
(rand-int height))})
:icon (str idx)})
programs))})
(defn tick-world (defn tick-world
"TODO" "TODO"