Other JVM Languages

From Robowiki
Revision as of 07:36, 2 March 2021 by BenHorner (talk | contribs) (Undo revision 56454 by BenHorner (talk) https://robowiki.net/wiki/Robocode/Gradle is much better!)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

Robocode is not limited to Java robots! Since Robocode allows you to load classes on the CLASSPATH, you can code robots in any language that can produce compiled CLASS files. Support for languages which don't have a compiler will require changes to Robocode which nobody has yet volunteered to implement.

The easiest way to add a language's runtime to Robocode's CLASSPATH is to edit the starter script. Make sure you add the path to your compiled files under the Development Options.

Scala

You need to add scala-library.jar to Robocode's CLASSPATH:

java -Xmx512M -Dsun.io.useCanonCaches=false -cp libs/robocode.jar;_SCALA_HOME_/lib/scala-library.jar robocode.Robocode %*

You also need to disable security, if your Scala program does anything that needs to actually access the Scala libraries.

Once that is done, coding robots in Scala is like coding in Java, but nicer:

// ScalaDemo.scala
package wiki

import robocode._
import robocode.util.Utils

/*
	A small demo to test robot coding in Scala.
*/
class ScalaDemo extends AdvancedRobot {

	// Main thread of robot
	override def run {
		setAdjustGunForRobotTurn(true)
		while (true) {
			// If we're not going anywhere, move randomly around battlefield
			if (Math.abs(getDistanceRemaining) < Rules.MAX_VELOCITY
					&& Math.abs(getTurnRemaining) < Rules.MAX_TURN_RATE)
			{
				setAhead((Math.random*2-1)*100)
				setTurnRight(Math.random*90-45)
			}
			// Tell RADAR to spin
			if (getRadarTurnRemaining == 0)
				setTurnRadarRightRadians(4)
			// Carry out pending actions
			execute
		}
	}

	// Picked up a robot on RADAR
	override def onScannedRobot(e : ScannedRobotEvent) {
		// Absolute bearing to detected robot
		val absBearing = e.getBearingRadians + getHeadingRadians

		// Tell RADAR to paint detected robot
		setTurnRadarRightRadians(3*Utils.normalRelativeAngle(
			absBearing - getRadarHeadingRadians ))

		// Tell gun to point at detected robot
		setTurnGunRightRadians(Utils.normalRelativeAngle(
			absBearing - getGunHeadingRadians ))

		// Tell robot to shoot with power 2
		setFire(2)
	}

	// Robot ran into a wall
	override def onHitWall(e : HitWallEvent) {
		// Turn in opposite direction to wall
		setTurnRightRadians(Utils.normalRelativeAngle(
			e.getBearingRadians + Math.Pi))
	}
}

Compile like you would in Java:

scalac -cp _PATH_TO_ROBOCODE_/libs/robocode.jar ScalaDemo.scala

Mirah

Mirah compiles to java bytecode with no runtime dependency, so the compiled .class files run in Robocode.

Here is a very basic Mirah bot:

package mirah

import robocode.AdvancedRobot

class MirahBot < AdvancedRobot

	def initialize
		puts 'constructor'
	end

	def run
		setAdjustGunForRobotTurn(true)
		while true do
			if (getRadarTurnRemaining == 0)
				setTurnRadarLeftRadians(2*Math.PI)
			end
			execute
		end
	end

end

Compile using the Mirah compile, and include like a regular Java robot.


Clojure

Coding robots in Clojure can be frustrating. You have to compile your code, but you don't get many of the benefits of using a compiler. For example, calls to non-existent methods are not caught until runtime. Typos in rarely run code can unexpectedly disable your robot (unless you use type hints everywhere and (set! *warn-on-reflection* true), which is beyond the scope of this introduction).

First, you need to add clojure.jar to Robocode's CLASSPATH. You also have to disable the security manager (by passing -DNOSECURITY=true on the Robocode command line) or your robot will be disabled for trying to read the Clojure JAR which is outside its root directory.

java -Xmx512M -Dsun.io.useCanonCaches=false -DNOSECURITY=true -cp libs/robocode.jar;_CLOJURE_HOME_/clojure.jar robocode.Robocode %*

Note that the ';' sign must be replaced with ':' on a Unix-like OS, e.g. on Linux and Mac OS X.

A sample clojure robot:

// ClojureDemo.clj
(ns wiki.ClojureDemo
  (:gen-class :extends robocode.AdvancedRobot))

; Private declarations
(declare normalise)

(defn -run [robot]
  (doto robot
    (.setAdjustRadarForGunTurn true)
    (.setAdjustRadarForRobotTurn true)
    (.setAdjustGunForRobotTurn true))
  (loop [r robot]
    (.setAhead r 30)
    (.setTurnRightRadians r 1)
    (when (= 0 (.getRadarTurnRemainingRadians r))
      (.setTurnRadarRightRadians r 4))
    (.execute r)
    (recur r)))

(defn -onScannedRobot [robot event]
  (.setTurnGunRightRadians 
   robot (normalise (- (+ (.getBearingRadians event) (.getHeadingRadians robot))
		       (.getGunHeadingRadians robot))))

  (when (< (Math/abs (normalise (.getGunTurnRemainingRadians robot)))
	   robocode.Rules/GUN_TURN_RATE)
    (.setFire robot 2))
  (.setTurnRadarRightRadians
   robot
   (* (normalise (- (+ (.getBearingRadians event) (.getHeadingRadians robot))
		    (.getRadarHeadingRadians robot)))
      1.9)))

(defn- normalise [angle]
  (robocode.util.Utils/normalRelativeAngle angle))

Since one of the oft-touted features of LISPs (of which Clojure is one) is being so dynamic, the compiler is rather unpleasant to use. You have to follow all the following steps, or you might encounter errors:

1. Put your source code in a folder called src. Files in this directory must follow the pattern src/PACKAGENAME/CLASSNAME.clj or the compiler won't be able to find them.

2. Create a directory named classes next to your src directory. This is where compiled files will be put, and must exist before the compiler will agree to do anything.

3. You have to pass the package qualified name of every class you want to compile to the compiler. Given a class named wiki.ClojureDemo, the compiler will try to read src/wiki/ClojureDemo.clj to find it.

The command to compile the demo bot is:

java -cp _CLOJURE_HOME_/clojure.jar -Dclojure.compile.path=classes clojure.lang.Compile wiki.ClojureDemo

Note that robocode.jar was not on the compiler's CLASSPATH. That is how dynamic Clojure is.

If you plan to use multiple classes, using the compiler can become rather tedious. Below is an Apache Ant script to compile all the files in the src sub-directory:

<project name="robocode-robots" default="compile">
  <!-- Change these to reflect your own installation paths -->
  <property name="clojure.jar"
    location="_PATH_TO_CLOJURE_/clojure.jar" />
  <property name="robocode.jar"
    location="_PATH_TO_ROBOCODE_/robocode.jar" />
  
  <!-- Directory under which source is located -->
  <property name="src" location="src" />
  <!-- Directory where compiled files will be put -->
  <property name="build" location="classes" />
  
  <target name="compile" description="Compile robots">
    <mkdir dir="${build}" />
    
    <pathconvert pathsep=" " property="compile.namespaces">
      <fileset dir="${src}" includes="**/*.clj" />
      <chainedmapper>
        <!--
        Packagemapper converts paths from PACKAGENAME/CLASSNAME.clj
        to fully qualified class names PACKAGE.CLASSNAME
        
        The back slash below is required for Windows. Might have to
        reverse it on other platforms.
        -->
        <packagemapper from="${src}\*.clj" to="*" />
        <filtermapper>
          <!--
          The Clojure compiler expects class names with hyphens to be
          replaced by underscores
          -->
          <replacestring from="_" to="-" />
        </filtermapper>
      </chainedmapper>
    </pathconvert>
    
    <java classname="clojure.lang.Compile">
      <classpath>
        <path location="${src}" />
        <path location="${build}" />
        <path location="${clojure.jar}" />
        <path location="${robocode.jar}" />
      </classpath>
      <sysproperty key="clojure.compile.path" value="${build}" />
      <arg line="${compile.namespaces}" />
    </java>
  </target>
  
  <target name="clean" description="Remove compiled files">
    <delete dir="${build}" />
  </target>
</project>