diff --git a/days/07.go b/days/07.go index 42d3503..5ba4c9d 100644 --- a/days/07.go +++ b/days/07.go @@ -28,7 +28,7 @@ func (d *Day07) Part1() string { var highestVal int64 var highestSequence []int64 - allSequences := utilities.GetPermutations([]int64{0, 1, 2, 3, 4}) + allSequences := utilities.GetPermutations([]int64{0, 1, 2, 3, 4}...) for _, sequence := range allSequences { if len(sequence) != len(d.amps) { panic("input sequence does not match up to number of amplifiers") @@ -68,7 +68,7 @@ func (d *Day07) Part2() string { var highestVal int64 var highestSequence []int64 - allSequences := utilities.GetPermutations([]int64{5, 6, 7, 8, 9}) + allSequences := utilities.GetPermutations([]int64{5, 6, 7, 8, 9}...) for _, sequence := range allSequences { if len(sequence) != len(d.amps) { panic("input sequence does not match up to number of amplifiers") diff --git a/days/15.go b/days/15.go new file mode 100644 index 0000000..bc85fa9 --- /dev/null +++ b/days/15.go @@ -0,0 +1,332 @@ +package days + +//go:generate stringer -type=cellStatus,responseType,dirType -output=15_types_string.go + +import ( + "fmt" + "math" + "sort" + + u "parnic.com/aoc2019/utilities" +) + +type point u.Pair[int, int] +type cellStatus int +type responseType int +type dirType int + +const ( + cellStatusUnknown cellStatus = iota + cellStatusWall + cellStatusOpen + cellStatusGoal +) + +const ( + responseWall responseType = iota + responseSuccess + responseFoundGoal +) + +const maxVisited = 3 + +const ( + dirNorth dirType = iota + 1 + dirSouth + dirWest + dirEast + + dirFirst = dirNorth + dirLast = dirEast +) + +var dirOrders = [][]dirType{ + {dirNorth, dirSouth, dirWest, dirEast}, + {dirSouth, dirWest, dirEast, dirNorth}, + {dirWest, dirEast, dirNorth, dirSouth}, + {dirEast, dirNorth, dirSouth, dirWest}, +} + +// turned out to be unnecessary on multiple datasets i tried. increases the iterations 6x +// var dirOrders = u.GetPermutations(dirNorth, dirSouth, dirWest, dirEast) + +type visitedStatus struct { + timesVisited int + distanceFromStart int +} + +type Day15 struct { + program u.IntcodeProgram + grid map[point]cellStatus + visited map[point]*visitedStatus + shortestPath []point + pos point + goalPos point +} + +func (d *Day15) Parse() { + d.program = u.LoadIntcodeProgram("15p") + d.grid = map[point]cellStatus{ + {First: 0, Second: 0}: cellStatusOpen, + } + d.visited = map[point]*visitedStatus{ + {First: 0, Second: 0}: {timesVisited: 1}, + } + d.shortestPath = []point{{}} +} + +func (d Day15) Num() int { + return 15 +} + +func (d Day15) getPointInDirection(pos point, dir dirType) point { + target := pos + switch dir { + case dirNorth: + target.First-- + case dirSouth: + target.First++ + case dirWest: + target.Second-- + case dirEast: + target.Second++ + } + + return target +} + +func (d Day15) getCellTypeInDirection(pos point, dir dirType) (cellStatus, point) { + target := d.getPointInDirection(pos, dir) + return d.grid[target], target +} + +func (d Day15) getAdjacentCellsOfType(pos point, cellType cellStatus) []point { + points := make([]point, 0, 4) + for i := dirFirst; i <= dirLast; i++ { + adjacentCell := d.getPointInDirection(pos, i) + if d.grid[adjacentCell] == cellType { + points = append(points, adjacentCell) + } + } + return points +} + +func (d Day15) getDirToNextCellType(pos point, t cellStatus, maxNumVisited int, dirs []dirType) (dirType, point, error) { + for _, dir := range dirs { + cellInDirection, targetCell := d.getCellTypeInDirection(pos, dir) + if cellInDirection == t { + _, visitedTargetExists := d.visited[targetCell] + foundUnknown := t == cellStatusUnknown && !visitedTargetExists + foundOther := t != cellStatusUnknown && visitedTargetExists && d.visited[targetCell].timesVisited <= maxNumVisited + if foundUnknown || foundOther { + return dir, targetCell, nil + } + } + } + + return dirFirst, point{}, fmt.Errorf("no %v tiles around %v", t, pos) +} + +func (d *Day15) Draw() { + min := point{First: math.MaxInt, Second: math.MaxInt} + max := point{First: math.MinInt, Second: math.MinInt} + for p := range d.grid { + if p.First < min.First { + min.First = p.First + } + if p.First > max.First { + max.First = p.First + } + if p.Second < min.Second { + min.Second = p.Second + } + if p.Second > max.Second { + max.Second = p.Second + } + } + + for x := min.First; x <= max.First; x++ { + for y := min.Second; y <= max.Second; y++ { + p := point{First: x, Second: y} + switch d.grid[p] { + case cellStatusGoal: + fmt.Printf("%s@%s", u.ColorBrightGreen, u.TextReset) + case cellStatusOpen: + if p == d.pos { + fmt.Print(u.BackgroundBrightRed) + } else if x == 0 && y == 0 { + fmt.Print(u.BackgroundYellow) + } else if u.ArrayContains(d.shortestPath, p) { + fmt.Print(u.BackgroundGreen) + } else if d.visited[p] != nil && d.visited[p].timesVisited > maxVisited { + fmt.Print(u.ColorYellow) + fmt.Print(u.BackgroundBlack) + } else if d.visited[p] != nil && d.visited[p].timesVisited > 1 { + fmt.Print(u.BackgroundMagenta) + } else { + fmt.Print(u.BackgroundBlue) + } + fmt.Printf(".%s", u.TextReset) + case cellStatusWall: + fmt.Print("█") + case cellStatusUnknown: + fmt.Print(" ") + } + } + fmt.Println() + } +} + +func (d *Day15) markShortestPath() { + pos := d.goalPos + checkOffsets := []point{ + {First: -1, Second: 0}, + {First: 1, Second: 0}, + {First: 0, Second: -1}, + {First: 0, Second: 1}, + } + + checkPt := func(pt point) (bool, int) { + if v, exists := d.visited[pt]; exists && d.grid[pt] == cellStatusOpen { + return true, v.distanceFromStart + } + return false, math.MaxInt + } + + d.shortestPath = []point{d.goalPos} + + for pos.First != 0 || pos.Second != 0 { + lowestDist := math.MaxInt + lowestPoint := point{} + for _, pt := range checkOffsets { + newPt := point{First: pos.First + pt.First, Second: pos.Second + pt.Second} + if found, dist := checkPt(newPt); found && dist < lowestDist { + lowestDist = dist + lowestPoint = newPt + } + } + + d.shortestPath = append(d.shortestPath, lowestPoint) + pos = lowestPoint + } +} + +func (d *Day15) exploreFullMap() map[point]*visitedStatus { + grids := make([]map[point]cellStatus, 0, len(dirOrders)) + goalVisited := d.visited + + for _, dirOrder := range dirOrders { + d.program.Reset() + + targetPos := point{} + nextDir := dirFirst + distFromStart := 0 + + d.pos = point{} + d.visited = map[point]*visitedStatus{ + {First: 0, Second: 0}: {timesVisited: 1}, + } + d.grid = map[point]cellStatus{ + {First: 0, Second: 0}: cellStatusOpen, + } + d.program.RunIn(func(inputStep int) int64 { + var err error + nextDir, targetPos, err = d.getDirToNextCellType(d.pos, cellStatusUnknown, 0, dirOrder) + if err != nil { + // ensure we never try to go back into the trapped spot + d.visited[d.pos].timesVisited = maxVisited + 1 + for x := 1; x <= maxVisited && err != nil; x++ { + nextDir, targetPos, err = d.getDirToNextCellType(d.pos, cellStatusOpen, x, dirOrder) + } + } + if err != nil { + // d.Draw() + // panic(err) + d.program.Stop() + } + + return int64(nextDir) + }, func(val int64, state u.IntcodeProgramState) { + rVal := responseType(val) + + p := d.getPointInDirection(d.pos, nextDir) + shouldMove := true + switch rVal { + case responseWall: + d.grid[p] = cellStatusWall + shouldMove = false + case responseSuccess: + d.grid[p] = cellStatusOpen + case responseFoundGoal: + d.grid[p] = cellStatusGoal + } + + if shouldMove { + d.pos = targetPos + if d.visited[d.pos] == nil { + d.visited[d.pos] = &visitedStatus{} + distFromStart++ + } else { + distFromStart-- + } + d.visited[d.pos].timesVisited++ + d.visited[d.pos].distanceFromStart = distFromStart + } + + if rVal == responseFoundGoal { + // d.Draw() + d.goalPos = targetPos + goalVisited = d.visited + } + }) + + grids = append(grids, d.grid) + } + + d.grid = map[point]cellStatus{ + {First: 0, Second: 0}: cellStatusOpen, + } + for _, grid := range grids { + keys := u.MapKeys(grid) + for _, key := range keys { + d.grid[key] = grid[key] + } + } + + return goalVisited +} + +func (d *Day15) tagDistanceRecursive(pos, last point, dist int, distances map[point]int) { + distances[pos] = dist + for _, cell := range d.getAdjacentCellsOfType(pos, cellStatusOpen) { + if cell == last { + continue + } + d.tagDistanceRecursive(cell, pos, dist+1, distances) + } +} + +func (d *Day15) Part1() string { + d.visited = d.exploreFullMap() + d.markShortestPath() + + for _, visited := range d.visited { + visited.timesVisited = 1 + } + d.pos = point{} + // d.Draw() + + return fmt.Sprintf("Moves required to reach target: %s%d%s", u.TextBold, d.visited[d.goalPos].distanceFromStart, u.TextReset) +} + +func (d *Day15) Part2() string { + startLoc := d.goalPos + distanceMap := map[point]int{startLoc: 0} + + d.tagDistanceRecursive(startLoc, point{}, 0, distanceMap) + + cellDistances := u.MapValues(distanceMap) + sort.Slice(cellDistances, func(i, j int) bool { return cellDistances[i] > cellDistances[j] }) + + return fmt.Sprintf("Time to fill the area with oxygen: %s%d%s minutes", u.TextBold, cellDistances[0], u.TextReset) +} diff --git a/days/15_types_string.go b/days/15_types_string.go new file mode 100644 index 0000000..6155258 --- /dev/null +++ b/days/15_types_string.go @@ -0,0 +1,66 @@ +// Code generated by "stringer -type=cellStatus,responseType,dirType -output=15_types_string.go"; DO NOT EDIT. + +package days + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[cellStatusUnknown-0] + _ = x[cellStatusWall-1] + _ = x[cellStatusOpen-2] + _ = x[cellStatusGoal-3] +} + +const _cellStatus_name = "cellStatusUnknowncellStatusWallcellStatusOpencellStatusGoal" + +var _cellStatus_index = [...]uint8{0, 17, 31, 45, 59} + +func (i cellStatus) String() string { + if i < 0 || i >= cellStatus(len(_cellStatus_index)-1) { + return "cellStatus(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _cellStatus_name[_cellStatus_index[i]:_cellStatus_index[i+1]] +} +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[responseWall-0] + _ = x[responseSuccess-1] + _ = x[responseFoundGoal-2] +} + +const _responseType_name = "responseWallresponseSuccessresponseFoundGoal" + +var _responseType_index = [...]uint8{0, 12, 27, 44} + +func (i responseType) String() string { + if i < 0 || i >= responseType(len(_responseType_index)-1) { + return "responseType(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _responseType_name[_responseType_index[i]:_responseType_index[i+1]] +} +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[dirNorth-1] + _ = x[dirSouth-2] + _ = x[dirWest-3] + _ = x[dirEast-4] +} + +const _dirType_name = "dirNorthdirSouthdirWestdirEast" + +var _dirType_index = [...]uint8{0, 8, 16, 23, 30} + +func (i dirType) String() string { + i -= 1 + if i < 0 || i >= dirType(len(_dirType_index)-1) { + return "dirType(" + strconv.FormatInt(int64(i+1), 10) + ")" + } + return _dirType_name[_dirType_index[i]:_dirType_index[i+1]] +} diff --git a/inputs/15p.txt b/inputs/15p.txt new file mode 100644 index 0000000..5ec6823 --- /dev/null +++ b/inputs/15p.txt @@ -0,0 +1 @@ +3,1033,1008,1033,1,1032,1005,1032,31,1008,1033,2,1032,1005,1032,58,1008,1033,3,1032,1005,1032,81,1008,1033,4,1032,1005,1032,104,99,102,1,1034,1039,101,0,1036,1041,1001,1035,-1,1040,1008,1038,0,1043,102,-1,1043,1032,1,1037,1032,1042,1105,1,124,102,1,1034,1039,1001,1036,0,1041,1001,1035,1,1040,1008,1038,0,1043,1,1037,1038,1042,1105,1,124,1001,1034,-1,1039,1008,1036,0,1041,1001,1035,0,1040,1001,1038,0,1043,1002,1037,1,1042,1105,1,124,1001,1034,1,1039,1008,1036,0,1041,102,1,1035,1040,101,0,1038,1043,1001,1037,0,1042,1006,1039,217,1006,1040,217,1008,1039,40,1032,1005,1032,217,1008,1040,40,1032,1005,1032,217,1008,1039,3,1032,1006,1032,165,1008,1040,33,1032,1006,1032,165,1101,0,2,1044,1105,1,224,2,1041,1043,1032,1006,1032,179,1101,1,0,1044,1106,0,224,1,1041,1043,1032,1006,1032,217,1,1042,1043,1032,1001,1032,-1,1032,1002,1032,39,1032,1,1032,1039,1032,101,-1,1032,1032,101,252,1032,211,1007,0,37,1044,1106,0,224,1102,1,0,1044,1105,1,224,1006,1044,247,101,0,1039,1034,101,0,1040,1035,1001,1041,0,1036,1002,1043,1,1038,101,0,1042,1037,4,1044,1106,0,0,42,4,15,10,25,91,86,34,69,14,50,9,24,24,54,10,18,63,17,2,88,36,31,60,20,13,20,76,94,25,41,36,78,3,39,17,94,10,25,22,16,67,72,31,47,15,25,66,8,17,54,8,89,67,29,28,92,11,54,14,4,64,78,28,80,66,6,70,36,56,13,63,17,19,83,17,27,29,34,54,4,93,24,71,6,66,22,21,92,93,39,4,31,76,72,25,74,89,18,62,18,27,57,35,83,39,14,23,95,2,79,25,97,86,13,79,1,34,90,81,29,45,31,38,67,17,92,32,31,50,1,42,81,1,2,87,7,52,74,20,85,22,32,47,16,77,96,28,14,74,22,55,15,75,44,29,19,8,73,2,54,18,26,64,95,21,98,48,25,36,11,78,77,5,16,70,18,10,76,51,51,10,25,43,56,12,13,48,8,17,68,10,64,25,93,42,3,52,24,72,99,23,54,13,44,17,15,8,68,59,15,95,61,9,50,8,51,23,8,39,13,95,64,12,28,56,90,1,62,27,12,60,6,5,18,24,13,99,12,18,92,97,7,56,22,48,91,34,87,32,98,20,89,74,16,51,84,21,46,14,23,52,17,57,12,50,17,97,23,99,11,21,68,21,61,89,13,45,64,89,18,36,40,35,90,9,1,3,81,33,32,83,99,97,34,4,46,31,21,90,62,14,93,11,22,99,51,70,88,51,2,4,29,36,35,48,17,25,30,69,34,3,39,89,31,89,33,30,88,77,18,30,67,17,40,61,19,40,85,26,23,49,22,41,30,13,79,6,34,40,33,43,49,84,19,78,43,10,74,18,61,15,22,51,86,2,78,11,33,92,24,88,27,24,44,2,97,4,4,49,72,93,24,65,79,21,60,33,46,36,22,15,87,33,78,2,49,70,7,78,78,11,14,64,41,61,41,6,1,49,35,78,47,65,14,66,10,86,76,2,32,88,3,24,14,87,9,95,32,19,4,10,67,60,15,19,53,47,24,29,65,5,95,35,1,70,16,43,53,11,64,17,34,84,74,65,30,18,58,2,35,48,38,33,46,16,87,27,12,79,11,88,35,7,5,35,67,83,38,6,17,56,82,13,45,32,30,67,25,62,7,43,63,9,36,14,58,53,25,98,12,38,78,13,63,93,33,11,54,9,66,32,79,62,47,28,6,67,31,53,71,2,30,59,12,90,59,67,2,58,52,1,30,51,49,22,89,88,27,19,41,27,13,19,76,5,82,58,12,49,51,17,15,73,35,25,74,90,29,14,96,83,69,11,18,14,10,40,93,35,31,35,36,58,36,16,48,7,66,98,31,47,34,47,33,5,28,82,88,1,30,80,95,32,87,2,19,91,74,74,19,8,25,63,65,51,30,14,41,98,99,21,90,15,91,3,31,74,27,31,77,28,74,4,27,88,82,11,54,35,52,13,88,71,93,20,82,18,36,68,33,83,1,18,5,42,46,29,62,10,78,67,9,84,48,22,33,74,36,53,58,31,5,8,55,10,24,49,34,81,1,4,86,5,25,2,75,36,49,2,24,88,72,8,64,36,38,10,23,36,93,28,51,90,4,99,57,31,10,14,94,21,27,61,34,70,41,32,14,91,20,83,30,54,26,44,30,85,96,87,35,16,61,99,16,32,53,68,87,1,89,43,9,17,4,39,50,61,8,49,27,48,13,51,34,47,30,89,68,50,18,63,99,50,32,41,33,71,1,43,57,64,24,95,9,89,8,64,18,75,23,97,74,67,24,55,1,87,97,44,0,0,21,21,1,10,1,0,0,0,0,0,0 \ No newline at end of file diff --git a/main.go b/main.go index 3e6caa5..0be53fe 100644 --- a/main.go +++ b/main.go @@ -45,6 +45,7 @@ var dayMap = []day{ &days.Day12{}, &days.Day13{}, &days.Day14{}, + &days.Day15{}, } func main() { diff --git a/utilities/intcode.go b/utilities/intcode.go index 04008e1..d037711 100644 --- a/utilities/intcode.go +++ b/utilities/intcode.go @@ -24,9 +24,10 @@ const ( ) type IntcodeProgram struct { - memory []int64 - program []int64 - relativeBase int + memory []int64 + program []int64 + relativeBase int + haltRequested bool } type IntcodeProgramState struct { @@ -147,7 +148,7 @@ func (p *IntcodeProgram) RunIn(inputFunc ProvideInputFunc, outputFunc ReceiveOut p.init() inputsRequested := 0 - for instructionPointer := 0; instructionPointer < len(p.program); { + for instructionPointer := 0; instructionPointer < len(p.program) && !p.haltRequested; { instruction := p.GetMemory(instructionPointer) instructionPointer++ @@ -253,4 +254,10 @@ func (p *IntcodeProgram) RunIn(inputFunc ProvideInputFunc, outputFunc ReceiveOut panic(fmt.Sprintf("exception executing program - unhandled opcode %d", opcode)) } } + + p.haltRequested = false +} + +func (p *IntcodeProgram) Stop() { + p.haltRequested = true } diff --git a/utilities/permutations.go b/utilities/permutations.go index fa220b3..e2b0b8b 100644 --- a/utilities/permutations.go +++ b/utilities/permutations.go @@ -4,7 +4,7 @@ type Permutable interface { ~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 } -func GetPermutations[T Permutable](arr []T) [][]T { +func GetPermutations[T Permutable](arr ...T) [][]T { var helper func([]T, int) res := [][]T{}