package main import ( "bufio" "fmt" "os" "sort" "strings" "github.com/davecgh/go-spew/spew" ) type GroupType int const ( GroupImmune GroupType = iota GroupInfection ) func (g GroupType) String() string { if g == GroupImmune { return "immunesystem" } return "infection" } type Group struct { ID int Type GroupType Amount int HP int Weak []string Immune []string Damage int DamageType string Initiative int EP int } func (g *Group) PotentialDamage(target *Group) int { if inSlice(g.DamageType, target.Immune) { return 0 } if inSlice(g.DamageType, target.Weak) { return g.EP * 2 } return g.EP } type ByInit []Group func (b ByInit) Len() int { return len(b) } func (b ByInit) Swap(i, j int) { b[i], b[j] = b[j], b[i] } func (b ByInit) Less(i, j int) bool { return b[i].Initiative > b[j].Initiative } func waitEnter() { fmt.Printf("Press ENTER to continue\n") bufio.NewReader(os.Stdin).ReadBytes('\n') } func sliceEqual(a, b []int) bool { if len(a) != len(b) { return false } for t := range a { if a[t] != b[t] { return false } } return true } func abs(a int) int { if a < 0 { a = -a } return a } func max(a, b int) int { if a > b { return a } return b } func manhattan(a, b Point) int { return abs(a.X-b.X) + abs(a.Y-b.Y) + abs(a.Z-b.Z) } func inSlice(a string, b []string) bool { for _, c := range b { if a == c { return true } } return false } func findGroup(groups []Group, id int, groupType GroupType) *Group { for t := range groups { if groups[t].ID == id && groups[t].Type == groupType { return &groups[t] } } return nil } func existsInTurnOrder(group *Group, turnorder []Turn) *Group { for t := range turnorder { if turnorder[t].Target.ID == group.ID && turnorder[t].Target.Type == group.Type { return turnorder[t].Target } } return nil } func findTarget(attacker *Group, turnorder []Turn, groups []Group) (*Group, int) { highest := -1 index := -1 init := -1 hEP := -1 for t := range groups { if groups[t].Amount <= 0 { continue } if (attacker.ID == groups[t].ID && attacker.Type == groups[t].Type) || attacker.Type == groups[t].Type { continue } if existsInTurnOrder(&groups[t], turnorder) != nil { continue } potential := attacker.PotentialDamage(&groups[t]) if potential > highest { highest = potential index = t init = groups[t].Initiative hEP = groups[t].EP } else if potential == highest && (groups[t].EP > hEP || groups[t].Initiative > init) { highest = potential index = t init = groups[t].Initiative hEP = groups[t].EP } } if index > -1 { return &groups[index], highest } return nil, -1 } type Point struct { X, Y, Z int } type Puzzle struct { Groups []Group } func NewPuzzle(filepath string, boost int) *Puzzle { p := Puzzle{ Groups: make([]Group, 0, 10), } input, err := os.Open(filepath) if err != nil { panic(err.Error()) } scanner := bufio.NewScanner(input) id := 1 groupType := GroupImmune for t := 0; t < 2; t++ { scanner.Scan() if err := scanner.Err(); err != nil { panic(err.Error()) } for scanner.Scan() { var num, hp, pow, init int immune := make([]string, 0, 4) weak := make([]string, 0, 4) var damagetype string line := scanner.Text() if line == "" { scanner.Scan() id = 1 groupType = GroupInfection continue } parts := strings.FieldsFunc(line, func(c rune) bool { return c == '(' || c == ')' }) if len(parts) > 1 { vparts := strings.Split(parts[1], ";") for _, vul := range vparts { vul = strings.Trim(vul, " ") vulp := strings.FieldsFunc(vul, func(c rune) bool { return c == ' ' || c == ',' }) for t := 2; t < len(vulp); t++ { switch vulp[0] { case "weak": weak = append(weak, vulp[t]) case "immune": immune = append(immune, vulp[t]) } } } } fmt.Sscanf(parts[0], "%d units each with %d hit points", &num, &hp) index := 0 if len(parts) > 1 { index = 2 } fmt.Sscanf(parts[index], " with an attack that does %d %s damage at initiative %d", &pow, &damagetype, &init) if groupType == GroupImmune { pow += boost } p.Groups = append(p.Groups, Group{ ID: id, Type: groupType, Amount: num, Damage: pow, DamageType: damagetype, HP: hp, Immune: immune, Initiative: init, Weak: weak, EP: num * pow, }) id++ } if err := scanner.Err(); err != nil { panic(err.Error()) } } // spew.Dump(p) input.Close() return &p } type Turn struct { Group *Group Target *Group } func (t *Turn) Exec() int { damage := t.Group.PotentialDamage(t.Target) deaths := damage / t.Target.HP t.Target.Amount -= deaths return deaths } func (p *Puzzle) Sim(pretty bool) GroupType { for { turnorder := make([]Turn, 0, 10) sort.Sort(ByInit(p.Groups)) if pretty { fmt.Println("\nTarget phase:") } // Decide turn order for t := range p.Groups { if p.Groups[t].Amount <= 0 { continue } target, potential := findTarget(&p.Groups[t], turnorder, p.Groups) if target == nil { if pretty { fmt.Println("no targets") } continue } if pretty { fmt.Printf("#%d:%s -> #%d:%s for %d damage\n", p.Groups[t].ID, p.Groups[t].Type, target.ID, target.Type, potential) } turnorder = append(turnorder, Turn{ Group: &p.Groups[t], Target: target, }) } if len(turnorder) == 0 { fmt.Println("Combat over") if pretty { spew.Dump(p.Groups) } sum := 0 groupType := GroupInfection for _, g := range p.Groups { if g.Amount > 0 { sum += g.Amount fmt.Printf("#%d:%s contains %d units\n", g.ID, g.Type, g.Amount) groupType = g.Type } } fmt.Println("Sum alive:", sum, groupType) return groupType } if pretty { fmt.Println("\nAttack phase:") } // Execute for _, t := range turnorder { if pretty { fmt.Printf("#%d:%s -> #%d:%s", t.Group.ID, t.Group.Type, t.Target.ID, t.Target.Type) num := t.Exec() fmt.Printf(", killing %d units\n", num) } else { t.Exec() } } } } func main() { // Test1 t := NewPuzzle("test1.txt", 0) t.Sim(true) // Test2 boost := 1 for { t := NewPuzzle("test1.txt", boost) if t.Sim(false) == GroupImmune { break } boost++ } // Puzzle1 p := NewPuzzle("input.txt", 0) p.Sim(false) // Puzzle2 boost = 1 for { p := NewPuzzle("input.txt", boost) if p.Sim(false) == GroupImmune { break } boost++ } }