Advertisement
Guest User

Untitled

a guest
Mar 26th, 2019
156
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 37.05 KB | None | 0 0
  1. // Example Bot #1: The Reference Bot
  2.  
  3.  
  4. /** This bot builds a 'direction value map' that assigns an attractiveness score to
  5. * each of the eight available 45-degree directions. Additional behaviors:
  6. * - aggressive missiles: approach an enemy master, then explode
  7. * - defensive missiles: approach an enemy slave and annihilate it
  8. *
  9. * The master bot uses the following state parameters:
  10. * - dontFireAggressiveMissileUntil
  11. * - dontFireDefensiveMissileUntil
  12. * - lastDirection
  13. * The mini-bots use the following state parameters:
  14. * - mood = Aggressive | Defensive | Lurking
  15. * - target = remaining offset to target location
  16. */
  17. import util.Random;
  18.  
  19. object ControlFunction
  20. {
  21. val rand = new Random();
  22. def forMaster(bot: Bot) {
  23. // demo: log the view of the master bot into the debug output (if running in the browser sandbox)
  24. bot.log(bot.view.cells.grouped(31).mkString("\n"))
  25.  
  26. val (directionValue, nearestEnemyMaster, nearestEnemySlave, nearestPlant, nearestEatThing) = analyzeViewAsMaster(bot.view)
  27. val (nearestRedEnemy) = analyzeViewAsMasterRedEnemy(bot.view)
  28.  
  29. val dontFireAggressiveMissileUntil = bot.inputAsIntOrElse("dontFireAggressiveMissileUntil", -1)
  30. val dontFireAggressiveMissileRedEnemyUntil = bot.inputAsIntOrElse("dontFireAggressiveMissileRedEnemyUntil", -1)
  31. val dontFireDefensiveMissileUntil = bot.inputAsIntOrElse("dontFireDefensiveMissileUntil", -1)
  32. val dontFireGatheringUntil = bot.inputAsIntOrElse("dontFireGatheringUntil", -1)
  33. val dontFireBaitUntil = bot.inputAsIntOrElse("dontFireBaitUntil", -1)
  34. val dontFireWallUntil = bot.inputAsIntOrElse("dontFireWallUntil", -1)
  35. val lastDirection = bot.inputAsIntOrElse("lastDirection", 0)
  36.  
  37. // determine movement direction
  38. directionValue(lastDirection) += 10 // try to break ties by favoring the last direction
  39. val bestDirection45 = directionValue.zipWithIndex.maxBy(_._1)._2
  40. val direction = XY.fromDirection45(bestDirection45)
  41. bot.move(direction)
  42. bot.set("lastDirection" -> bestDirection45)
  43.  
  44. //Missile for RedEnemy
  45. if(dontFireAggressiveMissileRedEnemyUntil < bot.time && bot.energy > 100) { // fire attack missile?
  46. nearestRedEnemy match {
  47. case None => // no-on nearby
  48. case Some(relPos) => // a redEnemy is nearby
  49. val unitDelta = relPos.signum
  50. val remainder = relPos - unitDelta // we place slave nearer target, so subtract that from overall delta
  51. bot.spawn(unitDelta, "mood" -> "AggressiveRedEnemy", "target" -> remainder)
  52. bot.set("dontFireAggressiveMissileRedEnemyUntil" -> (bot.time + relPos.stepCount + 1))
  53. }
  54. }
  55.  
  56. //Wall Planter
  57. if(dontFireWallUntil < bot.time && bot.energy > 1000){
  58. nearestEnemyMaster match {
  59. case None => // no-on nearby
  60. case Some(relPos) => // a master is nearby
  61. for (i <- 1 to 10){
  62. val unitDelta = relPos.signum
  63. val remainder = relPos - unitDelta
  64. bot.spawn(bot.view.center, "mood" -> "Lurking")
  65. bot.set("dontFireWallUntil" -> (bot.time + relPos.stepCount + 1))
  66. }
  67. }
  68. }
  69.  
  70. //Gathering V1
  71. /*if(dontFireGatheringUntil < bot.time && bot.energy > 100){
  72. bot.spawn(bot.view.center, "mood" -> "Gathering")
  73. if(bot.energy < 1000){
  74. bot.set("dontFireGatheringUntil" -> (bot.time + rand.nextInt(5)))
  75. }
  76. else{
  77. bot.set("dontFireGatheringUntil" -> (bot.time + rand.nextInt(2)))
  78. }
  79. }*/
  80.  
  81. //Gathering V2 Plant
  82. if(dontFireGatheringUntil < bot.time && bot.energy > 100){
  83. nearestPlant match {
  84. case None => // no-on nearby
  85. case Some(relPos) => // a master is nearby
  86. val unitDelta = relPos.signum
  87. val remainder = relPos - unitDelta // we place slave nearer target, so subtract that from overall delta
  88. bot.spawn(unitDelta, "mood" -> "Gathering")
  89. bot.set("dontFireBaitUntil" -> (bot.time + relPos.stepCount + 1))
  90. }
  91. }
  92. //Gathering V2 EatThingy
  93. if(dontFireGatheringUntil < bot.time && bot.energy > 100){
  94. nearestEatThing match {
  95. case None => // no-on nearby
  96. case Some(relPos) => // a master is nearby
  97. val unitDelta = relPos.signum
  98. val remainder = relPos - unitDelta // we place slave nearer target, so subtract that from overall delta
  99. bot.spawn(unitDelta, "mood" -> "Gathering")
  100. bot.set("dontFireBaitUntil" -> (bot.time + relPos.stepCount + 1))
  101. }
  102. }
  103. //Baiting V1
  104. /*if(dontFireBaitUntil < bot.time && bot.energy > 100){
  105. bot.spawn(bot.view.center, "mood" -> "Baiting")
  106. if(bot.energy < 1000){
  107. bot.set("dontFireBaitUntil" -> (bot.time + rand.nextInt(5)))
  108. }
  109. else{
  110. bot.set("dontFireBaitUntil" -> (bot.time + rand.nextInt(2)))
  111. }
  112. }*/
  113.  
  114. //Baiting V2
  115. if(dontFireBaitUntil < bot.time && bot.energy > 100){
  116. nearestRedEnemy match {
  117. case None => // no-on nearby
  118. case Some(relPos) => // a master is nearby
  119. val unitDelta = relPos.signum
  120. val remainder = relPos - unitDelta // we place slave nearer target, so subtract that from overall delta
  121. bot.spawn(unitDelta, "mood" -> "Baiting")
  122. bot.set("dontFireBaitUntil" -> (bot.time + relPos.stepCount + 1))
  123. }
  124. }
  125.  
  126. if(dontFireAggressiveMissileUntil < bot.time && bot.energy > 100) { // fire attack missile?
  127. nearestEnemyMaster match {
  128. case None => // no-on nearby
  129. case Some(relPos) => // a master is nearby
  130. val unitDelta = relPos.signum
  131. val remainder = relPos - unitDelta // we place slave nearer target, so subtract that from overall delta
  132. bot.spawn(unitDelta, "mood" -> "Aggressive", "target" -> remainder)
  133. bot.set("dontFireAggressiveMissileUntil" -> (bot.time + relPos.stepCount + 1))
  134. }
  135. }
  136. else
  137. if(dontFireDefensiveMissileUntil < bot.time && bot.energy > 100) { // fire defensive missile?
  138. nearestEnemySlave match {
  139. case None => // no-on nearby
  140. case Some(relPos) => // an enemy slave is nearby
  141. if(relPos.stepCount < 8) {
  142. // this one's getting too close!
  143. val unitDelta = relPos.signum
  144. val remainder = relPos - unitDelta // we place slave nearer target, so subtract that from overall delta
  145. bot.spawn(unitDelta, "mood" -> "Defensive", "target" -> remainder)
  146. bot.set("dontFireDefensiveMissileUntil" -> (bot.time + relPos.stepCount + 1))
  147. }
  148. }
  149. }
  150. }
  151.  
  152.  
  153. def forSlave(bot: MiniBot) {
  154. bot.inputOrElse("mood", "Lurking") match {
  155. case "Aggressive" => reactAsAggressiveMissile(bot)
  156. case "AggressiveRedEnemy" => reactAsAggressiveMissileRedEnemy(bot)
  157. case "Defensive" => reactAsDefensiveMissile(bot)
  158. case "Gathering" => reactAsGathering(bot)
  159. case "Returning" => reactAsReturningBot(bot)
  160. case "Baiting" => reactAsBaitingBot(bot)
  161. case s: String => bot.log("unknown mood: " + s)
  162. }
  163. }
  164.  
  165. def reactAsBaitingBot(bot: MiniBot){
  166. val (directionValue, nearestEnemySlave) = analyzeViewAsBaitingBot(bot.view)
  167.  
  168. val lastDirection = bot.inputAsIntOrElse("lastDirection", 0)
  169. directionValue(lastDirection) += 10
  170. val bestDirection45 = directionValue.zipWithIndex.maxBy(_._1)._2
  171. val direction = XY.fromDirection45(bestDirection45)
  172. bot.move(direction)
  173. bot.set("lastDirection" -> bestDirection45)
  174. }
  175.  
  176. def reactAsGathering(bot: MiniBot) {
  177. val (directionValue, nearestEnemyMaster, nearestEnemySlave, nearestPlant, nearestEatThing) = analyzeViewAsMaster(bot.view)
  178.  
  179. val energyLimit = bot.inputAsIntOrElse("limit", 1000)
  180. if (bot.energy > 1000){
  181. bot.set("mood" -> "Returning", "target" -> "")
  182. }
  183. else {
  184. val lastDirection = bot.inputAsIntOrElse("lastDirection", 0)
  185. directionValue(lastDirection) += 10
  186. val bestDirection45 = directionValue.zipWithIndex.maxBy(_._1)._2
  187. val direction = XY.fromDirection45(bestDirection45)
  188. bot.move(direction)
  189. bot.set("lastDirection" -> bestDirection45)
  190. }
  191. }
  192.  
  193. def reactAsReturningBot(bot: MiniBot){
  194. val (directionValue, nearestEnemyMaster) = analyzeViewAsReturningBot(bot, bot.view)
  195.  
  196. val lastDirection = bot.inputAsIntOrElse("lastDirection", 0)
  197. directionValue(lastDirection) += 10
  198. val bestDirection45 = directionValue.zipWithIndex.maxBy(_._1)._2
  199. val direction = XY.fromDirection45(bestDirection45)
  200. bot.move(direction)
  201. bot.set("lastDirection" -> bestDirection45)
  202. }
  203.  
  204. def reactAsAggressiveMissileRedEnemy(bot: MiniBot) {
  205. bot.view.offsetToNearest('b') match {
  206. case Some(delta: XY) =>
  207. // another master is visible at the given relative position (i.e. position delta)
  208.  
  209. // close enough to blow it up?
  210. if(delta.length <= 2) {
  211. // yes -- blow it up!
  212. bot.explode(4)
  213.  
  214. } else {
  215. // no -- move closer!
  216. bot.move(delta.signum)
  217. bot.set("rx" -> delta.x, "ry" -> delta.y)
  218. }
  219. case None =>
  220. // no target visible -- follow our targeting strategy
  221. val target = bot.inputAsXYOrElse("target", XY.Zero)
  222.  
  223. // did we arrive at the target?
  224. if(target.isNonZero) {
  225. // no -- keep going
  226. val unitDelta = target.signum // e.g. CellPos(-8,6) => CellPos(-1,1)
  227. bot.move(unitDelta)
  228.  
  229. // compute the remaining delta and encode it into a new 'target' property
  230. val remainder = target - unitDelta // e.g. = CellPos(-7,5)
  231. bot.set("target" -> remainder)
  232. } else {
  233. // yes -- but we did not detonate yet, and are not pursuing anything?!? => switch purpose
  234. bot.set("mood" -> "Lurking", "target" -> "")
  235. bot.say("Lurking")
  236. }
  237. }
  238. }
  239.  
  240. def reactAsAggressiveMissile(bot: MiniBot) {
  241. bot.view.offsetToNearest('m') match {
  242. case Some(delta: XY) =>
  243. // another master is visible at the given relative position (i.e. position delta)
  244.  
  245. // close enough to blow it up?
  246. if(delta.length <= 2) {
  247. // yes -- blow it up!
  248. bot.explode(4)
  249.  
  250. } else {
  251. // no -- move closer!
  252. bot.move(delta.signum)
  253. bot.set("rx" -> delta.x, "ry" -> delta.y)
  254. }
  255. case None =>
  256. // no target visible -- follow our targeting strategy
  257. val target = bot.inputAsXYOrElse("target", XY.Zero)
  258.  
  259. // did we arrive at the target?
  260. if(target.isNonZero) {
  261. // no -- keep going
  262. val unitDelta = target.signum // e.g. CellPos(-8,6) => CellPos(-1,1)
  263. bot.move(unitDelta)
  264.  
  265. // compute the remaining delta and encode it into a new 'target' property
  266. val remainder = target - unitDelta // e.g. = CellPos(-7,5)
  267. bot.set("target" -> remainder)
  268. } else {
  269. // yes -- but we did not detonate yet, and are not pursuing anything?!? => switch purpose
  270. bot.set("mood" -> "Lurking", "target" -> "")
  271. bot.say("Lurking")
  272. }
  273. }
  274. }
  275.  
  276.  
  277. def reactAsDefensiveMissile(bot: MiniBot) {
  278. bot.view.offsetToNearest('s') match {
  279. case Some(delta: XY) =>
  280. // another slave is visible at the given relative position (i.e. position delta)
  281. // move closer!
  282. bot.move(delta.signum)
  283. bot.set("rx" -> delta.x, "ry" -> delta.y)
  284.  
  285. case None =>
  286. // no target visible -- follow our targeting strategy
  287. val target = bot.inputAsXYOrElse("target", XY.Zero)
  288.  
  289. // did we arrive at the target?
  290. if(target.isNonZero) {
  291. // no -- keep going
  292. val unitDelta = target.signum // e.g. CellPos(-8,6) => CellPos(-1,1)
  293. bot.move(unitDelta)
  294.  
  295. // compute the remaining delta and encode it into a new 'target' property
  296. val remainder = target - unitDelta // e.g. = CellPos(-7,5)
  297. bot.set("target" -> remainder)
  298. } else {
  299. // yes -- but we did not annihilate yet, and are not pursuing anything?!? => switch purpose
  300. bot.set("mood" -> "Lurking", "target" -> "")
  301. bot.say("Lurking")
  302. }
  303. }
  304. }
  305.  
  306. def analyzeViewAsMasterRedEnemy(view: View) = {
  307. val directionValue = Array.ofDim[Double](8)
  308. //var nearestEnemyMaster: Option[XY] = None
  309. //var nearestEnemySlave: Option[XY] = None
  310. var nearestRedEnemy: Option[XY] = None
  311.  
  312. val cells = view.cells
  313. val cellCount = cells.length
  314. for(i <- 0 until cellCount) {
  315. val cellRelPos = view.relPosFromIndex(i)
  316. if(cellRelPos.isNonZero) {
  317. val stepDistance = cellRelPos.stepCount
  318. val value: Double = cells(i) match {
  319. case 'm' => // another master: not dangerous, but an obstacle
  320. //nearestEnemyMaster = Some(cellRelPos)
  321. if(stepDistance < 2) -1000 else 0
  322.  
  323. case 's' => // another slave: potentially dangerous?
  324. //nearestEnemySlave = Some(cellRelPos)
  325. -100 / stepDistance
  326.  
  327. case 'S' => // out own slave
  328. 0.0
  329.  
  330. case 'B' => // good beast: valuable, but runs away
  331. if(stepDistance == 1) 600
  332. else if(stepDistance == 2) 300
  333. else (150 - stepDistance * 15).max(10)
  334.  
  335. case 'P' => // good plant: less valuable, but does not run
  336. if(stepDistance == 1) 500
  337. else if(stepDistance == 2) 300
  338. else (150 - stepDistance * 10).max(10)
  339.  
  340. case 'b' => // bad beast: dangerous, but only if very close
  341. nearestRedEnemy = Some(cellRelPos)
  342. if(stepDistance < 4) -400 / stepDistance else -50 / stepDistance
  343.  
  344. case 'p' => // bad plant: bad, but only if I step on it
  345. if(stepDistance < 2) -1000 else 0
  346.  
  347. case 'W' => // wall: harmless, just don't walk into it
  348. if(stepDistance < 2) -1000 else 0
  349.  
  350. case _ => 0.0
  351. }
  352. val direction45 = cellRelPos.toDirection45
  353. directionValue(direction45) += value
  354. }
  355. }
  356. (nearestRedEnemy)
  357. }
  358.  
  359. def analyzeViewAsBaitingBot(view: View) = {
  360. val directionValue = Array.ofDim[Double](8)
  361. var nearestEnemyMaster: Option[XY] = None
  362. //var nearestEnemySlave: Option[XY] = None
  363.  
  364. val cells = view.cells
  365. val cellCount = cells.length
  366. for(i <- 0 until cellCount) {
  367. val cellRelPos = view.relPosFromIndex(i)
  368. if(cellRelPos.isNonZero) {
  369. val stepDistance = cellRelPos.stepCount
  370. val value: Double = cells(i) match {
  371. case 'M' => //master
  372. -1000
  373. case 'm' => // bait to him
  374. nearestEnemyMaster = Some(cellRelPos)
  375. 1000
  376. case 's' => // another slave: potentially dangerous?
  377. //nearestEnemySlave = Some(cellRelPos)
  378. -100 / stepDistance
  379.  
  380. case 'S' => // out own slave
  381. 0.0
  382.  
  383. case 'B' => // good beast: valuable, but runs away
  384. if(stepDistance == 1) 600
  385. else if(stepDistance == 2) 300
  386. else (150 - stepDistance * 15).max(10)
  387.  
  388. case 'P' => // good plant: less valuable, but does not run
  389. if(stepDistance == 1) 500
  390. else if(stepDistance == 2) 300
  391. else (150 - stepDistance * 10).max(10)
  392.  
  393. case 'b' => // bad beast: dangerous, but only if very close
  394. if (stepDistance > 4) 200
  395. else if(stepDistance < 4) -400 / stepDistance else -50 / stepDistance
  396.  
  397. case 'p' => // bad plant: bad, but only if I step on it
  398. if(stepDistance < 2) -1000 else 0
  399.  
  400. case 'W' => // wall: harmless, just don't walk into it
  401. if(stepDistance < 2) -1000 else 0
  402.  
  403. case _ => 0.0
  404. }
  405. val direction45 = cellRelPos.toDirection45
  406. directionValue(direction45) += value
  407. }
  408. }
  409. (directionValue, nearestEnemyMaster)
  410. }
  411.  
  412. def analyzeViewAsReturningBot(bot: MiniBot, view: View) = {
  413. val directionValue = Array.ofDim[Double](8)
  414. var nearestEnemyMaster: Option[XY] = None
  415.  
  416. val cells = view.cells
  417. val cellCount = cells.length
  418. for(i <- 0 until cellCount) {
  419. val cellRelPos = view.relPosFromIndex(i)
  420. if(cellRelPos.isNonZero) {
  421. val stepDistance = cellRelPos.stepCount
  422. val value: Double = cells(i) match {
  423. case 'M' => //master
  424. 1000
  425. case 'm' => // another master: not dangerous, but an obstacle
  426. nearestEnemyMaster = Some(cellRelPos)
  427. if(stepDistance < 2) -1000 else 0
  428.  
  429. case 'S' => // out own slave
  430. 0.0
  431.  
  432. case 'B' => // good beast: valuable, but runs away
  433. if(stepDistance == 1) 600
  434. else if(stepDistance == 2) 300
  435. else (150 - stepDistance * 15).max(10)
  436.  
  437. /* case 'P' => // good plant: less valuable, but does not run
  438. if(stepDistance == 1) 500
  439. else if(stepDistance == 2) 300
  440. else (150 - stepDistance * 10).max(10)
  441. */
  442. case 'b' => // bad beast: dangerous, but only if very close
  443. if(stepDistance < 4) -400 / stepDistance else -50 / stepDistance
  444.  
  445. case 'p' => // bad plant: bad, but only if I step on it
  446. if(stepDistance < 2) -1000 else 0
  447.  
  448. case 'W' => // wall: harmless, just don't walk into it
  449. if(stepDistance < 2) -1000 else 0
  450.  
  451. case _ => 0.0
  452. }
  453. val direction45 = cellRelPos.toDirection45
  454. directionValue(direction45) += value
  455. }
  456. }
  457. (directionValue, nearestEnemyMaster)
  458. }
  459. /** Analyze the view, building a map of attractiveness for the 45-degree directions and
  460. * recording other relevant data, such as the nearest elements of various kinds.
  461. */
  462. def analyzeViewAsMaster(view: View) = {
  463. val directionValue = Array.ofDim[Double](8)
  464. var nearestEnemyMaster: Option[XY] = None
  465. var nearestEnemySlave: Option[XY] = None
  466. var nearestPlant: Option[XY] = None
  467. var nearestEatThing: Option[XY] = None
  468.  
  469. val cells = view.cells
  470. val cellCount = cells.length
  471. for(i <- 0 until cellCount) {
  472. val cellRelPos = view.relPosFromIndex(i)
  473. if(cellRelPos.isNonZero) {
  474. val stepDistance = cellRelPos.stepCount
  475. val value: Double = cells(i) match {
  476. case 'm' => // another master: not dangerous, but an obstacle
  477. nearestEnemyMaster = Some(cellRelPos)
  478. if(stepDistance < 2) -1000 else 0
  479.  
  480. case 's' => // another slave: potentially dangerous?
  481. nearestEnemySlave = Some(cellRelPos)
  482. -100 / stepDistance
  483.  
  484. case 'S' => // out own slave
  485. 0.0
  486.  
  487. case 'B' => // good beast: valuable, but runs away
  488. nearestEatThing = Some(cellRelPos)
  489. if(stepDistance == 1) 600
  490. else if(stepDistance == 2) 300
  491. else (150 - stepDistance * 15).max(10)
  492.  
  493. case 'P' => // good plant: less valuable, but does not run
  494. nearestPlant = Some(cellRelPos)
  495. if(stepDistance == 1) 500
  496. else if(stepDistance == 2) 300
  497. else (150 - stepDistance * 10).max(10)
  498.  
  499. case 'b' => // bad beast: dangerous, but only if very close
  500. if(stepDistance < 4) -400 / stepDistance else -50 / stepDistance
  501.  
  502. case 'p' => // bad plant: bad, but only if I step on it
  503. if(stepDistance < 2) -1000 else 0
  504.  
  505. case 'W' => // wall: harmless, just don't walk into it
  506. if(stepDistance < 2) -1000 else 0
  507.  
  508. case _ => 0.0
  509. }
  510. val direction45 = cellRelPos.toDirection45
  511. directionValue(direction45) += value
  512. }
  513. }
  514. (directionValue, nearestEnemyMaster, nearestEnemySlave, nearestPlant, nearestEatThing)
  515. }
  516. }
  517.  
  518.  
  519.  
  520. // -------------------------------------------------------------------------------------------------
  521. // Framework
  522. // -------------------------------------------------------------------------------------------------
  523.  
  524. class ControlFunctionFactory {
  525. def create = (input: String) => {
  526. val (opcode, params) = CommandParser(input)
  527. opcode match {
  528. case "React" =>
  529. val bot = new BotImpl(params)
  530. if( bot.generation == 0 ) {
  531. ControlFunction.forMaster(bot)
  532. } else {
  533. ControlFunction.forSlave(bot)
  534. }
  535. bot.toString
  536. case _ => "" // OK
  537. }
  538. }
  539. }
  540.  
  541.  
  542. // -------------------------------------------------------------------------------------------------
  543.  
  544.  
  545. trait Bot {
  546. // inputs
  547. def inputOrElse(key: String, fallback: String): String
  548. def inputAsIntOrElse(key: String, fallback: Int): Int
  549. def inputAsXYOrElse(keyPrefix: String, fallback: XY): XY
  550. def view: View
  551. def energy: Int
  552. def time: Int
  553. def generation: Int
  554.  
  555. // outputs
  556. def move(delta: XY) : Bot
  557. def say(text: String) : Bot
  558. def status(text: String) : Bot
  559. def spawn(offset: XY, params: (String,Any)*) : Bot
  560. def set(params: (String,Any)*) : Bot
  561. def log(text: String) : Bot
  562. }
  563.  
  564. trait MiniBot extends Bot {
  565. // inputs
  566. def offsetToMaster: XY
  567.  
  568. // outputs
  569. def explode(blastRadius: Int) : Bot
  570. }
  571.  
  572.  
  573. case class BotImpl(inputParams: Map[String, String]) extends MiniBot {
  574. // input
  575. def inputOrElse(key: String, fallback: String) = inputParams.getOrElse(key, fallback)
  576. def inputAsIntOrElse(key: String, fallback: Int) = inputParams.get(key).map(_.toInt).getOrElse(fallback)
  577. def inputAsXYOrElse(key: String, fallback: XY) = inputParams.get(key).map(s => XY(s)).getOrElse(fallback)
  578.  
  579. val view = View(inputParams("view"))
  580. val energy = inputParams("energy").toInt
  581. val time = inputParams("time").toInt
  582. val generation = inputParams("generation").toInt
  583. def offsetToMaster = inputAsXYOrElse("master", XY.Zero)
  584.  
  585.  
  586. // output
  587.  
  588. private var stateParams = Map.empty[String,Any] // holds "Set()" commands
  589. private var commands = "" // holds all other commands
  590. private var debugOutput = "" // holds all "Log()" output
  591.  
  592. /** Appends a new command to the command string; returns 'this' for fluent API. */
  593. private def append(s: String) : Bot = { commands += (if(commands.isEmpty) s else "|" + s); this }
  594.  
  595. /** Renders commands and stateParams into a control function return string. */
  596. override def toString = {
  597. var result = commands
  598. if(!stateParams.isEmpty) {
  599. if(!result.isEmpty) result += "|"
  600. result += stateParams.map(e => e._1 + "=" + e._2).mkString("Set(",",",")")
  601. }
  602. if(!debugOutput.isEmpty) {
  603. if(!result.isEmpty) result += "|"
  604. result += "Log(text=" + debugOutput + ")"
  605. }
  606. result
  607. }
  608.  
  609. def log(text: String) = { debugOutput += text + "\n"; this }
  610. def move(direction: XY) = append("Move(direction=" + direction + ")")
  611. def say(text: String) = append("Say(text=" + text + ")")
  612. def status(text: String) = append("Status(text=" + text + ")")
  613. def explode(blastRadius: Int) = append("Explode(size=" + blastRadius + ")")
  614. def spawn(offset: XY, params: (String,Any)*) =
  615. append("Spawn(direction=" + offset +
  616. (if(params.isEmpty) "" else "," + params.map(e => e._1 + "=" + e._2).mkString(",")) +
  617. ")")
  618. def set(params: (String,Any)*) = { stateParams ++= params; this }
  619. def set(keyPrefix: String, xy: XY) = { stateParams ++= List(keyPrefix+"x" -> xy.x, keyPrefix+"y" -> xy.y); this }
  620. }
  621.  
  622.  
  623. // -------------------------------------------------------------------------------------------------
  624.  
  625.  
  626. /** Utility methods for parsing strings containing a single command of the format
  627. * "Command(key=value,key=value,...)"
  628. */
  629. object CommandParser {
  630. /** "Command(..)" => ("Command", Map( ("key" -> "value"), ("key" -> "value"), ..}) */
  631. def apply(command: String): (String, Map[String, String]) = {
  632. /** "key=value" => ("key","value") */
  633. def splitParameterIntoKeyValue(param: String): (String, String) = {
  634. val segments = param.split('=')
  635. (segments(0), if(segments.length>=2) segments(1) else "")
  636. }
  637.  
  638. val segments = command.split('(')
  639. if( segments.length != 2 )
  640. throw new IllegalStateException("invalid command: " + command)
  641. val opcode = segments(0)
  642. val params = segments(1).dropRight(1).split(',')
  643. val keyValuePairs = params.map(splitParameterIntoKeyValue).toMap
  644. (opcode, keyValuePairs)
  645. }
  646. }
  647.  
  648.  
  649. // -------------------------------------------------------------------------------------------------
  650.  
  651.  
  652. /** Utility class for managing 2D cell coordinates.
  653. * The coordinate (0,0) corresponds to the top-left corner of the arena on screen.
  654. * The direction (1,-1) points right and up.
  655. */
  656. case class XY(x: Int, y: Int) {
  657. override def toString = x + ":" + y
  658.  
  659. def isNonZero = x != 0 || y != 0
  660. def isZero = x == 0 && y == 0
  661. def isNonNegative = x >= 0 && y >= 0
  662.  
  663. def updateX(newX: Int) = XY(newX, y)
  664. def updateY(newY: Int) = XY(x, newY)
  665.  
  666. def addToX(dx: Int) = XY(x + dx, y)
  667. def addToY(dy: Int) = XY(x, y + dy)
  668.  
  669. def +(pos: XY) = XY(x + pos.x, y + pos.y)
  670. def -(pos: XY) = XY(x - pos.x, y - pos.y)
  671. def *(factor: Double) = XY((x * factor).intValue, (y * factor).intValue)
  672.  
  673. def distanceTo(pos: XY): Double = (this - pos).length // Phythagorean
  674. def length: Double = math.sqrt(x * x + y * y) // Phythagorean
  675.  
  676. def stepsTo(pos: XY): Int = (this - pos).stepCount // steps to reach pos: max delta X or Y
  677. def stepCount: Int = x.abs.max(y.abs) // steps from (0,0) to get here: max X or Y
  678.  
  679. def signum = XY(x.signum, y.signum)
  680.  
  681. def negate = XY(-x, -y)
  682. def negateX = XY(-x, y)
  683. def negateY = XY(x, -y)
  684.  
  685. /** Returns the direction index with 'Right' being index 0, then clockwise in 45 degree steps. */
  686. def toDirection45: Int = {
  687. val unit = signum
  688. unit.x match {
  689. case -1 =>
  690. unit.y match {
  691. case -1 =>
  692. if(x < y * 3) Direction45.Left
  693. else if(y < x * 3) Direction45.Up
  694. else Direction45.UpLeft
  695. case 0 =>
  696. Direction45.Left
  697. case 1 =>
  698. if(-x > y * 3) Direction45.Left
  699. else if(y > -x * 3) Direction45.Down
  700. else Direction45.LeftDown
  701. }
  702. case 0 =>
  703. unit.y match {
  704. case 1 => Direction45.Down
  705. case 0 => throw new IllegalArgumentException("cannot compute direction index for (0,0)")
  706. case -1 => Direction45.Up
  707. }
  708. case 1 =>
  709. unit.y match {
  710. case -1 =>
  711. if(x > -y * 3) Direction45.Right
  712. else if(-y > x * 3) Direction45.Up
  713. else Direction45.RightUp
  714. case 0 =>
  715. Direction45.Right
  716. case 1 =>
  717. if(x > y * 3) Direction45.Right
  718. else if(y > x * 3) Direction45.Down
  719. else Direction45.DownRight
  720. }
  721. }
  722. }
  723.  
  724. def rotateCounterClockwise45 = XY.fromDirection45((signum.toDirection45 + 1) % 8)
  725. def rotateCounterClockwise90 = XY.fromDirection45((signum.toDirection45 + 2) % 8)
  726. def rotateClockwise45 = XY.fromDirection45((signum.toDirection45 + 7) % 8)
  727. def rotateClockwise90 = XY.fromDirection45((signum.toDirection45 + 6) % 8)
  728.  
  729.  
  730. def wrap(boardSize: XY) = {
  731. val fixedX = if(x < 0) boardSize.x + x else if(x >= boardSize.x) x - boardSize.x else x
  732. val fixedY = if(y < 0) boardSize.y + y else if(y >= boardSize.y) y - boardSize.y else y
  733. if(fixedX != x || fixedY != y) XY(fixedX, fixedY) else this
  734. }
  735. }
  736.  
  737.  
  738. object XY {
  739. /** Parse an XY value from XY.toString format, e.g. "2:3". */
  740. def apply(s: String) : XY = { val a = s.split(':'); XY(a(0).toInt,a(1).toInt) }
  741.  
  742. val Zero = XY(0, 0)
  743. val One = XY(1, 1)
  744.  
  745. val Right = XY( 1, 0)
  746. val RightUp = XY( 1, -1)
  747. val Up = XY( 0, -1)
  748. val UpLeft = XY(-1, -1)
  749. val Left = XY(-1, 0)
  750. val LeftDown = XY(-1, 1)
  751. val Down = XY( 0, 1)
  752. val DownRight = XY( 1, 1)
  753.  
  754. def fromDirection45(index: Int): XY = index match {
  755. case Direction45.Right => Right
  756. case Direction45.RightUp => RightUp
  757. case Direction45.Up => Up
  758. case Direction45.UpLeft => UpLeft
  759. case Direction45.Left => Left
  760. case Direction45.LeftDown => LeftDown
  761. case Direction45.Down => Down
  762. case Direction45.DownRight => DownRight
  763. }
  764.  
  765. def fromDirection90(index: Int): XY = index match {
  766. case Direction90.Right => Right
  767. case Direction90.Up => Up
  768. case Direction90.Left => Left
  769. case Direction90.Down => Down
  770. }
  771.  
  772. def apply(array: Array[Int]): XY = XY(array(0), array(1))
  773. }
  774.  
  775.  
  776. object Direction45 {
  777. val Right = 0
  778. val RightUp = 1
  779. val Up = 2
  780. val UpLeft = 3
  781. val Left = 4
  782. val LeftDown = 5
  783. val Down = 6
  784. val DownRight = 7
  785. }
  786.  
  787.  
  788. object Direction90 {
  789. val Right = 0
  790. val Up = 1
  791. val Left = 2
  792. val Down = 3
  793. }
  794.  
  795.  
  796. // -------------------------------------------------------------------------------------------------
  797.  
  798.  
  799. case class View(cells: String) {
  800. val size = math.sqrt(cells.length).toInt
  801. val center = XY(size / 2, size / 2)
  802.  
  803. def apply(relPos: XY) = cellAtRelPos(relPos)
  804.  
  805. def indexFromAbsPos(absPos: XY) = absPos.x + absPos.y * size
  806. def absPosFromIndex(index: Int) = XY(index % size, index / size)
  807. def absPosFromRelPos(relPos: XY) = relPos + center
  808. def cellAtAbsPos(absPos: XY) = cells.charAt(indexFromAbsPos(absPos))
  809.  
  810. def indexFromRelPos(relPos: XY) = indexFromAbsPos(absPosFromRelPos(relPos))
  811. def relPosFromAbsPos(absPos: XY) = absPos - center
  812. def relPosFromIndex(index: Int) = relPosFromAbsPos(absPosFromIndex(index))
  813. def cellAtRelPos(relPos: XY) = cells.charAt(indexFromRelPos(relPos))
  814.  
  815. def offsetToNearest(c: Char) = {
  816. val matchingXY = cells.view.zipWithIndex.filter(_._1 == c)
  817. if( matchingXY.isEmpty )
  818. None
  819. else {
  820. val nearest = matchingXY.map(p => relPosFromIndex(p._2)).minBy(_.length)
  821. Some(nearest)
  822. }
  823. }
  824. }
  825. case class Node(key: Int, value: XY) extends (Int, XY)(key, value)
  826.  
  827. case class BreadthFirstSearch(bot: Bot, view: String){
  828. val viewMatrix = Array.ofDim[Char](31,31)
  829. var adjacentNodes = new Array[ListBuffer[Node]](961)
  830. var v = View(view)
  831. var center = 480
  832. var directions: List[XY] = List(XY.RightUp, XY.Up, XY.UpLeft, XY.Right,XY.Left,XY.DownRight,XY.Down,XY.LeftDown)
  833. var path = new StringBuilder
  834. def goodTarget(c: Char) = (c == '_' || c == 'P' || c == 'B')
  835. def stringViewToMatrix() = for (i <- 0 to 30) viewMatrix(i) = bot.viewString.substring(i*31, (i+1) * 31).toArray
  836. def findAdjacentNodes(i: Int, j: Int){
  837. var dir = 0
  838. adjacentNodes(i*31 + j) = new ListBuffer[Node]()
  839. var list = adjacentNodes(i * 31 + j)
  840. for (ii <- i-1 to i+1; jj <- j-1 to j+1){
  841. if(!(ii == i && jj == j)){
  842. if(ii >= 0 && jj >= 0 && ii < 31 && jj < 31 && goodTarget(viewMatrix(ii)(jj))){
  843. list += Node(ii*31 + jj, directions(dir))
  844. list.toList
  845. }
  846. dir += 1
  847. }
  848. }
  849. adjacentNodes(i*31 + j) = list
  850. }
  851. def findAllAdjacentNodes() = for(i <- 0 to 30; j <- 0 to 30) findAdjacentNodes(i, j)
  852.  
  853. def findPath(source: Int, destination: Int){
  854. if (source == destination) return
  855. var shortestPath: ArrayBuffer[Node] = new ArrayBuffer[Node]()
  856. var visited: Array[Boolean] = new Array[Boolean](961)
  857. for (i <- 0 to 960) visited(i) = false
  858. var queue = Queue[Node]()
  859. var pathStack = Stack[Node]()
  860. var sourceNode = Node(source, XY(0,0))
  861. queue.enqueue(sourceNode)
  862. pathStack.push(sourceNode)
  863. visited(source) = true
  864. val inner = new Breaks;
  865. val outer = new Breaks;
  866. while(!queue.isEmpty){
  867. var list = adjacentNodes(queue.dequeue.key).toList
  868. inner.breakable{
  869. for(ppp <- list){
  870. if(visited(ppp.key) == false){
  871. visited(ppp.key) = true
  872. queue.enqueue(ppp)
  873. pathStack.push(ppp)
  874. if(ppp.key == destination){
  875. inner.break;
  876. }
  877. }
  878. }
  879. }
  880. }
  881. var node: Node = Node(0, XY(0,0))
  882. var currentSource = Node(destination, XY(0,0))
  883. shortestPath += currentSource
  884. outer.breakable{
  885. while(!pathStack.isEmpty){
  886. node = pathStack.pop()
  887. var list = adjacentNodes(currentSource.key).toList
  888. inner.breakable{
  889. for(ppp <- list){
  890. if(ppp.key == node.key){
  891. shortestPath += node
  892. currentSource = node
  893. if (node.key == source){
  894. inner.break
  895. outer.break
  896. }
  897. }
  898. }
  899. }
  900. }
  901. }
  902. for (vvv <- shortestPath.toArray.reverse) path ++= vvv.value.toString() + ";"
  903. path.toString
  904. }
  905. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement