Major refactoring, same functionality

This commit is contained in:
Armin Friedl 2017-11-10 20:00:10 +01:00
parent 005d2a3ab6
commit b7f5febf15
22 changed files with 570 additions and 459 deletions

View file

@ -1,9 +1,8 @@
release:
cd src/main/scala/bot && sed -i 's/Dbg: Boolean = true/Dbg: Boolean = false/' DebugUtil.scala
cd src/main/scala/bot && sed -i 's/Dbg: Boolean = false/Dbg: Boolean = true/' DebugUtil.scala
cd src/main/scala/bot && sed -i 's/Dbg: Boolean = true/Dbg: Boolean = false/' util/Log.scala
cd src/main/scala && zip -r bot bot && mv bot.zip $(CURDIR)
cd src/main/scala/bot && sed -i 's/Dbg: Boolean = false/Dbg: Boolean = true/' util/Log.scala
dbg:
cd src/main/scala/bot && sed -i 's/Dbg: Boolean = false/Dbg: Boolean = true/' DebugUtil.scala
cd src/main/scala/bot && sed -i 's/Dbg: Boolean = true/Dbg: Boolean = false/' DebugUtil.scala
cd src/main/scala/bot && sed -i 's/Dbg: Boolean = false/Dbg: Boolean = true/' util/Log.scala
cd src/main/scala && zip -r bot bot && mv bot.zip $(CURDIR)

View file

@ -1,47 +1,51 @@
package bot
import scala.io.StdIn
import DebugUtil.{log,dbg,time}
import scala.collection.Iterable
import scala.annotation.tailrec
import scala.util.matching.Regex
import bot.util.Settings
import bot.environment.Board
import bot.cortex.Cortex
import bot.util.Log.dbg
object Main {
def main(args: Array[String]) = {
var input: String = ""
val settings = """^settings (.*)""".r
val character = """^action character (.*)""".r
val update = """^update (.*)""".r
val move = """^action move (\d+)""".r
// game is constructed after all settings are
// parsed. Accessing an unconstructed game is
// exceptional behavior -> NullPointerEx is acceptable
var game: Game = null
ioLoop { command => {
command match {
case settings (rest) => Settings.parse(rest)
None
case character (rest) => game = new Game()
Some("bixiette")
case update (rest) => game.parse(rest)
None
case move (rest) => val timebank = rest.toInt
game.timebank = timebank
val direction = Cortex getMove (game, timebank)
Some(direction.toString)
// case _ => let MatchError bubble to top
}}}
//Initialize general game settings
input = StdIn.readLine()
while (input startsWith "settings") {
S parse input
input = StdIn.readLine()
}
log("init current state")
val currentState = new State()
log("init cortex")
val cortex = new Cortex()
@tailrec
def ioLoop(f: String => Option[String]): Unit = {
val command = StdIn.readLine()
var tmpRound = currentState.round
// Main loop
while (input != null ){
if (tmpRound != currentState.round){
tmpRound = currentState.round
log("\n");
log(s"********** ROUND ${tmpRound} **********")
}
def cmd: Seq[String] = input.split(" ")
if (command != null) { f(command) foreach println }
else { return }
// dispatch
cmd match {
case Seq("update", _*) => currentState parse input
case Seq("action", "character", _) => println ("bixiette")
case Seq("action", "move", t) => {
log(s"Timebank: $t")
val move: Direction = cortex move (currentState, t.toInt)
log (move.label)
println (move.label)
}
}
input = StdIn.readLine()
}
ioLoop(f)
}
}

View file

@ -1,206 +0,0 @@
package bot
import scala.util.Random
import DebugUtil.{ log, dbg, time }
import scala.collection.mutable
sealed trait Direction {
val label: String
val deltaX: Int
val deltaY: Int
override def toString: String = label
}
final case object Left extends { val label = "left"; val deltaX = -1; val deltaY = 0 } with Direction
final case object Right extends { val label = "right"; val deltaX = 1; val deltaY = 0 } with Direction
final case object Up extends { val label = "up"; val deltaX = 0; val deltaY = -1 } with Direction
final case object Down extends { val label = "down"; val deltaX = 0; val deltaY = 1 } with Direction
object Scribble {
var lastFieldWeight = 0
var lastBofaField: Field = Field.empty
}
class Cortex {
def move(state: State, time: Int): Direction = {
val directions: Seq[Direction] = possibleDirections(state.bofaField, state)
val weightBoard = weightCodes(1000, weigh, state)
preventFallBack(weightBoard, state)
preventLethal(weightBoard, state)
val dir = getMaxDir(state.bofaField, directions, weightBoard, state)
Scribble.lastBofaField = state.bofaField
weightBoard(state.bofaField.x)(state.bofaField.y) = -999
printBoard(weightBoard)
dir
}
def weigh(start: Int, dist: Int, cur: Int) = {
(start / math.pow(1.3, (dist + 1))).intValue()
}
def getMaxField(weightBoard: Array[Array[Int]], state: State): Field = {
var maxField = (0, 0)
for (x <- 0 until S.width; y <- 0 until S.height) {
// generally, the highest is best
if (weightBoard(x)(y) > weightBoard(maxField _1)(maxField _2)) maxField = (x, y)
// if equal prefer to go down, right b/c up/left is evil first but
if (weightBoard(x)(y) == weightBoard(maxField _1)(maxField _2)) {
if (x > (maxField _1) || y > (maxField _2)) maxField = (x, y)
}
}
state.board(maxField _1)(maxField _2)
}
def shortestPathTo(field: Field, state: State) = {
0
}
def mod(a: Int)(b: Int): Int = (a % b) + (if (a < 0) b else 0)
def getMaxDir(field: Field, directions: Seq[Direction], weightBoard: Array[Array[Int]], state: State): Direction = {
var maxDir: Direction = randomDirection(directions)
val board = state.board
for (d <- directions) {
var (x, y) = (mod(field.x + d.deltaX)(S.width), mod(field.y + d.deltaY)(S.width))
var (maxX, maxY) = (mod(field.x + maxDir.deltaX)(S.width), mod(field.y + maxDir.deltaY)(S.width))
if (weightBoard(x)(y) > weightBoard(maxX)(maxY)) maxDir = d
}
maxDir
}
def manhattan(x: Int, y: Int)(a: Int, b: Int): Int = math.abs(x - a) + math.abs(y - b)
def possibleDirections(field: Field, state: State): Seq[Direction] = {
val pd = new mutable.ListBuffer[Direction]
val board = state.board
val toCheck = checkBorder(field, state)
for (d <- toCheck) {
if (d == Left && board(field.x)(field.y).citizens.contains(Gate("l"))) pd += d
else if (d == Right && board(field.x)(field.y).citizens.contains(Gate("r"))) pd += d
else {
val pfield = board(field.x + d.deltaX)(field.y + d.deltaY)
if (!(pfield.citizens contains Wall)) {
pd += d
}
}
}
pd
}
def preventFallBack(weightBoard: Array[Array[Int]], state: State): Unit = {
val lastBofaField = Scribble.lastBofaField
if (lastBofaField != Field.empty) {
// if something changed allow fallback
log(s"lastFieldWeight=${Scribble.lastFieldWeight}")
if (Scribble.lastFieldWeight == weightBoard(lastBofaField.x)(lastBofaField.y)) {
Scribble.lastFieldWeight = weightBoard(state.bofaField.x)(state.bofaField.y)
val x: Int = weightBoard(lastBofaField.x)(lastBofaField.y)
weightBoard(lastBofaField.x)(lastBofaField.y) = (0.5 * x).intValue()
} else {
Scribble.lastFieldWeight = weightBoard(state.bofaField.x)(state.bofaField.y)
}
}
}
def preventLethal(weightBoard: Array[Array[Int]], state: State): Unit = {
val board = state.board
for (bug <- state.bugs) {
val dirs = possibleDirections(bug, state)
for (d <- dirs) {
var (x, y) = (bug.x + d.deltaX, bug.y + d.deltaY)
if (d == Left && board(bug.x)(bug.y).citizens.contains(Gate("l"))) x = S.width - 1
else if (d == Right && board(bug.x)(bug.y).citizens.contains(Gate("r"))) x = 0
weightBoard(x)(y) = (weightBoard(x)(y) * 0.1).intValue()
}
weightBoard(bug.x)(bug.y) = 1
}
}
def weightCodes(start: Int, weigh: (Int, Int, Int) => Int, state: State): Array[Array[Int]] = {
val board = state.board
val codes = state.codes
val weightBoard = Array.ofDim[Int](S.width, S.height)
for (x <- 0 until S.width; y <- 0 until S.height) weightBoard(x)(y) = 10
log(s"Codes: ${codes.length}")
for (code <- codes) {
val visited = Array.ofDim[Boolean](S.width, S.height)
for (x <- 0 until S.width; y <- 0 until S.height) visited(x)(y) = false
var coords: Seq[Field] = Seq.empty
val frontier = new mutable.ListBuffer[Field]
frontier += code
visited(code.x)(code.y) = true
var weight = start
var dist = 0
while (!frontier.isEmpty) {
coords = frontier.toList
frontier.clear()
for (field <- coords) {
val dirs = possibleDirections(field, state)
for (d <- dirs) {
var (x, y) = (field.x + d.deltaX, field.y + d.deltaY)
if (d == Left && board(field.x)(field.y).citizens.contains(Gate("l"))) x = S.width - 1
else if (d == Right && board(field.x)(field.y).citizens.contains(Gate("r"))) x = 0
if (!visited(x)(y)) frontier += board(x)(y)
visited(x)(y) = true
}
weightBoard(field.x)(field.y) += weight
}
weight = weigh(start, dist, weight)
dist += 1
}
}
weightBoard
}
def checkBorder(field: Field, state: State): Seq[Direction] = {
val dirs = new mutable.ListBuffer[Direction]
if (field.y != 0) dirs += Up
if (field.y != (S.height - 1)) dirs += Down
if (field.x != 0 || field.citizens.contains(Gate("l"))) dirs += Left
if (field.x != (S.width - 1) || field.citizens.contains(Gate("r"))) dirs += Right
dirs
}
def randomDirection(directions: Seq[Direction]) = directions(Random.nextInt(directions.length))
def printBoard(board: Array[Array[Int]]) = {
val btp = board.transpose
val buf = new StringBuilder
buf append "\n+" + "-" * (S.width * 10) + "+" + "\n"
for (line <- btp) {
buf append s"""|${line.map(_.toString.padTo(4, ' ')).mkString("|")}|"""
buf append "\n+" + "-" * (S.width * 10) + "+" + "\n"
}
log(buf toString)
}
}

View file

@ -1,25 +0,0 @@
package bot
object DebugUtil {
var state: Option[State] = None // we need this to get access to the current game round
val Dbg: Boolean = false
val Log: Boolean = true
val Time: Boolean = true
def dbg(msg: String) = if(Dbg) Console.err.println(s"$round $msg")
def log(msg: String) = if(Log) Console.err.println(s"$round $msg")
def round: String = if (state.isDefined) s"[${state.get.round}]" else ""
def time[R](name: String)(block: => R): R = {
if(Time){
val t0 = System.nanoTime()
val result = block // call-by-name
val t1 = System.nanoTime()
log(s"$name: " + (t1 - t0)/1000000.0 + "ms")
result
}else{
block
}
}
}

View file

@ -0,0 +1,45 @@
package bot
import bot.environment.Board
import bot.util.Settings
import bot.util.Log.dbg
class Game {
var _round: Int = 0
var _timebank: Int = Settings.timebank
var board: Board = new Board(Settings.width, Settings.height)
var bofa: Player = new Player(Settings.bofaId, Settings.bofaName)
var oppo: Player = new Player(Settings.oppoId, Settings.oppoName)
def round = _round
def round_= (round: Int) = {
dbg(s"\n\n************ Round ${round} ************")
_round = round
}
def timebank = _timebank
def timebank_= (timebank: Int) = {
dbg(s"Timebank: $timebank")
_timebank = timebank
}
def playerBy(id: Int) = if (id == (Settings.bofaId)) bofa else oppo
def playerBy(name: String) = if (name equals (Settings.bofaName)) bofa else oppo
def bofaId = bofa.id
def bofaPoint = board.bots(bofaId).get.point
def parse (update: String) = {
val round = """^game round (\d+)""".r
val board = """^game field (.*)""".r
val snippets = """^(.*) snippets (\d+)""".r
val bombs = """^(.*) bombs (\d+)""".r
update match {
case round (rest) => this.round = rest.toInt
case board (rest) => this.board.parse(rest)
case snippets (name, rest) => playerBy(name).snippets = rest.toInt
case bombs (name, rest) => playerBy(name).bombs = rest.toInt
}
}
}

View file

@ -0,0 +1,6 @@
package bot
class Player(val id: Int, val name: String) {
var bombs: Int = 0
var snippets: Int = 0
}

View file

@ -1,46 +0,0 @@
package bot
import DebugUtil.{log,dbg,time}
/** Settings holder containing global settings set in the beginning. Only set once per game. */
object S {
var timebank: Int = -1
var timePerMove: Int = -1
var width: Int = -1
var height: Int = -1
var maxRounds: Int = -1
var players: Seq[String] = Seq()
var bofaId: Int = -1
var bofaName: String = ""
def oppoId = 1-bofaId
def oppoName = players(oppoId)
def parse(setting: String): Unit = {
val parts = setting.split(" ")
parts(1) match {
case "timebank" => timebank = parts(2).toInt
case "time_per_move" => timePerMove = parts(2).toInt
case "your_botid" => bofaId = parts(2).toInt
case "field_width" => width = parts(2).toInt
case "field_height" => height = parts(2).toInt
case "max_rounds" => maxRounds = parts(2).toInt
case "your_bot" => bofaName = parts(2)
case "player_names" => players = parts(2).split(",")
// case _ => let MatchError through, don't try recovering
}
}
override def toString: String = {
s"""
Settings {
.timebank = $timebank
.timePerMove = $timePerMove
.width = $width
.height = $height
.maxRounds = $maxRounds
}
"""
}
}

View file

@ -1,142 +0,0 @@
package bot
import scala.collection.Seq
import DebugUtil.{ log, dbg, time }
/** Field of the game board
*
* The field knows its citizens and location in the world
* for faster access
*/
class Field(val x: Int, val y: Int, var citizens: Seq[Citizen]) {
def this(x: Int, y: Int) = this(x, y, Seq.empty)
override def toString(): String = s"""($x,$y): ${citizens.map(_.getClass.getSimpleName).mkString(",")}"""
}
object Field {
val empty: Field = new Field(-1, -1)
def apply(x: Int, y: Int): Field = new Field (x, y)
def apply(x: Int, y: Int, citizen: Seq[Citizen]): Field = Field (x, y, citizen)
}
/** Potential population of the game board */
sealed trait Citizen
final case object Floor extends Citizen
final case object Wall extends Citizen
final case object Code extends Citizen
final case class Gate (direction: String) extends Citizen
final case class Spawner (rounds: Option[Int]) extends Citizen
final case class Bug (ai: Int) extends Citizen
final case class Bomb (rounds: Option[Int]) extends Citizen
final case class Player (id: Int, name: String) extends Citizen {
var snippets: Int = 0
var bombs: Int = 0
}
/** A State in the game
*
* A state consists at minimum of a populated game board and two players.
*/
class State {
// index top-left (0,0) to bottom-right (S.width,S.height)
val board: Array[Array[Field]] = Array.ofDim[Field](S.width, S.height)
for (i <- board.indices; j <- board(i).indices) board(i)(j) = Field (i, j) // init board
var round: Int = -1 // may not be set
var bofa: Player = new Player(S.bofaId, S.bofaName)
var oppo: Player = new Player(S.oppoId, S.oppoName)
var bofaField: Field = Field.empty
var oppoField: Field = Field.empty
var codes: Seq[Field] = Seq.empty
var bombs: Seq[Field] = Seq.empty
var bugs: Seq[Field] = Seq.empty
def update(x: Int, y: Int, citizens: Seq[Citizen]) {
val field = board(x)(y)
field.citizens = citizens
for (citizen <- citizens) {
citizen match {
case Player(id, name) if id == S.bofaId => bofaField = field
case Player(id, name) if id == S.oppoId => oppoField = field
case Code => codes = codes :+ field
case Bomb (_) => bombs = bombs :+ field
case Bug (_) => bugs = bugs :+ field
case _ =>
}
}
}
def playerBy(id: Int) = if (id == (S.bofaId)) bofa else oppo
def playerBy(name: String) = if (name equals (S.bofaName)) bofa else oppo
def parse(update: String): Unit = {
val cmd: Seq[String] = update split (" ") tail
cmd match {
case Seq("game", "round", uround) => round = uround.toInt
case Seq("game", "field", cellstring) => parseCells(cellstring)
case Seq(name, "snippets", snippets) => playerBy(name).snippets = snippets.toInt
case Seq(name, "bombs", bombs) => playerBy(name).bombs = bombs.toInt
// case _ => let MatchError through, don't try recovering
}
}
def parseCells(cellstring: String): Unit = {
codes = Seq.empty
bombs = Seq.empty
bugs = Seq.empty
val cellParts = cellstring.split(",")
for (cellId <- 0 until cellParts.length) {
val (x: Int, y: Int) = (cellId % S.width, cellId / S.width)
val citizens: Array[Citizen] =
cellParts(cellId).split(";")
.map(_.toCharArray())
.map {
case Array('.') => Floor
case Array('x') => Wall
case Array('P', id) => playerBy(id.asDigit)
case Array('G', direction) => Gate(direction.toString)
case Array('E', ai) => Bug(ai.asDigit)
case Array('C') => Code
case Array('S') => Spawner(None)
case Array('S', time @ _*) => Spawner(Some(time.mkString.toInt))
case Array('B') => Bomb(None)
case Array('B', time @ _*) => Bomb(Some(time.mkString.toInt))
}
update(x, y, citizens)
}
}
override def toString = {
val btp = board.transpose
val buf = new StringBuilder
buf append "\n+" + "-" * (S.width * 10 + S.width * 3 - 1) + "+" + "\n"
for (line <- btp) {
val maxCitizens = line map (_.citizens.length) max
for (citizenCounter <- 0 until maxCitizens) {
for (field <- line) {
if (field.citizens.length > citizenCounter)
buf append s"""| ${field.citizens(citizenCounter).getClass.getSimpleName().padTo(10, ' ')} """
else
buf append s"| ${" " * 10} "
}
buf append "|\n"
}
buf append "+" + "-" * (S.width * 10 + S.width * 3 - 1) + "+" + "\n"
}
buf toString
}
}

View file

@ -0,0 +1,25 @@
package bot.cortex
import bot.environment.topology.Point
import bot.environment.Board
import bot.Game
import bot.environment.topology.Grid
class BugAI extends Tactical {
def evaluate(game: Game): Tactic = {
val board = game.board
var tactic: Tactic = Map.empty
val weightGrid = new Grid(board.width, board.height, 0: Int)
for ( bug <- board.bugs; point <- board.walkablePoints(bug.point) ) {
weightGrid(point).content += -1000
}
board.walkableDirs(game.bofaPoint) foreach { dir =>
tactic = tactic + {dir -> weightGrid(board.walk(game.bofaPoint)(dir)).content}
}
tactic
}
}

View file

@ -0,0 +1,49 @@
package bot.cortex
import bot.Game
import bot.environment.topology.Direction
import bot.environment.topology.Left
import bot.environment.topology.Direction
import scala.collection.mutable
import bot.cortex.Tactical.Tactic
import bot.util.Log.dbg
object Cortex {
val tactics = Seq(new BugAI, new SnippetAI)
def getMove (game: Game, time: Int): Direction = {
val tacticResults = tactics map { _.evaluate(game) }
val moves = mergeTactics(tacticResults)
getMaxDir(moves.toMap)
}
private def mergeTactics(tactics: Seq[Tactic]): Tactic = {
var dirs: Tactic = Map.empty
for ( tactic <- tactics ) {
dbg(s"""Tactic ${tactic.getClass.getSimpleName}: { ${tactic.mkString(",")} }""")
for ( (dir, delta) <- tactic ){
val weight = dirs.getOrElse(dir, 0)
dirs = dirs + { dir -> (weight+delta) }
}
}
dirs
}
private def getMaxDir(moves: Map[Direction, Int]): Direction = {
var (maxDir, maxWeight) = moves.last
for ( (key, value) <- moves ){
if (value > maxWeight) {
maxDir = key
maxWeight = value
}
}
dbg(s"""Max dir: ($maxDir, $maxWeight)""")
maxDir
}
}

View file

@ -0,0 +1,77 @@
package bot.cortex
import scala.annotation.tailrec
import bot.environment.Board
import bot.environment.Board.Tokens
import bot.environment.topology.Direction
import bot.environment.topology.Grid
import bot.environment.topology.Point
import bot.environment.topology.Field
import bot.Game
import bot.util.Log.dbg
class SnippetAI extends Tactical {
var lastPoint: Option[Point] = None
var lastWeight = -1
def evaluate (game: Game): Tactic = {
val board = game.board
val point = game.bofaPoint
val snippets = board.snippets
val weightGrid = new Grid(board.width, board.height, 10: Int)
board.snippets foreach { snippet => distribute(snippet, weightGrid, board) }
preventFallBack(point, weightGrid, board)
weightGrid(game.bofaPoint) put -999
markWalls(weightGrid, board)
dbg(weightGrid.toString())
board.walkableDirs(point) // get all possible directions starting in point
.map { dir: Direction => (dir, weightGrid(board.walk(point)(dir)).content) } // bind direction to weight
.toMap // return as map
}
private def distribute(snippet: Field[Tokens], weightGrid: Grid[Int], board: Board) = {
val visited: Grid[Boolean] = new Grid(board.width, board.height, false)
runner(Set(snippet.point), 0)
@tailrec
def runner(frontier: Set[Point], distance: Int): Unit = {
if (frontier.isEmpty) return
// process the current frontier and build the new ones
val nextFrontier =
frontier filterNot { visited(_).get } flatMap { p =>
visited(p) put true
weightGrid(p).content += (1000 / math.pow(1.3, distance)).intValue
board.walkablePoints(p)
}
runner(nextFrontier, distance + 1)
}
}
private def preventFallBack(point: Point, weightGrid: Grid[Int], board: Board): Unit = {
if (!lastPoint.isEmpty){
val currentLastWeight = weightGrid(lastPoint.get).content
if (lastWeight == currentLastWeight){
weightGrid(lastPoint.get) apply { x => (0.5 * x).intValue }
dbg(s"Preventing fallback to ${lastPoint.get}: lastWeight=$lastWeight, newWeight=${weightGrid(lastPoint.get).get}")
}
}
lastPoint = Some(point)
lastWeight = weightGrid(point).get
}
private def markWalls(weightGrid: Grid[Int], board: Board) = {
board.walls foreach { wall => weightGrid(wall) put -88 }
}
}

View file

@ -0,0 +1,15 @@
package bot.cortex
import bot.environment.Board
import bot.environment.topology.Direction
import bot.environment.topology.Point
import bot.Game
trait Tactical {
type Tactic = Map[Direction, Int]
def evaluate(game: Game): Tactic
}
object Tactical {
type Tactic = Map[Direction, Int]
}

View file

@ -0,0 +1,98 @@
package bot.environment
import scala.collection.mutable
import bot.OptionInt
import bot.environment.Board.Tokens
import bot.environment.topology._
object Board {
type Tokens = Set[Token]
}
class Board(val width: Int, val height: Int) {
private val _snippets: mutable.Set[Field[Tokens]] = mutable.Set.empty
private val _bugs: mutable.Set[Field[Tokens]] = mutable.Set.empty
private val _bots: mutable.Map[Int, Field[Tokens]] = mutable.Map.empty
private val _walls: mutable.Set[Field[Tokens]] = mutable.Set.empty
private val _grid: Grid[Tokens] = new Grid(width, height, Set.empty)
private val _graph: Graph[Tokens] = new Graph
def asGrid = _grid
def asGraph = _graph
def snippets = _snippets.to[Set]
def bugs = _bugs.to[Set]
def bots(id: Int) = _bots.get(id)
def walls = _walls.to[Set]
def put(point: Point, tokens: Tokens) = {
val field = _grid(point)
field.content = tokens
tokens foreach {
case Snippet() => _snippets += field
case Bug(_) => _bugs += field
case Bot(id) => _bots += {id -> field}
case Wall() => _walls += field
case _ =>
}
}
def walkableDirs(point: Point): Set[Direction] = {
var dirs: Set[Direction] = _grid.directions(point)
dirs = dirs filterNot { x => _grid(point+x).content.contains(Wall()) }
_grid(point).content foreach {
case Gate(Left) => dirs = dirs + Left
case Gate(Right) => dirs = dirs + Right
case _ =>
}
return dirs
}
def walkablePoints(point: Point): Set[Point] = walkableDirs(point) map walk(point)
def walk(point: Point) (direction: Direction): Point = { //adjust for gates
val np = point + direction
np.x match {
case -1 => Point(width-1, np.y) // left gate
case this.width => Point(0, np.y) // right gate
case _ => np // otherwise
}
}
def parse(update: String): Unit = {
_snippets.clear
_bugs.clear
_bots.clear
_walls.clear
update
.split(",")
.zipWithIndex
.map { case (things, i) => (things, Point(i%width, i/width)) }
.foreach { case (things, point) => put (point, unpack(things)) }
}
private def unpack(things: String): Tokens = {
val tokens: mutable.Set[Token] = mutable.Set.empty
things
.split (';') // get single tokens
.map { thing => thing splitAt 1 } // extract designator (first char)
.map { case (".", _) => Floor()
case ("x", _) => Wall()
case ("P", id) => Bot(id.toInt)
case ("G", "l") => Gate(Left)
case ("G", "r") => Gate(Right)
case ("E", ai) => Bug(ai.toInt)
case ("C", _) => Snippet()
case ("S", time) => Spawner(time.toOptInt)
case ("B", time) => Bomb(time.toOptInt)
case _ => throw new MatchError } // map to token type
.map { tkn => tkn.asInstanceOf[Token] } // explicitely cast Array[Any] to Array[Token]
.toSet // return Array as Set[Token]
}
}

View file

@ -0,0 +1,17 @@
package bot.environment
import bot.environment.topology.Direction
/**
* Every potential token on the game board
*/
sealed trait Token
final case class Floor () extends Token
final case class Wall () extends Token
final case class Snippet () extends Token
final case class Gate (direction: Direction) extends Token
final case class Spawner (rounds: Option[Int]) extends Token
final case class Bug (ai: Int) extends Token
final case class Bomb (rounds: Option[Int]) extends Token
final case class Bot (id: Int) extends Token

View file

@ -0,0 +1,15 @@
package bot.environment.topology
sealed trait Direction {
val label: String
val delta: (Int, Int)
def deltaX = delta _1
def deltaY = delta _2
override def toString: String = label
}
final case object Left extends { val label = "left"; val delta = (-1, 0) } with Direction
final case object Right extends { val label = "right"; val delta = (1, 0) } with Direction
final case object Up extends { val label = "up"; val delta = (0, -1) } with Direction
final case object Down extends { val label = "down"; val delta = (0, 1) } with Direction

View file

@ -0,0 +1,14 @@
package bot.environment.topology
class Field[T] (val point: Point, var content: T) {
def x: Int = point.x
def y: Int = point.y
def put(update: T) = content = update
def get = content
def apply(f: T => T): Unit = content = f(content)
}
object Field {
def apply[T] (point: Point, content: T) = new Field(point, content)
}

View file

@ -0,0 +1,21 @@
package bot.environment.topology
import scala.collection.mutable
/**
* The game world viewed as a graph
*
* Each node of the Graph is a @see Field connected
* to other @see Fields.
*/
class Graph[T] {
private val _nodes: mutable.Set[Node[T]] = mutable.Set.empty
private val _edges: mutable.Set[Edge[T]] = mutable.Set.empty
def addNode(node: Node[T]) = _nodes += node
def addEdge(edge: Edge[T]) = _edges += edge
}
class Node[T](field: Field[T])
class Edge[T](a: Node[T], b: Node[T])

View file

@ -0,0 +1,41 @@
package bot.environment.topology
import scala.reflect.ClassTag
/** The game world viewed as a grid
*
* A Grid is a 2-d array where each element points to a @see Field
*/
class Grid[T: ClassTag] (width: Int, height: Int, default: => T) {
private val grid: Array[Array[Field[T]]] = Array.ofDim[Field[T]](width, height)
for (x <- 0 until width; y <- 0 until height)
grid(x)(y) = Field( Point(x,y), default )
def apply(x: Int, y: Int): Field[T] = grid(x)(y)
def apply(point: Point): Field[T] = apply(point.x, point.y)
def apply(f: Field[_]): Field[T] = apply(f.point)
def neighbors (point: Point): Set[Field[T]] = directions(point) map { dir => apply(point+dir) }
def directions (point: Point): Set[Direction] = {
Set(Left, Right, Up, Down)
.filter { dir => var (x,y) = (point.x+dir.deltaX, point.y+dir.deltaY)
0 <= x && x < width && 0 <= y && y < height }
.toSet
}
override def toString: String = {
val btp = grid.transpose
val buf = new StringBuilder
buf append "\n+" + "-" * (width * 10) + "+" + "\n"
for (line <- btp) {
buf append s"""|${line.map(_.content.toString.padTo(4, ' ')).mkString("|")}|"""
buf append "\n+" + "-" * (width * 10) + "+" + "\n"
}
buf.toString
}
}

View file

@ -0,0 +1,14 @@
package bot.environment.topology
class Point (val x: Int, val y: Int){
def asTuple: (Int, Int) = (x,y)
def +(deltaX: Int, deltaY: Int): Point = new Point(x+deltaX, y+deltaY)
def +(delta: (Int,Int)): Point = this + (delta _1, delta _2)
def +(direction: Direction): Point = this + direction.delta
override def toString: String = s"Point{.x=$x, .y=$y}"
}
object Point {
def apply(x: Int, y: Int) = new Point(x,y)
}

View file

@ -0,0 +1,10 @@
import scala.util.control.Exception.allCatch
package object bot {
implicit class OptionInt(s: String) {
def toOptInt: Option[Int] = s match {
case "" => None
case _ => allCatch.opt(s.toInt)
}
}
}

View file

@ -0,0 +1,27 @@
package bot.util
object Log {
var state = None
val Dbg: Boolean = true
val Log: Boolean = true
val Time: Boolean = true
//def dbg(msg: String) = if(Dbg) Console.err.println(s"$round $msg")
//def log(msg: String) = if(Log) Console.err.println(s"$round $msg")
def dbg(msg: String) = if(Dbg) Console.err.println(msg)
//def round: String = if (state.isDefined) s"[${state.get.round}]" else ""
def time[R](name: String)(block: => R): R = {
if(Time){
val t0 = System.nanoTime()
val result = block // call-by-name
val t1 = System.nanoTime()
//log(s"$name: " + (t1 - t0)/1000000.0 + "ms")
result
}else{
block
}
}
}

View file

@ -0,0 +1,53 @@
package bot.util
import scala.collection.Seq
/** Settings container
*
* Holds global, static settings set once at start of the game
*/
object Settings {
var timebank: Int = 10000
var timePerMove: Int = 1000
var width: Int = 15
var height: Int = 8
var maxRounds: Int = 250
var players: Seq[String] = Seq("bofa", "oppo")
var bofaId: Int = 0
var bofaName: String = players(bofaId)
def oppoId: Int = 1-bofaId
def oppoName: String = players(oppoId)
def playerId(name: String): Option[Int] = players.indexOf(name) match {
case -1 => None
case i => Some(i)
}
def parse(setting: String): Unit = {
val parts = setting.split(" ")
parts(0) match {
case "timebank" => timebank = parts(1).toInt
case "time_per_move" => timePerMove = parts(1).toInt
case "your_botid" => bofaId = parts(1).toInt
case "field_width" => width = parts(1).toInt
case "field_height" => height = parts(1).toInt
case "max_rounds" => maxRounds = parts(1).toInt
case "your_bot" => bofaName = parts(1)
case "player_names" => players = parts(1).split(",")
// case _ => let MatchError through, don't try recovering
}
}
override def toString: String = {
s"""
Settings {
.timebank = $timebank
.timePerMove = $timePerMove
.width = $width
.height = $height
.maxRounds = $maxRounds
}
"""
}
}