diff --git a/days/15.go b/days/15.go new file mode 100644 index 0000000..ef8f4d0 --- /dev/null +++ b/days/15.go @@ -0,0 +1,302 @@ +package days + +//go:generate stringer -type=cellStatus,responseType,dirType -output=15_types_string.go + +import ( + "fmt" + "math" + + 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) 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) 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 { + return fmt.Sprintf("%s%d%s", u.TextBold, 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..8d9b1b4 --- /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,1002,1034,1,1039,1001,1036,0,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,1106,0,124,1001,1034,-1,1039,1008,1036,0,1041,1002,1035,1,1040,1002,1038,1,1043,101,0,1037,1042,1106,0,124,1001,1034,1,1039,1008,1036,0,1041,101,0,1035,1040,1002,1038,1,1043,101,0,1037,1042,1006,1039,217,1006,1040,217,1008,1039,40,1032,1005,1032,217,1008,1040,40,1032,1005,1032,217,1008,1039,37,1032,1006,1032,165,1008,1040,9,1032,1006,1032,165,1101,2,0,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,50,1044,1106,0,224,1102,0,1,1044,1105,1,224,1006,1044,247,1001,1039,0,1034,102,1,1040,1035,102,1,1041,1036,101,0,1043,1038,102,1,1042,1037,4,1044,1106,0,0,37,22,74,27,37,99,30,8,72,31,49,29,51,32,85,21,39,72,2,2,43,94,31,11,76,43,95,21,38,8,90,13,39,97,54,47,14,6,20,49,5,30,97,9,99,64,71,24,36,87,52,94,36,18,52,42,83,38,98,53,26,87,69,32,18,94,2,93,97,15,65,65,21,40,99,19,91,13,4,89,38,70,65,41,73,49,62,54,37,46,14,49,88,86,13,89,23,89,10,3,48,57,92,43,65,4,35,97,48,10,19,64,3,79,38,87,6,13,71,49,74,43,92,8,4,71,6,35,85,98,94,6,38,59,80,65,46,62,63,62,49,61,68,6,7,64,66,40,56,82,59,30,85,45,57,36,86,70,25,83,31,96,65,19,16,67,55,36,49,54,29,75,69,3,3,37,75,49,23,65,22,6,52,75,31,7,87,85,19,48,97,65,51,78,10,35,40,59,54,14,85,6,30,94,68,42,87,46,75,26,82,36,21,65,90,16,59,14,76,55,37,41,99,80,9,79,12,59,17,75,2,40,52,45,76,45,16,82,13,55,61,14,11,49,97,81,99,38,35,20,98,51,64,13,24,85,94,38,25,87,1,42,89,18,32,54,55,17,15,84,98,25,31,21,55,44,57,59,11,78,49,72,87,20,7,33,91,80,75,18,33,37,52,7,26,87,65,36,52,92,6,8,95,89,37,38,57,25,23,71,75,47,20,87,90,37,54,38,77,32,39,67,16,69,62,15,96,47,91,95,18,96,24,45,21,64,9,72,2,54,65,39,36,54,23,71,74,18,26,97,35,44,29,87,54,48,31,55,33,85,74,13,99,82,39,35,97,43,20,62,58,86,98,41,47,92,79,74,10,85,28,66,86,18,35,5,84,67,13,91,47,44,1,84,56,32,96,7,77,21,88,92,38,31,65,82,87,45,55,4,60,58,64,49,53,3,63,32,52,43,10,66,75,96,53,11,95,44,36,16,65,91,47,32,9,3,73,29,25,93,29,18,88,45,41,46,12,94,13,89,5,36,94,88,33,10,10,2,52,90,19,63,26,84,12,76,16,42,75,63,39,32,72,72,84,70,2,63,33,74,43,68,38,84,72,44,89,18,24,78,69,4,80,41,54,75,72,4,16,91,5,48,30,64,38,4,52,38,30,95,99,32,38,52,35,58,71,38,89,86,25,84,88,41,39,32,56,79,12,52,19,80,46,66,38,32,69,67,6,87,88,36,59,51,5,33,46,45,82,15,57,80,91,12,86,29,34,15,61,19,73,46,82,60,73,13,52,36,67,3,49,87,39,12,98,58,87,32,82,47,65,6,87,71,13,17,65,69,14,34,42,82,42,1,77,63,10,63,28,90,24,13,99,19,38,68,62,44,2,65,81,95,7,54,24,58,16,58,48,95,9,80,9,51,73,23,96,49,64,58,1,6,72,69,39,2,10,63,36,9,85,59,90,41,2,72,77,23,23,80,75,33,6,20,18,59,39,36,89,35,89,42,42,22,37,24,30,51,53,43,78,48,27,76,84,22,81,72,25,95,28,15,51,58,48,7,1,90,72,19,37,52,60,39,81,20,70,6,39,82,26,77,14,96,52,30,84,33,66,80,5,52,15,72,46,55,2,21,8,97,79,43,8,91,27,67,5,18,74,71,34,51,6,83,25,52,92,5,15,85,11,72,33,85,30,59,6,84,29,51,77,99,43,95,44,83,95,89,27,54,16,85,90,82,34,98,59,87,12,73,25,74,29,95,82,51,5,81,46,51,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 a21ec5d..7424e02 100644 --- a/main.go +++ b/main.go @@ -45,6 +45,7 @@ var dayMap = []day{ &days.Day12{}, &days.Day13{}, &days.Day14{}, + &days.Day15{}, } func main() {