Major refactoring, same functionality
This commit is contained in:
parent
005d2a3ab6
commit
b7f5febf15
22 changed files with 570 additions and 459 deletions
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
//Initialize general game settings
|
||||
input = StdIn.readLine()
|
||||
while (input startsWith "settings") {
|
||||
S parse input
|
||||
input = StdIn.readLine()
|
||||
}
|
||||
val update = """^update (.*)""".r
|
||||
val move = """^action move (\d+)""".r
|
||||
|
||||
log("init current state")
|
||||
val currentState = new State()
|
||||
log("init cortex")
|
||||
val cortex = new Cortex()
|
||||
// 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
|
||||
}}}
|
||||
|
||||
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(" ")
|
||||
|
||||
// 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()
|
||||
}
|
||||
}
|
||||
|
||||
@tailrec
|
||||
def ioLoop(f: String => Option[String]): Unit = {
|
||||
val command = StdIn.readLine()
|
||||
|
||||
if (command != null) { f(command) foreach println }
|
||||
else { return }
|
||||
|
||||
ioLoop(f)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
45
bofa/src/main/scala/bot/Game.scala
Normal file
45
bofa/src/main/scala/bot/Game.scala
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
6
bofa/src/main/scala/bot/Player.scala
Normal file
6
bofa/src/main/scala/bot/Player.scala
Normal file
|
@ -0,0 +1,6 @@
|
|||
package bot
|
||||
|
||||
class Player(val id: Int, val name: String) {
|
||||
var bombs: Int = 0
|
||||
var snippets: Int = 0
|
||||
}
|
|
@ -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
|
||||
}
|
||||
"""
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
25
bofa/src/main/scala/bot/cortex/BugAI.scala
Normal file
25
bofa/src/main/scala/bot/cortex/BugAI.scala
Normal 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
|
||||
}
|
||||
}
|
49
bofa/src/main/scala/bot/cortex/Cortex.scala
Normal file
49
bofa/src/main/scala/bot/cortex/Cortex.scala
Normal 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
|
||||
}
|
||||
}
|
77
bofa/src/main/scala/bot/cortex/SnippetAI.scala
Normal file
77
bofa/src/main/scala/bot/cortex/SnippetAI.scala
Normal 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 }
|
||||
}
|
||||
}
|
15
bofa/src/main/scala/bot/cortex/Tactical.scala
Normal file
15
bofa/src/main/scala/bot/cortex/Tactical.scala
Normal 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]
|
||||
}
|
98
bofa/src/main/scala/bot/environment/Board.scala
Normal file
98
bofa/src/main/scala/bot/environment/Board.scala
Normal 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]
|
||||
}
|
||||
}
|
17
bofa/src/main/scala/bot/environment/Token.scala
Normal file
17
bofa/src/main/scala/bot/environment/Token.scala
Normal 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
|
15
bofa/src/main/scala/bot/environment/topology/Direction.scala
Normal file
15
bofa/src/main/scala/bot/environment/topology/Direction.scala
Normal 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
|
14
bofa/src/main/scala/bot/environment/topology/Field.scala
Normal file
14
bofa/src/main/scala/bot/environment/topology/Field.scala
Normal 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)
|
||||
}
|
21
bofa/src/main/scala/bot/environment/topology/Graph.scala
Normal file
21
bofa/src/main/scala/bot/environment/topology/Graph.scala
Normal 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])
|
41
bofa/src/main/scala/bot/environment/topology/Grid.scala
Normal file
41
bofa/src/main/scala/bot/environment/topology/Grid.scala
Normal 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
|
||||
}
|
||||
}
|
14
bofa/src/main/scala/bot/environment/topology/Point.scala
Normal file
14
bofa/src/main/scala/bot/environment/topology/Point.scala
Normal 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)
|
||||
}
|
10
bofa/src/main/scala/bot/package.scala
Normal file
10
bofa/src/main/scala/bot/package.scala
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
27
bofa/src/main/scala/bot/util/Log.scala
Normal file
27
bofa/src/main/scala/bot/util/Log.scala
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
53
bofa/src/main/scala/bot/util/Settings.scala
Normal file
53
bofa/src/main/scala/bot/util/Settings.scala
Normal 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
|
||||
}
|
||||
"""
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue