commit a429ffa00049cac43429fff14a61a15b7cf2e66e Author: Armin Friedl Date: Mon Oct 30 00:55:33 2017 +0100 MsHackman parser in Scala diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5aff4f0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,96 @@ + +# Created by https://www.gitignore.io/api/sbt,linux,scala,eclipse + +### Eclipse ### + +.metadata +bin/ +tmp/ +*.tmp +*.bak +*.swp +*~.nib +local.properties +.settings/ +.loadpath +.recommenders + +# External tool builders +.externalToolBuilders/ + +# Locally stored "Eclipse launch configurations" +*.launch + +# PyDev specific (Python IDE for Eclipse) +*.pydevproject + +# CDT-specific (C/C++ Development Tooling) +.cproject + +# Java annotation processor (APT) +.factorypath + +# PDT-specific (PHP Development Tools) +.buildpath + +# sbteclipse plugin +.target + +# Tern plugin +.tern-project + +# TeXlipse plugin +.texlipse + +# STS (Spring Tool Suite) +.springBeans + +# Code Recommenders +.recommenders/ + +# Scala IDE specific (Scala & Java development for Eclipse) +.cache-main +.scala_dependencies +.worksheet + +### Eclipse Patch ### +# Eclipse Core +.project + +# JDT-specific (Eclipse Java Development Tools) +.classpath + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### SBT ### +# Simple Build Tool +# http://www.scala-sbt.org/release/docs/Getting-Started/Directories.html#configuring-version-control + +dist/* +target/ +lib_managed/ +src_managed/ +project/boot/ +project/plugins/project/ +.history +.cache +.lib/ + +### Scala ### +*.class +*.log + +# End of https://www.gitignore.io/api/sbt,linux,scala,eclipse diff --git a/bofa/.cache-tests b/bofa/.cache-tests new file mode 100644 index 0000000..2c341b2 Binary files /dev/null and b/bofa/.cache-tests differ diff --git a/bofa/build.sbt b/bofa/build.sbt new file mode 100644 index 0000000..85baa1c --- /dev/null +++ b/bofa/build.sbt @@ -0,0 +1,12 @@ +import Dependencies._ + +lazy val root = (project in file(".")). + settings( + inThisBuild(List( + organization := "com.example", + scalaVersion := "2.12.3", + version := "0.1.0-SNAPSHOT" + )), + name := "Bofa", + libraryDependencies += scalaTest % Test + ) diff --git a/bofa/project/Dependencies.scala b/bofa/project/Dependencies.scala new file mode 100644 index 0000000..69e5af0 --- /dev/null +++ b/bofa/project/Dependencies.scala @@ -0,0 +1,5 @@ +import sbt._ + +object Dependencies { + lazy val scalaTest = "org.scalatest" %% "scalatest" % "3.0.3" +} diff --git a/bofa/project/build.properties b/bofa/project/build.properties new file mode 100644 index 0000000..b7dd3cb --- /dev/null +++ b/bofa/project/build.properties @@ -0,0 +1 @@ +sbt.version=1.0.2 diff --git a/bofa/project/plugins.sbt b/bofa/project/plugins.sbt new file mode 100644 index 0000000..ff404fd --- /dev/null +++ b/bofa/project/plugins.sbt @@ -0,0 +1,2 @@ +addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "5.2.3") +addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.5") diff --git a/bofa/src/main/scala/bot/Bofa.scala b/bofa/src/main/scala/bot/Bofa.scala new file mode 100644 index 0000000..97979a8 --- /dev/null +++ b/bofa/src/main/scala/bot/Bofa.scala @@ -0,0 +1,22 @@ +package bot + +import scala.io.StdIn + +package object bot { + val Dbg: Boolean = true + val Log: Boolean = true + + def dbg(msg: String) = if(Dbg) Console.err.println(msg) + def log(msg: String) = if(Log) Console.err.println(msg) +} + +object Main { + def main(args: Array[String]) = { + var input = StdIn.readLine() + + while(input != null){ + Parser.dispatch(input) + input = StdIn.readLine() + } + } +} \ No newline at end of file diff --git a/bofa/src/main/scala/bot/Brain.scala b/bofa/src/main/scala/bot/Brain.scala new file mode 100644 index 0000000..ce57721 --- /dev/null +++ b/bofa/src/main/scala/bot/Brain.scala @@ -0,0 +1,18 @@ +package bot + +import scala.util.Random +import bot.dbg + +object Brain { + def move(in: Int): String = { + val dirs = Array("left", "right", "up", "down") + val dir = Random.nextInt(4) + + dbg(Settings.toString) + dbg(CurrentState.board.toString()) + + dbg(dirs(dir)) + + dirs(dir) + } +} \ No newline at end of file diff --git a/bofa/src/main/scala/bot/Parser.scala b/bofa/src/main/scala/bot/Parser.scala new file mode 100644 index 0000000..99d55ba --- /dev/null +++ b/bofa/src/main/scala/bot/Parser.scala @@ -0,0 +1,76 @@ +package bot + +import bot.{dbg,log} + +object Parser { + + def dispatch(input: String){ + val parts: Seq[String] = input.split(" ") + + parts(0) match { + case "settings" => settings(parts.tail) + case "update" => update(parts.tail) + case "action" => { + if(parts(1) equals "character") println("bixiette") + else println(Brain.move(parts(2).toInt)) + } + } + } + + def settings(setting: Seq[String]): Unit = { + val splitNames = setting(1).split(",") + + setting(0) match { + case "timebank" => Settings.timebank = setting(1).toInt + case "time_per_move" => Settings.timePerMove = setting(1).toInt + case "your_botid" => Settings.bofaId = setting(1).toInt + case "field_width" => Settings.width = setting(1).toInt + case "field_height" => Settings.height = setting(1).toInt + case "max_rounds" => Settings.maxRounds = setting(1).toInt + + case "your_bot" => Settings.bofaName = setting(1) + case "player_names" => Settings.player1Name = splitNames(0) + Settings.player2Name = splitNames(1) + // case _ => let MatchError through, don't try recovering + } + } + + def update(update: Seq[String]): Unit = { + val cs = CurrentState + + update match { + case Seq("game", "round", newround) => cs.round = newround.toInt + case Seq("game", "field", cellstring) => cells(cellstring) + case Seq(playerName, "snippets", snippets) => cs.playerBy(playerName).snippets = snippets.toInt + case Seq(playerName, "bombs", bombs) => cs.playerBy(playerName).bombs = bombs.toInt + } + } + + def cells(cellstring: String): Unit = { + val cellParts = cellstring.split(",") + val board = CurrentState.board + + for(cellId <- 0 until cellParts.length) { + val (x: Int, y: Int) = (cellId % Settings.width, cellId / Settings.width) + val cellCmd = cellParts(cellId).toCharArray() + + cellCmd(0) match { + case '.' => board.put(EmptyCell(x,y)) + case 'x' => board.put(Wall(x,y)) + case 'P' => board.put(PlayerPos(x,y, cellCmd(1).toInt)) + case 'G' => board.put(Gate(x,y, cellCmd(1).toString())) + case 'E' => board.put(BugPos(x,y, cellCmd(1).toInt)) + case 'C' => board.put(CodePos(x,y)) + case 'S' => { + if(cellCmd.length == 2) board.put(BugSpawner(x,y, Some(cellCmd(1).toInt))) + else board.put(BugSpawner(x,y, None)) + } + case 'B' => { + if(cellCmd.length == 2) board.put(BombPos(x,y, Some(cellCmd(1).toInt))) + else board.put(BombPos(x,y, None)) + } + } + } + } + +} \ No newline at end of file diff --git a/bofa/src/main/scala/bot/State.scala b/bofa/src/main/scala/bot/State.scala new file mode 100644 index 0000000..234915f --- /dev/null +++ b/bofa/src/main/scala/bot/State.scala @@ -0,0 +1,123 @@ +package bot + +import bot.dbg + +class Field(val x: Int, val y: Int){ + override def toString: String = s"Field { .x = $x, .y = $y }" +} + +// static fields +case class EmptyCell (x0: Int, y0: Int) extends Field(x0,y0) +case class Wall (x0: Int, y0: Int) extends Field(x0,y0) +case class Gate (x0: Int, y0: Int, direction: String) extends Field(x0,y0) +case class BugSpawner (x0: Int, y0: Int, rounds: Option[Int]) extends Field(x0,y0) +// dynamic fields +case class PlayerPos (x0: Int, y0: Int, id: Int) extends Field(x0,y0) +case class BugPos (x0: Int, y0: Int, ai: Int) extends Field(x0,y0) +case class BombPos (x0: Int, y0: Int, rounds: Option[Int]) extends Field(x0,y0) +case class CodePos (x0: Int, y0: Int) extends Field(x0,y0) + + +class Player(id: Int){ + val name: String = Settings.players(id) + var snippets: Int = 0 + var bombs: Int = 0 + + override def toString = s"Player { .name = $name, .snippets = $snippets, .bombs = $bombs }" +} + +class Board { + // index top-left (0,0) to bottom-right (Settings.width,Settings.height) + private val _board: Array[Array[Field]] = Array.ofDim[Field](Settings.width, Settings.height) + + def put(field: Field){ + _board(field.x)(field.y) = field + } + + override def toString = { + val btp = _board.transpose + val buf = new StringBuilder + buf append "+" + "-" * (Settings.width*10+Settings.width*3-1) + "+" + "\n" + for(j <- 0 until Settings.height){ + buf append s"""| ${btp(j) map (_.getClass.getSimpleName().padTo(10, ' ')) mkString (" | ")} |\n""" + } + buf append "+" + "-" * (Settings.width*10+Settings.width*3-1) + "+" + "\n" + buf toString + } +} + +class State { + val bofa: Player = new Player(Settings.bofaId) + val oppo: Player = new Player(2) + val board: Board = new Board() + + def playerBy(name: String) = if (name equals bofa.name) bofa else oppo +} + +object CurrentState extends State { + var round: Int = 1 +} + + +/** Settings holder containing global settings set only once per game. + * + * These settings should not change after initialization and + * stay constant for the whole game. + */ +object Settings { + private var _timebank: Option[Int] = None + private var _timePerMove: Option[Int] = None + private var _player1Name: Option[String] = None + private var _player2Name: Option[String] = None + private var _bofaName: Option[String] = None + private var _bofaId: Option[Int] = None + private var _width: Option[Int] = None + private var _height: Option[Int] = None + private var _maxRounds: Option[Int] = None + + + /* Get Option or fail with exception + * Don't try to be smart but just fail if settings not initialized properly + */ + def timebank = _timebank.get + def timePerMove = _timePerMove.get + def player1Name = _player1Name.get + def player2Name = _player2Name.get + def bofaName = _bofaName.get + def bofaId = _bofaId.get + def width = _width.get + def height = _height.get + def maxRounds = _maxRounds.get + + def timebank_= (value: Int) = _timebank = Some(value) + def timePerMove_= (value: Int) = _timePerMove = Some(value) + def player1Name_= (value: String) = _player1Name = Some(value) + def player2Name_= (value: String) = _player2Name = Some(value) + def bofaName_= (value: String) = _bofaName = Some(value) + def bofaId_= (value: Int) = _bofaId = Some(value) + def width_= (value: Int) = _width = Some(value) + def height_= (value: Int) = _height = Some(value) + def maxRounds_= (value: Int) = _maxRounds = Some(value) + + def oppoName = if(bofaId == 1) player2Name else player1Name + def oppoId = if(bofaId == 1) 2 else 1 + def players(id: Int) = if(id == 1) player1Name else player2Name + + override def toString: String = { + s""" + Settings { + .timebank = ${_timebank.getOrElse("Not Set")} + .timePerMove = ${_timePerMove.getOrElse("Not Set")} + .player1Name = ${_player1Name.getOrElse("Not Set")} + .player2Name = ${_player2Name.getOrElse("Not Set")} + .bofaName = ${_bofaName.getOrElse("Not Set")} + .bofaId = ${_bofaId.getOrElse("Not Set")} + .width = ${_width.getOrElse("Not Set")} + .height = ${_height.getOrElse("Not Set")} + .maxRounds = ${_maxRounds.getOrElse("Not Set")} + } + """ + } +} + +