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