Created
May 9, 2012 10:32
-
-
Save jedws/2643617 to your computer and use it in GitHub Desktop.
Revisions
-
jedws revised this gist
May 9, 2012 . 1 changed file with 103 additions and 52 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -23,12 +23,12 @@ object Mapper { } } trait Parameters { def apply[A: Mapper](s: String): A } object Parameters { implicit def StringyMapToParameters(m: Map[String, String]) = new Parameters { def apply[A: Mapper](s: String): A = implicitly[Mapper[A]].apply(m(s)) } } @@ -48,19 +48,19 @@ object CommandParser { /** "Command(..)" => ("Command", Map( ("key" -> "value"), ("key" -> "value"), ..}) */ def apply(command: String): Command = { /** "key=value" => ("key","value") */ def split(param: String): (String, String) = { val seg = param.split('=') (seg(0), if (seg.length >= 2) seg(1) else "") } val segments = command.split('(') require(segments.length == 2, "invalid command: " + command) Command(segments(0), segments(1).dropRight(1).split(',').map(split).toMap) } } object Command { def apply(cmd: String, get: Parameters): Command = cmd match { case "Welcome" => Welcome(get[String]("name"), get[String]("path"), get[Int]("apocalypse"), get[Int]("round")) case "React" => React(get[Int]("generation"), get[String]("name"), get[Int]("time"), get[String]("view"), get[String]("energy"), get[Seq[(Int, Int)]]("master")) case "Goodbye" => Goodbye(get[Int]("energy")) @@ -132,7 +132,9 @@ sealed trait Op extends MiniOp * for master bot: 0 EU (free) * for mini-bot: 0 EU (free) */ case class Move(direction: Heading) extends Op { override def toString = "Move(direction=%s)".format(direction) } /** * Spawn(direction=int:int,name=string,energy=int,…) @@ -163,8 +165,10 @@ case class Move(direction: Heading) extends Op //— move one step in some direc * * Note that this means that mini-bots can spawn other mini-bots (if they have the required energy, i.e. at least 100 EU). */ case class Spawn(direction: Heading, name: Option[String] = None, energy: Int) extends Op { require(energy >= 100, "energy must be >= than 100") override def toString = Util.string("Spawn", "direction" -> direction, "energy" -> energy, "name" -> name) } /** @@ -178,7 +182,9 @@ case class Spawn(direction: Heading, name: String, energy: Int) extends Op { * * No Energy Cost/ All bots are permitted. */ case class Set(map: Map[String, String]) extends Op { override def toString = Util.string("Set", map.elements.toSeq: _*) } // // simulation neutral @@ -195,7 +201,9 @@ case class Set(map: Map[String, String]) extends Op //— set a state parameter * for master bot: permitted, no energy consumed * for mini-bot: permitted, no energy consumed */ case class Say(text: String) extends Op { override def toString = Util.string("Say", "text" -> text) } /** * Status(text=string) @@ -208,7 +216,9 @@ case class Say(text: String) extends Op * for master bot: permitted, no energy consumed * for mini-bot: permitted, no energy consumed */ case class Status(text: String) extends Op { override def toString = Util.string("Status", "text" -> text) } /** * MarkCell(position=int:int,color=string) @@ -222,7 +232,9 @@ case class Status(text: String) extends Op //— set a status message to be disp * for master bot: permitted, no energy consumed * for mini-bot: permitted, no energy consumed */ case class MarkCell(position: Heading, color: Color) extends Op { override def toString = Util.string("MarkCell", "position" -> position, "color" -> color) } /** * DrawLine(from=int:int,to=int:int,color=string) @@ -237,7 +249,9 @@ case class MarkCell(position: Heading, color: Color) extends Op * for master bot: permitted, no energy consumed * for mini-bot: permitted, no energy consumed */ case class DrawLine(from: Heading, to: Heading, color: Color) extends Op { override def toString = Util.string("DrawLine", "from" -> from, "to" -> to, "color" -> color) } /** * Log(text=string) @@ -250,7 +264,9 @@ case class DrawLine(from: Heading, to: Heading, color: Color) extends Op * for master bot: permitted, no energy consumed * for mini-bot: permitted, no energy consumed */ case class Log(text: String) extends Op { override def toString = Util.string("Log", "text" -> text) } /** * Explode(size=int) @@ -264,10 +280,18 @@ case class Log(text: String) extends Op // */ case class Explode(size: Int) extends MiniOp { require(2 < size && size < 10) override def toString = Util.string("Explode", "size" -> size) } object Color { private def req(i: Int): Boolean = {require(i < 256 && i >= 0); true} //TODO define color constants } case class Color(r: Int, g: Int, b: Int) { Color.req(r) && Color.req(g) && Color.req(b) override def toString = "#" + r.toHexString + g.toHexString + b.toHexString } sealed trait Cell @@ -284,7 +308,7 @@ object Cell { case object GoodBeast extends Cell case object BadBeast extends Cell def apply(c: Char): Cell = c match { case '?' => Unknown case '_' => Empty case 'W' => Wall @@ -305,39 +329,68 @@ object Displacement { case object Zero extends Displacement { val value = 0 } case object Pos extends Displacement { val value = 1 } def apply(in: Int) = in match { case -1 => Neg case 0 => Zero case 1 => Pos } } object Util { def parse[A](s: String)(f: (Int, Int) => A): A = { val a = s.split(':') f(a(0).toInt, a(1).toInt) } def string(name: String, is: (String, Any)*): String = { val w = new java.io.StringWriter().append(name).append("(") is.foreach { case (n, None) => case (n, Some(v)) => w.append(n).append(v.toString) case (n, v) => w.append(n).append(v.toString) } w.append(")").toString } } object Heading { /** parse a value from Heading.toString format, e.g. "0:1". */ def parse(s: String): Heading = Util.parse(s)(Heading.apply) def apply(x: Int, y: Int): Heading = if (x == 1) { if (y == 1) NorthEast else if (y == 0) East else SouthEast } else if (x == 0) { if (y == 1) North else if (y == 0) Nowhere else South } else { if (y == 1) NorthWest else if (y == 0) West else SouthWest } import Displacement.{ Pos, Zero, Neg } val East = new Heading(Pos, Zero) val NorthEast = Heading(Pos, Neg) val North = Heading(Zero, Neg) val NorthWest = Heading(Neg, Neg) val West = Heading(Neg, Zero) val SouthWest = Heading(Neg, Pos) val South = Heading(Zero, Pos) val SouthEast = Heading(Pos, Pos) val Nowhere = Heading(0, 0) } case class Heading private (x: Displacement, y: Displacement) { override def toString = x.value + ":" + y.value } object Coord { /** parse a value from Coord.toString format, e.g. "0:1". */ def parse(s: String): Coord = Util.parse(s)(Coord.apply) } case class Coord(x: Int, y: Int) { @@ -366,45 +419,43 @@ case class Coord(x: Int, y: Int) { def signum = Coord(x.signum, y.signum) def wrap(size: Coord) = { def fix(a: Int, len: Int) = if (a < 0) len + a else if (a >= len) a % len else a val (xx, yy) = (fix(x, size.x), fix(y, size.y)) if (xx != x || yy != y) Coord(xx, yy) else this } } object View { trait Projection extends (Coord => Cell) { def indexFrom(c: Coord): Int def fromAbsolute(c: Coord): Coord def fromRelative(c: Coord): Coord def fromIndex(index: Int): Coord } } case class View(cells: String) extends (Coord => Cell) { val size = math.sqrt(cells.length).toInt val center = Coord(size / 2, size / 2) def apply(c: Coord): Cell = Relative(c) def offsetToNearest(c: Char): Coord = cells.view.zipWithIndex.filter(_._1 == c).map(p => Relative.fromIndex(p._2)).minBy(_.length) object Relative extends View.Projection { def indexFrom(c: Coord) = Absolute.indexFrom(Absolute.fromRelative(c)) def fromAbsolute(c: Coord) = c - center def fromIndex(index: Int) = fromAbsolute(Absolute.fromIndex(index)) def fromRelative(c: Coord) = c def apply(c: Coord) = Cell(cells.charAt(indexFrom(c))) } object Absolute extends View.Projection { def indexFrom(c: Coord) = c.x + c.y * size def fromIndex(index: Int) = Coord(index % size, index / size) def fromRelative(c: Coord) = c + center def fromAbsolute(c: Coord) = c def apply(c: Coord) = Cell(cells.charAt(indexFrom(c))) } } -
jedws revised this gist
May 9, 2012 . 1 changed file with 10 additions and 6 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,4 +1,10 @@ // ------------------------------------------------------------------------------------------------- // Framework // ------------------------------------------------------------------------------------------------- /** Simple typeclass for turning Strings into things */ trait Mapper[A] extends (String => A) /** define all the Mapper typeclass instances and generators we need */ object Mapper { implicit val StringMapper = new Mapper[String] { def apply(s: String) = s @@ -43,15 +49,13 @@ object CommandParser { def apply(command: String): Command = { /** "key=value" => ("key","value") */ def splitParameterIntoKeyValue(param: String): (String, String) = { val seg = param.split('=') (seg(0), if (seg.length >= 2) seg(1) else "") } val segments = command.split('(') require(segments.length == 2, "invalid command: " + command) Command(segments(0), segments(1).dropRight(1).split(',').map(splitParameterIntoKeyValue).toMap) } } -
jedws revised this gist
May 9, 2012 . 1 changed file with 33 additions and 38 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,7 +1,3 @@ trait Mapper[A] extends (String => A) object Mapper { implicit val StringMapper = new Mapper[String] { @@ -11,7 +7,7 @@ object Mapper { def apply(s: String) = s.toInt } implicit def SeqMapper[A: Mapper] = new Mapper[Seq[A]] { def apply(s: String) = s.split("|").map(implicitly[Mapper[A]]) } implicit def Tuple2Mapper[A: Mapper, B: Mapper] = new Mapper[(A, B)] { def apply(s: String) = { @@ -59,13 +55,12 @@ object CommandParser { } } object Command { def apply(cmd: String, get: Mapped): Command = cmd match { case "Welcome" => Welcome(get[String]("name"), get[String]("path"), get[Int]("apocalypse"), get[Int]("round")) case "React" => React(get[Int]("generation"), get[String]("name"), get[Int]("time"), get[String]("view"), get[String]("energy"), get[Seq[(Int, Int)]]("master")) case "Goodbye" => Goodbye(get[Int]("energy")) } } sealed trait Command @@ -117,7 +112,7 @@ case class ViewProperty() extends Property case class Master() extends Property sealed trait MiniOp sealed trait Op extends MiniOp /** * Move(direction=int:int) @@ -133,7 +128,7 @@ sealed trait Opcode extends MiniOp * for master bot: 0 EU (free) * for mini-bot: 0 EU (free) */ case class Move(direction: Heading) extends Op //— move one step in some direction /** * Spawn(direction=int:int,name=string,energy=int,…) @@ -164,7 +159,7 @@ case class Move(direction: Heading) extends Opcode //— move one step in some d * * Note that this means that mini-bots can spawn other mini-bots (if they have the required energy, i.e. at least 100 EU). */ case class Spawn(direction: Heading, name: String, energy: Int) extends Op { require(energy >= 100, "energy must be >= than 100") } @@ -179,7 +174,7 @@ case class Spawn(direction: Heading, name: String, energy: Int) extends Opcode { * * No Energy Cost/ All bots are permitted. */ case class Set(map: Map[String, String]) extends Op //— set a state parameter maintained for the bot // // simulation neutral @@ -196,7 +191,7 @@ case class Set(map: Map[String, String]) extends Opcode //— set a state parame * for master bot: permitted, no energy consumed * for mini-bot: permitted, no energy consumed */ case class Say(text: String) extends Op /** * Status(text=string) @@ -209,7 +204,7 @@ case class Say(text: String) extends Opcode * for master bot: permitted, no energy consumed * for mini-bot: permitted, no energy consumed */ case class Status(text: String) extends Op //— set a status message to be displayed above the bot /** * MarkCell(position=int:int,color=string) @@ -223,7 +218,7 @@ case class Status(text: String) extends Opcode //— set a status message to be * for master bot: permitted, no energy consumed * for mini-bot: permitted, no energy consumed */ case class MarkCell(position: Heading, color: Color) extends Op /** * DrawLine(from=int:int,to=int:int,color=string) @@ -238,7 +233,8 @@ case class MarkCell(position: Heading, color: Color) * for master bot: permitted, no energy consumed * for mini-bot: permitted, no energy consumed */ case class DrawLine(from: Heading, to: Heading, color: Color) extends Op /** * Log(text=string) * Shortcut for setting the state property debug, which by convention contains an optional (multi-line) string with debug information related to the entity that issues this opcode. This text string can be displayed in the browser-based debug window to track what a bot or mini-bot is doing. The debug information is erased each time before the control function is called, so there is no need to set it to an empty string. @@ -250,7 +246,7 @@ case class DrawLine(from: Heading, to: Heading, color: Color) * for master bot: permitted, no energy consumed * for mini-bot: permitted, no energy consumed */ case class Log(text: String) extends Op // /** * Explode(size=int) @@ -266,23 +262,10 @@ case class Explode(size: Int) extends MiniOp { require(2 < size && size < 10) } case class Color(r: Int, g: Int, b: Int) { override def toString = null //TODO define html color string & various useful constants etc. } sealed trait Cell object Cell { case object Unknown extends Cell @@ -386,26 +369,38 @@ case class Coord(x: Int, y: Int) { } } object View { trait Projection { def indexFrom(c: Coord): Int def cellAt(c: Coord): Cell def fromAbsolute(c: Coord): Coord def fromRelative(c: Coord): Coord def fromIndex(index: Int): Coord } } case class View(cells: String) { val size = math.sqrt(cells.length).toInt val center = Coord(size / 2, size / 2) def apply(relative: Coord) = Relative.cellAt(relative) def offsetToNearest(c: Char) = cells.view.zipWithIndex.filter(_._1 == c).map(p => Relative.fromIndex(p._2)).minBy(_.length) object Relative extends View.Projection { def indexFrom(c: Coord) = Absolute.indexFrom(Absolute.fromRelative(c)) def fromAbsolute(c: Coord) = c - center def fromIndex(index: Int) = fromAbsolute(Absolute.fromIndex(index)) def fromRelative(c: Coord) = c def cellAt(c: Coord) = Cell parse cells.charAt(indexFrom(c)) } object Absolute extends View.Projection { def indexFrom(c: Coord) = c.x + c.y * size def fromIndex(index: Int) = Coord(index % size, index / size) def fromRelative(c: Coord) = c + center def fromAbsolute(c: Coord) = c def cellAt(c: Coord) = Cell parse cells.charAt(indexFrom(c)) } } -
jedws revised this gist
May 9, 2012 . 1 changed file with 15 additions and 4 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -4,12 +4,21 @@ trait Mapper[A] extends (String => A) object Mapper { implicit val StringMapper = new Mapper[String] { def apply(s: String) = s } implicit val IntMapper = new Mapper[Int] { def apply(s: String) = s.toInt } implicit def SeqMapper[A: Mapper] = new Mapper[Seq[A]] { def apply(s: String) = s.split("|").map(implicitly[Mapper[A]]) } implicit def Tuple2Mapper[A: Mapper, B: Mapper] = new Mapper[(A, B)] { def apply(s: String) = { val a = s.split("|") implicitly[Mapper[A]].apply(a(0)) -> implicitly[Mapper[B]].apply(a(1)) } } } trait Mapped { @@ -54,7 +63,9 @@ object CommandParser { object Command { def apply(cmd: String, get: Mapped): Command = cmd match { case "Welcome" => Welcome(get[String]("name"), get[String]("path"), get[Int]("apocalypse"), get[Int]("round")) case "React" => React(get[Int]("generation"), get[String]("name"), get[Int]("time"), get[String]("view"), get[String]("energy"), get[Seq[(Int, Int)]]("master")) } } sealed trait Command @@ -87,14 +98,14 @@ case class Welcome(name: String, path: String, apocalypse: Int, round: Int) exte * * The control function is expected to return a valid response, which may consist of zero or more commands separated by a pipe (|) character. The available commands are listed in the section Opcodes of Plugin-to-Server Commands. */ case class React(generation: Int, name: String, time: Int, view: String, energy: String, master: Seq[(Int, Int)]) extends Command /** * “Goodbye” is the last command sent by the server to a plug-in after all other invocations. The plug-in should use this opportunity to close any open files (such as those used for debug logging) and to relinquish control of any other resources it may hold. * * energy: the bot's final energy level */ case class Goodbye(energy: Int) extends Command sealed trait Property case class DirectionProperty() extends Property -
jedws revised this gist
May 9, 2012 . 1 changed file with 23 additions and 5 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,10 +1,27 @@ // ------------------------------------------------------------------------------------------------- // Framework // ------------------------------------------------------------------------------------------------- trait Mapper[A] extends (String => A) object Mapper { implicit val StringToString = new Mapper[String] { def apply(s: String) = s } implicit val IntToString = new Mapper[Int] { def apply(s: String) = s.toInt } } trait Mapped { def apply[A: Mapper](s: String): A } object Mapped { implicit def StringyMapToMapped(m: Map[String, String]) = new Mapped { def apply[A: Mapper](s: String): A = implicitly[Mapper[A]].apply(m(s)) } } class ControlFunctionFactory { def create = (input: String) => { val command = CommandParser(input) @@ -33,9 +50,10 @@ object CommandParser { } } object Command { def apply(cmd: String, get: Mapped): Command = cmd match { case "Welcome" => Welcome(get[String]("name"), get[String]("path"), get[Int]("apocalypse"), get[Int]("round")) } } -
jedws revised this gist
May 9, 2012 . 1 changed file with 0 additions and 76 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -12,82 +12,6 @@ class ControlFunctionFactory { } // ------------------------------------------------------------------------------------------------- /** * Utility methods for parsing strings containing a single command of the format * "Command(key=value,key=value,...)" -
jedws revised this gist
May 9, 2012 . 1 changed file with 10 additions and 11 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -377,9 +377,11 @@ case class Heading(x: Displacement, y: Displacement) { } object Heading { /** * object DirectionShow extends Show[Heading] { * def show(a: Heading) = a.toString * } */ /** Parse an XY value from XY.toString format, e.g. "2:3". */ def apply(s: String): Heading = { val a = s.split(':'); Heading(a(0).toInt, a(1).toInt) } @@ -423,13 +425,10 @@ case class Coord(x: Int, y: Int) { def signum = Coord(x.signum, y.signum) def wrap(size: Coord) = { def fix(a: Int, b: Int) = if (a < 0) b + a else if (a >= b) a - b else a val fixedX = fix(x, size.x) val fixedY = fix(y, size.y) if (fixedX != x || fixedY != y) Coord(fixedX, fixedY) else this } } @@ -440,7 +439,7 @@ case class View(cells: String) { def apply(relative: Coord) = Cell parse Relative.cellAt(relative) def offsetToNearest(c: Char) = cells.view.zipWithIndex.filter(_._1 == c).map(p => Relative.fromIndex(p._2)).minBy(_.length) object Relative { -
jedws created this gist
May 9, 2012 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,459 @@ object ControlFunction { } // ------------------------------------------------------------------------------------------------- // Framework // ------------------------------------------------------------------------------------------------- class ControlFunctionFactory { def create = (input: String) => { val command = CommandParser(input) } } // ------------------------------------------------------------------------------------------------- trait Bot { // inputs def inputOrElse(key: String, fallback: String): String def inputAsIntOrElse(key: String, fallback: Int): Int def inputAsXYOrElse(keyPrefix: String, fallback: Heading): Heading //def view: View def energy: Int def time: Int def generation: Int // outputs def move(delta: Heading): Bot def say(text: String): Bot def status(text: String): Bot def spawn(offset: Heading, params: (String, Any)*): Bot def set(params: (String, Any)*): Bot def log(text: String): Bot } trait MiniBot extends Bot { // inputs def offsetToMaster: Heading // outputs def explode(blastRadius: Int): Bot } case class BotImpl(inputParams: Map[String, String], commands: String = "", debugOutput: String = "", stateParams: Map[String, Any] = Map()) extends MiniBot { // input def inputOrElse(key: String, fallback: String) = inputParams.getOrElse(key, fallback) def inputAsIntOrElse(key: String, fallback: Int) = inputParams.get(key).map(_.toInt).getOrElse(fallback) def inputAsXYOrElse(key: String, fallback: Heading) = inputParams.get(key).map(s => Heading(s)).getOrElse(fallback) //val view = View(inputParams("view")) val energy = inputParams("energy").toInt val time = inputParams("time").toInt val generation = inputParams("generation").toInt def offsetToMaster = inputAsXYOrElse("master", Heading.Zero) // output /** Appends a new command to the command string; returns 'this' for fluent API. */ private def append(s: String): Bot = copy(commands = commands + (if (commands.isEmpty) s else "|" + s)) /** Renders commands and stateParams into a control function return string. */ override def toString = { var result = commands if (!stateParams.isEmpty) { if (!result.isEmpty) result += "|" result += stateParams.map(e => e._1 + "=" + e._2).mkString("Set(", ",", ")") } if (!debugOutput.isEmpty) { if (!result.isEmpty) result += "|" result += "Log(text=" + debugOutput + ")" } result } def log(text: String) = copy(debugOutput = debugOutput + text + "\n") def move(direction: Heading) = append("Move(direction=" + direction + ")") def say(text: String) = append("Say(text=" + text + ")") def status(text: String) = append("Status(text=" + text + ")") def explode(blastRadius: Int) = append("Explode(size=" + blastRadius + ")") def spawn(offset: Heading, params: (String, Any)*) = append("Spawn(direction=" + offset + (if (params.isEmpty) "" else "," + params.map(e => e._1 + "=" + e._2).mkString(",")) + ")") def set(params: (String, Any)*) = copy(stateParams = stateParams ++ params) def set(keyPrefix: String, xy: Heading) = copy(stateParams = stateParams ++ Seq(keyPrefix + "x" -> xy.x.toString, keyPrefix + "y" -> xy.y.toString)) } // ------------------------------------------------------------------------------------------------- /** * Utility methods for parsing strings containing a single command of the format * "Command(key=value,key=value,...)" */ object CommandParser { /** "Command(..)" => ("Command", Map( ("key" -> "value"), ("key" -> "value"), ..}) */ def apply(command: String): Command = { /** "key=value" => ("key","value") */ def splitParameterIntoKeyValue(param: String): (String, String) = { val segments = param.split('=') (segments(0), if (segments.length >= 2) segments(1) else "") } val segments = command.split('(') if (segments.length != 2) throw new IllegalStateException("invalid command: " + command) val params = segments(1).dropRight(1).split(',') Command(segments(0), params.map(splitParameterIntoKeyValue).toMap) } } object Command { def apply(cmd: String, map: Map[String, String]): Command = { null } } sealed trait Command /** * Welcome(name=String,path=string,apocalypse=int,round=int) * “Welcome” is the first command sent by the server to a plug-in before any other invocations of the control function. * * Parameters: * * name: the player name associated with the plug-in. The player name is set based on the name of the directory containing the plug-in. * path: the path of the directory from which the server loaded the plug-in (which the plug-in would otherwise have no way of knowing about, aside from a hard-coded string). Contains no terminating slash. The plug-in can store this path and create log-files in it, ideally incorporating the round index provided in the round parameter and optionally the time (step index) and entity name passed with each later React command. Note: copious logging will slow down gameplay, so be reasonable and restrict logging to specific rounds and steps. For suggestions, refer to the Debugging section in the Scalatron Tutorial. * apocalypse: the number of steps that will be performed in the upcoming game round. This allows bots to plan ahead and to e.g. schedule the recall of harvesting drones. Keep in mind, however, that the control function of master bots is only invoked with React every second simulation step! See the Game Rules for details. * round: the index of the round for which the control function was instantiated. A game server continually runs rounds of the game, and the round index is incremented each time. */ case class Welcome(name: String, path: String, apocalypse: Int, round: Int) extends Command /** * React(generation=int,name=string,time=int,view=string,energy=string,master=int:int,…) * “React” is invoked by the server once for each entity for each step in which the entity is allowed to move (mini-bots every cycle, bots every second cycle - see the Game Rules for details). The plug-in must return a response for the entity with the given entity name that is appropriate for the given view of the world. * * Parameters: * * generation: the generation of this bot. The master bot is the only bot of generation 0 (zero);? the mini-bots it spawned are of generation 1 (one); the mini-bots spawned by ? these are generation 2 (two), etc. Use this parameter to distinguish between ? mini-bots (slaves) and your master bot. * name: the name of the entity. For master bots, this is the name of the player (which in turn is the name of the plug-in directory the bot was loaded from). For mini-bots, this is either the name provided to Spawn() or a default name that was auto-generated by the server when the mini-bot was spawned. * time: a non-negative, monotonically increasing integer that represents the simulation time (basically a simulation step counter). * view: the view that the player has of the playing field. The view is a square region containing N*N cells, where N is the width and height of the region. Each cell is represented as a single ASCII character. The meaning of the characters is defined in the table View/Cell Encoding. * energy: the entity's current energy level * master: for mini-bots only: relative position of the master, in cells, in the format “x:y”, e.g. “-1:1”. To return to the master, the mini-bot can use this as the move direction (with some obstacle avoidance, of course). * In addition to these system-generated parameters, the server passes in all state parameters of the entity that were set by the player via Spawn() or Set() (see below). If, for example, a mini-bot was spawned with Spawn(...,role=missile), the React invocation will contain a parameter called role with the value missile. * * The control function is expected to return a valid response, which may consist of zero or more commands separated by a pipe (|) character. The available commands are listed in the section Opcodes of Plugin-to-Server Commands. */ case class React(generation: Int, name: String, time: Int, view: String, energy: String, master: Seq[(Int, Int)]) /** * “Goodbye” is the last command sent by the server to a plug-in after all other invocations. The plug-in should use this opportunity to close any open files (such as those used for debug logging) and to relinquish control of any other resources it may hold. * * energy: the bot's final energy level */ case class Goodbye(energy: Int) sealed trait Property case class DirectionProperty() extends Property case class Generation() extends Property case class Name() extends Property case class Energy() extends Property case class Time() extends Property case class ViewProperty() extends Property case class Master() extends Property sealed trait MiniOp sealed trait Opcode extends MiniOp /** * Move(direction=int:int) * Moves the bot one cell in a given direction, if possible. The delta values are signed integers. The permitted values are -1, 0 or 1. * * Parameters: * direction desired displacement for the move, e.g. 1:1 or 0:-1 * * Example: * Move(direction=-1:1) moves the entity left and down. * * Energy Cost/Permissions: * for master bot: 0 EU (free) * for mini-bot: 0 EU (free) */ case class Move(direction: Heading) extends Opcode //— move one step in some direction /** * Spawn(direction=int:int,name=string,energy=int,…) * Spawns a mini-bot from the position of the current entity at the given cell position, expressed relative to the current position. * * Parameters: * * direction: desired displacement for the spawned mini-bot, e.g. -1:1 * name: arbitrary string, except the following characters are not permitted: |, ,, =, ( * energy: energy budget to transfer to the spawned mini-bot (minimum: 100 EU) * * Defaults: * name = Slave_nn an auto-generated unique slave name * energy = 100 the minimum permissible energy * * Additional Parameters: * In addition to the parameters listed above, the command can contain arbitrary additional parameter key/value pairs. These will be set as the initial state parameters of the entity and will be passed along to all subsequent control function invocations with React. This allows a master bot to “program” a mini-bot with arbitrary starting parameters. * The usual restrictions for strings apply (no comma, parentheses, equals sign or pipe characters). * The following property names are reserved and must not be used for custom properties: generation, name, energy, time, view, direction, master. * Properties whose values are empty strings are ignored. * Example: * * Spawn(direction=-1:1,energy=100) spawns a new mini-bot one cell to the left and one cell down, with an initial energy of 100 EU. * * Energy Cost/Permissions: * for master bot: as allocated via energy * for mini-bot: as allocated via energy * * Note that this means that mini-bots can spawn other mini-bots (if they have the required energy, i.e. at least 100 EU). */ case class Spawn(direction: Heading, name: String, energy: Int) extends Opcode { require(energy >= 100, "energy must be >= than 100") } /** * Set(key=value,…) * Sets one or more state parameters with the given names to the given values. The state parameters of the entity will be passed along to all subsequent control function invocations with React. This allows an entity to store state information on the server, making its implementation immutable and delegating state maintenance to the server. * * The usual restrictions for strings apply (no comma, parentheses, equals sign or pipe characters). * * The following property names are reserved and must not be used for custom properties: generation, name, energy, time, view, direction, master. * Properties whose values are empty strings are deleted from the state properties. * * No Energy Cost/ All bots are permitted. */ case class Set(map: Map[String, String]) extends Opcode //— set a state parameter maintained for the bot // // simulation neutral // /** * Say(text=string) * Displays a little text bubble that remains at the position where it was created. Use this to drop textual breadcrumbs associated with events. You can also use this as a debugging tool. Don't go overboard with this, it'll eventually slow down the gameplay. * * Parameters: * text the message to display; maximum length: 10 chars; can be an arbitrary string, except the following characters are not permitted: |, ,, =, ( * * Energy Cost/Permissions: * for master bot: permitted, no energy consumed * for mini-bot: permitted, no energy consumed */ case class Say(text: String) extends Opcode /** * Status(text=string) * Shortcut for setting the state property 'status', which displays a little text bubble near the entity which moves around with the entity. Use this to tell spectators about what your bot thinks. You can also use this as a debugging tool. If you return the opcode Status, do not also set the status property via Set, since no particular order of execution is guaranteed. * * Parameters: * text the message to display; maximum length: 20 chars; can be an arbitrary string, except the following characters are not permitted: |, ,, =, ( * * Energy Cost/Permissions: * for master bot: permitted, no energy consumed * for mini-bot: permitted, no energy consumed */ case class Status(text: String) extends Opcode //— set a status message to be displayed above the bot /** * MarkCell(position=int:int,color=string) * Displays a cell as marked. You can use this as a debugging tool. * * Parameters: * position desired displacement relative to the current bot, e.g. -2:4 (defaults to 0:0) * color color to use for marking the cell, using HTML color notation, e.g. #ff8800 (default: #8888ff) * * Energy Cost/Permissions: * for master bot: permitted, no energy consumed * for mini-bot: permitted, no energy consumed */ case class MarkCell(position: Heading, color: Color) /** * DrawLine(from=int:int,to=int:int,color=string) * Draws a line. You can use this as a debugging tool. * * Parameters: * from starting cell of the line to draw, e.g. -2:4 (defaults to 0:0) * to destination cell of the line to draw, e.g. 3:-2 (defaults to 0:0) * color color to use for marking the cell, using HTML color notation, e.g. #ff8800 (default: #8888ff) * * Energy Cost/Permissions: * for master bot: permitted, no energy consumed * for mini-bot: permitted, no energy consumed */ case class DrawLine(from: Heading, to: Heading, color: Color) /** * Log(text=string) * Shortcut for setting the state property debug, which by convention contains an optional (multi-line) string with debug information related to the entity that issues this opcode. This text string can be displayed in the browser-based debug window to track what a bot or mini-bot is doing. The debug information is erased each time before the control function is called, so there is no need to set it to an empty string. * * Parameters: * text the debug message to store. The usual restrictions for string values apply (no commas, parentheses, equals signs or pipe characters). Newline characters are permitted, however. * * Energy Cost/Permissions: * for master bot: permitted, no energy consumed * for mini-bot: permitted, no energy consumed */ case class Log(text: String) extends Opcode // /** * Explode(size=int) * Detonates the mini-bot, dissipating its energy over some blast radius and damaging nearby entities. The mini-bot disappears. Parameters: * * size an integer value 2 < x < 10 indicating the desired blast radius * * Energy Cost/Permissions: * for master bot: cannot explode itself * for mini-bot: entire stored energy */ case class Explode(size: Int) extends MiniOp { require(2 < size && size < 10) } case class Color(r: Int, g: Int, b: Int) // ------------------------------------------------------------------------------------------------- /** * “?” cell whose content is occluded by a wall * “_” empty cell * “W” wall * “M” Bot (=master; yours, always in the center unless seen by a slave) * “m” Bot (=master; enemy, not you) * “S” Mini-bot (=slave, yours) * “s” Mini-bot (=slave; enemy's, not yours) * “P” Zugar (=good plant, food) * “p” Toxifera (=bad plant, poisonous) * “B” Fluppet (=good beast, food) * “b” Snorg (=bad beast, predator) */ sealed trait Cell object Cell { case object Unknown extends Cell case object Empty extends Cell case object Wall extends Cell case object You extends Cell case object Enemy extends Cell case object YourSlave extends Cell case object EnemySlave extends Cell case object GoodPlant extends Cell // case object BadPlant extends Cell case object GoodBeast extends Cell case object BadBeast extends Cell def parse(c: Char): Cell = c match { case '?' => Unknown case '_' => Empty case 'W' => Wall case 'M' => You case 'm' => Enemy case 'S' => YourSlave case 's' => EnemySlave case 'P' => GoodPlant case 'p' => BadPlant case 'B' => GoodBeast case 'b' => BadBeast } } sealed trait Displacement { def value: Int } object Displacement { case object Neg extends Displacement { val value = -1 } case object Zero extends Displacement { val value = 0 } case object Pos extends Displacement { val value = 1 } def parse(in: Int) = in match { case -1 => Neg case 0 => Zero case 1 => Pos } } case class Heading(x: Displacement, y: Displacement) { override def toString = x.value + ":" + y.value } object Heading { /**object DirectionShow extends Show[Heading] { def show(a: Heading) = a.toString }*/ /** Parse an XY value from XY.toString format, e.g. "2:3". */ def apply(s: String): Heading = { val a = s.split(':'); Heading(a(0).toInt, a(1).toInt) } def apply(x: Int, y: Int): Heading = Heading(Displacement.parse(x), Displacement.parse(y)) val Zero = Heading(0, 0) val One = Heading(1, 1) val Right = Heading(1, 0) val RightUp = Heading(1, -1) val Up = Heading(0, -1) val UpLeft = Heading(-1, -1) val Left = Heading(-1, 0) val LeftDown = Heading(-1, 1) val Down = Heading(0, 1) val DownRight = Heading(1, 1) } case class Coord(x: Int, y: Int) { override def toString = x + ":" + y def isNonZero = x != 0 || y != 0 def isZero = x == 0 && y == 0 def isNonNegative = x >= 0 && y >= 0 def updateX(newX: Int) = Coord(newX, y) def updateY(newY: Int) = Coord(x, newY) def addToX(dx: Int) = Coord(x + dx, y) def addToY(dy: Int) = Coord(x, y + dy) def +(pos: Coord) = Coord(x + pos.x, y + pos.y) def -(pos: Coord) = Coord(x - pos.x, y - pos.y) def *(factor: Double) = Coord((x * factor).intValue, (y * factor).intValue) def distanceTo(pos: Coord): Double = (this - pos).length // Phythagorean def length: Double = math.sqrt(x * x + y * y) // Phythagorean def stepsTo(pos: Coord): Int = (this - pos).stepCount // steps to reach pos: max delta X or Y def stepCount: Int = x.abs.max(y.abs) // steps from (0,0) to get here: max X or Y def signum = Coord(x.signum, y.signum) def negate = Coord(-x, -y) def negateX = Coord(-x, y) def negateY = Coord(x, -y) def wrap(boardSize: Coord) = { val fixedX = if (x < 0) boardSize.x + x else if (x >= boardSize.x) x - boardSize.x else x val fixedY = if (y < 0) boardSize.y + y else if (y >= boardSize.y) y - boardSize.y else y if (fixedX != x || fixedY != y) Coord(fixedX, fixedY) else this } } case class View(cells: String) { val size = math.sqrt(cells.length).toInt val center = Coord(size / 2, size / 2) def apply(relative: Coord) = Cell parse Relative.cellAt(relative) def offsetToNearest(c: Char) = cells.view.zipWithIndex.filter(_._1 == c).map(p => Relative.fromIndex(p._2)).minBy(_.length) object Relative { def indexFrom(c: Coord) = Absolute.indexFrom(Absolute.fromRelative(c)) def fromAbsolute(c: Coord) = c - center def fromIndex(index: Int) = fromAbsolute(Absolute.fromIndex(index)) def cellAt(c: Coord) = cells.charAt(indexFrom(c)) } object Absolute { def indexFrom(c: Coord) = c.x + c.y * size def fromIndex(index: Int) = Coord(index % size, index / size) def fromRelative(c: Coord) = c + center def cellAt(c: Coord) = cells.charAt(indexFrom(c)) } }