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 ( CLEAR_SCREEN = "\033[H\033[2J" MAX_INT = 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 { // Sort the players on the field 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)) // Tick every player for _, v := range charSlice { if char, ok := characters[*v]; ok { if !char.tick() { break } } } if true { time.Sleep(time.Millisecond * 250) printBattlefield() fmt.Println() } 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 { // Check if this character has any enemies on the field if !c.hasEnemies() { fmt.Println(c.string(), "is unopposed") return false } // Now move/attack if _, err := c.easiestAdjacentTarget(); err != nil { // Ok, figure out a move nxt, tgt := c.findMove() if nxt != nil { fmt.Println(c.string(), "is moving to", *nxt, "(", *tgt, ")") c.moveTo(*nxt) } } if tPos, err := c.easiestAdjacentTarget(); err == nil { c.attack(tPos) } return c.hasEnemies() } // findMove returns the position this character should move to and the position // that is it's ultimate target func (c *Character) findMove() (*complex64, *complex64) { var opps []*complex64 closestTargetDistance := MAX_INT dist, path := findAllPaths(&c.pos) for _, v := range characters { if v.health <= 0 { continue } if v.tp != c.tp { for _, t := range v.getOpenSides() { if d, ok := dist[t]; ok && d <= closestTargetDistance { if d < closestTargetDistance { closestTargetDistance = d opps = []*complex64{} } opps = append(opps, &t) } } } sort.Sort(ByPos(opps)) if len(opps) > 0 { t := opps[0] curr := *t for { if pv == c.pos { return &curr, t } curr = pv } } } return nil, nil } 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) 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 getOpenSides(c complex64) []complex64 { var ret []complex64 for _, d := range []complex64{DIR_N, DIR_E, DIR_S, DIR_W} { if getByte(c+d) == '.' { ret = append(ret, c+d) } } return ret } // findAllPaths returns a map of all distances and a map of all paths func findAllPaths(start *complex64) (map[complex64]int, map[complex64]complex64) { all := []complex64{*start} dist := map[complex64]int{*start: 0} prev := map[complex64]complex64{*start: 0} for len(all) > 0 { c := all[0] all = all[1:] for _, n := range getOpenSides(c) { if _, ok := dist[n]; !ok { all = append(all, n) dist[n] = dist[c] + 1 prev[n] = c } } } return dist, prev } func printBattlefield() { //fmt.Print(CLEAR_SCREEN) 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: MAX_INT, } 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 == MAX_INT { 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])) }