mirror of
https://github.com/richardharrington/robotwar.git
synced 2024-06-09 08:29:32 +00:00
refactored, changed module names, untangled dependencies between kernel language and register names. Also removed 'repl'
This commit is contained in:
parent
d59467d2d2
commit
08dcf38214
|
@ -1,6 +1,6 @@
|
|||
(ns robotwar.core
|
||||
(:use [clojure.pprint]
|
||||
(robotwar create exec)))
|
||||
(robotwar foundry robot world game-lexicon)))
|
||||
|
||||
(def src-code1 " START
|
||||
0 TO A
|
||||
|
@ -17,7 +17,7 @@
|
|||
(def src-code2 "WAIT GOTO WAIT")
|
||||
(def src-code3 "500 TO RANDOM RANDOM RANDOM RANDOM")
|
||||
|
||||
(def world (init-world 30 30 (map assemble [src-code1 src-code2 src-code3])))
|
||||
(def world (init-world 30 30 (map #(assemble reg-names %) [src-code1 src-code2 src-code3])))
|
||||
|
||||
(def step (fn [initial-state n]
|
||||
(nth (iterate tick-robot initial-state) n)))
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
(ns robotwar.create
|
||||
(ns robotwar.foundry
|
||||
(:use (clojure [string :only [split join]]
|
||||
[pprint :only [pprint]])
|
||||
[clojure.core.match :only [match]]
|
||||
[robotwar.lexicon :only [registers op-commands commands]]))
|
||||
[robotwar.kernel-lexicon]))
|
||||
|
||||
(defn re-seq-with-pos
|
||||
"Returns a lazy sequence of successive matches of pattern in string with position.
|
||||
|
@ -54,33 +54,32 @@
|
|||
|
||||
(def return-err (constantly "Invalid word or symbol"))
|
||||
|
||||
(def parser-priority
|
||||
[[(set registers) :register]
|
||||
[(set commands) :command]
|
||||
[str->int :number]
|
||||
[valid-word :label]
|
||||
[return-err :error]])
|
||||
|
||||
(defn parse-token
|
||||
"removes the token-str field and adds two new fields:
|
||||
:val and :type"
|
||||
[{token-str :token-str :as token}]
|
||||
(loop [[[parser token-type] & tail] parser-priority]
|
||||
(if-let [token-val (parser token-str)]
|
||||
(dissoc (into token {:val token-val, :type token-type})
|
||||
:token-str)
|
||||
(recur tail))))
|
||||
:val and :type, based on sending the :token-str value
|
||||
through a series of parsing functions until a match is found."
|
||||
[reg-names token]
|
||||
(let [parser-priority [[(set reg-names) :register]
|
||||
[(set commands) :command]
|
||||
[str->int :number]
|
||||
[valid-word :label]
|
||||
[return-err :error]]]
|
||||
(loop [[[parser token-type] & tail] parser-priority]
|
||||
(if-let [token-val (parser (:token-str token))]
|
||||
(dissoc (into token {:val token-val, :type token-type})
|
||||
:token-str)
|
||||
(recur tail)))))
|
||||
|
||||
(defn parse
|
||||
"take the tokens and convert them to structured source code ready for compiling.
|
||||
if there's an error, returns a different type: just the token,
|
||||
outside of any sequence."
|
||||
[initial-tokens]
|
||||
[reg-names initial-tokens]
|
||||
(loop [[token & tail :as tokens] initial-tokens
|
||||
parsed-tokens []]
|
||||
(if (empty? tokens)
|
||||
parsed-tokens
|
||||
(let [parsed-token (parse-token token)]
|
||||
(let [parsed-token (parse-token reg-names token)]
|
||||
(if (= (:type parsed-token) :error)
|
||||
parsed-token
|
||||
(recur tail (conj parsed-tokens parsed-token)))))))
|
||||
|
@ -138,25 +137,17 @@
|
|||
(recur tail (assoc-in result [:labels (command :val)] next-instr-num))
|
||||
(recur tail (assoc-in result [:instrs next-instr-num] instr)))))))
|
||||
|
||||
(defn assemble [string]
|
||||
(defn assemble [reg-names string]
|
||||
"compiles robotwar code, with error-checking beginning after the lexing
|
||||
step. All functions that return errors will return a map with the keyword
|
||||
:error, and then a token with a :val field containing the error string,
|
||||
and metadata containing :pos and :line fields containing the location.
|
||||
So far only parse implements error-checking."
|
||||
(let [lexed (-> string split-lines strip-comments lex)]
|
||||
(let [parse-with-reg-names (partial parse reg-names)
|
||||
lexed (-> string split-lines strip-comments lex)]
|
||||
(reduce (fn [result step]
|
||||
(if (= (:type result) :error)
|
||||
result
|
||||
(step result)))
|
||||
lexed
|
||||
[parse disambiguate-minus-signs make-instr-pairs map-labels])))
|
||||
|
||||
(defn repl
|
||||
"make it so"
|
||||
[]
|
||||
(loop [input (read-line)]
|
||||
(when (not= input "exit")
|
||||
(-> input assemble pprint)
|
||||
(recur (read-line)))))
|
||||
|
||||
[parse-with-reg-names disambiguate-minus-signs make-instr-pairs map-labels])))
|
6
src/robotwar/game_lexicon.clj
Normal file
6
src/robotwar/game_lexicon.clj
Normal file
|
@ -0,0 +1,6 @@
|
|||
(ns robotwar.game-lexicon)
|
||||
|
||||
(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" ])
|
7
src/robotwar/kernel_lexicon.clj
Normal file
7
src/robotwar/kernel_lexicon.clj
Normal file
|
@ -0,0 +1,7 @@
|
|||
(ns robotwar.kernel-lexicon)
|
||||
|
||||
(def op-commands [ "-" "+" "*" "/" "=" "#" "<" ">" ])
|
||||
(def word-commands [ "TO" "IF" "GOTO" "GOSUB" "ENDSUB" ])
|
||||
|
||||
(def commands (concat op-commands word-commands))
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
(ns robotwar.lexicon)
|
||||
|
||||
(def registers [ "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" "DATA" ])
|
||||
|
||||
(def op-commands [ "-" "+" "*" "/" "=" "#" "<" ">" ])
|
||||
(def word-commands [ "TO" "IF" "GOTO" "GOSUB" "ENDSUB" ])
|
||||
|
||||
(def commands (concat op-commands word-commands))
|
||||
|
|
@ -1,19 +1,22 @@
|
|||
(ns robotwar.exec
|
||||
(:require (robotwar [lexicon :as lexicon]))
|
||||
(:use [clojure.string :only [join]]))
|
||||
(ns robotwar.robot
|
||||
(:use [clojure.string :only [join]]
|
||||
(robotwar kernel-lexicon game-lexicon)))
|
||||
|
||||
(def op-map (zipmap lexicon/op-commands
|
||||
; 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)))
|
||||
lexicon/op-commands)))
|
||||
op-commands)))
|
||||
|
||||
(defn resolve-register [registers reg]
|
||||
(case reg
|
||||
"RANDOM" (rand-int (registers reg))
|
||||
"DATA" (registers (lexicon/registers (registers "INDEX")))
|
||||
"DATA" (registers (reg-names (registers "INDEX")))
|
||||
(registers reg)))
|
||||
|
||||
(defn resolve-arg [{arg-val :val arg-type :type} registers labels]
|
||||
|
@ -68,48 +71,12 @@
|
|||
|
||||
(defn init-robot-state
|
||||
"initialize all the state variables that go along
|
||||
with the robot program when it's running."
|
||||
[program register-vals]
|
||||
with the robot program when it's running.
|
||||
(Optionally, pass in a hash-map of register names and values)."
|
||||
[program reg-names & [registers]]
|
||||
{:program program
|
||||
:acc 0
|
||||
:instr-ptr 0
|
||||
:registers (into (zipmap lexicon/registers (repeat 0))
|
||||
register-vals)
|
||||
:registers (into (zipmap reg-names (repeat 0))
|
||||
registers)
|
||||
:call-stack []})
|
||||
|
||||
(defn init-world
|
||||
"initialize all the variables for a robot world"
|
||||
[width height programs]
|
||||
{:width width
|
||||
:height height
|
||||
:shells []
|
||||
:robots (map-indexed (fn [idx program]
|
||||
{:internal-state (init-robot-state program
|
||||
{"X" (rand-int width)
|
||||
"Y" (rand-int height)})
|
||||
:external-state {:icon (str idx)}})
|
||||
programs)})
|
||||
|
||||
(defn tick-world
|
||||
"TODO"
|
||||
[world-state])
|
||||
|
||||
(defn arena-text-grid
|
||||
"outputs the arena, with borders"
|
||||
[{:keys [width height robots]}]
|
||||
(let [horiz-border-char "-"
|
||||
vert-border-char "+"
|
||||
header-footer (apply str (repeat (+ width 2) horiz-border-char))
|
||||
field (for [y (range height), x (range width)]
|
||||
(some (fn [{{{robot-x "X" robot-y "Y"} :registers} :internal-state
|
||||
{icon :icon} :external-state}]
|
||||
(if (= [x y] [robot-x robot-y])
|
||||
icon
|
||||
" "))
|
||||
robots))]
|
||||
(str header-footer
|
||||
"\n"
|
||||
(join "\n" (map #(join (apply str %) (repeat 2 vert-border-char))
|
||||
(partition width field)))
|
||||
"\n"
|
||||
header-footer)))
|
40
src/robotwar/world.clj
Normal file
40
src/robotwar/world.clj
Normal file
|
@ -0,0 +1,40 @@
|
|||
(ns robotwar.world
|
||||
(:use [clojure.string :only [join]]
|
||||
(robotwar foundry robot)))
|
||||
|
||||
(defn init-world
|
||||
"initialize all the variables for a robot world"
|
||||
[width height programs]
|
||||
{:width width
|
||||
:height height
|
||||
:shells []
|
||||
:robots (map-indexed (fn [idx program]
|
||||
{:internal-state (init-robot-state program
|
||||
{"X" (rand-int width)
|
||||
"Y" (rand-int height)})
|
||||
:external-state {:icon (str idx)}})
|
||||
programs)})
|
||||
|
||||
(defn tick-world
|
||||
"TODO"
|
||||
[world-state])
|
||||
|
||||
(defn arena-text-grid
|
||||
"outputs the arena, with borders"
|
||||
[{:keys [width height robots]}]
|
||||
(let [horiz-border-char "-"
|
||||
vert-border-char "+"
|
||||
header-footer (apply str (repeat (+ width 2) horiz-border-char))
|
||||
field (for [y (range height), x (range width)]
|
||||
(some (fn [{{{robot-x "X" robot-y "Y"} :registers} :internal-state
|
||||
{icon :icon} :external-state}]
|
||||
(if (= [x y] [robot-x robot-y])
|
||||
icon
|
||||
" "))
|
||||
robots))]
|
||||
(str header-footer
|
||||
"\n"
|
||||
(join "\n" (map #(join (apply str %) (repeat 2 vert-border-char))
|
||||
(partition width field)))
|
||||
"\n"
|
||||
header-footer)))
|
|
@ -1,6 +1,7 @@
|
|||
(ns robotwar.create-test
|
||||
(ns robotwar.foundry-test
|
||||
(:require [clojure.test :refer :all]
|
||||
[robotwar.create :refer :all])
|
||||
[robotwar.foundry :refer :all]
|
||||
[robotwar.game-lexicon :refer :all])
|
||||
(:use [clojure.string :only [join]]))
|
||||
|
||||
(def line1 "IF DAMAGE # D GOTO MOVE ; comment or something")
|
||||
|
@ -211,47 +212,47 @@
|
|||
|
||||
(deftest parse-token-register
|
||||
(testing "parsing register token"
|
||||
(is (= (parse-token {:token-str "AIM"})
|
||||
(is (= (parse-token reg-names {:token-str "AIM"})
|
||||
{:val "AIM", :type :register}))))
|
||||
|
||||
(deftest parse-token-command-word
|
||||
(testing "parsing command token (word)"
|
||||
(is (= (parse-token {:token-str "GOTO"})
|
||||
(is (= (parse-token reg-names {:token-str "GOTO"})
|
||||
{:val "GOTO", :type :command}))))
|
||||
|
||||
(deftest parse-token-command-operator
|
||||
(testing "parsing command token (operator)"
|
||||
(is (= (parse-token {:token-str "#"})
|
||||
(is (= (parse-token reg-names {:token-str "#"})
|
||||
{:val "#", :type :command}))))
|
||||
|
||||
(deftest parse-token-number
|
||||
(testing "parsing number token"
|
||||
(is (= (parse-token {:token-str "-17"}))
|
||||
(is (= (parse-token reg-names {:token-str "-17"}))
|
||||
{:val -17, :type :number})))
|
||||
|
||||
(deftest parse-token-label
|
||||
(testing "parsing label token"
|
||||
(is (= (parse-token {:token-str "SCAN"})
|
||||
(is (= (parse-token reg-names {:token-str "SCAN"})
|
||||
{:val "SCAN", :type :label}))))
|
||||
|
||||
(deftest parse-token-error
|
||||
(testing "parsing error token"
|
||||
(is (= (parse-token {:token-str "-GOTO"})
|
||||
(is (= (parse-token reg-names {:token-str "-GOTO"})
|
||||
{:val "Invalid word or symbol", :type :error}))))
|
||||
|
||||
(deftest parse-tokens-minus-sign
|
||||
(testing "parsing tokens with a binary minus sign"
|
||||
(is (= (parse lexed-tokens2)
|
||||
(is (= (parse reg-names lexed-tokens2)
|
||||
parsed-tokens2))))
|
||||
|
||||
(deftest parse-tokens-negative-sign
|
||||
(testing "parsing tokens with a unary negative sign"
|
||||
(is (= (parse lexed-tokens3)
|
||||
(is (= (parse reg-names lexed-tokens3)
|
||||
parsed-tokens3))))
|
||||
|
||||
(deftest parse-tokens-error
|
||||
(testing "parsing tokens with an invalid operator"
|
||||
(is (= (parse lexed-tokens4)
|
||||
(is (= (parse reg-names lexed-tokens4)
|
||||
parsed-tokens4))))
|
||||
|
||||
(def minus-sign-disambiguated-tokens2 parsed-tokens2)
|
||||
|
@ -293,17 +294,17 @@
|
|||
|
||||
(deftest assemble-test-success
|
||||
(testing "compiling successfully"
|
||||
(is (= (assemble (join "\n" [line1 line2 line3]))
|
||||
(is (= (assemble reg-names (join "\n" [line1 line2 line3]))
|
||||
multi-line-assembled))))
|
||||
|
||||
(deftest assemble-test-failure
|
||||
(testing "assemble results in error"
|
||||
(is (= (assemble (join "\n" [line1 line2 line3 line4]))
|
||||
(is (= (assemble reg-names (join "\n" [line1 line2 line3 line4]))
|
||||
multi-line-assembled-error))))
|
||||
|
||||
(deftest preserving-line-and-pos-metadata-test
|
||||
(testing "line and pos metadata preserved through assembly process"
|
||||
(is (= (meta (get-in (assemble (join "\n" [line1 line2 line3]))
|
||||
(is (= (meta (get-in (assemble reg-names (join "\n" [line1 line2 line3]))
|
||||
[:instrs 8 1]))
|
||||
{:line 3, :pos 14}))))
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
(ns robotwar.exec-test
|
||||
(:require [clojure.test :refer :all]
|
||||
(robotwar [create :refer :all]
|
||||
[exec :refer :all])))
|
||||
(ns robotwar.robot-test
|
||||
(:use [clojure.test]
|
||||
(robotwar foundry robot game-lexicon)))
|
||||
|
||||
(def src-codes [ ; program 0: multi-use program
|
||||
" START
|
||||
|
@ -34,7 +33,7 @@
|
|||
1 TO INDEX
|
||||
DATA " ])
|
||||
|
||||
(def robot-history #(iterate tick-robot (init-robot-state (assemble %) {})))
|
||||
(def robot-history #(iterate tick-robot (init-robot-state (assemble reg-names %) {})))
|
||||
(def robot-histories (map robot-history src-codes))
|
||||
(def multi-use-history (nth robot-histories 0))
|
||||
(def random-check-history (nth robot-histories 1))
|
Loading…
Reference in New Issue
Block a user