Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- // IFF 7/3
- // Julius Siaulys
- //Master bot is using gatherers to collect energy,
- //when it has a specific amount of energy he starts using LandMines which do not move, and explode if enemy slave or master approaches it
- import scala.collection.mutable.Queue
- import scala.collection.mutable.Stack
- import scala.util.control._
- import util.Random
- object ControlFunction
- {
- val random = new Random()
- def forMaster(bot: Bot) {
- val (directionValue, nearestEnemyMaster, nearestEnemySlave, _) = analyzeView(bot.view)
- val dontPlantLandMineUntil = bot.inputAsIntOrElse("dontPlantLandMineUntil", -1)
- val dontSpawnGathererUntil = bot.inputAsIntOrElse("dontSpawnGathererUntil", -1)
- // Master Movement
- var direction = MovementAlgo(bot.view)
- // If search failed and return 0,0 we will check for bad positions and go to the random one.
- var Zero = XY(0,0)
- if(direction == Zero)
- {
- direction = XY(random.nextInt(3)-1,random.nextInt(3)-1) // random dir
- var Color = bot.view(direction)
- while(Color == 'W' || Color == 'b' || Color == 'p') // Wall, bad enemy, bad plant
- {
- direction = XY(random.nextInt(3)-1,random.nextInt(3)-1) // random dir
- Color = bot.view(direction)
- }
- }
- bot.move(direction)
- //Game Logic
- if(dontPlantLandMineUntil< bot.time && bot.energy > 5000){
- bot.spawn(bot.view.center, "mood" -> "LandMine", "target" -> "")
- bot.set("dontPlantLandMineUntil" -> (bot.time + 40))
- }
- if(dontSpawnGathererUntil < bot.time && bot.energy > 100){
- if(bot.energy < 500){
- //bot.set("dontSpawnGathererUntil" -> (bot.time))
- bot.spawn(bot.view.center, "mood" -> "Gathering", "target" -> "", "gather" -> 1000)
- }
- else if(bot.energy > 2000 && bot.energy < 10000){
- // bot.set("dontSpawnGathererUntil" -> (bot.time ))
- bot.spawn(bot.view.center, "mood" -> "Gathering", "target" -> "", "gather" -> 2000)
- }
- else if(bot.energy > 20000){
- bot.set("dontSpawnGathererUntil" -> (bot.time+50))
- bot.spawn(bot.view.center, "mood" -> "Gathering", "target" -> "", "gather" -> 4000)
- }
- else{
- bot.set("dontSpawnGathererUntil" -> (bot.time))
- bot.spawn(bot.view.center, "mood" -> "Gathering", "target" -> "", "gather" -> 1000)
- }
- }
- }
- def forSlave(bot: MiniBot) {
- bot.inputOrElse("mood","Gathering") match {
- case "LandMine" => reactAsLandMine(bot)
- case "Gathering" => reactAsGatherer(bot)
- }
- }
- // Breadth First Search
- def MovementAlgo(view: View) : XY = {
- var queue = Queue[XY]()
- var VisitedLocations = Set[XY]()
- var path = Map[XY,XY]()
- //create new with starting coordinates of 0;0
- queue.enqueue(XY(0, 0))
- while (!queue.isEmpty) { // if its not empty
- val next = queue.dequeue() // take next value from queue
- if (next.length > 10) { // If it is too far, do not move
- return XY(0, 0)
- }
- for (i <- -1 to 1; j <- -1 to 1) {
- val coords = XY(i, j) + next
- val Color = view(coords)
- if (Color == 'B' || Color == 'P') { // If food is found
- var Now = next
- if (coords.length < 1.5) {
- return coords
- }
- while (Now.length > 1.5) {
- val temp = view(Now)
- if(temp != 'W' && temp != 'b' && temp != 'p') // Checks for enemies
- {
- Now = path(Now)
- }
- }
- return Now
- }
- if (Color == '_' && !VisitedLocations.contains(coords)) {
- queue.enqueue(coords)
- VisitedLocations += coords
- path += (coords -> next)
- }
- }
- }
- // default return - not move
- XY(0, 0)
- }
- def reactAsLandMine(bot: MiniBot){
- bot.view.offsetToNearest('m') match {
- case Some(delta: XY) =>
- if(delta.length <= 2) {
- bot.explode(4)
- }
- }
- bot.view.offsetToNearest('s') match { // enemy slave
- case Some(delta: XY) =>
- if(delta.length <= 2) {
- bot.explode(4)
- }
- }
- }
- def GetEnemy(view: View) = {
- val directionValue = Array.ofDim[Double](8)
- var nearestEnemyMaster: Option[XY] = None
- var nearestEnemySlave: Option[XY] = None
- var master: Option[XY] = None
- view.cells.zipWithIndex foreach {case (c, i) =>
- val cellRelPos = view.relPosFromIndex(i)
- if (cellRelPos.isNonZero){
- val stepDistance = cellRelPos.stepCount
- val value: Double = c match{
- case 'm' => // another master: not dangerous, but an obstacle
- nearestEnemyMaster = Some(cellRelPos)
- 4000 / stepDistance
- case 's' => // another slave: potentially dangerous?
- nearestEnemySlave = Some(cellRelPos)
- 1000 / stepDistance
- case _ =>
- 0.0
- }
- val direction45 = cellRelPos.toDirection45
- directionValue(direction45) += value
- }
- }
- (directionValue, nearestEnemyMaster, nearestEnemySlave, master)
- }
- def reactAsGatherer(bot: MiniBot){
- val (directionValue, nearestEnemyMaster, _, master) = analyzeView(bot.view)
- val gather = bot.inputAsIntOrElse("gather", 0)
- if (bot.energy >= bot.inputAsIntOrElse("gather", 0))
- {
- val (directionValue, nearestEnemyMaster, _, master) = ReturnToMaster(bot.view)
- //bot.say("HOME")
- val masterDirectionXY = bot.inputAsXYOrElse("master",XY.Zero)
- val masterDirectionReal = masterDirectionXY.toDirection45
- val masterDirectionFinal = XY.fromDirection45(masterDirectionReal)
- directionValue(masterDirectionReal) += 20
- if(bot.view(masterDirectionFinal) == 'p' || bot.view(masterDirectionFinal) == 'W' || bot.view(masterDirectionFinal) == 'b'
- || bot.view(masterDirectionFinal) == 's')
- {
- directionValue(masterDirectionReal) -= 60
- }
- val bestDirection = directionValue.zipWithIndex.maxBy(_._1)._2
- val dir = XY.fromDirection45(bestDirection)
- bot.move(dir)
- }
- else{
- val lastDirection = bot.inputAsIntOrElse("lastDirection", 0)
- directionValue(lastDirection) += 2
- val bestDirection45 = directionValue.zipWithIndex.maxBy(_._1)._2
- val direction = XY.fromDirection45(bestDirection45)
- bot.move(direction)
- bot.set("lastDirection" -> bestDirection45)
- }
- }
- def ReturnToMaster(view: View) = {
- val directionValue = Array.ofDim[Double](8)
- var nearestEnemyMaster: Option[XY] = None
- var nearestEnemySlave: Option[XY] = None
- var master: Option[XY] = None
- view.cells.zipWithIndex foreach {case (c, i) =>
- val cellRelPos = view.relPosFromIndex(i)
- if (cellRelPos.isNonZero){
- val stepDistance = cellRelPos.stepCount
- val value: Double = c match{
- case 'm' => // another master: not dangerous, but an obstacle
- nearestEnemyMaster = Some(cellRelPos)
- -100 / stepDistance
- case 's' => // another slave: potentially dangerous?
- nearestEnemySlave = Some(cellRelPos)
- -100 / stepDistance
- case 'S' => // our own slave
- -50 / stepDistance
- case 'M' => // our own master
- master = Some(cellRelPos)
- 1000
- case 'B' => // good beast: valuable, but runs away
- if (stepDistance == 1) 600
- else if (stepDistance == 2) 300
- else (150 - stepDistance * 15).max(10)
- case 'P' => // good plant: less valuable, but does not run
- if (stepDistance == 1) 500
- else if (stepDistance == 2) 300
- else (150 - stepDistance * 10).max(10)
- case 'b' => // bad beast: dangerous, but only if very close
- if (stepDistance < 4) -400 / stepDistance else -50 / stepDistance
- case 'p' => // bad plant: bad, but only if I step on it
- if (stepDistance < 2) -1000 else 0
- case 'W' => // wall: harmless, just don't walk into it
- if (stepDistance < 2) -1000 else 0
- case _ =>
- 0.0
- }
- val direction45 = cellRelPos.toDirection45
- directionValue(direction45) += value
- }
- }
- (directionValue, nearestEnemyMaster, nearestEnemySlave, master)
- }
- def analyzeView(view: View) = {
- val directionValue = Array.ofDim[Double](8)
- var nearestEnemyMaster: Option[XY] = None
- var nearestEnemySlave: Option[XY] = None
- var master: Option[XY] = None
- view.cells.zipWithIndex foreach {case (c, i) =>
- val cellRelPos = view.relPosFromIndex(i)
- if (cellRelPos.isNonZero){
- val stepDistance = cellRelPos.stepCount
- val value: Double = c match{
- case 'm' => // another master: not dangerous, but an obstacle
- nearestEnemyMaster = Some(cellRelPos)
- -100 / stepDistance
- case 's' => // another slave: potentially dangerous?
- nearestEnemySlave = Some(cellRelPos)
- -100 / stepDistance
- case 'S' => // our own slave
- -50 / stepDistance
- case 'M' => // our own master
- master = Some(cellRelPos)
- 0.0
- case 'B' => // good beast: valuable, but runs away
- if (stepDistance == 1) 600
- else if (stepDistance == 2) 300
- else (150 - stepDistance * 15).max(10)
- case 'P' => // good plant: less valuable, but does not run
- if (stepDistance == 1) 500
- else if (stepDistance == 2) 300
- else (150 - stepDistance * 10).max(10)
- case 'b' => // bad beast: dangerous, but only if very close
- if (stepDistance < 4) -400 / stepDistance else -50 / stepDistance
- case 'p' => // bad plant: bad, but only if I step on it
- if (stepDistance < 2) -1000 else 0
- case 'W' => // wall: harmless, just don't walk into it
- if (stepDistance < 2) -1000 else 0
- case _ =>
- 0.0
- }
- val direction45 = cellRelPos.toDirection45
- directionValue(direction45) += value
- }
- }
- (directionValue, nearestEnemyMaster, nearestEnemySlave, master)
- }
- }
- // -------------------------------------------------------------------------------------------------
- // Framework
- // -------------------------------------------------------------------------------------------------
- class ControlFunctionFactory {
- def create = (input: String) => {
- val (opcode, params) = CommandParser(input)
- opcode match {
- case "React" =>
- val bot = new BotImpl(params)
- if( bot.generation == 0 ) {
- ControlFunction.forMaster(bot)
- } else {
- ControlFunction.forSlave(bot)
- }
- bot.toString
- case _ => "" // OK
- }
- }
- }
- // -------------------------------------------------------------------------------------------------
- trait Bot {
- // inputs
- def inputOrElse(key: String, fallback: String): String
- def inputAsIntOrElse(key: String, fallback: Int): Int
- def inputAsXYOrElse(keyPrefix: String, fallback: XY): XY
- def view: View
- def energy: Int
- def time: Int
- def generation: Int
- // outputs
- def move(delta: XY) : Bot
- def say(text: String) : Bot
- def status(text: String) : Bot
- def spawn(offset: XY, params: (String,Any)*) : Bot
- def set(params: (String,Any)*) : Bot
- def log(text: String) : Bot
- }
- trait MiniBot extends Bot {
- // inputs
- def offsetToMaster: XY
- // outputs
- def explode(blastRadius: Int) : Bot
- }
- case class BotImpl(inputParams: Map[String, String]) 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: XY) = inputParams.get(key).map(s => XY(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", XY.Zero)
- // output
- private var stateParams = Map.empty[String,Any] // holds "Set()" commands
- private var commands = "" // holds all other commands
- private var debugOutput = "" // holds all "Log()" output
- /** Appends a new command to the command string; returns 'this' for fluent API. */
- private def append(s: String) : Bot = { commands += (if(commands.isEmpty) s else "|" + s); this }
- /** 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) = { debugOutput += text + "\n"; this }
- def move(direction: XY) = 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: XY, params: (String,Any)*) =
- append("Spawn(direction=" + offset +
- (if(params.isEmpty) "" else "," + params.map(e => e._1 + "=" + e._2).mkString(",")) +
- ")")
- def set(params: (String,Any)*) = { stateParams ++= params; this }
- def set(keyPrefix: String, xy: XY) = { stateParams ++= List(keyPrefix+"x" -> xy.x, keyPrefix+"y" -> xy.y); this }
- }
- // -------------------------------------------------------------------------------------------------
- /** 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): (String, Map[String, String]) = {
- /** "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 opcode = segments(0)
- val params = segments(1).dropRight(1).split(',')
- val keyValuePairs = params.map(splitParameterIntoKeyValue).toMap
- (opcode, keyValuePairs)
- }
- }
- // -------------------------------------------------------------------------------------------------
- /** Utility class for managing 2D cell coordinates.
- * The coordinate (0,0) corresponds to the top-left corner of the arena on screen.
- * The direction (1,-1) points right and up.
- */
- case class XY(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) = XY(newX, y)
- def updateY(newY: Int) = XY(x, newY)
- def addToX(dx: Int) = XY(x + dx, y)
- def addToY(dy: Int) = XY(x, y + dy)
- def +(pos: XY) = XY(x + pos.x, y + pos.y)
- def -(pos: XY) = XY(x - pos.x, y - pos.y)
- def *(factor: Double) = XY((x * factor).intValue, (y * factor).intValue)
- def distanceTo(pos: XY): Double = (this - pos).length // Phythagorean
- def length: Double = math.sqrt(x * x + y * y) // Phythagorean
- def stepsTo(pos: XY): 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 = XY(x.signum, y.signum)
- def negate = XY(-x, -y)
- def negateX = XY(-x, y)
- def negateY = XY(x, -y)
- /** Returns the direction index with 'Right' being index 0, then clockwise in 45 degree steps. */
- def toDirection45: Int = {
- val unit = signum
- unit.x match {
- case -1 =>
- unit.y match {
- case -1 =>
- if(x < y * 3) Direction45.Left
- else if(y < x * 3) Direction45.Up
- else Direction45.UpLeft
- case 0 =>
- Direction45.Left
- case 1 =>
- if(-x > y * 3) Direction45.Left
- else if(y > -x * 3) Direction45.Down
- else Direction45.LeftDown
- }
- case 0 =>
- unit.y match {
- case 1 => Direction45.Down
- case 0 => throw new IllegalArgumentException("cannot compute direction index for (0,0)")
- case -1 => Direction45.Up
- }
- case 1 =>
- unit.y match {
- case -1 =>
- if(x > -y * 3) Direction45.Right
- else if(-y > x * 3) Direction45.Up
- else Direction45.RightUp
- case 0 =>
- Direction45.Right
- case 1 =>
- if(x > y * 3) Direction45.Right
- else if(y > x * 3) Direction45.Down
- else Direction45.DownRight
- }
- }
- }
- def rotateCounterClockwise45 = XY.fromDirection45((signum.toDirection45 + 1) % 8)
- def rotateCounterClockwise90 = XY.fromDirection45((signum.toDirection45 + 2) % 8)
- def rotateClockwise45 = XY.fromDirection45((signum.toDirection45 + 7) % 8)
- def rotateClockwise90 = XY.fromDirection45((signum.toDirection45 + 6) % 8)
- def wrap(boardSize: XY) = {
- 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) XY(fixedX, fixedY) else this
- }
- }
- object XY {
- /** Parse an XY value from XY.toString format, e.g. "2:3". */
- def apply(s: String) : XY = { val a = s.split(':'); XY(a(0).toInt,a(1).toInt) }
- val Zero = XY(0, 0)
- val One = XY(1, 1)
- val Right = XY( 1, 0)
- val RightUp = XY( 1, -1)
- val Up = XY( 0, -1)
- val UpLeft = XY(-1, -1)
- val Left = XY(-1, 0)
- val LeftDown = XY(-1, 1)
- val Down = XY( 0, 1)
- val DownRight = XY( 1, 1)
- def fromDirection45(index: Int): XY = index match {
- case Direction45.Right => Right
- case Direction45.RightUp => RightUp
- case Direction45.Up => Up
- case Direction45.UpLeft => UpLeft
- case Direction45.Left => Left
- case Direction45.LeftDown => LeftDown
- case Direction45.Down => Down
- case Direction45.DownRight => DownRight
- }
- def fromDirection90(index: Int): XY = index match {
- case Direction90.Right => Right
- case Direction90.Up => Up
- case Direction90.Left => Left
- case Direction90.Down => Down
- }
- def apply(array: Array[Int]): XY = XY(array(0), array(1))
- }
- object Direction45 {
- val Right = 0
- val RightUp = 1
- val Up = 2
- val UpLeft = 3
- val Left = 4
- val LeftDown = 5
- val Down = 6
- val DownRight = 7
- }
- object Direction90 {
- val Right = 0
- val Up = 1
- val Left = 2
- val Down = 3
- }
- // -------------------------------------------------------------------------------------------------
- case class View(cells: String) {
- val size = math.sqrt(cells.length).toInt
- val center = XY(size / 2, size / 2)
- def apply(relPos: XY) = cellAtRelPos(relPos)
- def indexFromAbsPos(absPos: XY) = absPos.x + absPos.y * size
- def absPosFromIndex(index: Int) = XY(index % size, index / size)
- def absPosFromRelPos(relPos: XY) = relPos + center
- def cellAtAbsPos(absPos: XY) = cells.charAt(indexFromAbsPos(absPos))
- def indexFromRelPos(relPos: XY) = indexFromAbsPos(absPosFromRelPos(relPos))
- def relPosFromAbsPos(absPos: XY) = absPos - center
- def relPosFromIndex(index: Int) = relPosFromAbsPos(absPosFromIndex(index))
- def cellAtRelPos(relPos: XY) = cells.charAt(indexFromRelPos(relPos))
- def offsetToNearest(c: Char) = {
- val matchingXY = cells.view.zipWithIndex.filter(_._1 == c)
- if( matchingXY.isEmpty )
- None
- else {
- val nearest = matchingXY.map(p => relPosFromIndex(p._2)).minBy(_.length)
- Some(nearest)
- }
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement