diff --git a/2018/day15/day15-overthink b/2018/day15/day15-overthink new file mode 100644 index 0000000..84a794c --- /dev/null +++ b/2018/day15/day15-overthink @@ -0,0 +1,203 @@ +package overthink + +import ( + "bufio" + "fmt" + "math" + "os" + "sort" + "time" +) + +const ( + UseEmoji = false + MaxInt = int(^uint(0) >> 1) + ClearScreen = "\033[H\033[2J" + + DIR_N = -1i + DIR_E = 1 + DIR_S = 1i + DIR_W = -1 +) + +var width int +var input []byte + +var elves []*complex64 +var goblins []*complex64 +var allChars []*complex64 +var charMap map[complex64]*Character + +func main() { + stdinToByteSlice() + setupBattle() + part1() +} + +func part1() { + //for { + sortCharacters() + printBattleField() + for _, c := range allChars { + charMap[*c].tick() + } + time.Sleep(time.Millisecond * 250) + printBattleField() + //} + fmt.Println("") +} + +func sortCharacters() { + sort.Sort(ByPos(allChars)) + sort.Sort(ByPos(elves)) + sort.Sort(ByPos(goblins)) +} + +type Character struct { + tp byte + pos complex64 + power int + health int +} + +func (c *Character) tick() { + // If we're already in range of a target, don't look for a new one + var alreadyAtTarget bool + var chosenTarget *complex64 + lowestTargetHp := MaxInt + for _, v := range elves { + if c.isAdjacentTo(*v) { + if charMap[*v].health < lowestTargetHp { + chosenTarget = v + } + alreadyAtTarget = true + } + } + if alreadyAtTarget { + // Attack the target + charMap[*chosenTarget].health -= c.power + } else { + // Looking for a target + if c.tp == 'G' { + // First identify all possible targets (elves that have an open adjacent space) + for _, v := range elves { + + } + } else { + // First identify all possible targets (goblins that have an open adjacent space) + for _, v := range goblins { + } + } + } +} + +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) hasOpenFlank() bool { + +} + +// Not sure if we'll use this... +func manhattanDistance(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))) +} + +// We have to sort the characters on each tick by y,x position +// y is the imaginary part, x is the real part +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])) +} + +// 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] +} + +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 printBattleField() { + fmt.Print(ClearScreen) + for i := 0; i < len(input); i++ { + pos := getPosFromInt(i) + var bt byte + if c, ok := charMap[pos]; ok { + if UseEmoji { + switch c.tp { + case 'G': + if UseEmoji { + fmt.Print("👺") + } + case 'E': + fmt.Print("😃") + } + } else { + fmt.Print(string(c.tp)) + } + bt = c.tp + } else { + bt = getByte(pos) + if UseEmoji { + if bt == '#' { + fmt.Print("🏿") + } else { + fmt.Print(" ") + } + } else { + fmt.Print(string(bt)) + } + } + if i%width == width-1 { + fmt.Println("") + } + } +} + +func setupBattle() { + charMap = make(map[complex64]*Character) + for i := 0; i < len(input); i++ { + pos := complex(float32(i%width), float32(i/width)) + bt := getByte(pos) + if bt == 'G' || bt == 'E' { + charMap[pos] = &Character{ + tp: bt, + pos: pos, + power: 3, + health: 200, + } + setByte(pos, '.') + allChars = append(allChars, &charMap[pos].pos) + switch bt { + case 'G': + goblins = append(goblins, &charMap[pos].pos) + case 'E': + elves = append(elves, &charMap[pos].pos) + } + } + } +} + +func stdinToByteSlice() { + scanner := bufio.NewScanner(os.Stdin) + for scanner.Scan() { + data := scanner.Bytes() + if width == 0 { + width = len(data) + } + input = append(input, data...) + } +} diff --git a/2018/day15/day15.go b/2018/day15/day15.go new file mode 100644 index 0000000..d7aae8d --- /dev/null +++ b/2018/day15/day15.go @@ -0,0 +1,413 @@ +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])) +} diff --git a/2018/day15/input b/2018/day15/input new file mode 100644 index 0000000..10831a4 --- /dev/null +++ b/2018/day15/input @@ -0,0 +1,32 @@ +################################ +##########################..#### +#########################...#### +#########################..##### +########################G..##### +#####################.#.....##.# +#####################..........# +##############.#####...........# +########G...G#.####............# +#######......G....#.....#......# +#######...G....GG.#............# +#######G.G.............####....# +#######.#.....#####....E.....### +#######......#######.G.......### +#..####..G..#########.###..##### +#........G..#########.########## +#..#..#G....#########.########## +#.###...E...#########.########## +#####...G.G.#########.########## +########G....#######..########## +####..........#####...########## +####......E........G..########## +#.G..................########### +#G...................########### +###.....##E.......E..########### +###....#............############ +###.................############ +##G.....#.............########## +###########...#E..##..########## +###########.E...###.E.EE.####### +###########......#.......####### +################################ diff --git a/2018/day15/testinput1 b/2018/day15/testinput1 new file mode 100644 index 0000000..ac399d6 --- /dev/null +++ b/2018/day15/testinput1 @@ -0,0 +1,7 @@ +####### +#G..#E# +#E#E.E# +#G.##.# +#...#E# +#...E.# +####### diff --git a/2018/day15/testinput2 b/2018/day15/testinput2 new file mode 100644 index 0000000..58f778d --- /dev/null +++ b/2018/day15/testinput2 @@ -0,0 +1,7 @@ +####### +#E..EG# +#.#G.E# +#E.##E# +#G..#.# +#..E#.# +####### diff --git a/2018/day15/testinput3 b/2018/day15/testinput3 new file mode 100644 index 0000000..6dc1c08 --- /dev/null +++ b/2018/day15/testinput3 @@ -0,0 +1,7 @@ +####### +#E.G#.# +#.#G..# +#G.#.G# +#G..#.# +#...E.# +####### diff --git a/2018/day15/testinput4 b/2018/day15/testinput4 new file mode 100644 index 0000000..2343d7b --- /dev/null +++ b/2018/day15/testinput4 @@ -0,0 +1,7 @@ +####### +#.E...# +#.#..G# +#.###.# +#E#G#G# +#...#G# +####### diff --git a/2018/day15/testinput5 b/2018/day15/testinput5 new file mode 100644 index 0000000..95882b2 --- /dev/null +++ b/2018/day15/testinput5 @@ -0,0 +1,9 @@ +######### +#G......# +#.E.#...# +#..##..G# +#...##..# +#...#...# +#.G...G.# +#.....G.# +#########