1
0
Fork 0
mirror of https://github.com/Luzifer/aoc2019.git synced 2024-12-22 22:11:16 +00:00
aoc2019/day13.go

206 lines
4.4 KiB
Go
Raw Normal View History

package aoc2019
import (
"fmt"
"io/ioutil"
"log"
"strings"
"sync"
"time"
"github.com/pkg/errors"
)
type day13TileType int64
const (
day13TileTypeEmpty day13TileType = iota // No game object appears in this tile.
day13TileTypeWall // Walls are indestructible barriers.
day13TileTypeBlock // Blocks can be broken by the ball.
day13TileTypeHPaddle // The paddle is indestructible.
day13TileTypeBall // The ball moves diagonally and bounces off objects.
)
type day13Tile struct {
X, Y int64
Type day13TileType
}
func (d day13Tile) key() string { return fmt.Sprintf("%d:%d", d.X, d.Y) }
type day13Field struct {
tiles map[string]day13Tile
score int64
maxX, maxY int64
ball day13Tile
tileLock sync.RWMutex
}
func (d *day13Field) getPosition(tileType day13TileType) (int64, int64) {
d.tileLock.RLock()
defer d.tileLock.RUnlock()
if tileType == day13TileTypeBall {
return d.ball.X, d.ball.Y
}
for _, t := range d.tiles {
if t.Type == tileType {
return t.X, t.Y
}
}
return -1, -1
}
func (d *day13Field) remainingTiles(tileType day13TileType) int {
d.tileLock.RLock()
defer d.tileLock.RUnlock()
var count int
for _, t := range d.tiles {
if t.Type == tileType {
count++
}
}
return count
}
func day13PlayGame(code []int64, field *day13Field, in func() (int64, error)) error {
var (
err error
out = make(chan int64)
output []int64
wg = new(sync.WaitGroup)
)
in = func(f func() (int64, error)) func() (int64, error) {
return func() (int64, error) {
// Ensure every output is already processed before deciding on the input.
// Without this there is a race which randomly breaks the program in
// different ways m(
for len(output) > 0 {
time.Sleep(time.Nanosecond)
}
return f()
}
}(in)
go func() {
for o := range out {
output = append(output, o)
if len(output) >= 3 {
field.tileLock.Lock()
if output[0] == -1 && output[1] == 0 {
field.score = output[2]
output = output[3:]
field.tileLock.Unlock()
continue
}
tile := day13Tile{X: output[0], Y: output[1], Type: day13TileType(output[2])}
field.tiles[tile.key()] = tile
if tile.Type == day13TileTypeBall {
// Ball sometimes disappear, store it extra
field.ball = tile
}
if tile.X > field.maxX {
field.maxX = tile.X
}
if tile.Y > field.maxY {
field.maxY = tile.Y
}
output = output[3:]
field.tileLock.Unlock()
}
}
wg.Done()
}()
wg.Add(1)
_, err = executeIntcode(code, in, out)
wg.Wait()
return errors.Wrap(err, "Unable to execute intcode")
}
func solveDay13Part1(inFile string) (int, error) {
raw, err := ioutil.ReadFile(inFile)
if err != nil {
return 0, errors.Wrap(err, "Unable to read code")
}
code, err := parseIntcode(strings.TrimSpace(string(raw)))
if err != nil {
return 0, errors.Wrap(err, "Unable to parse code")
}
var field = &day13Field{tiles: make(map[string]day13Tile)}
if err := day13PlayGame(code, field, nil); err != nil {
return 0, errors.Wrap(err, "Unable to draw field")
}
return field.remainingTiles(day13TileTypeBlock), nil
}
func solveDay13Part2(inFile string) (int64, error) {
raw, err := ioutil.ReadFile(inFile)
if err != nil {
return 0, errors.Wrap(err, "Unable to read code")
}
code, err := parseIntcode(strings.TrimSpace(string(raw)))
if err != nil {
return 0, errors.Wrap(err, "Unable to parse code")
}
// Let the game initialize once to get tick count for initialization
var field = &day13Field{tiles: make(map[string]day13Tile)}
// Start the real game
code[0] = 2 // Insert two quarters
// Input callback
var in = func() (int64, error) {
field.tileLock.RLock()
defer field.tileLock.RUnlock()
var (
ballX, _ = field.getPosition(day13TileTypeBall)
dir int64
paddleX, _ = field.getPosition(day13TileTypeHPaddle)
)
// Move paddle in ball direction
switch {
case ballX == paddleX || ballX == -1:
dir = 0
case ballX < paddleX:
dir = -1
case ballX > paddleX:
dir = 1
}
return dir, nil
}
//intcodeDebugging = true
if err := day13PlayGame(code, field, in); err != nil {
log.Printf("err=%s", err)
}
if rb := field.remainingTiles(day13TileTypeBlock); rb > 0 {
return 0, errors.Errorf("Game logic quit with blocks remaining: %d", rb)
}
return field.score, nil
}