package main import ( "AOC2021/src/helper" "fmt" "strings" ) type board struct { corridor [11]rune rooms [4][4]rune } var costs = map[rune]int{ 'A': 1, 'B': 10, 'C': 100, 'D': 1000, } var destX = map[rune]int{ 'A': 0, 'B': 1, 'C': 2, 'D': 3, } var enterableCorridorPos = [7]int{0, 1, 3, 5, 7, 9, 10} func main() { playBoard := board{ [11]rune{'.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.'}, [4][4]rune{{'D', 'D', 'D', 'B'}, {'D', 'C', 'B', 'A'}, {'C', 'B', 'A', 'A'}, {'B', 'A', 'C', 'C'}}} currentBoards := make(map[board]int) endBoards := make(map[board]int) currentBoards[playBoard] = 0 for len(currentBoards) > 0 { step(¤tBoards, &endBoards) } minEnergy := 9999999 for endBoard, energy := range endBoards { if checkWinner(endBoard) && energy < minEnergy { if minEnergy > energy { minEnergy = energy } } } fmt.Println(minEnergy) } func checkWinner(board board) bool { for amphoidType, room := range destX { for _, val := range board.rooms[room] { if val != amphoidType { return false } } } return true } func step(currentBoards *map[board]int, endBoards *map[board]int) { for selectedBoard, selectedEnergy := range *currentBoards { delete(*currentBoards, selectedBoard) possibleMoves := getPossibleMoves(selectedBoard) if len(possibleMoves) == 0 { if (*endBoards)[selectedBoard] == 0 || selectedEnergy < (*endBoards)[selectedBoard] { (*endBoards)[selectedBoard] = selectedEnergy } } for start, targets := range possibleMoves { for _, target := range targets { tmpNewBoard, tmpNewErnergy := useMove(selectedBoard, [2][2]int{start, target}, selectedEnergy) if (*currentBoards)[tmpNewBoard] == 0 || tmpNewErnergy < (*currentBoards)[tmpNewBoard] { (*currentBoards)[tmpNewBoard] = tmpNewErnergy } } } } return } func useMove(board board, move [2][2]int, energy int) (board, int) { //Remove moved Amphoid var amphoidType rune if move[0][0] == 0 { amphoidType = board.corridor[move[0][1]] board.corridor[move[0][1]] = '.' } else { amphoidType = board.rooms[move[0][0]-1][move[0][1]] board.rooms[move[0][0]-1][move[0][1]] = '.' } //Place moved Amphoid if move[1][0] == 0 { board.corridor[move[1][1]] = amphoidType } else { board.rooms[move[1][0]-1][move[1][1]] = amphoidType } //Calculate cost traveledDistance := 0 if move[0][0] == 0 { traveledDistance += Abs(move[0][1] - ((move[1][0]-1)*2 + 2)) traveledDistance += move[1][1] + 1 } else { traveledDistance += Abs(move[1][1] - ((move[0][0]-1)*2 + 2)) traveledDistance += move[0][1] + 1 } energy += costs[amphoidType] * (traveledDistance) return board, energy } func getPossibleMoves(board board) map[[2]int][][2]int { possibleMoves := make(map[[2]int][][2]int) for i, val := range board.corridor { if val != '.' { tmpPossibleMoves := getPossibleMovesFromPos(board, [2]int{0, i}) if len(tmpPossibleMoves) > 0 { possibleMoves[[2]int{0, i}] = tmpPossibleMoves } } } for i, room := range board.rooms { for j, val := range room { if val != '.' { tmpPossibleMoves := getPossibleMovesFromPos(board, [2]int{i + 1, j}) if len(tmpPossibleMoves) > 0 { possibleMoves[[2]int{i + 1, j}] = tmpPossibleMoves } } } } return possibleMoves } func getPossibleMovesFromPos(board board, position [2]int) [][2]int { if position[0] == 0 { enterableRooms := [][2]int{} for i, _ := range board.rooms { possiblePos := roomCanEnter(board, i, position[1], board.corridor[position[1]]) if possiblePos != -1 { enterableRooms = append(enterableRooms, [2]int{i + 1, possiblePos}) } } return enterableRooms } else { if isInCorrectRoom(board, position) || cantLeaveRoom(board, position) { return [][2]int{} } enterableCoordinates := [][2]int{} for _, pos := range enterableCorridorPos { if corridorCoordinateCanEnter(board.corridor, position[0]-1, pos) { enterableCoordinates = append(enterableCoordinates, [2]int{0, pos}) } } //for i, _ := range board.rooms { // possiblePos := roomCanEnter(board, i, position[1]*2+2, board.rooms[position[0]-1][position[1]]) // if possiblePos != -1 { // enterableCoordinates = append(enterableCoordinates, [2]int{i + 1, possiblePos}) // } //} return enterableCoordinates } } func cantLeaveRoom(board board, position [2]int) bool { room := board.rooms[position[0]-1] for i := 0; i < position[1]; i++ { if room[i] != '.' { return true } } return false } func isInCorrectRoom(board board, position [2]int) bool { amphoidType := board.rooms[position[0]-1][position[1]] if !(destX[amphoidType] == position[0]-1) { return false } otherAmphoidTypes := helper.RemoveCharactersFromString("ABCD", string(amphoidType)) for _, element := range board.rooms[position[0]-1] { if strings.ContainsRune(otherAmphoidTypes, element) { return false } } return true } func roomCanEnter(board board, positionRoom int, positionAmphoid int, amphoidType rune) int { room := board.rooms[positionRoom] corridor := board.corridor if !(destX[amphoidType] == positionRoom) { return -1 } otherAmphoidTypes := helper.RemoveCharactersFromString("ABCD", string(amphoidType)) for _, element := range room { if strings.ContainsRune(otherAmphoidTypes, element) { return -1 } } target := 2 + positionRoom*2 n := positionAmphoid if target < positionAmphoid { n-- } else { n++ } for n != target { if corridor[n] != '.' { return -1 } if target < positionAmphoid { n-- } else { n++ } } for i := len(room) - 1; i >= 0; i-- { if room[i] == '.' { return i } } return -1 } func corridorCoordinateCanEnter(corridor [11]rune, currentRoom int, target int) bool { if corridor[target] != '.' { return false } startPos := 2 + currentRoom*2 i := startPos for i != target { if corridor[i] != '.' { return false } if target < startPos { i-- } else { i++ } } return true } func Abs(x int) int { if x < 0 { return -x } return x }