package main import ( "bufio" "errors" "fmt" "math" "os" "sort" "time" ) /* * 173327 is too low */ var input []byte var width int var characters map[complex64]*Character var charSlice []*complex64 var turnCount int const ( ClearScreen = "\033[H\033[2J" MaxInt = int(^uint(0) >> 1) DIR_N = -1i DIR_E = 1 DIR_S = 1i DIR_W = -1 ) func main() { characters = make(map[complex64]*Character) stdinToByteSlice() setupBattle() part1() } func part1() { for { charSlice = charSlice[0:0] for i := 0; i < len(input); i++ { pos := getPosFromInt(i) bt := getByte(pos) if bt == 'G' || bt == 'E' { charSlice = append(charSlice, &pos) } } sort.Sort(ByPos(charSlice)) fmt.Printf("= Turn %d =\n", turnCount) printBattlefield() for _, v := range charSlice { if char, ok := characters[*v]; ok { if !char.tick() { break } } } if false { time.Sleep(time.Millisecond * 250) } printBattlefield() if checkBattleOver() { break } turnCount++ } var totalHP int sort.Sort(ByPos(charSlice)) for _, v := range charSlice { if c, ok := characters[*v]; ok { fmt.Println(c.string()) totalHP += c.health } } fmt.Println(turnCount, totalHP) fmt.Println("Result:", (totalHP * turnCount)) } func checkBattleOver() bool { var elves, gobs int for _, v := range characters { if v.tp == 'E' { elves++ } else if v.tp == 'G' { gobs++ } } return elves == 0 || gobs == 0 } type Character struct { tp byte health int power int pos complex64 kills int } func (c *Character) hasEnemies() bool { for _, oppPos := range charSlice { if v, ok := characters[*oppPos]; ok { if v.tp != c.tp { return true } } } return false } func (c *Character) tick() bool { // If we're already in range of a target, don't look for a new one if tPos, err := c.easiestAdjacentTarget(); err == nil { c.attack(tPos) return c.hasEnemies() } // If we have no open sides, we can't more if !c.hasOpenFlank() { fmt.Println(c.string(), "blocked in") return true } // Otherwise, find a target var opportunities []complex64 for _, oppPos := range charSlice { if v, ok := characters[*oppPos]; ok { if v.tp != c.tp { opportunities = append(opportunities, *oppPos) } } } /* var opportunities []complex64 for _, oppPos := range charSlice { if v, ok := characters[*oppPos]; ok { if v.tp != c.tp { fmt.Println(c.string(), "checking:", v.string()) opportunities = append(opportunities, v.getOpenSides()...) } } } closestDistance := MaxInt var closestPos complex64 for _, v := range opportunities { if distance(c.pos, v) < closestDistance { closestDistance = distance(c.pos, v) closestPos = v } } */ // If there are no opportunities, we're done if len(opportunities) == 0 { fmt.Println(c.string(), "sees no opportunities") return true } var bestOpportunities []*complex64 moves := make(map[complex64]complex64) bestDist := MaxInt for _, opp := range opportunities { paths := append([]pathPoint{}, pathPoint{ pos: opp, count: 0, }) move, length := findMove(c.pos, paths) if length < bestDist { bestOpportunities = append([]*complex64{}, &opp) for k := range moves { delete(moves, k) } moves[opp] = move bestDist = length } else if length == bestDist { bestOpportunities = append(bestOpportunities, &opp) moves[opp] = move } } sort.Sort(ByPos(bestOpportunities)) //paths := append([]pathPoint{}, pathPoint{ // pos: closestPos, // count: 0, //}) //move := findMove(c.pos, paths) if len(bestOpportunities) > 0 { goWith := bestOpportunities[0] fmt.Println(c.string(), "moving to", getCoordString(moves[*goWith]), "(->", getCoordString(*goWith), bestDist, ")") c.moveTo(*goWith) } else { fmt.Println(c.string(), "can't find a path") } // After moving, check for an attack if tPos, err := c.easiestAdjacentTarget(); err == nil { c.attack(tPos) } return c.hasEnemies() } func (c *Character) string() string { return fmt.Sprintf("[%s%s:%d]", string(c.tp), getCoordString(c.pos), c.health) } func (c *Character) attack(p complex64) { fmt.Println(c.string(), "attacks", characters[p].string()) characters[p].health -= c.power if characters[p].health <= 0 { c.kills++ delete(characters, p) setByte(p, '.') } } func (c *Character) easiestAdjacentTarget() (complex64, error) { var wrk *Character if v, ok := characters[c.pos+DIR_N]; ok && v != nil && v.tp != c.tp { wrk = v } if v, ok := characters[c.pos+DIR_W]; ok && v != nil && v.tp != c.tp { if wrk == nil || v.health < wrk.health { wrk = v } } if v, ok := characters[c.pos+DIR_E]; ok && v != nil && v.tp != c.tp { if wrk == nil || v.health < wrk.health { wrk = v } } if v, ok := characters[c.pos+DIR_S]; ok && v != nil && v.tp != c.tp { if wrk == nil || v.health < wrk.health { wrk = v } } if wrk != nil { return wrk.pos, nil } return 0i, errors.New("No adjacent target") } func (c *Character) isAdjacentTo(p complex64) bool { return c.pos+DIR_N == p || c.pos+DIR_E == p || c.pos+DIR_S == p || c.pos+DIR_W == p } func (c *Character) getOpenSides() []complex64 { var ret []complex64 for _, d := range []complex64{DIR_N, DIR_W, DIR_E, DIR_S} { if getByte(c.pos+d) == '.' { ret = append(ret, c.pos+d) } } return ret } func (c *Character) hasOpenFlank() bool { for _, d := range []complex64{DIR_N, DIR_E, DIR_S, DIR_W} { if getByte(c.pos+d) == '.' { return true } } return false } func (c *Character) move(dir complex64) bool { if getByte(c.pos+dir) != '.' { return false } delete(characters, c.pos) setByte(c.pos, '.') c.pos += dir characters[c.pos] = c setByte(c.pos, c.tp) return true } func (c *Character) moveTo(pos complex64) bool { if getByte(pos) != '.' { return false } delete(characters, c.pos) setByte(c.pos, '.') c.pos = pos characters[c.pos] = c setByte(c.pos, c.tp) return true } func printBattlefield() { //fmt.Print(ClearScreen) for i := 0; i < len(input)/width; i++ { fmt.Println(string(input[i*width : (i+1)*width])) } } func setupBattle() { characters = make(map[complex64]*Character) for i := 0; i < len(input); i++ { pos := getPosFromInt(i) bt := getByte(pos) if bt == 'G' || bt == 'E' { characters[pos] = &Character{ tp: bt, pos: pos, power: 3, health: 200, } charSlice = append(charSlice, &pos) } } } func isInMap(pos complex64) bool { idx := int(real(pos)) + int(imag(pos))*width return idx >= 0 && idx < len(input) } // getByte pulls a byte from the given position in the input func getByte(pos complex64) byte { return input[int(real(pos))+int(imag(pos))*width] } // setByte sets a byte in the input func setByte(pos complex64, b byte) { input[int(real(pos))+int(imag(pos))*width] = b } func getPosFromInt(i int) complex64 { return complex(float32(i%width), float32(i/width)) } func getCoordString(p complex64) string { return fmt.Sprintf("(%d,%d)", int(real(p)), int(imag(p))) } func stdinToByteSlice() { scanner := bufio.NewScanner(os.Stdin) for scanner.Scan() { data := scanner.Bytes() if width == 0 { width = len(data) } input = append(input, data...) } } // Returns the next move for the shortest path from p1 to p2 type pathPoint struct { pos complex64 count int } func findMove(p1 complex64, paths []pathPoint) (complex64, int) { pathCount := len(paths) // First check if p1 has _any_ possible moves lowest := pathPoint{ pos: 0 - 1i, count: MaxInt, } for _, v := range paths { for _, d := range []complex64{DIR_N, DIR_E, DIR_S, DIR_W} { wrkPt := pathPoint{ pos: v.pos + d, count: v.count + 1, } if !isInMap(wrkPt.pos) { continue } if wrkPt.pos == p1 { if wrkPt.count < lowest.count { lowest.pos = v.pos lowest.count = v.count } } if getByte(wrkPt.pos) != '.' { continue } var skip bool for _, v2 := range paths { if v2.pos == wrkPt.pos && v2.count <= wrkPt.count { skip = true break } } if skip { continue } paths = append(paths, wrkPt) } } if len(paths) != pathCount && lowest.count == MaxInt { return findMove(p1, paths) } // We hit the end, return the lowest part return lowest.pos, lowest.count } // (Manhattan Distance, thanks earlier day) func distance(p1, p2 complex64) int { x1, y1, x2, y2 := real(p1), imag(p1), real(p2), imag(p2) return int(math.Abs(float64(x1)-float64(x2)) + math.Abs(float64(y1)-float64(y2))) } type ByPos []*complex64 func (c ByPos) Len() int { return len(c) } func (c ByPos) Swap(i, j int) { c[i], c[j] = c[j], c[i] } func (c ByPos) Less(i, j int) bool { return imag(*c[i]) < imag(*c[j]) || (imag(*c[i]) == imag(*c[j]) && real(*c[i]) < real(*c[j])) }