402 lines
8.1 KiB
Go
402 lines
8.1 KiB
Go
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]))
|
|
}
|