mirror of
https://github.com/mx42/adventofcode.git
synced 2026-01-14 13:59:51 +01:00
Move scala codebase in a directory on its own
This commit is contained in:
236
scalaAoC/2018/day15/part1.scala
Normal file
236
scalaAoC/2018/day15/part1.scala
Normal file
@@ -0,0 +1,236 @@
|
||||
import scala.io.StdIn.readLine
|
||||
import scala.collection.mutable
|
||||
import scala.annotation.tailrec
|
||||
|
||||
val ESC = "\u001B"
|
||||
val RED = s"$ESC[1;31m"
|
||||
val BLU = s"$ESC[1;34m"
|
||||
val CLR = s"$ESC[0;37m"
|
||||
|
||||
val CLEAR = false
|
||||
val CLEAR_SCREEN = s"$ESC[2J"
|
||||
val SLEEP_TIME = 200
|
||||
|
||||
val ELF = 'E'
|
||||
val GOBLIN = 'G'
|
||||
|
||||
case class Pos(x: Int, y: Int) extends Ordered[Pos] {
|
||||
lazy val neighbours: List[Pos] = posOrder.map(_ + this)
|
||||
|
||||
def +(that: Pos): Pos = Pos(this.x + that.x, this.y + that.y)
|
||||
|
||||
def compare(that: Pos): Int =
|
||||
this.y - that.y match {
|
||||
case 0 => this.x - that.x
|
||||
case ydiff => ydiff
|
||||
}
|
||||
|
||||
override def toString: String = s"@${x}x${y}"
|
||||
}
|
||||
|
||||
object Origin extends Pos(0, 0)
|
||||
object Up extends Pos(0, -1)
|
||||
object Down extends Pos(0, 1)
|
||||
object Left extends Pos(-1, 0)
|
||||
object Right extends Pos(1, 0)
|
||||
|
||||
val posOrder: List[Pos] = List(Up, Left, Right, Down)
|
||||
|
||||
sealed trait Environment
|
||||
case object Wall extends Environment
|
||||
case object Free extends Environment
|
||||
|
||||
case class Entity(p: Pos, race: Char, h: Int = 200, atk: Int = 3) extends Ordered[Entity] {
|
||||
override def toString: String = s"$race($h)"
|
||||
def compare(that: Entity): Int = this.p.compare(that.p)
|
||||
}
|
||||
|
||||
case class Game(map: Set[Pos], startUnits: List[Entity]) {
|
||||
lazy val Pos(maxX, maxY) =
|
||||
map.foldLeft(Origin: Pos) {
|
||||
case (Pos(mx, my), Pos(x, y)) if x > mx && y > my => Pos(x, y)
|
||||
case (Pos(mx, my), Pos(x, y)) if x > mx => Pos(x, my)
|
||||
case (Pos(mx, my), Pos(x, y)) if y > my => Pos(mx, y)
|
||||
case (acc, _) => acc
|
||||
}
|
||||
|
||||
lazy val getOutcome: Round = Iterator.from(2)
|
||||
.scanLeft(Round(startUnits, 1))(nextRound)
|
||||
.filter(_.gameFinished)
|
||||
.next
|
||||
|
||||
def nextRound(r: Round, nb: Int): Round =
|
||||
Round(r.remainingEntities, nb)
|
||||
|
||||
def moveToClosest(entity: Entity, targetPos: Set[Pos], freeCells: Set[Pos]): Entity = entity.p match {
|
||||
case p if targetPos.contains(p) => entity
|
||||
case _ if targetPos.isEmpty => entity
|
||||
case p => val closest = closestPos(p, targetPos, freeCells).sorted.headOption
|
||||
closest.map(c => entity.copy(p = moveTowardsTarget(p, c, freeCells))).getOrElse(entity)
|
||||
}
|
||||
|
||||
def moveTowardsTarget(origin: Pos, target: Pos, free: Set[Pos]): Pos =
|
||||
closestPos(target, posOrder.map(_ + origin).filter(free.contains).toSet, free).min
|
||||
|
||||
def closestPos(p: Pos, targetPos: Set[Pos], freeCells: Set[Pos]): List[Pos] = {
|
||||
@tailrec
|
||||
def explore(todo: List[(Pos, Int)], free: Set[Pos], result: List[(Pos, Int)]): List[(Pos, Int)] =
|
||||
todo match {
|
||||
case Nil => result
|
||||
case (p, d) :: tail =>
|
||||
explore(
|
||||
tail ++ p.neighbours
|
||||
.filter(free.contains)
|
||||
.filterNot(q => todo.map(_._1).contains(q) || result.map(_._1).contains(q))
|
||||
.map(_ -> (d + 1)),
|
||||
free,
|
||||
(p -> d) :: result)
|
||||
}
|
||||
val distPos = explore(List(p -> 0), freeCells, List())
|
||||
.filter(q => targetPos.contains(q._1))
|
||||
distPos.filter(_._2 == distPos.map(_._2).min).map(_._1)
|
||||
}
|
||||
|
||||
case class Round(units: List[Entity], roundNb: Int) {
|
||||
lazy val (remainingEntities, roundFinished, gameFinished) = getOutcome
|
||||
|
||||
def getOutcome: (List[Entity], Boolean, Boolean) = {
|
||||
def processTurn(entityTodo: List[Entity], doneEntity: List[Entity]): (List[Entity], Boolean) =
|
||||
entityTodo.sorted match {
|
||||
case Nil => (doneEntity, true)
|
||||
case e :: tail => {
|
||||
// println(s"ENTITY $e (${e.p}) TURN")
|
||||
val others = tail ++ doneEntity
|
||||
val freeCells = map.filter(p => !others.exists(_.p == p))
|
||||
val enemies = others.filter(_.race != e.race)
|
||||
|
||||
// Pick move target
|
||||
val movedE = enemies
|
||||
.flatMap(o => o.p.neighbours)
|
||||
.filter(freeCells.contains) match {
|
||||
case Nil => e
|
||||
case ps => moveToClosest(e, ps.toSet, freeCells)
|
||||
}
|
||||
// if (movedE != e) {
|
||||
// println(s"MOVED TO ${movedE.p}")
|
||||
// }
|
||||
|
||||
// Pick attack target
|
||||
val attackTarget = enemies
|
||||
.filter(o => movedE.p.neighbours.contains(o.p))
|
||||
.sortWith { case (a, b) => a.h - b.h match {
|
||||
case 0 => a.p.compare(b.p) < 0
|
||||
case hdiff => hdiff < 0
|
||||
} }
|
||||
.headOption
|
||||
// if (attackTarget.nonEmpty) {
|
||||
// println(s"ATTACK TARGET ${attackTarget.get} (@${attackTarget.get.p})")
|
||||
// }
|
||||
|
||||
// Update queues
|
||||
val (updTail, updDone) = attackTarget match {
|
||||
case None => (tail, doneEntity)
|
||||
case Some(a) if a.h > e.atk =>
|
||||
val updated = a.copy(h = a.h - e.atk)
|
||||
tail match {
|
||||
case _ if tail.contains(a) =>
|
||||
((updated :: tail.filterNot(_ == a)), doneEntity)
|
||||
case _ if doneEntity.contains(a) =>
|
||||
(tail, (updated :: doneEntity.filterNot(_ == a)))
|
||||
case _ =>
|
||||
println("!!!!!!!!!!!!!!!!!11")
|
||||
println(s"Attack target : $a")
|
||||
println(s"Tail : $tail")
|
||||
println(s"Done Entity : $doneEntity")
|
||||
(tail.filterNot(_ == a), doneEntity.filterNot(_ == a))
|
||||
}
|
||||
case Some(a) =>
|
||||
println(s"Entity $a died at pos ${a.p} - attacker $e (@${e.p})")
|
||||
(tail.filterNot(_ == a), doneEntity.filterNot(_ == a))
|
||||
}
|
||||
|
||||
enemies match {
|
||||
case Nil => (movedE :: updTail ++ updDone, false)
|
||||
case _ => processTurn(updTail, movedE :: updDone)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
printRound(units, roundNb)
|
||||
processTurn(units, Nil) match {
|
||||
case (u, f) if u.groupBy(_.race).size == 2 => (u, f, false) // f should be always true ?
|
||||
case (u, f) => (u, f, true)
|
||||
}
|
||||
}
|
||||
|
||||
def printRound(units: List[Entity], roundNb: Int): Unit =
|
||||
if (CLEAR) {
|
||||
println(CLEAR_SCREEN)
|
||||
}
|
||||
println(
|
||||
(s"Round $roundNb" ::
|
||||
((0 to maxY) map {
|
||||
y =>
|
||||
val lb = mutable.ListBuffer.empty[Entity]
|
||||
val chars = (0 to maxX) map {
|
||||
x => val p = Pos(x, y)
|
||||
(map.contains(p), units.find(_.p == p)) match {
|
||||
case (_, Some(e: Entity)) if e.race == ELF => lb += e; BLU + e.race + CLR
|
||||
case (_, Some(e: Entity)) if e.race != ELF => lb += e; RED + e.race + CLR
|
||||
case (false, _) => "#"
|
||||
case (true, _) => "."
|
||||
}
|
||||
}
|
||||
chars.mkString("") + " " + lb.mkString(", ")
|
||||
}).toList
|
||||
).mkString("\n"))
|
||||
if (CLEAR) {
|
||||
Thread.sleep(SLEEP_TIME)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val input = Iterator
|
||||
.continually(readLine)
|
||||
.takeWhile(_ != null)
|
||||
.toList
|
||||
.zipWithIndex
|
||||
.flatMap {
|
||||
case (l, y) => l.zipWithIndex.map {
|
||||
case (c, x) => Pos(x, y) -> c
|
||||
}
|
||||
}
|
||||
.flatMap {
|
||||
case (p, '#') => List()
|
||||
case (p, '.') => List(p -> Free)
|
||||
case (p, 'E') => List(p -> Entity(p, ELF), p -> Free)
|
||||
case (p, 'G') => List(p -> Entity(p, GOBLIN), p -> Free)
|
||||
}
|
||||
|
||||
val gameMap: Set[Pos] = input
|
||||
.flatMap {
|
||||
case (k: Pos, v: Environment) => Some(k)
|
||||
case _ => None
|
||||
}.toSet
|
||||
|
||||
val units = input
|
||||
.flatMap {
|
||||
case (p: Pos, v: Entity) => Some(v)
|
||||
case _ => None
|
||||
}
|
||||
|
||||
val lastRound = Game(gameMap, units).getOutcome
|
||||
|
||||
val lastRoundId = if (lastRound.roundFinished) {
|
||||
lastRound.roundNb
|
||||
} else {
|
||||
lastRound.roundNb - 1
|
||||
}
|
||||
|
||||
val remainingUnitsHP = lastRound.remainingEntities.map(_.h).sum
|
||||
|
||||
println(s"Last round nb: ${lastRound.roundNb}")
|
||||
println(s"Last complete round: ${lastRoundId}")
|
||||
println(s"Remaining units cumulative HP: ${remainingUnitsHP}")
|
||||
|
||||
println(s"Final score ${lastRoundId * remainingUnitsHP}")
|
||||
Reference in New Issue
Block a user