Skip to content

Commit ddaf7f8

Browse files
author
Liz Fong-Jones
committed
add day24ab solution.
1 parent e148473 commit ddaf7f8

File tree

1 file changed

+234
-0
lines changed

1 file changed

+234
-0
lines changed

2018/day24.go

Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
package main
2+
3+
import (
4+
"bufio"
5+
"flag"
6+
"fmt"
7+
"os"
8+
"sort"
9+
"strconv"
10+
"strings"
11+
)
12+
13+
var inputFile = flag.String("inputFile", "inputs/day24.input", "Relative file path to use as input.")
14+
var verbose = flag.Bool("verbose", false, "Whether to print verbose debug output.")
15+
16+
type Group struct {
17+
Side string
18+
Count int
19+
PerUnitHP int
20+
Weaknesses map[string]bool
21+
Immunities map[string]bool
22+
Damage map[string]int
23+
Init int
24+
}
25+
26+
func (u *Group) ComputeDamage(t *Group) int {
27+
totalDmg := 0
28+
for kind, dmg := range u.Damage {
29+
if t.Immunities[kind] {
30+
continue
31+
}
32+
totalDmg += dmg * u.Count
33+
if t.Weaknesses[kind] {
34+
totalDmg += dmg * u.Count
35+
}
36+
}
37+
return totalDmg
38+
}
39+
40+
type ByTurnOrder []*Group
41+
42+
func (b ByTurnOrder) Len() int { return len(b) }
43+
func (b ByTurnOrder) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
44+
func (b ByTurnOrder) Less(i, j int) bool {
45+
iDmg := 0
46+
for _, v := range b[i].Damage {
47+
iDmg += v
48+
}
49+
jDmg := 0
50+
for _, v := range b[j].Damage {
51+
jDmg += v
52+
}
53+
if iDmg*b[i].Count != jDmg*b[j].Count {
54+
return iDmg*b[i].Count > jDmg*b[j].Count
55+
}
56+
return b[i].Init > b[j].Init
57+
}
58+
59+
func main() {
60+
flag.Parse()
61+
for b := 0; ; b++ {
62+
winner, units := RunCombat(b)
63+
if b == 0 {
64+
fmt.Printf("Number of units remaining with no boost: %d\n", units)
65+
}
66+
if winner {
67+
fmt.Printf("Immune system wins with %d boost: %d units alive\n", b, units)
68+
break
69+
}
70+
}
71+
}
72+
73+
func RunCombat(b int) (bool, int) {
74+
f, err := os.Open(*inputFile)
75+
if err != nil {
76+
return true, -1
77+
}
78+
defer f.Close()
79+
80+
combatants := make([]*Group, 0)
81+
combatantsByInit := make(map[int]*Group)
82+
83+
r := bufio.NewReader(f)
84+
var side string
85+
var maxInit int
86+
for {
87+
l, err := r.ReadString('\n')
88+
if err != nil {
89+
break
90+
}
91+
if len(l) <= 1 {
92+
continue
93+
}
94+
l = l[:len(l)-1]
95+
if l[len(l)-1] == ':' {
96+
// This is a new side definition.
97+
side = l[:len(l)-1]
98+
continue
99+
}
100+
parsed := strings.SplitN(l, " ", 8)
101+
count, _ := strconv.Atoi(parsed[0])
102+
hp, _ := strconv.Atoi(parsed[4])
103+
parsed = strings.Split(parsed[7], "with an attack that does ")
104+
immunitiesString := parsed[0]
105+
immune := make(map[string]bool)
106+
weak := make(map[string]bool)
107+
if len(immunitiesString) > 1 {
108+
parts := strings.Split(immunitiesString[1:len(immunitiesString)-2], "; ")
109+
for _, v := range parts {
110+
spec := strings.SplitN(v, " ", 3)
111+
var kind map[string]bool
112+
switch spec[0] {
113+
case "immune":
114+
kind = immune
115+
case "weak":
116+
kind = weak
117+
}
118+
for _, t := range strings.Split(spec[2], ", ") {
119+
kind[t] = true
120+
}
121+
}
122+
}
123+
124+
damageAndInitString := strings.Split(parsed[1], " damage at initiative ")
125+
damage := strings.Split(damageAndInitString[0], " ")
126+
dmgType := damage[1]
127+
dmg, _ := strconv.Atoi(damage[0])
128+
if side == "Immune System" {
129+
dmg += b
130+
}
131+
damageMap := map[string]int{dmgType: dmg}
132+
init, _ := strconv.Atoi(damageAndInitString[1])
133+
if init > maxInit {
134+
maxInit = init
135+
}
136+
group := Group{side, count, hp, weak, immune, damageMap, init}
137+
combatants = append(combatants, &group)
138+
combatantsByInit[init] = &group
139+
}
140+
141+
for round := 1; ; round++ {
142+
if *verbose {
143+
fmt.Printf("\nRound %d\n", round)
144+
}
145+
// Pick targets.
146+
turnOrder := make([]*Group, len(combatants))
147+
148+
// Indexed by targeting group.
149+
targets := make(map[*Group]*Group)
150+
151+
// Indexed by targeted group.
152+
targeted := make(map[*Group]bool)
153+
154+
copy(turnOrder, combatants)
155+
sort.Sort(ByTurnOrder(turnOrder))
156+
157+
for _, u := range turnOrder {
158+
if u.Count <= 0 {
159+
// We're dead, skip.
160+
continue
161+
}
162+
highestDamage := 0
163+
potentialTargets := make([]*Group, 0)
164+
for _, t := range combatants {
165+
if u.Side == t.Side || t.Count <= 0 || targeted[t] {
166+
continue
167+
}
168+
// We know this is an enemy unit. Compute the damage we would do.
169+
totalDmg := u.ComputeDamage(t)
170+
if totalDmg > 0 {
171+
if totalDmg > highestDamage {
172+
potentialTargets = []*Group{t}
173+
highestDamage = totalDmg
174+
} else if totalDmg < highestDamage {
175+
continue
176+
}
177+
potentialTargets = append(potentialTargets, t)
178+
}
179+
}
180+
if len(potentialTargets) == 0 {
181+
// Couldn't damage anything
182+
continue
183+
}
184+
sort.Sort(ByTurnOrder(potentialTargets))
185+
target := potentialTargets[0]
186+
targeted[target] = true
187+
targets[u] = target
188+
}
189+
190+
deaths := 0
191+
for i := maxInit; i >= 0; i-- {
192+
u := combatantsByInit[i]
193+
if u == nil || u.Count <= 0 {
194+
continue
195+
}
196+
t := targets[u]
197+
if t == nil {
198+
continue
199+
}
200+
killed := u.ComputeDamage(t) / t.PerUnitHP
201+
if killed > t.Count {
202+
killed = t.Count
203+
}
204+
t.Count -= killed
205+
deaths += killed
206+
if *verbose {
207+
fmt.Printf("Unit at init %d killed %d units of init %d.\n", u.Init, killed, t.Init)
208+
}
209+
}
210+
if deaths == 0 {
211+
if *verbose {
212+
fmt.Println("Stalemated with boost %d.\n", b)
213+
}
214+
return false, -1
215+
}
216+
217+
// See if only one side is left alive.
218+
alive := make(map[string]int)
219+
for _, c := range combatants {
220+
if c.Count > 0 {
221+
alive[c.Side] += c.Count
222+
}
223+
}
224+
225+
if len(alive) <= 1 {
226+
for k, v := range alive {
227+
if *verbose {
228+
fmt.Printf("Winning side is %s with %d alive.\n", k, v)
229+
}
230+
return k == "Immune System", v
231+
}
232+
}
233+
}
234+
}

0 commit comments

Comments
 (0)