From a429ffa00049cac43429fff14a61a15b7cf2e66e Mon Sep 17 00:00:00 2001 From: Armin Friedl Date: Mon, 30 Oct 2017 00:55:33 +0100 Subject: [PATCH] MsHackman parser in Scala --- .gitignore | 96 +++++++++++++++++++++ bofa/.cache-tests | Bin 0 -> 2343 bytes bofa/build.sbt | 12 +++ bofa/project/Dependencies.scala | 5 ++ bofa/project/build.properties | 1 + bofa/project/plugins.sbt | 2 + bofa/src/main/scala/bot/Bofa.scala | 22 +++++ bofa/src/main/scala/bot/Brain.scala | 18 ++++ bofa/src/main/scala/bot/Parser.scala | 76 +++++++++++++++++ bofa/src/main/scala/bot/State.scala | 123 +++++++++++++++++++++++++++ 10 files changed, 355 insertions(+) create mode 100644 .gitignore create mode 100644 bofa/.cache-tests create mode 100644 bofa/build.sbt create mode 100644 bofa/project/Dependencies.scala create mode 100644 bofa/project/build.properties create mode 100644 bofa/project/plugins.sbt create mode 100644 bofa/src/main/scala/bot/Bofa.scala create mode 100644 bofa/src/main/scala/bot/Brain.scala create mode 100644 bofa/src/main/scala/bot/Parser.scala create mode 100644 bofa/src/main/scala/bot/State.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 0000000000000000000000000000000000000000..2c341b2988c90fc15e533f58e0d06794db75955d GIT binary patch literal 2343 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 sKlk}fCI78|Fv>qa|JnHW%704?^uG=;L$I>_u3 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")} + } + """ + } +} + +