Advertisement
Guest User

Untitled

a guest
Apr 1st, 2020
162
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 22.97 KB | None | 0 0
  1. // IFF 7/3
  2. // Julius Siaulys
  3.  
  4. //Master bot is using gatherers to collect energy,
  5. //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
  6.  
  7. import scala.collection.mutable.Queue
  8. import scala.collection.mutable.Stack
  9. import scala.util.control._
  10. import util.Random
  11.  
  12. object ControlFunction
  13. {
  14. val random = new Random()
  15.  
  16. def forMaster(bot: Bot) {
  17. val (directionValue, nearestEnemyMaster, nearestEnemySlave, _) = analyzeView(bot.view)
  18. val dontPlantLandMineUntil = bot.inputAsIntOrElse("dontPlantLandMineUntil", -1)
  19. val dontSpawnGathererUntil = bot.inputAsIntOrElse("dontSpawnGathererUntil", -1)
  20.  
  21. // Master Movement
  22. var direction = MovementAlgo(bot.view)
  23. // If search failed and return 0,0 we will check for bad positions and go to the random one.
  24. var Zero = XY(0,0)
  25. if(direction == Zero)
  26. {
  27. direction = XY(random.nextInt(3)-1,random.nextInt(3)-1) // random dir
  28. var Color = bot.view(direction)
  29. while(Color == 'W' || Color == 'b' || Color == 'p') // Wall, bad enemy, bad plant
  30. {
  31. direction = XY(random.nextInt(3)-1,random.nextInt(3)-1) // random dir
  32. Color = bot.view(direction)
  33. }
  34. }
  35. bot.move(direction)
  36.  
  37. //Game Logic
  38. if(dontPlantLandMineUntil< bot.time && bot.energy > 5000){
  39. bot.spawn(bot.view.center, "mood" -> "LandMine", "target" -> "")
  40. bot.set("dontPlantLandMineUntil" -> (bot.time + 40))
  41. }
  42. if(dontSpawnGathererUntil < bot.time && bot.energy > 100){
  43. if(bot.energy < 500){
  44. //bot.set("dontSpawnGathererUntil" -> (bot.time))
  45. bot.spawn(bot.view.center, "mood" -> "Gathering", "target" -> "", "gather" -> 1000)
  46. }
  47. else if(bot.energy > 2000 && bot.energy < 10000){
  48. // bot.set("dontSpawnGathererUntil" -> (bot.time ))
  49. bot.spawn(bot.view.center, "mood" -> "Gathering", "target" -> "", "gather" -> 2000)
  50. }
  51. else if(bot.energy > 20000){
  52. bot.set("dontSpawnGathererUntil" -> (bot.time+50))
  53. bot.spawn(bot.view.center, "mood" -> "Gathering", "target" -> "", "gather" -> 4000)
  54. }
  55. else{
  56. bot.set("dontSpawnGathererUntil" -> (bot.time))
  57. bot.spawn(bot.view.center, "mood" -> "Gathering", "target" -> "", "gather" -> 1000)
  58. }
  59. }
  60.  
  61. }
  62.  
  63. def forSlave(bot: MiniBot) {
  64. bot.inputOrElse("mood","Gathering") match {
  65. case "LandMine" => reactAsLandMine(bot)
  66. case "Gathering" => reactAsGatherer(bot)
  67. }
  68. }
  69. // Breadth First Search
  70. def MovementAlgo(view: View) : XY = {
  71.  
  72. var queue = Queue[XY]()
  73. var VisitedLocations = Set[XY]()
  74. var path = Map[XY,XY]()
  75.  
  76. //create new with starting coordinates of 0;0
  77. queue.enqueue(XY(0, 0))
  78.  
  79. while (!queue.isEmpty) { // if its not empty
  80. val next = queue.dequeue() // take next value from queue
  81. if (next.length > 10) { // If it is too far, do not move
  82. return XY(0, 0)
  83. }
  84. for (i <- -1 to 1; j <- -1 to 1) {
  85. val coords = XY(i, j) + next
  86. val Color = view(coords)
  87. if (Color == 'B' || Color == 'P') { // If food is found
  88. var Now = next
  89. if (coords.length < 1.5) {
  90. return coords
  91. }
  92. while (Now.length > 1.5) {
  93. val temp = view(Now)
  94. if(temp != 'W' && temp != 'b' && temp != 'p') // Checks for enemies
  95. {
  96. Now = path(Now)
  97. }
  98. }
  99. return Now
  100. }
  101. if (Color == '_' && !VisitedLocations.contains(coords)) {
  102. queue.enqueue(coords)
  103. VisitedLocations += coords
  104. path += (coords -> next)
  105. }
  106. }
  107. }
  108. // default return - not move
  109. XY(0, 0)
  110. }
  111.  
  112. def reactAsLandMine(bot: MiniBot){
  113. bot.view.offsetToNearest('m') match {
  114. case Some(delta: XY) =>
  115. if(delta.length <= 2) {
  116. bot.explode(4)
  117. }
  118. }
  119. bot.view.offsetToNearest('s') match { // enemy slave
  120. case Some(delta: XY) =>
  121. if(delta.length <= 2) {
  122. bot.explode(4)
  123. }
  124. }
  125. }
  126. def GetEnemy(view: View) = {
  127. val directionValue = Array.ofDim[Double](8)
  128. var nearestEnemyMaster: Option[XY] = None
  129. var nearestEnemySlave: Option[XY] = None
  130. var master: Option[XY] = None
  131.  
  132. view.cells.zipWithIndex foreach {case (c, i) =>
  133. val cellRelPos = view.relPosFromIndex(i)
  134. if (cellRelPos.isNonZero){
  135. val stepDistance = cellRelPos.stepCount
  136. val value: Double = c match{
  137. case 'm' => // another master: not dangerous, but an obstacle
  138. nearestEnemyMaster = Some(cellRelPos)
  139. 4000 / stepDistance
  140.  
  141. case 's' => // another slave: potentially dangerous?
  142. nearestEnemySlave = Some(cellRelPos)
  143. 1000 / stepDistance
  144. case _ =>
  145. 0.0
  146. }
  147. val direction45 = cellRelPos.toDirection45
  148. directionValue(direction45) += value
  149. }
  150. }
  151. (directionValue, nearestEnemyMaster, nearestEnemySlave, master)
  152. }
  153. def reactAsGatherer(bot: MiniBot){
  154. val (directionValue, nearestEnemyMaster, _, master) = analyzeView(bot.view)
  155.  
  156. val gather = bot.inputAsIntOrElse("gather", 0)
  157. if (bot.energy >= bot.inputAsIntOrElse("gather", 0))
  158. {
  159. val (directionValue, nearestEnemyMaster, _, master) = ReturnToMaster(bot.view)
  160. //bot.say("HOME")
  161. val masterDirectionXY = bot.inputAsXYOrElse("master",XY.Zero)
  162. val masterDirectionReal = masterDirectionXY.toDirection45
  163. val masterDirectionFinal = XY.fromDirection45(masterDirectionReal)
  164. directionValue(masterDirectionReal) += 20
  165. if(bot.view(masterDirectionFinal) == 'p' || bot.view(masterDirectionFinal) == 'W' || bot.view(masterDirectionFinal) == 'b'
  166. || bot.view(masterDirectionFinal) == 's')
  167. {
  168. directionValue(masterDirectionReal) -= 60
  169. }
  170. val bestDirection = directionValue.zipWithIndex.maxBy(_._1)._2
  171. val dir = XY.fromDirection45(bestDirection)
  172. bot.move(dir)
  173. }
  174. else{
  175. val lastDirection = bot.inputAsIntOrElse("lastDirection", 0)
  176. directionValue(lastDirection) += 2
  177. val bestDirection45 = directionValue.zipWithIndex.maxBy(_._1)._2
  178. val direction = XY.fromDirection45(bestDirection45)
  179. bot.move(direction)
  180. bot.set("lastDirection" -> bestDirection45)
  181. }
  182. }
  183. def ReturnToMaster(view: View) = {
  184. val directionValue = Array.ofDim[Double](8)
  185. var nearestEnemyMaster: Option[XY] = None
  186. var nearestEnemySlave: Option[XY] = None
  187. var master: Option[XY] = None
  188.  
  189. view.cells.zipWithIndex foreach {case (c, i) =>
  190. val cellRelPos = view.relPosFromIndex(i)
  191. if (cellRelPos.isNonZero){
  192. val stepDistance = cellRelPos.stepCount
  193. val value: Double = c match{
  194. case 'm' => // another master: not dangerous, but an obstacle
  195. nearestEnemyMaster = Some(cellRelPos)
  196. -100 / stepDistance
  197.  
  198. case 's' => // another slave: potentially dangerous?
  199. nearestEnemySlave = Some(cellRelPos)
  200. -100 / stepDistance
  201.  
  202. case 'S' => // our own slave
  203. -50 / stepDistance
  204.  
  205. case 'M' => // our own master
  206. master = Some(cellRelPos)
  207. 1000
  208.  
  209. case 'B' => // good beast: valuable, but runs away
  210. if (stepDistance == 1) 600
  211. else if (stepDistance == 2) 300
  212. else (150 - stepDistance * 15).max(10)
  213.  
  214. case 'P' => // good plant: less valuable, but does not run
  215. if (stepDistance == 1) 500
  216. else if (stepDistance == 2) 300
  217. else (150 - stepDistance * 10).max(10)
  218.  
  219. case 'b' => // bad beast: dangerous, but only if very close
  220. if (stepDistance < 4) -400 / stepDistance else -50 / stepDistance
  221.  
  222. case 'p' => // bad plant: bad, but only if I step on it
  223. if (stepDistance < 2) -1000 else 0
  224.  
  225. case 'W' => // wall: harmless, just don't walk into it
  226. if (stepDistance < 2) -1000 else 0
  227.  
  228. case _ =>
  229. 0.0
  230. }
  231. val direction45 = cellRelPos.toDirection45
  232. directionValue(direction45) += value
  233. }
  234. }
  235. (directionValue, nearestEnemyMaster, nearestEnemySlave, master)
  236. }
  237.  
  238. def analyzeView(view: View) = {
  239. val directionValue = Array.ofDim[Double](8)
  240. var nearestEnemyMaster: Option[XY] = None
  241. var nearestEnemySlave: Option[XY] = None
  242. var master: Option[XY] = None
  243.  
  244. view.cells.zipWithIndex foreach {case (c, i) =>
  245. val cellRelPos = view.relPosFromIndex(i)
  246. if (cellRelPos.isNonZero){
  247. val stepDistance = cellRelPos.stepCount
  248. val value: Double = c match{
  249. case 'm' => // another master: not dangerous, but an obstacle
  250. nearestEnemyMaster = Some(cellRelPos)
  251. -100 / stepDistance
  252.  
  253. case 's' => // another slave: potentially dangerous?
  254. nearestEnemySlave = Some(cellRelPos)
  255. -100 / stepDistance
  256.  
  257. case 'S' => // our own slave
  258. -50 / stepDistance
  259.  
  260. case 'M' => // our own master
  261. master = Some(cellRelPos)
  262. 0.0
  263.  
  264. case 'B' => // good beast: valuable, but runs away
  265. if (stepDistance == 1) 600
  266. else if (stepDistance == 2) 300
  267. else (150 - stepDistance * 15).max(10)
  268.  
  269. case 'P' => // good plant: less valuable, but does not run
  270. if (stepDistance == 1) 500
  271. else if (stepDistance == 2) 300
  272. else (150 - stepDistance * 10).max(10)
  273.  
  274. case 'b' => // bad beast: dangerous, but only if very close
  275. if (stepDistance < 4) -400 / stepDistance else -50 / stepDistance
  276.  
  277. case 'p' => // bad plant: bad, but only if I step on it
  278. if (stepDistance < 2) -1000 else 0
  279.  
  280. case 'W' => // wall: harmless, just don't walk into it
  281. if (stepDistance < 2) -1000 else 0
  282.  
  283. case _ =>
  284. 0.0
  285. }
  286. val direction45 = cellRelPos.toDirection45
  287. directionValue(direction45) += value
  288. }
  289. }
  290. (directionValue, nearestEnemyMaster, nearestEnemySlave, master)
  291. }
  292. }
  293.  
  294.  
  295.  
  296. // -------------------------------------------------------------------------------------------------
  297. // Framework
  298. // -------------------------------------------------------------------------------------------------
  299.  
  300. class ControlFunctionFactory {
  301. def create = (input: String) => {
  302. val (opcode, params) = CommandParser(input)
  303. opcode match {
  304. case "React" =>
  305. val bot = new BotImpl(params)
  306. if( bot.generation == 0 ) {
  307. ControlFunction.forMaster(bot)
  308. } else {
  309. ControlFunction.forSlave(bot)
  310. }
  311. bot.toString
  312. case _ => "" // OK
  313. }
  314. }
  315. }
  316.  
  317.  
  318. // -------------------------------------------------------------------------------------------------
  319.  
  320.  
  321. trait Bot {
  322. // inputs
  323. def inputOrElse(key: String, fallback: String): String
  324. def inputAsIntOrElse(key: String, fallback: Int): Int
  325. def inputAsXYOrElse(keyPrefix: String, fallback: XY): XY
  326. def view: View
  327. def energy: Int
  328. def time: Int
  329. def generation: Int
  330.  
  331. // outputs
  332. def move(delta: XY) : Bot
  333. def say(text: String) : Bot
  334. def status(text: String) : Bot
  335. def spawn(offset: XY, params: (String,Any)*) : Bot
  336. def set(params: (String,Any)*) : Bot
  337. def log(text: String) : Bot
  338. }
  339.  
  340. trait MiniBot extends Bot {
  341. // inputs
  342. def offsetToMaster: XY
  343.  
  344. // outputs
  345. def explode(blastRadius: Int) : Bot
  346. }
  347.  
  348.  
  349. case class BotImpl(inputParams: Map[String, String]) extends MiniBot {
  350. // input
  351. def inputOrElse(key: String, fallback: String) = inputParams.getOrElse(key, fallback)
  352. def inputAsIntOrElse(key: String, fallback: Int) = inputParams.get(key).map(_.toInt).getOrElse(fallback)
  353. def inputAsXYOrElse(key: String, fallback: XY) = inputParams.get(key).map(s => XY(s)).getOrElse(fallback)
  354.  
  355. val view = View(inputParams("view"))
  356. val energy = inputParams("energy").toInt
  357. val time = inputParams("time").toInt
  358. val generation = inputParams("generation").toInt
  359. def offsetToMaster = inputAsXYOrElse("master", XY.Zero)
  360.  
  361.  
  362. // output
  363.  
  364. private var stateParams = Map.empty[String,Any] // holds "Set()" commands
  365. private var commands = "" // holds all other commands
  366. private var debugOutput = "" // holds all "Log()" output
  367.  
  368. /** Appends a new command to the command string; returns 'this' for fluent API. */
  369. private def append(s: String) : Bot = { commands += (if(commands.isEmpty) s else "|" + s); this }
  370.  
  371. /** Renders commands and stateParams into a control function return string. */
  372. override def toString = {
  373. var result = commands
  374. if(!stateParams.isEmpty) {
  375. if(!result.isEmpty) result += "|"
  376. result += stateParams.map(e => e._1 + "=" + e._2).mkString("Set(",",",")")
  377. }
  378. if(!debugOutput.isEmpty) {
  379. if(!result.isEmpty) result += "|"
  380. result += "Log(text=" + debugOutput + ")"
  381. }
  382. result
  383. }
  384.  
  385. def log(text: String) = { debugOutput += text + "\n"; this }
  386. def move(direction: XY) = append("Move(direction=" + direction + ")")
  387. def say(text: String) = append("Say(text=" + text + ")")
  388. def status(text: String) = append("Status(text=" + text + ")")
  389. def explode(blastRadius: Int) = append("Explode(size=" + blastRadius + ")")
  390. def spawn(offset: XY, params: (String,Any)*) =
  391. append("Spawn(direction=" + offset +
  392. (if(params.isEmpty) "" else "," + params.map(e => e._1 + "=" + e._2).mkString(",")) +
  393. ")")
  394. def set(params: (String,Any)*) = { stateParams ++= params; this }
  395. def set(keyPrefix: String, xy: XY) = { stateParams ++= List(keyPrefix+"x" -> xy.x, keyPrefix+"y" -> xy.y); this }
  396. }
  397.  
  398.  
  399. // -------------------------------------------------------------------------------------------------
  400.  
  401.  
  402. /** Utility methods for parsing strings containing a single command of the format
  403. * "Command(key=value,key=value,...)"
  404. */
  405. object CommandParser {
  406. /** "Command(..)" => ("Command", Map( ("key" -> "value"), ("key" -> "value"), ..}) */
  407. def apply(command: String): (String, Map[String, String]) = {
  408. /** "key=value" => ("key","value") */
  409. def splitParameterIntoKeyValue(param: String): (String, String) = {
  410. val segments = param.split('=')
  411. (segments(0), if(segments.length>=2) segments(1) else "")
  412. }
  413.  
  414. val segments = command.split('(')
  415. if( segments.length != 2 )
  416. throw new IllegalStateException("invalid command: " + command)
  417. val opcode = segments(0)
  418. val params = segments(1).dropRight(1).split(',')
  419. val keyValuePairs = params.map(splitParameterIntoKeyValue).toMap
  420. (opcode, keyValuePairs)
  421. }
  422. }
  423.  
  424.  
  425. // -------------------------------------------------------------------------------------------------
  426.  
  427.  
  428. /** Utility class for managing 2D cell coordinates.
  429. * The coordinate (0,0) corresponds to the top-left corner of the arena on screen.
  430. * The direction (1,-1) points right and up.
  431. */
  432. case class XY(x: Int, y: Int) {
  433. override def toString = x + ":" + y
  434.  
  435. def isNonZero = x != 0 || y != 0
  436. def isZero = x == 0 && y == 0
  437. def isNonNegative = x >= 0 && y >= 0
  438.  
  439. def updateX(newX: Int) = XY(newX, y)
  440. def updateY(newY: Int) = XY(x, newY)
  441.  
  442. def addToX(dx: Int) = XY(x + dx, y)
  443. def addToY(dy: Int) = XY(x, y + dy)
  444.  
  445. def +(pos: XY) = XY(x + pos.x, y + pos.y)
  446. def -(pos: XY) = XY(x - pos.x, y - pos.y)
  447. def *(factor: Double) = XY((x * factor).intValue, (y * factor).intValue)
  448.  
  449. def distanceTo(pos: XY): Double = (this - pos).length // Phythagorean
  450. def length: Double = math.sqrt(x * x + y * y) // Phythagorean
  451.  
  452. def stepsTo(pos: XY): Int = (this - pos).stepCount // steps to reach pos: max delta X or Y
  453. def stepCount: Int = x.abs.max(y.abs) // steps from (0,0) to get here: max X or Y
  454.  
  455. def signum = XY(x.signum, y.signum)
  456.  
  457. def negate = XY(-x, -y)
  458. def negateX = XY(-x, y)
  459. def negateY = XY(x, -y)
  460.  
  461. /** Returns the direction index with 'Right' being index 0, then clockwise in 45 degree steps. */
  462. def toDirection45: Int = {
  463. val unit = signum
  464. unit.x match {
  465. case -1 =>
  466. unit.y match {
  467. case -1 =>
  468. if(x < y * 3) Direction45.Left
  469. else if(y < x * 3) Direction45.Up
  470. else Direction45.UpLeft
  471. case 0 =>
  472. Direction45.Left
  473. case 1 =>
  474. if(-x > y * 3) Direction45.Left
  475. else if(y > -x * 3) Direction45.Down
  476. else Direction45.LeftDown
  477. }
  478. case 0 =>
  479. unit.y match {
  480. case 1 => Direction45.Down
  481. case 0 => throw new IllegalArgumentException("cannot compute direction index for (0,0)")
  482. case -1 => Direction45.Up
  483. }
  484. case 1 =>
  485. unit.y match {
  486. case -1 =>
  487. if(x > -y * 3) Direction45.Right
  488. else if(-y > x * 3) Direction45.Up
  489. else Direction45.RightUp
  490. case 0 =>
  491. Direction45.Right
  492. case 1 =>
  493. if(x > y * 3) Direction45.Right
  494. else if(y > x * 3) Direction45.Down
  495. else Direction45.DownRight
  496. }
  497. }
  498. }
  499.  
  500. def rotateCounterClockwise45 = XY.fromDirection45((signum.toDirection45 + 1) % 8)
  501. def rotateCounterClockwise90 = XY.fromDirection45((signum.toDirection45 + 2) % 8)
  502. def rotateClockwise45 = XY.fromDirection45((signum.toDirection45 + 7) % 8)
  503. def rotateClockwise90 = XY.fromDirection45((signum.toDirection45 + 6) % 8)
  504.  
  505.  
  506. def wrap(boardSize: XY) = {
  507. val fixedX = if(x < 0) boardSize.x + x else if(x >= boardSize.x) x - boardSize.x else x
  508. val fixedY = if(y < 0) boardSize.y + y else if(y >= boardSize.y) y - boardSize.y else y
  509. if(fixedX != x || fixedY != y) XY(fixedX, fixedY) else this
  510. }
  511. }
  512.  
  513.  
  514. object XY {
  515. /** Parse an XY value from XY.toString format, e.g. "2:3". */
  516. def apply(s: String) : XY = { val a = s.split(':'); XY(a(0).toInt,a(1).toInt) }
  517.  
  518. val Zero = XY(0, 0)
  519. val One = XY(1, 1)
  520.  
  521. val Right = XY( 1, 0)
  522. val RightUp = XY( 1, -1)
  523. val Up = XY( 0, -1)
  524. val UpLeft = XY(-1, -1)
  525. val Left = XY(-1, 0)
  526. val LeftDown = XY(-1, 1)
  527. val Down = XY( 0, 1)
  528. val DownRight = XY( 1, 1)
  529.  
  530. def fromDirection45(index: Int): XY = index match {
  531. case Direction45.Right => Right
  532. case Direction45.RightUp => RightUp
  533. case Direction45.Up => Up
  534. case Direction45.UpLeft => UpLeft
  535. case Direction45.Left => Left
  536. case Direction45.LeftDown => LeftDown
  537. case Direction45.Down => Down
  538. case Direction45.DownRight => DownRight
  539. }
  540.  
  541. def fromDirection90(index: Int): XY = index match {
  542. case Direction90.Right => Right
  543. case Direction90.Up => Up
  544. case Direction90.Left => Left
  545. case Direction90.Down => Down
  546. }
  547.  
  548. def apply(array: Array[Int]): XY = XY(array(0), array(1))
  549. }
  550.  
  551.  
  552. object Direction45 {
  553. val Right = 0
  554. val RightUp = 1
  555. val Up = 2
  556. val UpLeft = 3
  557. val Left = 4
  558. val LeftDown = 5
  559. val Down = 6
  560. val DownRight = 7
  561. }
  562.  
  563.  
  564. object Direction90 {
  565. val Right = 0
  566. val Up = 1
  567. val Left = 2
  568. val Down = 3
  569. }
  570.  
  571.  
  572. // -------------------------------------------------------------------------------------------------
  573.  
  574.  
  575. case class View(cells: String) {
  576. val size = math.sqrt(cells.length).toInt
  577. val center = XY(size / 2, size / 2)
  578.  
  579. def apply(relPos: XY) = cellAtRelPos(relPos)
  580.  
  581. def indexFromAbsPos(absPos: XY) = absPos.x + absPos.y * size
  582. def absPosFromIndex(index: Int) = XY(index % size, index / size)
  583. def absPosFromRelPos(relPos: XY) = relPos + center
  584. def cellAtAbsPos(absPos: XY) = cells.charAt(indexFromAbsPos(absPos))
  585.  
  586. def indexFromRelPos(relPos: XY) = indexFromAbsPos(absPosFromRelPos(relPos))
  587. def relPosFromAbsPos(absPos: XY) = absPos - center
  588. def relPosFromIndex(index: Int) = relPosFromAbsPos(absPosFromIndex(index))
  589. def cellAtRelPos(relPos: XY) = cells.charAt(indexFromRelPos(relPos))
  590.  
  591. def offsetToNearest(c: Char) = {
  592. val matchingXY = cells.view.zipWithIndex.filter(_._1 == c)
  593. if( matchingXY.isEmpty )
  594. None
  595. else {
  596. val nearest = matchingXY.map(p => relPosFromIndex(p._2)).minBy(_.length)
  597. Some(nearest)
  598. }
  599. }
  600. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement