mirror of
https://github.com/Luzifer/aoc2019.git
synced 2024-12-22 05:51:16 +00:00
Add solution for Day 13
Signed-off-by: Knut Ahlers <knut@ahlers.me>
This commit is contained in:
parent
5f676fd738
commit
1c34ac5186
3 changed files with 227 additions and 0 deletions
205
day13.go
Normal file
205
day13.go
Normal file
|
@ -0,0 +1,205 @@
|
||||||
|
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
|
||||||
|
}
|
1
day13_input.txt
Normal file
1
day13_input.txt
Normal file
File diff suppressed because one or more lines are too long
21
day13_test.go
Normal file
21
day13_test.go
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
package aoc2019
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestCalculateDay13_Part1(t *testing.T) {
|
||||||
|
count, err := solveDay13Part1("day13_input.txt")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Day 13 solver failed: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Solution Day 13 Part 1: %d", count)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCalculateDay13_Part2(t *testing.T) {
|
||||||
|
res, err := solveDay13Part2("day13_input.txt")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Day 13 solver failed: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Solution Day 13 Part 2: %d", res)
|
||||||
|
}
|
Loading…
Reference in a new issue