Skip to content

Instantly share code, notes, and snippets.

@jedws
Created May 9, 2012 10:32
Show Gist options
  • Select an option

  • Save jedws/2643617 to your computer and use it in GitHub Desktop.

Select an option

Save jedws/2643617 to your computer and use it in GitHub Desktop.

Revisions

  1. jedws revised this gist May 9, 2012. 1 changed file with 103 additions and 52 deletions.
    155 changes: 103 additions & 52 deletions scalatronFramework.scala
    Original file line number Diff line number Diff line change
    @@ -23,12 +23,12 @@ object Mapper {
    }
    }

    trait Mapped {
    trait Parameters {
    def apply[A: Mapper](s: String): A
    }

    object Mapped {
    implicit def StringyMapToMapped(m: Map[String, String]) = new Mapped {
    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 splitParameterIntoKeyValue(param: String): (String, String) = {
    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(splitParameterIntoKeyValue).toMap)
    Command(segments(0), segments(1).dropRight(1).split(',').map(split).toMap)
    }
    }

    object Command {
    def apply(cmd: String, get: Mapped): Command = cmd match {
    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 //— move one step in some direction
    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: String, energy: Int) extends Op {
    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 //— set a state parameter maintained for the bot
    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
    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 //— set a status message to be displayed above the bot
    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
    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
    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 //
    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) {
    override def toString = null //TODO define html color string & various useful constants etc.
    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 parse(c: Char): Cell = c match {
    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 parse(in: Int) = in match {
    def apply(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 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 {
    /**
    * 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)
    /** 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, 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
    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 {
    trait Projection extends (Coord => Cell) {
    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) {
    case class View(cells: String) extends (Coord => Cell) {
    val size = math.sqrt(cells.length).toInt
    val center = Coord(size / 2, size / 2)

    def apply(relative: Coord) = Relative.cellAt(relative)
    def apply(c: Coord): Cell = Relative(c)

    def offsetToNearest(c: Char) =
    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 cellAt(c: Coord) = Cell parse cells.charAt(indexFrom(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 cellAt(c: Coord) = Cell parse cells.charAt(indexFrom(c))
    def apply(c: Coord) = Cell(cells.charAt(indexFrom(c)))
    }
    }
  2. jedws revised this gist May 9, 2012. 1 changed file with 10 additions and 6 deletions.
    16 changes: 10 additions & 6 deletions scalatronFramework.scala
    Original 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 segments = param.split('=')
    (segments(0), if (segments.length >= 2) segments(1) else "")
    val seg = param.split('=')
    (seg(0), if (seg.length >= 2) seg(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)
    require(segments.length == 2, "invalid command: " + command)
    Command(segments(0), segments(1).dropRight(1).split(',').map(splitParameterIntoKeyValue).toMap)
    }
    }

  3. jedws revised this gist May 9, 2012. 1 changed file with 33 additions and 38 deletions.
    71 changes: 33 additions & 38 deletions scalatronFramework.scala
    Original file line number Diff line number Diff line change
    @@ -1,7 +1,3 @@
    // -------------------------------------------------------------------------------------------------
    // Framework
    // -------------------------------------------------------------------------------------------------

    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]])
    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 "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 Opcode extends 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 Opcode //— move one step in some direction
    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 Opcode {
    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 Opcode //— set a state parameter maintained for the bot
    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 Opcode
    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 Opcode //— set a status message to be displayed above the bot
    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)
    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)
    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 Opcode //
    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)

    // -------------------------------------------------------------------------------------------------
    case class Color(r: Int, g: Int, b: Int) {
    override def toString = null //TODO define html color string & various useful constants etc.
    }

    /**
    * “?” 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
    @@ -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) = Cell parse Relative.cellAt(relative)
    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 {
    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 cellAt(c: Coord) = cells.charAt(indexFrom(c))
    def fromRelative(c: Coord) = c
    def cellAt(c: Coord) = Cell parse cells.charAt(indexFrom(c))
    }

    object Absolute {
    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 cellAt(c: Coord) = cells.charAt(indexFrom(c))
    def fromAbsolute(c: Coord) = c
    def cellAt(c: Coord) = Cell parse cells.charAt(indexFrom(c))
    }
    }
  4. jedws revised this gist May 9, 2012. 1 changed file with 15 additions and 4 deletions.
    19 changes: 15 additions & 4 deletions scalatronFramework.scala
    Original file line number Diff line number Diff line change
    @@ -4,12 +4,21 @@

    trait Mapper[A] extends (String => A)
    object Mapper {
    implicit val StringToString = new Mapper[String] {
    implicit val StringMapper = new Mapper[String] {
    def apply(s: String) = s
    }
    implicit val IntToString = new Mapper[Int] {
    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)])
    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)
    case class Goodbye(energy: Int) extends Command

    sealed trait Property
    case class DirectionProperty() extends Property
  5. jedws revised this gist May 9, 2012. 1 changed file with 23 additions and 5 deletions.
    28 changes: 23 additions & 5 deletions scalatronFramework.scala
    Original file line number Diff line number Diff line change
    @@ -1,10 +1,27 @@
    object ControlFunction {
    }

    // -------------------------------------------------------------------------------------------------
    // 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, map: Map[String, String]): Command = {
    null
    def apply(cmd: String, get: Mapped): Command = cmd match {
    case "Welcome" => Welcome(get[String]("name"), get[String]("path"), get[Int]("apocalypse"), get[Int]("round"))
    }
    }

  6. jedws revised this gist May 9, 2012. 1 changed file with 0 additions and 76 deletions.
    76 changes: 0 additions & 76 deletions scalatronFramework.scala
    Original file line number Diff line number Diff line change
    @@ -12,82 +12,6 @@ class ControlFunctionFactory {
    }

    // -------------------------------------------------------------------------------------------------

    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,...)"
  7. jedws revised this gist May 9, 2012. 1 changed file with 10 additions and 11 deletions.
    21 changes: 10 additions & 11 deletions scalatronFramework.scala
    Original 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
    }*/
    /**
    * 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 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
    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) =
    def offsetToNearest(c: Char) =
    cells.view.zipWithIndex.filter(_._1 == c).map(p => Relative.fromIndex(p._2)).minBy(_.length)

    object Relative {
  8. jedws created this gist May 9, 2012.
    459 changes: 459 additions & 0 deletions scalatronFramework.scala
    Original 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))
    }
    }