diff --git a/2021/nim/common.nim b/2021/nim/common.nim index 02992b3..09e990d 100644 --- a/2021/nim/common.nim +++ b/2021/nim/common.nim @@ -86,3 +86,7 @@ proc reduce*[T, X](s: openArray[T], op: (X, T) -> X, init: X): X = proc findFirst*[T](s: openArray[T], op: (T) -> bool): T = for n in s: if op(n): return n + +proc findFirstO*[T](s: openArray[T], op: (T) -> bool): Option[T] = + for n in s: + if op(n): return some n diff --git a/2021/nim/day23.nim b/2021/nim/day23.nim new file mode 100644 index 0000000..7151338 --- /dev/null +++ b/2021/nim/day23.nim @@ -0,0 +1,170 @@ +import ./common, std/[strutils, sugar, sequtils, options, tables, sets, strformat, algorithm] + +type + Vec2 = tuple[x: int, y: int] + AmphipodBreed = enum A, B, C, D + Amphipod = tuple[breed: AmphipodBreed, pos: Vec2] + AmphipodCaveState = seq[Amphipod] + +const HOMES = [2, 4, 6, 8] +let homeSet = HOMES.toHashSet + +proc `$`(s: AmphipodCaveState): string = + var ss = @["...........", " . . . .", " . . . .", ""] + for a in s: + let bb = $a.breed + ss[a.pos.y][a.pos.x] = bb[0] + ss.join("\n") + +proc weight(b: AmphipodBreed): int = [1, 10, 100, 1000][b.ord] +proc home(b: AmphipodBreed): int = HOMES[b.ord] +proc newAmphipod(b: AmphipodBreed, x: int, y: int): Amphipod = (breed: b, pos: (x: x, y: y)) +proc parseAmphipodBreed(c: char): Option[AmphipodBreed] = + case c: + of 'A': some A + of 'B': some B + of 'C': some C + of 'D': some D + else: none AmphipodBreed + +proc v2(x: int, y: int): Vec2 = (x, y) + +proc parseCaveState(l: Lines): AmphipodCaveState = + for y,s in l: + for x,c in s: + let b = c.parseAmphipodBreed + if b.isSome: result.add newAmphipod(b.get, x - 1, y - 1) + +proc roomAvailable(s: AmphipodCaveState, column: int): bool = + true + +proc path(s: AmphipodCaveState, ai: int, goal: Vec2): seq[Vec2] = + let a = s[ai] + if a.pos.y > 0: + for y in toSeq(0.. 0: + for y in 1..goal.y: + result.add (x: goal.x, y: y) + +proc move(s: AmphipodCaveState, ai: int, p: Vec2): (AmphipodCaveState, uint64) = + var newState = s + newState[ai].pos = p + echo &"Moving {ai} from {s[ai].pos} to {p}" + (newState, s.path(ai, p).len.uint64 * s[ai].breed.weight.uint64) + +proc spaceOccupied(s: AmphipodCaveState, p: Vec2): bool = + s.anyIt(it.pos == p) or (p.y > 2 or p.y < 0 or p.x < 0 or p.x > 10) + +proc canMoveTo(s: AmphipodCaveState, ai: int, goal: Vec2): bool = + let a = s[ai] + let pos = s[ai].pos + # can't move anywhere but home once we've left + if pos.y == 0 and goal.y == 0: return false + # no blocking + if goal.y == 0 and homeSet.contains(goal.x): return false + # non-positions + if goal.y > 0 and not homeSet.contains(goal.x): return false + # can't leave empty spots below when going home + if goal.y == 1 and not s.spaceOccupied(v2(goal.x, 2)): return false + # can't enter home if occupied by another breed + if goal.y == 1: + for sib in s: + if sib.pos.x == goal.x and sib.pos.y > goal.y: return false + # can't enter somebody else's home + if goal.y > 0 and goal.x != a.breed.home: return false + # can't move through any occupied positions + let path = s.path(ai, goal) + if path.anyIt(s.spaceOccupied(it)): return false + true + +proc isWin(s: AmphipodCaveState): bool = + result = s.allIt(it.pos.x == it.breed.home and it.pos.y > 0) + if result: + echo s + echo "I WON" + +proc possibleMoves(s: AmphipodCaveState, ai: int): HashSet[Vec2] = + let a = s[ai] + var possibleMoves = newSeq[Vec2]() + if a.pos.y > 0: + for x in 0..10: + if not homeSet.contains x: result.incl v2(x, 0) + for y in 1..2: + result.incl (x: a.breed.home, y: y) + +proc getByPos(s: AmphipodCaveState, pos: Vec2): Option[Amphipod] = s.findFirstO(a => a.pos == pos) +proc isHome(a: Amphipod): bool = a.pos.x == a.breed.home and a.pos.y > 0 +proc doneMoving(s: AmphipodCaveState, ai: int): bool = + let a = s[ai] + if a.isHome and a.pos.y >= 2: return true + let below = s.getByPos((x: a.pos.x, y: 1)) + if below.isSome: return a.isHome and below.get.isHome + +var cheapestWins = newTable[AmphipodCaveState, uint64]() +proc cheapestWin(s: AmphipodCaveState, cost: uint64, depth = 0): uint64 = + result = uint64.high + echo &"cheapestWin: {cost} {depth}\n{s}" + if cheapestWins.hasKey s: return cheapestWins[s] + if s.isWin or cost > 50000: return cost + var costs = initHashSet[uint64]() + for ai,a in s: + if s.doneMoving(ai): continue + for p in s.possibleMoves ai: + if not s.canMoveTo(ai, p): continue + let (newState, moveCost) = s.move(ai, p) + costs.incl newState.cheapestWin(cost + moveCost, depth + 1) + for c in costs: result = min(result, c) + cheapestWins[s] = result + +proc testMoves(state: AmphipodCaveState, moves: seq[(int, Vec2)]) = + var s = state + var cost: uint64 = 0 + for m in moves: + echo s + let (ai, goal) = m + let suggestions = s.possibleMoves(ai) + echo suggestions + var sugs = @["...........", " . . . .", " . . . .", ""] + for sug in suggestions: + sugs[sug.y][sug.x] = 'X' + sugs[s[ai].pos.y][s[ai].pos.x] = 'O' + sugs[goal.y][goal.x] = '#' + echo sugs.join("\n") + let allowed = s.canMoveTo(ai, goal) + let suggested = suggestions.contains goal + echo &"Can move {ai} from {s[ai].pos} to {goal}: allowed: {allowed}, suggested: {suggested}" + var (newState, moveCost) = s.move(ai, goal) + cost += moveCost + s = newState + echo &"Did we win? {s.isWin} -- Cost so far: {cost}" + +proc p1(input: Lines): uint64 = + echo input.parseCaveState + var s = input.parseCaveState + s.testMoves(@[ + (2, (x: 3, y: 0)), + (1, (x: 6, y: 1)), + (5, (x: 5, y: 0)), + (2, v2(4, 2)), + (0, v2(4, 1)), + (3, v2(7, 0)), + (7, v2(9, 0)), + (3, v2(8, 2)), + (5, v2(8, 1)), + (7, v2(2, 1)), + ]) + echo "Crunching..." + input.parseCaveState.cheapestWin(0) + +const rt = (""" +############# +#...........# +###B#C#B#D### + #A#D#C#A# + ######### +""".strip().split('\n'), 12521'u64, 0'u64) +doDayX 23, (n: int) => n.loadInput, p1, p1, rt