Robocode uses Java as the programming language, and robots are shared by packaging them into Jar files. This means we should, with very little effort, be able to use Clojure to write a robot. Start by downloading the latest version of Robocode, available here.
The first fly in the ointment is that Robocode restricts the use of third-party JARs. If you try to just add clojure.jar to the class path you'll get an error message like this:
Preventing uk.co.fatvat.robot.FatRobot (1) from access: (java.io.FilePermission clojure.jar read):
You may only read files in your own root package directory.
SYSTEM: An error occurred during initialization of uk.co.fatvat.robot.FatRobot (1)
To fix this, edit the start up script (
robocode.shdepending on whether you are running Windows or not) and make sure you disable the security manager by adding
-DNOSECURITY=trueto the startup line. This disables the security manager meaning that there are no restrictions on what the robot can do. The security manager is in place in case you are a sentient robot killing machine. Be warned.
Without further ado, here's the most basic robot imaginable, borrowed entirely from My First Robot and converted over to Clojure. This robot moves back and forth, turns the gun around and fires at anything that looks at him slightly funny.
(:gen-class :extends robocode.Robot))
"Infinite loop whilst robot is alive"
It's not exactly the most exciting robot in the world and even loses against a robot that sits still and turns the turret around! How can we make it a little bit cleverer?
The robot below extends from
AdvancedRobotwhich allows non-blocking calls, writing to the file system and custom events.
(import (java.awt Color))
(import (robocode Rules))
(import (robocode.util Utils))
(:gen-class :extends robocode.AdvancedRobot :init create-robot :state state))
This shows two bits of the
:gen-classdirective I haven't used before.
:initallows you to specify a constructor routine. The return value for this function is unusual in that it is always a vector with two elements. The first represents any arguments to the superclass (in this case empty) and the second represents any state that the object needs. The
:statekeyword allows you to name the method of accessing the state.
(defstruct target-details :distance :bearing :energy :velocity)
"Robot records a list of events and performs actions based on these observations"
[ (ref )])
The constructor function simply returns a reference to the state which is initially empty. We also define a structure representing a sighting of the enemy. These sightings will be logged in our state and will be used to determine the strategy.
"Infinite loop whilst robot is alive"
(loop [x 1] ;; TODO is there a better idiom for an infinite loop?
(let [distance (.getDistance event)
name (.getName event)
energy (.getEnergy event)
velocity (.getVelocity event)
bearing (.getBearing event)]
(alter (.state robot) conj (struct target-details distance bearing energy velocity)))
onScannedRobotnow records the details of the last observation (
dosyncsets up the transaction,
alterapplies the function given (
conj) with the given arguments.
"Ensure robot looks pretty"
(.setColors Color/RED Color/BLACK Color/RED)))
"Based on the accrued events, hurt robots"
(let [latest (last @(.state robot))]
(.turnRight robot (get latest :bearing))
(when (zero? (get latest :velocity))
(.fire robot 3))
(.setTurnRadarRight robot 360)))
"Go for a walk around the outside of the building"
(let [x (mod (.getHeading robot) 90)]
(.ahead robot 50)
(when (not (zero? x))
(.turnLeft robot x))
(when (zero? (.getVelocity robot))
(.turnRight robot 90))))
walksimply makes the robot run around the outside of the arena. This is heavily based on the example code here.
attackjust waits for a stationary robot, turns and fires!
This at least allows me to beat the Fire default robot 10-0 (most of the time!).
There are a load more strategies and patterns available on the Robocode Wiki.
This was quite a useful learning exercise because I found out about
state. One problem I ran into was that
initdoesn't allow you to call methods on the object that is about to be constructed. This was fixed recently, and now there is a corresponding
You can find all the code on GitHub.