diff --git a/day19.go b/day19.go new file mode 100644 index 0000000..e47b729 --- /dev/null +++ b/day19.go @@ -0,0 +1,231 @@ +package aoc2019 + +import ( + "io/ioutil" + "log" + "math" + "strings" + + "github.com/pkg/errors" +) + +type day19Fact uint8 + +const ( + day19FactNoMoveDownPossible day19Fact = 1 << iota + day19FactNoMoveLeftPossible + day19FactNoMoveRightPossible + day19FactNoMoveTopPossible + day19FactOriginNotInBeam + day19FactX100NotInBeam + day19FactY100NotInBeam +) + +func (f day19Fact) has(t day19Fact) bool { return f&t != 0 } +func (f day19Fact) String() string { + return map[day19Fact]string{ + day19FactNoMoveDownPossible: "NoMoveDownPossible", + day19FactNoMoveLeftPossible: "NoMoveLeftPossible", + day19FactNoMoveRightPossible: "NoMoveRightPossible", + day19FactNoMoveTopPossible: "NoMoveTopPossible", + day19FactOriginNotInBeam: "OriginNotInBeam", + day19FactX100NotInBeam: "X100NotInBeam", + day19FactY100NotInBeam: "Y100NotInBeam", + }[f] +} + +func day19CountFieldsInTractorBeam(code []int64, maxX, maxY int64) int64 { + var count int64 + + for y := int64(0); y <= maxY; y++ { + for x := int64(0); x <= maxX; x++ { + + var ( + in = make(chan int64) + out = make(chan int64) + ) + go executeIntcode(code, in, out) + + // Submit coordinates + in <- x + in <- y + + // Count fields with tractor beam + o := <-out + count += o + } + } + + return count +} + +func day19Find100x100ShipPlace(code []int64) int64 { + var ( + x, y int64 + bvL, bvR int64 + ) + + getBeamVectors := func() { + for x := int64(0); x <= math.MaxInt64; x++ { + + var ( + in = make(chan int64) + out = make(chan int64) + ) + go executeIntcode(code, in, out) + + // Submit coordinates + in <- x + in <- 100 + + // Count fields with tractor beam + o := <-out + switch { + case o == 0 && bvR == 0: + // Did not yet find the beam + case o == 1 && bvL == 0: + // Left beam end has not been set + bvL, bvR = x, x + case o == 1 && x > bvR: + // New right "edge" found + bvR = x + case 0 == 0 && bvR > 0: + // End of right edge found, end + return + } + + } + } + + checkCoordinate := func(x, y int64) day19Fact { + var facts day19Fact + + for flag, c := range map[day19Fact][2]int64{ + day19FactNoMoveDownPossible: {x, y + 100}, + day19FactNoMoveLeftPossible: {x - 1, y + 99}, + day19FactNoMoveRightPossible: {x + 100, y}, + day19FactNoMoveTopPossible: {x + 99, y - 1}, + day19FactOriginNotInBeam: {x, y}, + day19FactX100NotInBeam: {x + 99, y}, + day19FactY100NotInBeam: {x, y + 99}, + } { + if c[0] < 0 || c[1] < 0 { + // Never check negative coordinates + facts |= flag + continue + } + + var ( + in = make(chan int64) + out = make(chan int64) + ) + defer close(in) + + go executeIntcode(code, in, out) + + // Submit coordinates + in <- c[0] + in <- c[1] + + o := <-out + if o == 0 { + facts |= flag + } + + } + + return facts + } + + // Initially get vectors for beam edges + getBeamVectors() + + // Try to get to the best position through movement + var success bool + for !success { + var f = checkCoordinate(x, y) + + switch { + case f.has(day19FactOriginNotInBeam): + panic("Origin placed outside beam") + + case f.has(day19FactX100NotInBeam) || f.has(day19FactY100NotInBeam): + // Ship does not fit, move further away + y += 100 + x = ((bvL * (y / 100)) + (bvR * (y / 100))) / 2 + + case !f.has(day19FactNoMoveTopPossible): + // Ship can be moved up in beam + y -= 1 + + case !f.has(day19FactNoMoveLeftPossible): + // Ship can be moved left in beam + x -= 1 + + case f.has(day19FactNoMoveTopPossible) && f.has(day19FactNoMoveLeftPossible): + // No movement towards ship is possible and ship fits in: Perfect + success = true + + } + } + + log.Printf("Result before force-move: x=%d y=%d res=%d", x, y, x*10000+y) + + // This MIGHT not be the perfect position, force further movement + // by ignoring some factors set above in order to compensate inaccurate + // vectors due to working with integers only + var fX, fY = x, y + for success { + var f = checkCoordinate(fX, fY) + + if !f.has(day19FactX100NotInBeam) && !f.has(day19FactY100NotInBeam) { + // Found a better position through forcing + x, y = fX, fY + } + + switch { + case !f.has(day19FactNoMoveTopPossible): + // Ship can be moved up in beam + fY -= 1 + + case f.has(day19FactOriginNotInBeam): + // No further tests, there will be no better position + success = false + + default: + fX -= 1 + } + } + + log.Printf("Result after force-move: x=%d y=%d res=%d", x, y, x*10000+y) + + return x*10000 + y +} + +func solveDay19Part1(inFile string) (int64, error) { + rawCode, err := ioutil.ReadFile(inFile) + if err != nil { + return 0, errors.Wrap(err, "Unable to read intcode") + } + + code, err := parseIntcode(strings.TrimSpace(string(rawCode))) + if err != nil { + return 0, errors.Wrap(err, "Unable to parse intcode") + } + + return day19CountFieldsInTractorBeam(code, 49, 49), nil +} + +func solveDay19Part2(inFile string) (int64, error) { + rawCode, err := ioutil.ReadFile(inFile) + if err != nil { + return 0, errors.Wrap(err, "Unable to read intcode") + } + + code, err := parseIntcode(strings.TrimSpace(string(rawCode))) + if err != nil { + return 0, errors.Wrap(err, "Unable to parse intcode") + } + + return day19Find100x100ShipPlace(code), nil +} diff --git a/day19_input.txt b/day19_input.txt new file mode 100644 index 0000000..1f14549 --- /dev/null +++ b/day19_input.txt @@ -0,0 +1 @@ +109,424,203,1,21101,11,0,0,1105,1,282,21102,1,18,0,1105,1,259,2102,1,1,221,203,1,21102,1,31,0,1105,1,282,21102,38,1,0,1105,1,259,20101,0,23,2,22102,1,1,3,21101,1,0,1,21101,57,0,0,1105,1,303,2101,0,1,222,21002,221,1,3,21002,221,1,2,21102,1,259,1,21101,0,80,0,1105,1,225,21102,83,1,2,21102,1,91,0,1106,0,303,2101,0,1,223,20102,1,222,4,21101,0,259,3,21101,0,225,2,21101,225,0,1,21101,118,0,0,1106,0,225,20101,0,222,3,21101,34,0,2,21101,133,0,0,1105,1,303,21202,1,-1,1,22001,223,1,1,21102,1,148,0,1106,0,259,1201,1,0,223,20102,1,221,4,20101,0,222,3,21101,12,0,2,1001,132,-2,224,1002,224,2,224,1001,224,3,224,1002,132,-1,132,1,224,132,224,21001,224,1,1,21101,195,0,0,105,1,108,20207,1,223,2,20101,0,23,1,21102,1,-1,3,21102,214,1,0,1105,1,303,22101,1,1,1,204,1,99,0,0,0,0,109,5,1202,-4,1,249,22101,0,-3,1,22101,0,-2,2,21201,-1,0,3,21101,0,250,0,1105,1,225,21201,1,0,-4,109,-5,2106,0,0,109,3,22107,0,-2,-1,21202,-1,2,-1,21201,-1,-1,-1,22202,-1,-2,-2,109,-3,2106,0,0,109,3,21207,-2,0,-1,1206,-1,294,104,0,99,22101,0,-2,-2,109,-3,2106,0,0,109,5,22207,-3,-4,-1,1206,-1,346,22201,-4,-3,-4,21202,-3,-1,-1,22201,-4,-1,2,21202,2,-1,-1,22201,-4,-1,1,21201,-2,0,3,21101,343,0,0,1105,1,303,1105,1,415,22207,-2,-3,-1,1206,-1,387,22201,-3,-2,-3,21202,-2,-1,-1,22201,-3,-1,3,21202,3,-1,-1,22201,-3,-1,2,21201,-4,0,1,21101,384,0,0,1105,1,303,1106,0,415,21202,-4,-1,-4,22201,-4,-3,-4,22202,-3,-2,-2,22202,-2,-4,-4,22202,-3,-2,-3,21202,-4,-1,-2,22201,-3,-2,1,21202,1,1,-4,109,-5,2106,0,0 diff --git a/day19_test.go b/day19_test.go new file mode 100644 index 0000000..9fe652d --- /dev/null +++ b/day19_test.go @@ -0,0 +1,21 @@ +package aoc2019 + +import "testing" + +func TestCalculateDay19_Part1(t *testing.T) { + res, err := solveDay19Part1("day19_input.txt") + if err != nil { + t.Fatalf("Day 19 solver failed: %s", err) + } + + t.Logf("Solution Day 19 Part 1: %d", res) +} + +func TestCalculateDay19_Part2(t *testing.T) { + res, err := solveDay19Part2("day19_input.txt") + if err != nil { + t.Fatalf("Day 19 solver failed: %s", err) + } + + t.Logf("Solution Day 19 Part 2: %d", res) +}