2018 Day 15 Completed!
This commit is contained in:
parent
bd274c826c
commit
f03772c578
153
2018/day15/cave.go
Normal file
153
2018/day15/cave.go
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Cave struct {
|
||||||
|
Units SortableUnits
|
||||||
|
Map Map
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
KindSpace = 1 << iota
|
||||||
|
KindElf
|
||||||
|
KindGoblin
|
||||||
|
KindWall
|
||||||
|
KindHighlight
|
||||||
|
)
|
||||||
|
|
||||||
|
var KindRunes = map[int]rune{
|
||||||
|
KindSpace: '.',
|
||||||
|
KindElf: 'E',
|
||||||
|
KindGoblin: 'G',
|
||||||
|
KindWall: '#',
|
||||||
|
KindHighlight: '@',
|
||||||
|
}
|
||||||
|
|
||||||
|
var RuneKinds = map[rune]int{
|
||||||
|
'.': KindSpace,
|
||||||
|
'E': KindElf,
|
||||||
|
'G': KindGoblin,
|
||||||
|
'#': KindWall,
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsUnit(bit int) bool {
|
||||||
|
return (KindElf|KindGoblin)&bit != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCave(input []string, elfPower int) *Cave {
|
||||||
|
c := &Cave{}
|
||||||
|
c.ParseMap(input, elfPower)
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cave) ParseMap(input []string, elfPower int) {
|
||||||
|
m := make(Map)
|
||||||
|
|
||||||
|
for y, row := range input {
|
||||||
|
for x, col := range row {
|
||||||
|
kind, ok := RuneKinds[col]
|
||||||
|
if !ok {
|
||||||
|
kind = KindWall
|
||||||
|
}
|
||||||
|
tile := &Tile{Kind: kind}
|
||||||
|
if IsUnit(kind) {
|
||||||
|
c.Units = append(c.Units, NewUnit(tile, kind, elfPower))
|
||||||
|
}
|
||||||
|
m.SetTile(tile, x, y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.Map = m
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Cave) PrintMap(highlight *Tile) {
|
||||||
|
for y := 0; y < len(c.Map); y++ {
|
||||||
|
var units []string
|
||||||
|
for x := 0; x < len(c.Map[y]); x++ {
|
||||||
|
t := c.Map.Tile(x, y)
|
||||||
|
if t == highlight {
|
||||||
|
fmt.Print(string(KindRunes[KindHighlight]))
|
||||||
|
} else {
|
||||||
|
fmt.Print(string(KindRunes[t.Kind]))
|
||||||
|
}
|
||||||
|
if t.Unit != nil {
|
||||||
|
units = append(units, fmt.Sprintf("%c(%d)", KindRunes[t.Unit.Kind], t.Unit.Hitpoints))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(units) > 0 {
|
||||||
|
fmt.Print(" ", strings.Join(units, ", "))
|
||||||
|
}
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Cave) PrintDistance(t *Tile) {
|
||||||
|
distances, _ := c.Map.FindWalkableTiles(t)
|
||||||
|
for y := 0; y < len(c.Map); y++ {
|
||||||
|
for x := 0; x < len(c.Map[y]); x++ {
|
||||||
|
curT := c.Map.Tile(x, y)
|
||||||
|
if d, ok := distances[curT]; ok && curT != t {
|
||||||
|
fmt.Print(d)
|
||||||
|
} else {
|
||||||
|
fmt.Print(string(KindRunes[curT.Kind]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Cave) Status() (int, bool) {
|
||||||
|
var elves, goblins bool
|
||||||
|
var hp int
|
||||||
|
|
||||||
|
for _, u := range c.Units {
|
||||||
|
if u.Hitpoints <= 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if u.Kind == KindElf {
|
||||||
|
elves = true
|
||||||
|
} else {
|
||||||
|
goblins = true
|
||||||
|
}
|
||||||
|
hp = hp + u.Hitpoints
|
||||||
|
}
|
||||||
|
return hp, elves && goblins
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cave) RemoveTheDead() {
|
||||||
|
var newUnits SortableUnits
|
||||||
|
for _, unit := range c.Units {
|
||||||
|
if unit.Hitpoints > 0 {
|
||||||
|
newUnits = append(newUnits, unit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.Units = newUnits
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cave) RemoveUnit(u *Unit) {
|
||||||
|
u.Tile.Kind = KindSpace
|
||||||
|
u.Tile.Unit = nil
|
||||||
|
u.Tile = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tick returns false if combat ended during the round, and whether or not an elf has died this round
|
||||||
|
func (c *Cave) Tick(stopOnElfDeath bool) (bool, bool) {
|
||||||
|
c.RemoveTheDead()
|
||||||
|
sort.Sort(c.Units)
|
||||||
|
for _, unit := range c.Units {
|
||||||
|
if unit.Hitpoints <= 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !unit.Targets(c) {
|
||||||
|
return false, false
|
||||||
|
}
|
||||||
|
unit.Move(c)
|
||||||
|
if unit.Attack(c) && stopOnElfDeath {
|
||||||
|
return false, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true, false
|
||||||
|
}
|
@ -2,400 +2,60 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
|
||||||
"os"
|
"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() {
|
func main() {
|
||||||
characters = make(map[complex64]*Character)
|
inp := StdinToStringSlice()
|
||||||
stdinToByteSlice()
|
fmt.Println("# Part 1")
|
||||||
setupBattle()
|
Pt1Combat(inp)
|
||||||
part1()
|
|
||||||
|
fmt.Println("# Part 2")
|
||||||
|
Pt2Combat(inp)
|
||||||
}
|
}
|
||||||
|
|
||||||
func part1() {
|
func Pt1Combat(input []string) int {
|
||||||
for {
|
cave := NewCave(input, defaultPower)
|
||||||
// Sort the players on the field
|
for i := 1; true; i++ {
|
||||||
charSlice = charSlice[0:0]
|
hp, combat := cave.Status()
|
||||||
for i := 0; i < len(input); i++ {
|
if !combat {
|
||||||
pos := getPosFromInt(i)
|
res := (i - 1) * hp
|
||||||
bt := getByte(pos)
|
fmt.Printf("Result: %d\n", res)
|
||||||
if bt == 'G' || bt == 'E' {
|
return res
|
||||||
charSlice = append(charSlice, &pos)
|
}
|
||||||
|
if cleanRound, _ := cave.Tick(false); !cleanRound {
|
||||||
|
i--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
func Pt2Combat(input []string) int {
|
||||||
|
for power := defaultPower; true; power++ {
|
||||||
|
cave := NewCave(input, power)
|
||||||
|
for i := 1; true; i++ {
|
||||||
|
hp, combat := cave.Status()
|
||||||
|
if !combat {
|
||||||
|
res := (i - 1) * hp
|
||||||
|
fmt.Printf("Result: %d; Attack Power: %d\n", res, power)
|
||||||
|
return (i - 1) * hp
|
||||||
}
|
}
|
||||||
}
|
if cleanRound, elfDied := cave.Tick(true); elfDied {
|
||||||
sort.Sort(ByPos(charSlice))
|
break
|
||||||
|
} else if !cleanRound {
|
||||||
// Tick every player
|
i--
|
||||||
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
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Character) tick() bool {
|
func StdinToStringSlice() []string {
|
||||||
// Check if this character has any enemies on the field
|
var input []string
|
||||||
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)
|
scanner := bufio.NewScanner(os.Stdin)
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
data := scanner.Bytes()
|
input = append(input, scanner.Text())
|
||||||
if width == 0 {
|
|
||||||
width = len(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
input = append(input, data...)
|
|
||||||
}
|
}
|
||||||
}
|
return input
|
||||||
|
|
||||||
// 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]))
|
|
||||||
}
|
}
|
||||||
|
3
2018/day15/go.mod
Normal file
3
2018/day15/go.mod
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
module git.bullercodeworks.com/brian/adventofcode/2018/day15
|
||||||
|
|
||||||
|
go 1.13
|
49
2018/day15/map.go
Normal file
49
2018/day15/map.go
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
type Coordinate struct {
|
||||||
|
X, Y int
|
||||||
|
}
|
||||||
|
|
||||||
|
var offsets = []Coordinate{
|
||||||
|
{0, -1},
|
||||||
|
{-1, 0},
|
||||||
|
{1, 0},
|
||||||
|
{0, 1},
|
||||||
|
}
|
||||||
|
|
||||||
|
type Map map[int]map[int]*Tile
|
||||||
|
|
||||||
|
func (m Map) SetTile(t *Tile, x, y int) {
|
||||||
|
if m[y] == nil {
|
||||||
|
m[y] = make(map[int]*Tile)
|
||||||
|
}
|
||||||
|
m[y][x] = t
|
||||||
|
t.X = x
|
||||||
|
t.Y = y
|
||||||
|
t.Map = m
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m Map) Tile(x, y int) *Tile {
|
||||||
|
if m[y] == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return m[y][x]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m Map) FindWalkableTiles(t *Tile) (map[*Tile]int, map[*Tile]*Tile) {
|
||||||
|
frontier := []*Tile{t}
|
||||||
|
distance := map[*Tile]int{t: 0}
|
||||||
|
cameFrom := map[*Tile]*Tile{t: nil}
|
||||||
|
for len(frontier) > 0 {
|
||||||
|
current := frontier[0]
|
||||||
|
frontier = frontier[1:]
|
||||||
|
for _, next := range current.WalkableNeighbors() {
|
||||||
|
if _, ok := distance[next]; !ok {
|
||||||
|
frontier = append(frontier, next)
|
||||||
|
distance[next] = distance[current] + 1
|
||||||
|
cameFrom[next] = current
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return distance, cameFrom
|
||||||
|
}
|
34
2018/day15/tile.go
Normal file
34
2018/day15/tile.go
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
type Tile struct {
|
||||||
|
Kind int
|
||||||
|
X, Y int
|
||||||
|
Map Map
|
||||||
|
Unit *Unit
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Tile) WalkableNeighbors() []*Tile {
|
||||||
|
var neighbors []*Tile
|
||||||
|
for _, offset := range offsets {
|
||||||
|
if n := t.Map.Tile(t.X+offset.X, t.Y+offset.Y); n != nil && n.Kind == KindSpace {
|
||||||
|
neighbors = append(neighbors, n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return neighbors
|
||||||
|
}
|
||||||
|
|
||||||
|
type SortableTiles []*Tile
|
||||||
|
|
||||||
|
func (s SortableTiles) Len() int {
|
||||||
|
return len(s)
|
||||||
|
}
|
||||||
|
func (s SortableTiles) Less(i, j int) bool {
|
||||||
|
if s[i].Y == s[j].Y {
|
||||||
|
return s[i].X < s[j].X
|
||||||
|
}
|
||||||
|
return s[i].Y < s[j].Y
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s SortableTiles) Swap(i, j int) {
|
||||||
|
s[i], s[j] = s[j], s[i]
|
||||||
|
}
|
144
2018/day15/unit.go
Normal file
144
2018/day15/unit.go
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"sort"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Unit struct {
|
||||||
|
Kind int
|
||||||
|
Hitpoints int
|
||||||
|
Power int
|
||||||
|
Tile *Tile
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultHitpoints = 200
|
||||||
|
defaultPower = 3
|
||||||
|
)
|
||||||
|
|
||||||
|
type SortableUnits []*Unit
|
||||||
|
|
||||||
|
func (s SortableUnits) Len() int {
|
||||||
|
return len(s)
|
||||||
|
}
|
||||||
|
func (s SortableUnits) Less(i, j int) bool {
|
||||||
|
if s[i].Tile.Y == s[j].Tile.Y {
|
||||||
|
return s[i].Tile.X < s[j].Tile.X
|
||||||
|
}
|
||||||
|
return s[i].Tile.Y < s[j].Tile.Y
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s SortableUnits) Swap(i, j int) {
|
||||||
|
s[i], s[j] = s[j], s[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUnit(tile *Tile, kind, elfPower int) *Unit {
|
||||||
|
unit := &Unit{
|
||||||
|
Kind: kind,
|
||||||
|
Hitpoints: defaultHitpoints,
|
||||||
|
Power: defaultPower,
|
||||||
|
Tile: tile,
|
||||||
|
}
|
||||||
|
tile.Unit = unit
|
||||||
|
if unit.Kind == KindElf {
|
||||||
|
unit.Power = elfPower
|
||||||
|
}
|
||||||
|
return unit
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u Unit) Targets(c *Cave) bool {
|
||||||
|
for _, unit := range c.Units {
|
||||||
|
if unit.Kind != u.Kind && unit.Hitpoints > 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// NextTile returns the next tile to move to and the target tile (or nil if no target found)
|
||||||
|
func (u *Unit) NextTile(c *Cave) (*Tile, *Tile) {
|
||||||
|
var targets SortableTiles
|
||||||
|
closestTargetDistance := math.MaxInt32
|
||||||
|
distances, path := c.Map.FindWalkableTiles(u.Tile)
|
||||||
|
enemies := u.Enemies(c)
|
||||||
|
for _, enemy := range enemies {
|
||||||
|
for _, target := range enemy.Tile.WalkableNeighbors() {
|
||||||
|
if distance, ok := distances[target]; ok && distance <= closestTargetDistance {
|
||||||
|
if distance < closestTargetDistance {
|
||||||
|
closestTargetDistance = distance
|
||||||
|
targets = SortableTiles{}
|
||||||
|
}
|
||||||
|
targets = append(targets, target)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sort.Sort(targets)
|
||||||
|
if len(targets) > 0 {
|
||||||
|
target := targets[0]
|
||||||
|
current := target
|
||||||
|
for {
|
||||||
|
if path[current] == u.Tile {
|
||||||
|
return current, target
|
||||||
|
}
|
||||||
|
current = path[current]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enemies returns enemy units sorted by map position in reading order
|
||||||
|
func (u *Unit) Enemies(c *Cave) SortableUnits {
|
||||||
|
var enemies SortableUnits
|
||||||
|
for _, unit := range c.Units {
|
||||||
|
if unit.Kind != u.Kind && unit.Hitpoints > 0 {
|
||||||
|
enemies = append(enemies, unit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sort.Sort(enemies)
|
||||||
|
return enemies
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *Unit) EnemyNeighbor(c *Cave) *Unit {
|
||||||
|
var target *Unit
|
||||||
|
for _, offset := range offsets {
|
||||||
|
if t := c.Map.Tile(u.Tile.X+offset.X, u.Tile.Y+offset.Y); t != nil && t.Unit != nil && t.Unit.Kind != u.Kind && t.Unit.Hitpoints > 0 {
|
||||||
|
if target == nil || t.Unit.Hitpoints < target.Hitpoints {
|
||||||
|
target = t.Unit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return target
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *Unit) Move(c *Cave) {
|
||||||
|
if u.EnemyNeighbor(c) != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
next, _ := u.NextTile(c)
|
||||||
|
if next != nil {
|
||||||
|
next.Unit = u
|
||||||
|
next.Kind = u.Kind
|
||||||
|
u.Tile.Kind = KindSpace
|
||||||
|
u.Tile.Unit = nil
|
||||||
|
u.Tile = next
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *Unit) Attack(c *Cave) bool {
|
||||||
|
enemy := u.EnemyNeighbor(c)
|
||||||
|
if enemy != nil {
|
||||||
|
killed := enemy.Damage(c, u.Power)
|
||||||
|
return killed && enemy.Kind == KindElf
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *Unit) Damage(c *Cave, damage int) bool {
|
||||||
|
u.Hitpoints = u.Hitpoints - damage
|
||||||
|
if u.Hitpoints <= 0 {
|
||||||
|
c.RemoveUnit(u)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user