382 lines
9.3 KiB
Go
382 lines
9.3 KiB
Go
package main
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"os"
|
|
"time"
|
|
)
|
|
|
|
const FILE_PATH = "./input.txt"
|
|
|
|
//const FILE_PATH = "./sample.txt"
|
|
|
|
func LoadInput(filename string) ([]string, error) {
|
|
file, err := os.Open(filename)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer file.Close()
|
|
|
|
var lines []string
|
|
scanner := bufio.NewScanner(file)
|
|
for scanner.Scan() {
|
|
lines = append(lines, scanner.Text())
|
|
}
|
|
|
|
if err := scanner.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return lines, nil
|
|
}
|
|
|
|
type Vector2 struct {
|
|
x int
|
|
y int
|
|
}
|
|
|
|
const (
|
|
EMPTY = iota
|
|
ROBOT = iota
|
|
BOX = iota
|
|
WALL = iota
|
|
LEFT_BOX = iota
|
|
RIGHT_BOX = iota
|
|
)
|
|
|
|
const (
|
|
UP = '^'
|
|
DOWN = 'v'
|
|
LEFT = '<'
|
|
RIGHT = '>'
|
|
)
|
|
|
|
var MOVEMENT = map[byte]Vector2{
|
|
UP: Vector2{0, -1},
|
|
DOWN: Vector2{0, 1},
|
|
LEFT: Vector2{-1, 0},
|
|
RIGHT: Vector2{1, 0},
|
|
}
|
|
|
|
type Lab struct {
|
|
field [][]int
|
|
command string
|
|
}
|
|
|
|
func cloneLab(lab Lab) Lab {
|
|
var field = make([][]int, len(lab.field))
|
|
|
|
for y := 0; y < len(lab.field); y++ {
|
|
field[y] = make([]int, len(lab.field[y]))
|
|
for x := 0; x < len(lab.field[y]); x++ {
|
|
field[y][x] = lab.field[y][x]
|
|
}
|
|
}
|
|
|
|
return Lab{field, lab.command}
|
|
}
|
|
|
|
func cloneWideLab(lab Lab) Lab {
|
|
var field = make([][]int, len(lab.field))
|
|
for y := 0; y < len(lab.field); y++ {
|
|
field[y] = make([]int, len(lab.field[y])*2)
|
|
for x := 0; x < len(lab.field[y]); x++ {
|
|
if lab.field[y][x] == BOX {
|
|
field[y][x*2] = LEFT_BOX
|
|
field[y][x*2+1] = RIGHT_BOX
|
|
} else if lab.field[y][x] == ROBOT {
|
|
field[y][x*2] = ROBOT
|
|
field[y][x*2+1] = EMPTY
|
|
} else {
|
|
field[y][x*2] = lab.field[y][x]
|
|
field[y][x*2+1] = lab.field[y][x]
|
|
}
|
|
}
|
|
}
|
|
return Lab{field, lab.command}
|
|
}
|
|
|
|
type structuredInput = Lab
|
|
|
|
func TransformInput(lines []string) structuredInput {
|
|
var field = make([][]int, 0)
|
|
var row []int
|
|
var i = 0
|
|
var endField = false
|
|
var command string = ""
|
|
|
|
for !endField {
|
|
if lines[i] == "" {
|
|
endField = true
|
|
break
|
|
}
|
|
row = make([]int, 0)
|
|
|
|
for j := 0; j < len(lines[i]); j++ {
|
|
if lines[i][j] == '@' {
|
|
row = append(row, ROBOT)
|
|
} else if lines[i][j] == 'O' {
|
|
row = append(row, BOX)
|
|
} else if lines[i][j] == '#' {
|
|
row = append(row, WALL)
|
|
} else {
|
|
row = append(row, EMPTY)
|
|
}
|
|
}
|
|
field = append(field, row)
|
|
i += 1
|
|
}
|
|
|
|
for ; i < len(lines); i++ {
|
|
command += lines[i]
|
|
}
|
|
|
|
return Lab{field, command}
|
|
}
|
|
|
|
func tryPush(lab *Lab, position Vector2, direction Vector2) {
|
|
if lab.field[position.y+direction.y][position.x+direction.x] == WALL {
|
|
return
|
|
}
|
|
|
|
if lab.field[position.y+direction.y][position.x+direction.x] == EMPTY {
|
|
lab.field[position.y+direction.y][position.x+direction.x] = lab.field[position.y][position.x]
|
|
lab.field[position.y][position.x] = EMPTY
|
|
return
|
|
}
|
|
|
|
tryPush(lab, Vector2{position.x + direction.x, position.y + direction.y}, direction)
|
|
|
|
if lab.field[position.y+direction.y][position.x+direction.x] == EMPTY {
|
|
lab.field[position.y+direction.y][position.x+direction.x] = lab.field[position.y][position.x]
|
|
lab.field[position.y][position.x] = EMPTY
|
|
}
|
|
}
|
|
|
|
func getRobot(lab Lab) Vector2 {
|
|
for y := 0; y < len(lab.field); y++ {
|
|
for x := 0; x < len(lab.field[y]); x++ {
|
|
if lab.field[y][x] == ROBOT {
|
|
return Vector2{x, y}
|
|
}
|
|
}
|
|
}
|
|
return Vector2{-1, -1}
|
|
}
|
|
|
|
func printLab(lab Lab, command byte) {
|
|
for y := 0; y < len(lab.field); y++ {
|
|
for x := 0; x < len(lab.field[y]); x++ {
|
|
if lab.field[y][x] == ROBOT {
|
|
fmt.Printf("%c", command)
|
|
}
|
|
if lab.field[y][x] == EMPTY {
|
|
fmt.Print(".")
|
|
}
|
|
if lab.field[y][x] == WALL {
|
|
fmt.Print("#")
|
|
}
|
|
if lab.field[y][x] == LEFT_BOX {
|
|
fmt.Print("[")
|
|
}
|
|
if lab.field[y][x] == RIGHT_BOX {
|
|
fmt.Print("]")
|
|
}
|
|
if lab.field[y][x] == BOX {
|
|
fmt.Print("O")
|
|
}
|
|
}
|
|
fmt.Println()
|
|
}
|
|
}
|
|
|
|
func calculateLabScore(lab Lab) int {
|
|
var result = 0
|
|
for y := 0; y < len(lab.field); y++ {
|
|
for x := 0; x < len(lab.field[y]); x++ {
|
|
if lab.field[y][x] == BOX {
|
|
result += x + y*100
|
|
}
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
func calculateWideLabScore(lab Lab) int {
|
|
var result = 0
|
|
for y := 0; y < len(lab.field); y++ {
|
|
for x := 0; x < len(lab.field[y]); x++ {
|
|
if lab.field[y][x] == LEFT_BOX {
|
|
result += x + y*100
|
|
}
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
func Part1(input structuredInput) int {
|
|
var lab = cloneLab(input)
|
|
|
|
for i := 0; i < len(lab.command); i++ {
|
|
tryPush(&lab, getRobot(lab), MOVEMENT[lab.command[i]])
|
|
}
|
|
|
|
return calculateLabScore(lab)
|
|
}
|
|
|
|
func clearScreen() {
|
|
fmt.Print("\033[H\033[2J") // Clear entire screen
|
|
}
|
|
|
|
func moveCursorUp(lines int) {
|
|
fmt.Printf("\033[%dA", lines) // Move cursor up n lines
|
|
}
|
|
|
|
func canPushRobot(lab *Lab, position Vector2, direction Vector2) bool {
|
|
if lab.field[position.y+direction.y][position.x+direction.x] == WALL {
|
|
return false
|
|
}
|
|
if lab.field[position.y+direction.y][position.x+direction.x] == EMPTY {
|
|
return true
|
|
}
|
|
return canPushBox(lab, Vector2{position.x + direction.x, position.y + direction.y}, direction)
|
|
}
|
|
|
|
func canPushBox(lab *Lab, position Vector2, direction Vector2) bool {
|
|
var left_position = position
|
|
if lab.field[position.y][position.x] == RIGHT_BOX {
|
|
left_position = Vector2{position.x - 1, position.y}
|
|
}
|
|
|
|
if direction == MOVEMENT[UP] || direction == MOVEMENT[DOWN] {
|
|
if lab.field[left_position.y+direction.y][left_position.x] == WALL ||
|
|
lab.field[left_position.y+direction.y][left_position.x+1] == WALL {
|
|
return false
|
|
}
|
|
if lab.field[left_position.y+direction.y][left_position.x] == EMPTY &&
|
|
lab.field[left_position.y+direction.y][left_position.x+1] == EMPTY {
|
|
return true
|
|
}
|
|
if (lab.field[left_position.y+direction.y][left_position.x] == RIGHT_BOX || lab.field[left_position.y+direction.y][left_position.x] == LEFT_BOX) && !canPushBox(lab, Vector2{left_position.x, left_position.y + direction.y}, direction) {
|
|
return false
|
|
}
|
|
|
|
if lab.field[left_position.y+direction.y][left_position.x+1] == LEFT_BOX && !canPushBox(lab, Vector2{left_position.x + 1, left_position.y + direction.y}, direction) {
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
if direction == MOVEMENT[LEFT] {
|
|
if lab.field[left_position.y][left_position.x-1] == WALL {
|
|
return false
|
|
}
|
|
if lab.field[left_position.y][left_position.x-1] == EMPTY {
|
|
return true
|
|
}
|
|
|
|
return canPushBox(lab, Vector2{left_position.x - 1, left_position.y}, direction)
|
|
}
|
|
|
|
if lab.field[left_position.y][left_position.x+2] == WALL {
|
|
return false
|
|
}
|
|
|
|
if lab.field[left_position.y][left_position.x+2] == EMPTY {
|
|
return true
|
|
}
|
|
|
|
return canPushBox(lab, Vector2{left_position.x + 2, left_position.y}, direction)
|
|
}
|
|
|
|
func pushRobot(lab *Lab, position Vector2, direction Vector2) {
|
|
if lab.field[position.y+direction.y][position.x+direction.x] != EMPTY {
|
|
pushBox(lab, Vector2{position.x + direction.x, position.y + direction.y}, direction)
|
|
}
|
|
lab.field[position.y+direction.y][position.x+direction.x] = ROBOT
|
|
lab.field[position.y][position.x] = EMPTY
|
|
}
|
|
|
|
func pushBox(lab *Lab, position Vector2, direction Vector2) {
|
|
var left_position = position
|
|
if lab.field[position.y][position.x] == RIGHT_BOX {
|
|
left_position = Vector2{position.x - 1, position.y}
|
|
}
|
|
|
|
if direction == MOVEMENT[UP] || direction == MOVEMENT[DOWN] {
|
|
if lab.field[left_position.y+direction.y][left_position.x] == LEFT_BOX || lab.field[left_position.y+direction.y][left_position.x] == RIGHT_BOX {
|
|
pushBox(lab, Vector2{left_position.x, left_position.y + direction.y}, direction)
|
|
}
|
|
|
|
if lab.field[left_position.y+direction.y][left_position.x+1] == LEFT_BOX {
|
|
pushBox(lab, Vector2{left_position.x + 1, left_position.y + direction.y}, direction)
|
|
}
|
|
|
|
lab.field[left_position.y+direction.y][left_position.x] = LEFT_BOX
|
|
lab.field[left_position.y+direction.y][left_position.x+1] = RIGHT_BOX
|
|
lab.field[left_position.y][left_position.x] = EMPTY
|
|
lab.field[left_position.y][left_position.x+1] = EMPTY
|
|
}
|
|
|
|
if direction == MOVEMENT[LEFT] {
|
|
if lab.field[left_position.y][left_position.x-1] == RIGHT_BOX {
|
|
pushBox(lab, Vector2{position.x - 1, position.y}, direction)
|
|
}
|
|
|
|
lab.field[left_position.y][left_position.x-1] = LEFT_BOX
|
|
lab.field[left_position.y][left_position.x] = RIGHT_BOX
|
|
lab.field[left_position.y][left_position.x+1] = EMPTY
|
|
}
|
|
|
|
if direction == MOVEMENT[RIGHT] {
|
|
if lab.field[left_position.y][left_position.x+2] == LEFT_BOX {
|
|
pushBox(lab, Vector2{position.x + 2, position.y}, direction)
|
|
}
|
|
|
|
lab.field[left_position.y][left_position.x+1] = LEFT_BOX
|
|
lab.field[left_position.y][left_position.x+2] = RIGHT_BOX
|
|
lab.field[left_position.y][left_position.x] = EMPTY
|
|
}
|
|
}
|
|
|
|
func Part2(input structuredInput) int {
|
|
var lab = cloneWideLab(input)
|
|
//clearScreen()
|
|
//printLab(lab, lab.command[0])
|
|
//time.Sleep(1000 * time.Millisecond)
|
|
for i := 0; i < len(lab.command); i++ {
|
|
//moveCursorUp(len(lab.field))
|
|
if canPushRobot(&lab, getRobot(lab), MOVEMENT[lab.command[i]]) {
|
|
pushRobot(&lab, getRobot(lab), MOVEMENT[lab.command[i]])
|
|
}
|
|
//printLab(lab, lab.command[i+1])
|
|
//time.Sleep(100 * time.Millisecond)
|
|
}
|
|
|
|
return calculateWideLabScore(lab)
|
|
}
|
|
|
|
func main() {
|
|
var start time.Time
|
|
var elapsed time.Duration = 0
|
|
input, err := LoadInput(FILE_PATH)
|
|
if err != nil {
|
|
fmt.Println("Error loading input:", err)
|
|
return
|
|
}
|
|
|
|
structuredInput := TransformInput(input)
|
|
fmt.Println("Structured Input:", structuredInput)
|
|
|
|
start = time.Now()
|
|
fmt.Println("Solution Part 1: ", Part1(structuredInput))
|
|
elapsed = time.Since(start)
|
|
fmt.Printf("Part 1 took %s\n", elapsed)
|
|
|
|
start = time.Now()
|
|
fmt.Println("Solution Part 2: ", Part2(structuredInput))
|
|
elapsed = time.Since(start)
|
|
fmt.Printf("Part 2 took %s\n", elapsed)
|
|
}
|