Day 15 commit, not done

This commit is contained in:
Brian Buller 2018-12-16 01:57:03 +00:00
parent 943079f64f
commit 08c6209701
8 changed files with 685 additions and 0 deletions

203
2018/day15/day15-overthink Normal file
View File

@ -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...)
}
}

413
2018/day15/day15.go Normal file
View File

@ -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]))
}

32
2018/day15/input Normal file
View File

@ -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.#######
###########......#.......#######
################################

7
2018/day15/testinput1 Normal file
View File

@ -0,0 +1,7 @@
#######
#G..#E#
#E#E.E#
#G.##.#
#...#E#
#...E.#
#######

7
2018/day15/testinput2 Normal file
View File

@ -0,0 +1,7 @@
#######
#E..EG#
#.#G.E#
#E.##E#
#G..#.#
#..E#.#
#######

7
2018/day15/testinput3 Normal file
View File

@ -0,0 +1,7 @@
#######
#E.G#.#
#.#G..#
#G.#.G#
#G..#.#
#...E.#
#######

7
2018/day15/testinput4 Normal file
View File

@ -0,0 +1,7 @@
#######
#.E...#
#.#..G#
#.###.#
#E#G#G#
#...#G#
#######

9
2018/day15/testinput5 Normal file
View File

@ -0,0 +1,9 @@
#########
#G......#
#.E.#...#
#..##..G#
#...##..#
#...#...#
#.G...G.#
#.....G.#
#########