Watch
1
0
Fork
You've already forked advent-of-code
0
advent-of-code/2021/day21/main.go
2021-12-20 22:52:44 -08:00

204 lines
3.4 KiB
Go

package main
import (
"fmt"
"github.com/jdkaplan/advent-of-code/aoc"
)
func main() {
lines := aoc.Input().ReadLines("day21.txt")
p1, p2 := parseInput(lines)
fmt.Println(part1(p1, p2))
fmt.Println(part2(p1, p2))
}
func parseInput(lines []string) (p1, p2 int) {
aoc.MustScan(lines[0], "Player 1 starting position: %d", &p1)
aoc.MustScan(lines[1], "Player 2 starting position: %d", &p2)
return
}
func part1(p1, p2 int) int {
g := NewGame(p1, p2, &DD100{})
for i := 1; true; i++ {
hash := g.Turn(i)
if hash != 0 {
return hash
}
}
return 0
}
type Game struct {
p1, p2 int
s1, s2 int
die Roller
}
func NewGame(p1, p2 int, die Roller) *Game {
return &Game{p1, p2, 0, 0, die}
}
func (g *Game) String() string {
return fmt.Sprintf("P1: %2d, S1: %5d | P1: %2d, S1: %5d", g.p1, g.s1, g.p2, g.s2)
}
func (g *Game) Turn(turns int) (hash int) {
if g.P1() {
return g.s2 * (6*turns - 3)
}
if g.P2() {
return g.s1 * (6 * turns)
}
return 0
}
func (g *Game) P1() (won bool) {
roll := g.die.Roll() + g.die.Roll() + g.die.Roll()
g.p1 = move(g.p1, roll)
g.s1 += score(g.p1)
return g.s1 >= 1000
}
func (g *Game) P2() (won bool) {
roll := g.die.Roll() + g.die.Roll() + g.die.Roll()
g.p2 = move(g.p2, roll)
g.s2 += score(g.p2)
return g.s2 >= 1000
}
func move(pos, roll int) int {
// 0-index for math, 1-index for position.
return ((pos-1)+roll)%10 + 1
}
func score(pos int) int {
// 0-index for math, 1-index for position.
return (pos-1)%10 + 1
}
type Roller interface {
Roll() int
}
type DD100 struct{ val int }
func (d *DD100) Roll() int {
val := d.val
d.val = (d.val + 1) % 100
return val + 1
}
func part2(p1, p2 int) int {
active := make(StateCounts)
active[State{p1, p2, 0, 0}] = 1
done := make(map[State]int)
for len(active) > 0 {
state, count := active.Pop()
nextActive, nextDone := state.Successors()
for next, extra := range nextActive {
active[next] += count * extra
}
for next, extra := range nextDone {
done[next] += count * extra
}
}
w1, w2 := 0, 0
for state, count := range done {
if state.Win1() {
w1 += count
} else {
w2 += count
}
}
return max(w1, w2)
}
type StateCounts map[State]int
func (sc StateCounts) Pop() (State, int) {
for state, count := range sc {
delete(sc, state)
return state, count
}
panic("Pop from empty map")
}
func (sc StateCounts) Total() (count int) {
for _, c := range sc {
count += c
}
return
}
func max(a, b int) int {
if a > b {
return a
}
return b
}
type State struct {
p1, p2 int
s1, s2 int
}
func (s State) Done() bool {
return s.s1 >= 21 || s.s2 >= 21
}
func (s State) Win1() bool {
return s.s1 >= 21
}
var diracDie = []int{1, 2, 3}
var diracRolls = makeDiracRolls()
func makeDiracRolls() (rolls []int) {
for _, r1 := range diracDie {
for _, r2 := range diracDie {
for _, r3 := range diracDie {
rolls = append(rolls, r1+r2+r3)
}
}
}
return
}
func (s State) Successors() (active StateCounts, done StateCounts) {
active, done = make(StateCounts), make(StateCounts)
for _, r1 := range diracRolls {
t1 := s.P1(r1)
if t1.Done() {
done[t1]++
continue
}
for _, r2 := range diracRolls {
t2 := t1.P2(r2)
if t2.Done() {
done[t2]++
continue
}
active[t2]++
}
}
return
}
func (s State) P1(roll int) State {
next := s
next.p1 = move(s.p1, roll)
next.s1 = s.s1 + score(next.p1)
return next
}
func (s State) P2(roll int) State {
next := s
next.p2 = move(s.p2, roll)
next.s2 = s.s2 + score(next.p2)
return next
}