🌅
This commit is contained in:
59
solutions/day1/day1.go
Normal file
59
solutions/day1/day1.go
Normal file
@@ -0,0 +1,59 @@
|
||||
package day1
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func Run(filename string) {
|
||||
file, err := os.Open(filename)
|
||||
|
||||
if err != nil {
|
||||
fmt.Println("Error opening file:", err)
|
||||
return
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(file)
|
||||
scanner.Split(bufio.ScanLines)
|
||||
|
||||
var elf_totals []int
|
||||
var current_total int
|
||||
var max_total int
|
||||
|
||||
max_total = 0
|
||||
for scanner.Scan() {
|
||||
num, err := strconv.Atoi(scanner.Text())
|
||||
|
||||
if err == nil {
|
||||
current_total += num
|
||||
} else {
|
||||
// this means we got a newline, I'd guess
|
||||
elf_totals = append(elf_totals, current_total)
|
||||
|
||||
if current_total > max_total {
|
||||
max_total = current_total
|
||||
}
|
||||
|
||||
current_total = 0
|
||||
}
|
||||
}
|
||||
|
||||
if current_total > 0 {
|
||||
elf_totals = append(elf_totals, current_total)
|
||||
if current_total > max_total {
|
||||
max_total = current_total
|
||||
}
|
||||
}
|
||||
|
||||
file.Close()
|
||||
|
||||
fmt.Println("Maximum food value is", max_total)
|
||||
sort.Slice(elf_totals, func(i, j int) bool {
|
||||
return elf_totals[i] > elf_totals[j]
|
||||
})
|
||||
fmt.Println("Top three items are", elf_totals[0:3])
|
||||
fmt.Println("which adds to", elf_totals[0]+elf_totals[1]+elf_totals[2])
|
||||
}
|
||||
98
solutions/day10/day10.go
Normal file
98
solutions/day10/day10.go
Normal file
@@ -0,0 +1,98 @@
|
||||
package day10
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func clearLine(line *[]rune) {
|
||||
for idx := range *line {
|
||||
(*line)[idx] = '.'
|
||||
}
|
||||
}
|
||||
|
||||
func printLine(line *[]rune) {
|
||||
for _, c := range *line {
|
||||
fmt.Printf("%c", c)
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
func registerCycle(cycle int, x int, line *[]rune) int {
|
||||
result := 0
|
||||
var position int
|
||||
|
||||
for position = cycle - 1; position > 40; position -= 40 {
|
||||
}
|
||||
|
||||
if cycle == 20 || (cycle > 20 && ((cycle-20)%40 == 0)) {
|
||||
result = x * cycle
|
||||
//fmt.Println("register cycle", cycle, "value of x is", x, "signal strength is", result)
|
||||
}
|
||||
|
||||
//if position == 0 {
|
||||
// fmt.Println("position is", position, "and x is", x)
|
||||
//}
|
||||
|
||||
if position >= x-1 && position <= x+1 {
|
||||
(*line)[position] = '#'
|
||||
}
|
||||
|
||||
if position == 40 {
|
||||
printLine(line)
|
||||
clearLine(line)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func Run(filename string) {
|
||||
file, err := os.Open(filename)
|
||||
if err != nil {
|
||||
fmt.Println("Error opening file:", err)
|
||||
return
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(file)
|
||||
scanner.Split(bufio.ScanLines)
|
||||
cycle := 1
|
||||
x := 1
|
||||
signalStrengthSum := 0
|
||||
currentLine := []rune{'.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.'}
|
||||
if len(currentLine) != 40 {
|
||||
fmt.Println("line length", len(currentLine))
|
||||
return
|
||||
}
|
||||
|
||||
registerCycle(cycle, x, ¤tLine)
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
|
||||
if strings.HasPrefix(line, "addx ") {
|
||||
cycle += 1
|
||||
signalStrengthSum += registerCycle(cycle, x, ¤tLine)
|
||||
cycle += 1
|
||||
mod, err := strconv.Atoi(string(line[5:]))
|
||||
if err != nil {
|
||||
fmt.Println("Invalid x modifier", err, "having read", string(line[5:]))
|
||||
return
|
||||
}
|
||||
x += mod
|
||||
signalStrengthSum += registerCycle(cycle, x, ¤tLine)
|
||||
} else if strings.HasPrefix(line, "noop") {
|
||||
cycle += 1
|
||||
signalStrengthSum += registerCycle(cycle, x, ¤tLine)
|
||||
} else {
|
||||
fmt.Println("PANIC: Unknown instruction:", line)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
file.Close()
|
||||
//printLine(line)
|
||||
|
||||
fmt.Println("Signal strength fun", signalStrengthSum)
|
||||
}
|
||||
226
solutions/day11/day11.go
Normal file
226
solutions/day11/day11.go
Normal file
@@ -0,0 +1,226 @@
|
||||
package day11
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Operation int
|
||||
|
||||
const (
|
||||
Times Operation = iota
|
||||
Plus
|
||||
Square
|
||||
)
|
||||
|
||||
type Monkey struct {
|
||||
items []int
|
||||
operation Operation
|
||||
operation_by int
|
||||
divisible_by int
|
||||
if_true int
|
||||
if_false int
|
||||
}
|
||||
|
||||
func printMonkeys(monkeys []*Monkey) {
|
||||
for monkey_no, monkey := range monkeys {
|
||||
if monkey != nil {
|
||||
fmt.Printf("Monkey #%d: ", monkey_no)
|
||||
var items []int = (*monkey).items
|
||||
for idx, item := range items {
|
||||
fmt.Printf("%d", item)
|
||||
if idx != len(items)-1 {
|
||||
fmt.Printf(", ")
|
||||
}
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func lcm(items []int) int {
|
||||
worstCase := 1
|
||||
smallest := items[0]
|
||||
for _, val := range items {
|
||||
worstCase *= val
|
||||
if val < smallest {
|
||||
smallest = val
|
||||
}
|
||||
}
|
||||
|
||||
retval := worstCase
|
||||
for candidate := worstCase; candidate > 2; candidate -= smallest {
|
||||
works := true
|
||||
for _, val := range items {
|
||||
works = works && (candidate%val == 0)
|
||||
}
|
||||
if works {
|
||||
retval = candidate
|
||||
}
|
||||
}
|
||||
|
||||
return retval
|
||||
}
|
||||
|
||||
func runRound(monkeys *[]*Monkey, counts *[]int, ceiling int) {
|
||||
//three := big.NewInt(3)
|
||||
for monkey_no, monkey := range *monkeys {
|
||||
if monkey == nil {
|
||||
break
|
||||
}
|
||||
//fmt.Printf("Monkey %d:\n", monkey_no)
|
||||
items := (*monkey).items
|
||||
(*monkey).items = []int{}
|
||||
for _, item := range items {
|
||||
(*counts)[monkey_no] += 1
|
||||
//fmt.Printf(" Monkey inspects an item with a worry level of %d.\n", item)
|
||||
var result int = item
|
||||
switch monkey.operation {
|
||||
case Times:
|
||||
result = item * monkey.operation_by
|
||||
//fmt.Printf(" Worry level is multiplied by %d to %d.\n", monkey.operation_by, result)
|
||||
case Square:
|
||||
result = item * item
|
||||
//fmt.Printf(" Worry level is squared to %d.\n", result)
|
||||
case Plus:
|
||||
result = item + monkey.operation_by
|
||||
//fmt.Printf(" Worry level is increased by %d to %d.\n", monkey.operation_by, result)
|
||||
default:
|
||||
//fmt.Printf("PANIC: Don't understand operation\n", monkey.operation_by)
|
||||
return
|
||||
}
|
||||
result %= ceiling
|
||||
//result = result.Div(result, three)
|
||||
//fmt.Printf(" Monkey gets bored with item. Worry level is divided by 3 to %d.\n", result)
|
||||
var targetMonkey int
|
||||
|
||||
if result%monkey.divisible_by == 0 {
|
||||
//fmt.Printf(" Current worry level is divisible by %d.\n", monkey.divisible_by)
|
||||
targetMonkey = monkey.if_true
|
||||
} else {
|
||||
//fmt.Printf(" Current worry level is not divisible by %d.\n", monkey.divisible_by)
|
||||
targetMonkey = monkey.if_false
|
||||
}
|
||||
//fmt.Printf(" Item with worry level %d is thrown to monkey %d.\n", result, targetMonkey)
|
||||
(*monkeys)[targetMonkey].items = append((*monkeys)[targetMonkey].items, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Run(filename string) {
|
||||
file, err := os.Open(filename)
|
||||
|
||||
if err != nil {
|
||||
fmt.Println("Error opening file:", err)
|
||||
return
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(file)
|
||||
scanner.Split(bufio.ScanLines)
|
||||
|
||||
monkeys := []*Monkey{nil, nil, nil, nil, nil, nil, nil, nil}
|
||||
inspectionCounts := []int{0, 0, 0, 0, 0, 0, 0, 0}
|
||||
divisors := []int{}
|
||||
var currentMonkey *Monkey = nil
|
||||
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
|
||||
if strings.HasPrefix(line, "Monkey ") {
|
||||
number, err := strconv.Atoi(line[7 : len(line)-1])
|
||||
if err != nil {
|
||||
fmt.Println("Error parsing monkey number", line[7:len(line)-1])
|
||||
return
|
||||
}
|
||||
|
||||
newMonkey := Monkey{[]int{}, Times, 0, 0, 0, 0}
|
||||
monkeys[number] = &newMonkey
|
||||
currentMonkey = &newMonkey
|
||||
} else if strings.HasPrefix(line, " Starting items: ") {
|
||||
list := line[18:]
|
||||
splits := strings.Split(list, ", ")
|
||||
if currentMonkey == nil {
|
||||
fmt.Println("Item list with no monkey")
|
||||
return
|
||||
}
|
||||
for _, split := range splits {
|
||||
splitInt, err := strconv.Atoi(split)
|
||||
if err != nil {
|
||||
fmt.Println("Error parsing monkey's item", split)
|
||||
return
|
||||
}
|
||||
(*currentMonkey).items = append((*¤tMonkey).items, splitInt)
|
||||
}
|
||||
} else if strings.HasPrefix(line, " Operation: new = old * ") {
|
||||
amtString := line[25:]
|
||||
if amtString == "old" {
|
||||
(*currentMonkey).operation = Square
|
||||
} else {
|
||||
amt, err := strconv.Atoi(amtString)
|
||||
if err != nil {
|
||||
fmt.Println("Error parsing operation number", amtString)
|
||||
return
|
||||
}
|
||||
(*¤tMonkey).operation = Times
|
||||
(*¤tMonkey).operation_by = amt
|
||||
}
|
||||
} else if strings.HasPrefix(line, " Operation: new = old + ") {
|
||||
amtString := line[25:]
|
||||
amt, err := strconv.Atoi(amtString)
|
||||
if err != nil {
|
||||
fmt.Println("Error parsing plus operation number", amtString)
|
||||
return
|
||||
}
|
||||
(*¤tMonkey).operation = Plus
|
||||
(*¤tMonkey).operation_by = amt
|
||||
} else if strings.HasPrefix(line, " Test: divisible by ") {
|
||||
amtString := line[21:]
|
||||
amt, err := strconv.Atoi(amtString)
|
||||
if err != nil {
|
||||
fmt.Println("Error parsing divisible by number", amtString)
|
||||
return
|
||||
}
|
||||
(*¤tMonkey).divisible_by = amt
|
||||
divisors = append(divisors, amt)
|
||||
} else if strings.HasPrefix(line, " If true: throw to monkey ") {
|
||||
amtString := line[29:]
|
||||
amt, err := strconv.Atoi(amtString)
|
||||
if err != nil {
|
||||
fmt.Println("Error parsing if true number", amtString)
|
||||
return
|
||||
}
|
||||
(*¤tMonkey).if_true = amt
|
||||
} else if strings.HasPrefix(line, " If false: throw to monkey ") {
|
||||
amtString := line[30:]
|
||||
amt, err := strconv.Atoi(amtString)
|
||||
if err != nil {
|
||||
fmt.Println("Error parsing if false number", amtString)
|
||||
return
|
||||
}
|
||||
(*¤tMonkey).if_false = amt
|
||||
} else if len(line) == 0 {
|
||||
} else {
|
||||
fmt.Printf("Invalid line: '%s'\n", line)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
ceiling := lcm(divisors)
|
||||
|
||||
printMonkeys(monkeys)
|
||||
for i := 0; i < 10000; i++ {
|
||||
if i%100 == 0 {
|
||||
fmt.Println("Running round", i)
|
||||
}
|
||||
runRound(&monkeys, &inspectionCounts, ceiling)
|
||||
//printMonkeys(monkeys)
|
||||
}
|
||||
for monkey_no, count := range inspectionCounts {
|
||||
fmt.Printf("Monkey %d inspected items %d times\n", monkey_no, count)
|
||||
}
|
||||
|
||||
file.Close()
|
||||
}
|
||||
174
solutions/day12/day12.go
Normal file
174
solutions/day12/day12.go
Normal file
@@ -0,0 +1,174 @@
|
||||
package day12
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
type Point struct {
|
||||
x int
|
||||
y int
|
||||
}
|
||||
|
||||
func to_level(r rune) int {
|
||||
return int(r - 'a')
|
||||
}
|
||||
|
||||
func from_level(l int) rune {
|
||||
return rune('a' + l)
|
||||
}
|
||||
|
||||
func validMove(p1 Point, p2 Point, grid [][]int) bool {
|
||||
if p2.x < 0 || p2.y < 0 || p2.x >= len(grid[0]) || p2.y >= len(grid) {
|
||||
return false
|
||||
}
|
||||
|
||||
diff := grid[p2.y][p2.x] - grid[p1.y][p1.x]
|
||||
return (diff <= 1)
|
||||
}
|
||||
|
||||
func updateDistances(grid [][]int, distances [][]int) bool {
|
||||
y := 0
|
||||
updatedSomething := false
|
||||
|
||||
for _, row := range distances {
|
||||
for x := range row {
|
||||
best := distances[y][x]
|
||||
nexts := nextSteps(Point{x, y}, grid)
|
||||
|
||||
for _, next := range nexts {
|
||||
if distances[next.y][next.x] != -1 {
|
||||
if best == -1 || distances[next.y][next.x]+1 < best {
|
||||
fmt.Println("Found a live one; at", Point{x, y}, "with score", distances[y][x], "going to", next, "with score", distances[next.y][next.x])
|
||||
best = distances[next.y][next.x] + 1
|
||||
updatedSomething = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
distances[y][x] = best
|
||||
}
|
||||
y += 1
|
||||
}
|
||||
|
||||
return updatedSomething
|
||||
}
|
||||
|
||||
func nextSteps(p Point, grid [][]int) []Point {
|
||||
retval := []Point{}
|
||||
|
||||
// point above
|
||||
above := Point{p.x, p.y - 1}
|
||||
if validMove(p, above, grid) {
|
||||
retval = append(retval, above)
|
||||
}
|
||||
// point below
|
||||
below := Point{p.x, p.y + 1}
|
||||
if validMove(p, below, grid) {
|
||||
retval = append(retval, below)
|
||||
}
|
||||
// point left
|
||||
left := Point{p.x - 1, p.y}
|
||||
if validMove(p, left, grid) {
|
||||
retval = append(retval, left)
|
||||
}
|
||||
// point right
|
||||
right := Point{p.x + 1, p.y}
|
||||
if validMove(p, right, grid) {
|
||||
retval = append(retval, right)
|
||||
}
|
||||
|
||||
return retval
|
||||
}
|
||||
|
||||
func printMap(grid [][]int) {
|
||||
for _, line := range grid {
|
||||
for _, v := range line {
|
||||
fmt.Printf("%c", from_level(v))
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
}
|
||||
|
||||
func printDistances(grid [][]int, distances [][]int) {
|
||||
for y, line := range distances {
|
||||
for x, v := range line {
|
||||
fmt.Printf("%5d/%c", v, from_level(grid[y][x]))
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
}
|
||||
|
||||
func Run(filename string) {
|
||||
file, err := os.Open(filename)
|
||||
|
||||
if err != nil {
|
||||
fmt.Println("Error opening file:", err)
|
||||
return
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(file)
|
||||
scanner.Split(bufio.ScanLines)
|
||||
grid := [][]int{}
|
||||
distances := [][]int{}
|
||||
var start Point
|
||||
var end Point
|
||||
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
row := []int{}
|
||||
drow := []int{}
|
||||
|
||||
y := len(grid)
|
||||
for x, r := range []rune(line) {
|
||||
if r == 'S' {
|
||||
r = 'a'
|
||||
start = Point{x, y}
|
||||
}
|
||||
if r == 'E' {
|
||||
r = 'z'
|
||||
end = Point{x, y}
|
||||
}
|
||||
row = append(row, to_level(r))
|
||||
drow = append(drow, -1)
|
||||
}
|
||||
grid = append(grid, row)
|
||||
distances = append(distances, drow)
|
||||
}
|
||||
|
||||
file.Close()
|
||||
|
||||
distances[end.y][end.x] = 0
|
||||
|
||||
printMap(grid)
|
||||
printDistances(grid, distances)
|
||||
fmt.Printf("Start position is (%d, %d)\n", start.x, start.y)
|
||||
fmt.Printf("End position is (%d, %d)\n", end.x, end.y)
|
||||
fmt.Println("Valid first steps:", nextSteps(start, grid))
|
||||
iteration := 0
|
||||
keepGoing := true
|
||||
for keepGoing {
|
||||
fmt.Println("Running iteration", iteration)
|
||||
printDistances(grid, distances)
|
||||
keepGoing = updateDistances(grid, distances)
|
||||
iteration += 1
|
||||
}
|
||||
fmt.Printf("Best path is %d steps long.\n", distances[start.y][start.x])
|
||||
|
||||
closestAIs := -1
|
||||
for y, line := range grid {
|
||||
for x, v := range line {
|
||||
if from_level(v) == 'a' {
|
||||
distance := distances[y][x]
|
||||
fmt.Printf("Point (%d, %d) is level `a`, and is %d steps from end.\n", x, y, distance)
|
||||
if distance != -1 {
|
||||
if closestAIs == -1 || distance < closestAIs {
|
||||
closestAIs = distance
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fmt.Println("The closest 'a' is", closestAIs, "steps away.")
|
||||
}
|
||||
208
solutions/day13/day13.go
Normal file
208
solutions/day13/day13.go
Normal file
@@ -0,0 +1,208 @@
|
||||
package day13
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Element struct {
|
||||
isNumber bool
|
||||
number int
|
||||
sublist []Element
|
||||
}
|
||||
|
||||
func parseElement(line string) (retval Element, remainder string, err error) {
|
||||
line = strings.TrimSpace(line)
|
||||
//fmt.Printf("Working on line: '%s'\n", line)
|
||||
if line[0] == '[' {
|
||||
sublist := []Element{}
|
||||
|
||||
if line[1] == ']' {
|
||||
retval = Element{false, 0, sublist}
|
||||
remainder = line[2:]
|
||||
return
|
||||
}
|
||||
|
||||
work := line[1:]
|
||||
for {
|
||||
element, rest, suberr := parseElement(work)
|
||||
//fmt.Println("Back from subcall with element", element, "rest", rest, "suberr", suberr)
|
||||
if suberr != nil {
|
||||
err = fmt.Errorf("suberror: %s", suberr)
|
||||
return
|
||||
}
|
||||
sublist = append(sublist, element)
|
||||
if rest[0] == ',' {
|
||||
work = rest[1:]
|
||||
} else if rest[0] == ']' {
|
||||
work = rest[1:]
|
||||
break
|
||||
} else {
|
||||
err = fmt.Errorf("don't know how to deal with list that ends with '%s'", rest)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
retval = Element{false, 0, sublist}
|
||||
remainder = work
|
||||
return
|
||||
} else if line[0] >= '0' && line[0] <= '9' {
|
||||
var computed int
|
||||
|
||||
for computed = 0; line[0] >= '0' && line[0] <= '9'; line = line[1:] {
|
||||
computed = (computed * 10) + int(line[0]-'0')
|
||||
}
|
||||
|
||||
retval = Element{true, computed, nil}
|
||||
remainder = line
|
||||
return
|
||||
} else {
|
||||
err = fmt.Errorf("panic: Don't know how to parse line element '%s'", line)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func renderElement(element Element) string {
|
||||
if element.isNumber {
|
||||
return fmt.Sprintf("%d", element.number)
|
||||
} else {
|
||||
retval := "["
|
||||
|
||||
if len(element.sublist) > 0 {
|
||||
retval += renderElement(element.sublist[0])
|
||||
}
|
||||
|
||||
for i := 1; i < len(element.sublist); i++ {
|
||||
retval += fmt.Sprintf(",%s", renderElement(element.sublist[i]))
|
||||
}
|
||||
|
||||
retval += "]"
|
||||
return retval
|
||||
}
|
||||
}
|
||||
|
||||
type OrderResult int
|
||||
|
||||
const (
|
||||
ProperlyOrdered OrderResult = iota
|
||||
ImproperlyOrdered
|
||||
Unknown
|
||||
)
|
||||
|
||||
func compareElements(a Element, b Element) OrderResult {
|
||||
if a.isNumber && b.isNumber {
|
||||
return compareNumbers(a.number, b.number)
|
||||
} else if a.isNumber && !b.isNumber {
|
||||
return compareLists([]Element{{true, a.number, nil}}, b.sublist)
|
||||
} else if !a.isNumber && b.isNumber {
|
||||
return compareLists(a.sublist, []Element{{true, b.number, nil}})
|
||||
} else {
|
||||
return compareLists(a.sublist, b.sublist)
|
||||
}
|
||||
}
|
||||
|
||||
func compareNumbers(a int, b int) OrderResult {
|
||||
if a < b {
|
||||
return ProperlyOrdered
|
||||
}
|
||||
|
||||
if a > b {
|
||||
return ImproperlyOrdered
|
||||
}
|
||||
|
||||
return Unknown
|
||||
}
|
||||
|
||||
func compareLists(a []Element, b []Element) OrderResult {
|
||||
if len(a) > 0 && len(b) > 0 {
|
||||
headResult := compareElements(a[0], b[0])
|
||||
|
||||
if headResult != Unknown {
|
||||
return headResult
|
||||
}
|
||||
|
||||
return compareLists(a[1:], b[1:])
|
||||
}
|
||||
|
||||
if len(a) == 0 && len(b) == 0 {
|
||||
return Unknown
|
||||
}
|
||||
|
||||
if len(a) == 0 {
|
||||
return ProperlyOrdered
|
||||
}
|
||||
|
||||
return ImproperlyOrdered
|
||||
}
|
||||
|
||||
func Run(filename string) {
|
||||
file, err := os.Open(filename)
|
||||
|
||||
if err != nil {
|
||||
fmt.Println("Error opening file:", err)
|
||||
return
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(file)
|
||||
scanner.Split(bufio.ScanLines)
|
||||
elements := []Element{}
|
||||
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
if len(line) == 0 {
|
||||
continue
|
||||
}
|
||||
element, remainder, err := parseElement(line)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
if len(remainder) > 0 {
|
||||
fmt.Printf("Unexpected remainder in line: '%s'\n", remainder)
|
||||
return
|
||||
}
|
||||
fmt.Println("Parsed element", renderElement(element))
|
||||
elements = append(elements, element)
|
||||
}
|
||||
|
||||
total := 0
|
||||
for i := 0; i < len(elements); i += 2 {
|
||||
index := i/2 + 1
|
||||
switch compareElements(elements[i], elements[i+1]) {
|
||||
case ProperlyOrdered:
|
||||
fmt.Printf("Pair #%d is properly ordered\n", index)
|
||||
total += index
|
||||
case ImproperlyOrdered:
|
||||
fmt.Printf("Pair #%d is NOT properly ordered\n", index)
|
||||
case Unknown:
|
||||
fmt.Printf("Pair #%d is UNKNOWN\n", index)
|
||||
default:
|
||||
fmt.Printf("PANIC PANIC PANIC: Unknown ordering?!\n")
|
||||
}
|
||||
}
|
||||
fmt.Println("\nTotal proper index value is", total)
|
||||
|
||||
divider2, _, _ := parseElement("[[2]]")
|
||||
divider6, _, _ := parseElement("[[6]]")
|
||||
elements = append(elements, divider2, divider6)
|
||||
|
||||
sort.SliceStable(elements, func(i, j int) bool {
|
||||
return compareElements(elements[i], elements[j]) == ProperlyOrdered
|
||||
})
|
||||
|
||||
fmt.Println()
|
||||
fmt.Println("Sorted order:")
|
||||
decoderKey := 1
|
||||
for i := 0; i < len(elements); i++ {
|
||||
fmt.Println(renderElement(elements[i]))
|
||||
if compareElements(elements[i], divider2) == Unknown || compareElements(elements[i], divider6) == Unknown {
|
||||
decoderKey *= i + 1
|
||||
}
|
||||
}
|
||||
fmt.Println("Decoder key is", decoderKey)
|
||||
|
||||
file.Close()
|
||||
}
|
||||
295
solutions/day14/day14.go
Normal file
295
solutions/day14/day14.go
Normal file
@@ -0,0 +1,295 @@
|
||||
package day14
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Point struct {
|
||||
x int
|
||||
y int
|
||||
}
|
||||
|
||||
func parsePoint(v string) (result Point, err error) {
|
||||
var parts []string = strings.Split(v, ",")
|
||||
if len(parts) != 2 {
|
||||
err = errors.New("Weird number of parts in point")
|
||||
return
|
||||
}
|
||||
|
||||
x, xerr := strconv.Atoi(parts[0])
|
||||
if xerr != nil {
|
||||
err = xerr
|
||||
return
|
||||
}
|
||||
|
||||
y, yerr := strconv.Atoi(parts[1])
|
||||
if yerr != nil {
|
||||
err = yerr
|
||||
return
|
||||
}
|
||||
|
||||
result = Point{x, y}
|
||||
return
|
||||
}
|
||||
|
||||
type Segment struct {
|
||||
start Point
|
||||
end Point
|
||||
}
|
||||
|
||||
func parseSegments(line string) (result []Segment, err error) {
|
||||
parts := strings.Split(line, " -> ")
|
||||
result = []Segment{}
|
||||
var left *Point = nil
|
||||
|
||||
for _, rightString := range parts {
|
||||
right, rerr := parsePoint(rightString)
|
||||
if rerr != nil {
|
||||
err = rerr
|
||||
return
|
||||
}
|
||||
if left != nil {
|
||||
result = append(result, Segment{*left, right})
|
||||
}
|
||||
left = &right
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func segmentPoints(segments []Segment) []Point {
|
||||
retval := []Point{}
|
||||
|
||||
for _, segment := range segments {
|
||||
retval = append(retval, segment.start, segment.end)
|
||||
}
|
||||
|
||||
return retval
|
||||
}
|
||||
|
||||
func findExtents(segments []Segment) (leftmost int, rightmost int, bottom int) {
|
||||
leftmost = int(^uint(0) >> 1)
|
||||
rightmost = 0
|
||||
bottom = 0
|
||||
|
||||
for _, point := range segmentPoints(segments) {
|
||||
if point.x < leftmost {
|
||||
leftmost = point.x
|
||||
}
|
||||
if point.x > rightmost {
|
||||
rightmost = point.x
|
||||
}
|
||||
if point.y > bottom {
|
||||
bottom = point.y
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
type Cell int
|
||||
|
||||
const (
|
||||
Air Cell = iota
|
||||
Stone
|
||||
Sand
|
||||
Start
|
||||
)
|
||||
|
||||
func drawGraph(graph [][]Cell) {
|
||||
for _, line := range graph {
|
||||
for _, v := range line {
|
||||
switch v {
|
||||
case Air:
|
||||
fmt.Printf(".")
|
||||
case Stone:
|
||||
fmt.Printf("#")
|
||||
case Sand:
|
||||
fmt.Printf("O")
|
||||
case Start:
|
||||
fmt.Printf("+")
|
||||
default:
|
||||
fmt.Printf("?")
|
||||
}
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
type State int
|
||||
|
||||
const (
|
||||
Good State = iota
|
||||
Stop
|
||||
Done
|
||||
)
|
||||
|
||||
func nextStep(curX int, curY int, graph [][]Cell) (nextX int, nextY int, state State) {
|
||||
if curY+1 >= len(graph) {
|
||||
state = Done
|
||||
return
|
||||
}
|
||||
|
||||
if graph[curY+1][curX] == Air {
|
||||
nextX = curX
|
||||
nextY = curY + 1
|
||||
state = Good
|
||||
return
|
||||
}
|
||||
|
||||
if curX == 0 {
|
||||
state = Done
|
||||
return
|
||||
}
|
||||
|
||||
if graph[curY+1][curX-1] == Air {
|
||||
nextX = curX - 1
|
||||
nextY = curY + 1
|
||||
state = Good
|
||||
return
|
||||
}
|
||||
|
||||
if curX+1 >= len(graph[0]) {
|
||||
state = Done
|
||||
return
|
||||
}
|
||||
|
||||
if graph[curY+1][curX+1] == Air {
|
||||
nextX = curX + 1
|
||||
nextY = curY + 1
|
||||
state = Good
|
||||
return
|
||||
}
|
||||
|
||||
state = Stop
|
||||
return
|
||||
}
|
||||
|
||||
func dropGrain(startX int, graph [][]Cell) bool {
|
||||
curX := startX
|
||||
curY := 0
|
||||
|
||||
for true {
|
||||
nextX, nextY, result := nextStep(curX, curY, graph)
|
||||
switch result {
|
||||
case Done:
|
||||
return true
|
||||
case Stop:
|
||||
result := graph[curY][curX] == Start
|
||||
graph[curY][curX] = Sand
|
||||
return result
|
||||
case Good:
|
||||
curX = nextX
|
||||
curY = nextY
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func Run(filename string) {
|
||||
file, err := os.Open(filename)
|
||||
|
||||
if err != nil {
|
||||
fmt.Println("Error opening file:", err)
|
||||
return
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(file)
|
||||
scanner.Split(bufio.ScanLines)
|
||||
segments := []Segment{}
|
||||
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
newSegments, err := parseSegments(line)
|
||||
|
||||
if err != nil {
|
||||
fmt.Println("PANIC", err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, segment := range newSegments {
|
||||
segments = append(segments, segment)
|
||||
}
|
||||
}
|
||||
|
||||
file.Close()
|
||||
|
||||
fmt.Println(segments)
|
||||
leftmostX, rightmostX, bottomY := findExtents(segments)
|
||||
fmt.Println("Leftmost X value is", leftmostX)
|
||||
fmt.Println("Rightmost X value is", rightmostX)
|
||||
fmt.Println("Bottom Y value is", bottomY)
|
||||
leftmostX = 0
|
||||
rightmostX += 500
|
||||
width := rightmostX - leftmostX
|
||||
graph := [][]Cell{}
|
||||
for y := 0; y <= bottomY+2; y++ {
|
||||
line := []Cell{}
|
||||
|
||||
for x := 0; x <= width; x++ {
|
||||
line = append(line, Air)
|
||||
}
|
||||
graph = append(graph, line)
|
||||
}
|
||||
segments = append(segments, Segment{Point{leftmostX, bottomY + 2}, Point{rightmostX, bottomY + 2}})
|
||||
for _, segment := range segments {
|
||||
if segment.start.x == segment.end.x {
|
||||
var top int
|
||||
var bottom int
|
||||
|
||||
if segment.start.y < segment.end.y {
|
||||
top = segment.start.y
|
||||
bottom = segment.end.y
|
||||
} else {
|
||||
top = segment.end.y
|
||||
bottom = segment.start.y
|
||||
}
|
||||
|
||||
for current := top; current <= bottom; current++ {
|
||||
graph[current][segment.start.x-leftmostX] = Stone
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if segment.start.y == segment.end.y {
|
||||
var left int
|
||||
var right int
|
||||
|
||||
if segment.start.x < segment.end.x {
|
||||
left = segment.start.x
|
||||
right = segment.end.x
|
||||
} else {
|
||||
left = segment.end.x
|
||||
right = segment.start.x
|
||||
}
|
||||
|
||||
for current := left; current <= right; current++ {
|
||||
graph[segment.start.y][current-leftmostX] = Stone
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
fmt.Println("PANIC: Diagonal line!?")
|
||||
return
|
||||
}
|
||||
graph[0][500-leftmostX] = Start
|
||||
drawGraph(graph)
|
||||
count := 0
|
||||
for !dropGrain(500-leftmostX, graph) {
|
||||
count += 1
|
||||
}
|
||||
drawGraph(graph)
|
||||
fmt.Println("Dropped", count, "grains of sand")
|
||||
if graph[0][500-leftmostX] != Sand {
|
||||
fmt.Println("PANIC: NOT WIDE ENOUGH")
|
||||
}
|
||||
}
|
||||
182
solutions/day15/day15.go
Normal file
182
solutions/day15/day15.go
Normal file
@@ -0,0 +1,182 @@
|
||||
package day15
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type Point struct {
|
||||
x int
|
||||
y int
|
||||
}
|
||||
|
||||
func pointDistance(a Point, b Point) int {
|
||||
return diff(a.x, b.x) + diff(a.y, b.y)
|
||||
}
|
||||
|
||||
func diff(a int, b int) int {
|
||||
if a > b {
|
||||
return a - b
|
||||
} else {
|
||||
return b - a
|
||||
}
|
||||
}
|
||||
|
||||
type Sensor struct {
|
||||
location Point
|
||||
distance int
|
||||
}
|
||||
|
||||
func parseLine(line string) (sensor Sensor, beacon Point) {
|
||||
var sensorX int
|
||||
var sensorY int
|
||||
var beaconX int
|
||||
var beaconY int
|
||||
|
||||
fmt.Sscanf(line, "Sensor at x=%d, y=%d: closest beacon is at x=%d, y=%d", &sensorX, &sensorY, &beaconX, &beaconY)
|
||||
sensorLoc := Point{sensorX, sensorY}
|
||||
beacon = Point{beaconX, beaconY}
|
||||
distance := pointDistance(sensorLoc, beacon)
|
||||
sensor = Sensor{sensorLoc, distance}
|
||||
return
|
||||
}
|
||||
|
||||
func isBeacon(x int, y int, beacons []Point) bool {
|
||||
for _, beacon := range beacons {
|
||||
if beacon.x == x && beacon.y == y {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func inSensorRange(x int, y int, sensors []Sensor) *Sensor {
|
||||
point := Point{x, y}
|
||||
for _, sensor := range sensors {
|
||||
distance := pointDistance(point, sensor.location)
|
||||
if distance <= sensor.distance {
|
||||
return &sensor
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func leftmostPoint(sensors []Sensor) int {
|
||||
leftmost := 0
|
||||
|
||||
for _, sensor := range sensors {
|
||||
candidate := sensor.location.x - sensor.distance
|
||||
if candidate < leftmost {
|
||||
leftmost = candidate
|
||||
}
|
||||
}
|
||||
|
||||
return leftmost
|
||||
}
|
||||
|
||||
func rightmostPoint(sensors []Sensor) int {
|
||||
rightmost := 0
|
||||
|
||||
for _, sensor := range sensors {
|
||||
candidate := sensor.location.x + sensor.distance
|
||||
if candidate > rightmost {
|
||||
rightmost = candidate
|
||||
}
|
||||
}
|
||||
|
||||
return rightmost
|
||||
}
|
||||
|
||||
func addLinePoints(pointSet *[]Point, start Point, end Point) {
|
||||
offsetX := 1
|
||||
offsetY := 1
|
||||
|
||||
if end.x < start.x {
|
||||
offsetX = -1
|
||||
}
|
||||
if end.y < start.y {
|
||||
offsetY = -1
|
||||
}
|
||||
|
||||
for x, y := start.x, start.y; y != end.y; x, y = x+offsetX, y+offsetY {
|
||||
*pointSet = append(*pointSet, Point{x, y})
|
||||
}
|
||||
}
|
||||
|
||||
func addEdgePoints(pointSet *[]Point, sensor Sensor) {
|
||||
distance := sensor.distance + 1
|
||||
top := Point{sensor.location.x, sensor.location.y - distance}
|
||||
right := Point{sensor.location.x + distance, sensor.location.y}
|
||||
bottom := Point{sensor.location.x, sensor.location.y + distance}
|
||||
left := Point{sensor.location.x - distance, sensor.location.y}
|
||||
|
||||
addLinePoints(pointSet, top, right)
|
||||
addLinePoints(pointSet, right, bottom)
|
||||
addLinePoints(pointSet, bottom, left)
|
||||
addLinePoints(pointSet, left, top)
|
||||
}
|
||||
|
||||
func Run(filename string, yStr string, maxCoordStr string) {
|
||||
y, err := strconv.Atoi(yStr)
|
||||
if err != nil {
|
||||
fmt.Println("PANIC: bad y argument", yStr)
|
||||
return
|
||||
}
|
||||
|
||||
maxCoord, err := strconv.Atoi(maxCoordStr)
|
||||
if err != nil {
|
||||
fmt.Println("PANIC: bad y argument", maxCoordStr)
|
||||
return
|
||||
}
|
||||
|
||||
file, err := os.Open(filename)
|
||||
|
||||
if err != nil {
|
||||
fmt.Println("Error opening file:", err)
|
||||
return
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(file)
|
||||
scanner.Split(bufio.ScanLines)
|
||||
sensors := []Sensor{}
|
||||
beacons := []Point{}
|
||||
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
sensor, beacon := parseLine(line)
|
||||
sensors = append(sensors, sensor)
|
||||
beacons = append(beacons, beacon)
|
||||
}
|
||||
|
||||
file.Close()
|
||||
|
||||
fmt.Println(sensors)
|
||||
|
||||
count := 0
|
||||
for x := leftmostPoint(sensors); x <= rightmostPoint(sensors); x++ {
|
||||
if isBeacon(x, y, beacons) {
|
||||
continue
|
||||
}
|
||||
|
||||
if inSensorRange(x, y, sensors) != nil {
|
||||
count += 1
|
||||
}
|
||||
}
|
||||
fmt.Println("Beacons couldn't be in", count, "places on line", y)
|
||||
|
||||
candidates := []Point{}
|
||||
for _, sensor := range sensors {
|
||||
addEdgePoints(&candidates, sensor)
|
||||
}
|
||||
|
||||
for _, candidate := range candidates {
|
||||
if candidate.x >= 0 && candidate.y >= 0 && candidate.x <= maxCoord && candidate.y <= maxCoord {
|
||||
closestSensor := inSensorRange(candidate.x, candidate.y, sensors)
|
||||
if closestSensor == nil {
|
||||
fmt.Printf("Found possible beacon location at (%d, %d). Frequency value is %d\n", candidate.x, candidate.y, candidate.x*4000000+candidate.y)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
185
solutions/day16/day16.go
Normal file
185
solutions/day16/day16.go
Normal file
@@ -0,0 +1,185 @@
|
||||
package day16
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
type Valve struct {
|
||||
flowRate int
|
||||
leadsTo []string
|
||||
}
|
||||
|
||||
type SimulationPoint struct {
|
||||
time int
|
||||
imAt string
|
||||
elephantAt string
|
||||
opened string
|
||||
}
|
||||
|
||||
type MovePair struct {
|
||||
me string
|
||||
elephant string
|
||||
}
|
||||
|
||||
func newMovePair(useElephant bool, a string, b string) MovePair {
|
||||
if useElephant && a != "**" && b != "**" && b < a {
|
||||
return MovePair{b, a}
|
||||
} else {
|
||||
return MovePair{a, b}
|
||||
}
|
||||
}
|
||||
|
||||
func addMovePair(pair MovePair, list *[]MovePair) {
|
||||
for _, existing := range *list {
|
||||
if existing.me == pair.me && existing.elephant == pair.elephant {
|
||||
return
|
||||
}
|
||||
}
|
||||
*list = append(*list, pair)
|
||||
}
|
||||
|
||||
func alreadyVisited(place string, point SimulationPoint) bool {
|
||||
for i := 0; i < len(point.opened); i += 2 {
|
||||
if place[0] == point.opened[i] && place[1] == point.opened[i+1] {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func addPlace(place string, opened string) string {
|
||||
injectionPoint := 0
|
||||
|
||||
for injectionPoint < len(opened) && (opened[injectionPoint] < place[0] || (opened[injectionPoint] == place[0] && opened[injectionPoint+1] < place[1])) {
|
||||
injectionPoint += 2
|
||||
}
|
||||
|
||||
return opened[0:injectionPoint] + place + opened[injectionPoint:]
|
||||
}
|
||||
|
||||
var everythingOpened string
|
||||
var simulatedItems map[SimulationPoint]int
|
||||
|
||||
func runSimulations(useElephant bool, time int, myPlace string, elephantAt string, opened string, valves map[string]Valve) int {
|
||||
maxTime := 30
|
||||
if useElephant {
|
||||
maxTime = 26
|
||||
}
|
||||
|
||||
if time == maxTime || everythingOpened == opened {
|
||||
return 0
|
||||
}
|
||||
|
||||
point := SimulationPoint{time, myPlace, elephantAt, opened}
|
||||
|
||||
if previousValue, ok := simulatedItems[point]; ok {
|
||||
return previousValue
|
||||
}
|
||||
|
||||
bestResult := 0
|
||||
|
||||
myMoves := []string{}
|
||||
if !alreadyVisited(myPlace, point) && valves[myPlace].flowRate != 0 {
|
||||
myMoves = append(myMoves, "**")
|
||||
}
|
||||
myMoves = append(myMoves, valves[myPlace].leadsTo...)
|
||||
|
||||
elephantMoves := []string{"AA"}
|
||||
if useElephant {
|
||||
elephantMoves = []string{}
|
||||
if !alreadyVisited(elephantAt, point) && valves[elephantAt].flowRate != 0 && myPlace != elephantAt {
|
||||
elephantMoves = append(elephantMoves, "**")
|
||||
}
|
||||
elephantMoves = append(elephantMoves, valves[elephantAt].leadsTo...)
|
||||
}
|
||||
|
||||
moves := []MovePair{}
|
||||
for _, myMove := range myMoves {
|
||||
for _, elephantMove := range elephantMoves {
|
||||
pair := newMovePair(useElephant, myMove, elephantMove)
|
||||
addMovePair(pair, &moves)
|
||||
}
|
||||
}
|
||||
|
||||
for _, pair := range moves {
|
||||
myMove := pair.me
|
||||
elephantMove := pair.elephant
|
||||
openReleases := 0
|
||||
myNext := myPlace
|
||||
elephantNext := elephantAt
|
||||
nowOpen := opened
|
||||
|
||||
if myMove == "**" {
|
||||
openReleases += valves[myPlace].flowRate * (maxTime - (time + 1))
|
||||
nowOpen = addPlace(myPlace, nowOpen)
|
||||
} else {
|
||||
myNext = myMove
|
||||
}
|
||||
|
||||
if elephantMove == "**" {
|
||||
openReleases += valves[elephantAt].flowRate * (maxTime - (time + 1))
|
||||
nowOpen = addPlace(elephantAt, nowOpen)
|
||||
} else {
|
||||
elephantNext = elephantMove
|
||||
}
|
||||
|
||||
candidate := openReleases + runSimulations(useElephant, time+1, myNext, elephantNext, nowOpen, valves)
|
||||
if candidate > bestResult {
|
||||
bestResult = candidate
|
||||
}
|
||||
}
|
||||
|
||||
simulatedItems[point] = bestResult
|
||||
|
||||
return bestResult
|
||||
}
|
||||
|
||||
func Run(filename string) {
|
||||
file, err := os.Open(filename)
|
||||
|
||||
if err != nil {
|
||||
fmt.Println("Error opening file:", err)
|
||||
return
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(file)
|
||||
scanner.Split(bufio.ScanLines)
|
||||
valveMap := map[string]Valve{}
|
||||
everythingOpened = ""
|
||||
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
var valve string
|
||||
var rate int
|
||||
fmt.Sscanf(line, "Valve %s has flow rate=%d", &valve, &rate)
|
||||
_, after_semi, found_semi := strings.Cut(line, ";")
|
||||
if !found_semi {
|
||||
fmt.Println("PANIC: Couldn't find semi colon in line.")
|
||||
return
|
||||
}
|
||||
after_semi = strings.TrimLeftFunc(after_semi, func(r rune) bool {
|
||||
return !unicode.IsUpper(r)
|
||||
})
|
||||
targets := strings.Split(after_semi, ", ")
|
||||
|
||||
if rate > 0 {
|
||||
everythingOpened = addPlace(valve, everythingOpened)
|
||||
}
|
||||
|
||||
valveMap[valve] = Valve{rate, targets}
|
||||
}
|
||||
|
||||
simulatedItems = map[SimulationPoint]int{}
|
||||
bestRelease1 := runSimulations(false, 0, "AA", "AA", "", valveMap)
|
||||
fmt.Println("Working on my own, best pressure release found is", bestRelease1)
|
||||
|
||||
simulatedItems = map[SimulationPoint]int{}
|
||||
bestRelease2 := runSimulations(true, 0, "AA", "AA", "", valveMap)
|
||||
fmt.Println("Working with an elephant, best pressure release found is", bestRelease2)
|
||||
|
||||
file.Close()
|
||||
}
|
||||
301
solutions/day17/day17.go
Normal file
301
solutions/day17/day17.go
Normal file
@@ -0,0 +1,301 @@
|
||||
package day17
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Piece []string
|
||||
|
||||
var horizontalBar Piece = Piece{"@@@@"}
|
||||
var plus Piece = Piece{".@.", "@@@", ".@."}
|
||||
var backwardsL Piece = Piece{"..@", "..@", "@@@"}
|
||||
var verticalBar Piece = Piece{"@", "@", "@", "@"}
|
||||
var block Piece = Piece{"@@", "@@"}
|
||||
|
||||
var pieces []Piece = []Piece{horizontalBar, plus, backwardsL, verticalBar, block}
|
||||
|
||||
type Board [][]rune
|
||||
|
||||
func newBoard() Board {
|
||||
return Board{}
|
||||
}
|
||||
|
||||
func pieceToRows(piece Piece) Board {
|
||||
retval := Board{}
|
||||
|
||||
for i := range piece {
|
||||
newRow := append([]rune(".."), []rune(piece[i])...)
|
||||
|
||||
for len(newRow) < 7 {
|
||||
newRow = append(newRow, '.')
|
||||
}
|
||||
|
||||
retval = append(retval, newRow)
|
||||
}
|
||||
|
||||
return retval
|
||||
}
|
||||
|
||||
func rowEmpty(row int, board Board) bool {
|
||||
if row >= len(board) {
|
||||
return false
|
||||
}
|
||||
|
||||
for i := 0; i < 7; i++ {
|
||||
if board[row][i] != '.' {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func addPiece(piece Piece, board *Board) {
|
||||
rows := pieceToRows(piece)
|
||||
for !(rowEmpty(0, *board) && rowEmpty(1, *board) && rowEmpty(2, *board)) {
|
||||
*board = append(Board{[]rune(".......")}, *board...)
|
||||
}
|
||||
for len(*board) >= 3 && rowEmpty(3, *board) {
|
||||
*board = (*board)[1:]
|
||||
}
|
||||
*board = append(rows, *board...)
|
||||
}
|
||||
|
||||
func printBoard(board *Board) {
|
||||
for _, row := range *board {
|
||||
fmt.Printf("|")
|
||||
for _, r := range row {
|
||||
fmt.Printf("%c", r)
|
||||
}
|
||||
fmt.Printf("|\n")
|
||||
}
|
||||
fmt.Printf("+-------+\n")
|
||||
}
|
||||
|
||||
func copyBoard(clearPiece bool, currentBoard *Board, newBoard *Board) {
|
||||
for len(*newBoard) < len(*currentBoard) {
|
||||
*newBoard = append(Board{[]rune(".......")}, *newBoard...)
|
||||
}
|
||||
for len(*newBoard) > len(*currentBoard) {
|
||||
*newBoard = (*newBoard)[1:]
|
||||
}
|
||||
for y, row := range *currentBoard {
|
||||
for x, value := range row {
|
||||
if clearPiece && value == '@' {
|
||||
(*newBoard)[y][x] = '.'
|
||||
} else {
|
||||
(*newBoard)[y][x] = (*currentBoard)[y][x]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func movePiece(direction rune, currentBoard *Board, newBoard *Board) bool {
|
||||
/// match the values
|
||||
copyBoard(true, currentBoard, newBoard)
|
||||
for y, row := range *currentBoard {
|
||||
for x, value := range row {
|
||||
if value == '@' {
|
||||
newX := x
|
||||
newY := y
|
||||
|
||||
switch direction {
|
||||
case '<':
|
||||
newX = x - 1
|
||||
case '>':
|
||||
newX = x + 1
|
||||
case 'v':
|
||||
newY = y + 1
|
||||
default:
|
||||
fmt.Printf("PANIC: Invalid direction %c\n", direction)
|
||||
return false
|
||||
}
|
||||
|
||||
if newX < 0 || newX >= 7 || newY >= len(*currentBoard) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (*currentBoard)[newY][newX] == '#' {
|
||||
return false
|
||||
}
|
||||
|
||||
(*newBoard)[newY][newX] = '@'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func freezeBoard(board *Board) int {
|
||||
for _, row := range *board {
|
||||
for x, value := range row {
|
||||
if value == '@' {
|
||||
row[x] = '#'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for y := 0; y < len(*board)-3; y++ {
|
||||
allCovered := true
|
||||
for x := 0; x < 7; x++ {
|
||||
allCovered = allCovered && ((*board)[y][x] == '#' || (*board)[y+1][x] == '#' || (*board)[y+2][x] == '#' || (*board)[y+3][x] == '#')
|
||||
}
|
||||
|
||||
if allCovered {
|
||||
originalLength := len(*board)
|
||||
//fmt.Println("--- original board ---")
|
||||
//printBoard(board)
|
||||
*board = (*board)[0 : y+4]
|
||||
//fmt.Println("--- cleaned board ---")
|
||||
//printBoard(board)
|
||||
//fmt.Println("---", originalLength-len(*board), "rows removed ---")
|
||||
removedRows := originalLength - len(*board)
|
||||
//if removedRows > 0 {
|
||||
// fmt.Println("Removed", removedRows, "rows.")
|
||||
//}
|
||||
return removedRows
|
||||
}
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
func sameBoard(board1 Board, board2 Board) bool {
|
||||
if len(board1) != len(board2) {
|
||||
return false
|
||||
}
|
||||
|
||||
for y, row := range board1 {
|
||||
for x := range row {
|
||||
if board1[y][x] != board2[y][x] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func duplicateBoard(board Board) Board {
|
||||
newBoard := Board{}
|
||||
|
||||
for _, row := range board {
|
||||
newRow := []rune(".......")
|
||||
copy(newRow, row)
|
||||
newBoard = append(newBoard, newRow)
|
||||
}
|
||||
|
||||
return newBoard
|
||||
}
|
||||
|
||||
type PreviousState struct {
|
||||
moveIndex int
|
||||
pieceIndex int
|
||||
}
|
||||
|
||||
type PreviousLocation struct {
|
||||
dropNo int
|
||||
removedRows int
|
||||
board Board
|
||||
}
|
||||
|
||||
func Run(filename string, targetStr string) {
|
||||
targetPieces, err := strconv.Atoi(targetStr)
|
||||
if err != nil {
|
||||
fmt.Println("PANIC: Don't understand target number")
|
||||
return
|
||||
}
|
||||
|
||||
file, err := os.Open(filename)
|
||||
|
||||
if err != nil {
|
||||
fmt.Println("Error opening file:", err)
|
||||
return
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(file)
|
||||
scanner.Split(bufio.ScanLines)
|
||||
instructions := []rune{}
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
line = strings.TrimFunc(line, func(r rune) bool {
|
||||
return r != '>' && r != '<'
|
||||
})
|
||||
instructions = append(instructions, []rune(line)...)
|
||||
}
|
||||
file.Close()
|
||||
|
||||
board1 := newBoard()
|
||||
board2 := newBoard()
|
||||
|
||||
moveIndex := 0
|
||||
pieceIndex := 0
|
||||
previousStates := map[PreviousState][]PreviousLocation{}
|
||||
removedRows := 0
|
||||
skippedAhead := false
|
||||
|
||||
for dropNo := 1; dropNo <= targetPieces; dropNo++ {
|
||||
//fmt.Println("Dropping piece", dropNo, "with piece", pieceIndex, "and move", moveIndex)
|
||||
|
||||
if !skippedAhead {
|
||||
state := PreviousState{moveIndex, pieceIndex}
|
||||
previousLocations, previousStateExisted := previousStates[state]
|
||||
if previousStateExisted {
|
||||
for _, location := range previousLocations {
|
||||
if sameBoard(board1, location.board) {
|
||||
dropDifference := dropNo - location.dropNo
|
||||
removedRowsDifference := removedRows - location.removedRows
|
||||
fmt.Println("Got it, current drop", dropNo, "previous version", location.dropNo, "removed row diff", removedRowsDifference)
|
||||
repeats := (targetPieces - dropNo) / dropDifference
|
||||
fmt.Println("Drop difference of", dropDifference, "meaning we can do this gap", repeats, "more times")
|
||||
fmt.Println("removed rows", removedRows, "+", repeats*removedRowsDifference, "=", removedRows+(repeats*removedRowsDifference))
|
||||
removedRows += repeats * removedRowsDifference
|
||||
fmt.Println("Drop number", dropNo, "+", repeats*dropDifference, "=", dropNo+(repeats*dropDifference))
|
||||
dropNo += repeats*dropDifference - 1
|
||||
skippedAhead = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if skippedAhead {
|
||||
continue
|
||||
} else {
|
||||
previousStates[state] = append(previousStates[state], PreviousLocation{dropNo, removedRows, duplicateBoard(board1)})
|
||||
}
|
||||
} else {
|
||||
previousStates[state] = []PreviousLocation{{dropNo, removedRows, duplicateBoard(board1)}}
|
||||
}
|
||||
}
|
||||
|
||||
addPiece(pieces[pieceIndex], &board1)
|
||||
for {
|
||||
//fmt.Println("Move", moveIndex, "is", string(instructions[moveIndex]))
|
||||
if !movePiece(instructions[moveIndex], &board1, &board2) {
|
||||
copyBoard(false, &board1, &board2)
|
||||
}
|
||||
moveIndex = (moveIndex + 1) % len(instructions)
|
||||
if !movePiece('v', &board2, &board1) {
|
||||
copyBoard(false, &board2, &board1)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
removedRows += freezeBoard(&board1)
|
||||
pieceIndex = (pieceIndex + 1) % len(pieces)
|
||||
}
|
||||
|
||||
//printBoard(&board1)
|
||||
fmt.Println("Removed", removedRows, "rows.")
|
||||
rowsWithRocks := removedRows
|
||||
for i := 0; i < len(board1); i++ {
|
||||
if !rowEmpty(i, board1) {
|
||||
rowsWithRocks += 1
|
||||
}
|
||||
}
|
||||
fmt.Println("Rocks are", rowsWithRocks, "rows tall.")
|
||||
}
|
||||
148
solutions/day18/day18.go
Normal file
148
solutions/day18/day18.go
Normal file
@@ -0,0 +1,148 @@
|
||||
package day18
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
type Point struct {
|
||||
x int
|
||||
y int
|
||||
z int
|
||||
}
|
||||
|
||||
const (
|
||||
Lava int = iota
|
||||
Air
|
||||
Steam
|
||||
)
|
||||
|
||||
func surroundingPoints(point Point) []Point {
|
||||
return []Point{
|
||||
{point.x - 1, point.y, point.z},
|
||||
{point.x + 1, point.y, point.z},
|
||||
{point.x, point.y - 1, point.z},
|
||||
{point.x, point.y + 1, point.z},
|
||||
{point.x, point.y, point.z - 1},
|
||||
{point.x, point.y, point.z + 1},
|
||||
}
|
||||
}
|
||||
|
||||
func countBlankSides(points map[Point]int, against int) int {
|
||||
blankSides := 0
|
||||
for point, original := range points {
|
||||
if original == Lava {
|
||||
pointBlankSides := 0
|
||||
for _, surrounding := range surroundingPoints(point) {
|
||||
if points[surrounding] == against {
|
||||
pointBlankSides += 1
|
||||
}
|
||||
}
|
||||
//fmt.Println("Point", point, "had", pointBlankSides, "blank sides.")
|
||||
blankSides += pointBlankSides
|
||||
}
|
||||
}
|
||||
return blankSides
|
||||
}
|
||||
|
||||
func inUniverse(point Point, min Point, max Point) bool {
|
||||
return point.x >= min.x && point.x <= max.x &&
|
||||
point.y >= min.y && point.y <= max.y &&
|
||||
point.z >= min.z && point.z <= max.x
|
||||
}
|
||||
|
||||
func floodSteam(minPoint Point, maxPoint Point, points map[Point]int) {
|
||||
stack := []Point{minPoint}
|
||||
|
||||
for len(stack) > 0 {
|
||||
nextIdx := len(stack) - 1
|
||||
next := stack[nextIdx]
|
||||
stack = stack[0:nextIdx]
|
||||
|
||||
if inUniverse(next, minPoint, maxPoint) && points[next] == Air {
|
||||
points[next] = Steam
|
||||
stack = append(stack, surroundingPoints(next)...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func findExtendedBoundingBox(points map[Point]int) (minPoint Point, maxPoint Point) {
|
||||
minPoint = Point{1000000, 100000, 100000}
|
||||
maxPoint = Point{0, 0, 0}
|
||||
|
||||
for point := range points {
|
||||
if point.x < minPoint.x {
|
||||
minPoint.x = point.x
|
||||
}
|
||||
if point.x > maxPoint.x {
|
||||
maxPoint.x = point.x
|
||||
}
|
||||
if point.y < minPoint.y {
|
||||
minPoint.y = point.y
|
||||
}
|
||||
if point.y > maxPoint.y {
|
||||
maxPoint.y = point.y
|
||||
}
|
||||
if point.z < minPoint.z {
|
||||
minPoint.z = point.z
|
||||
}
|
||||
if point.z > maxPoint.z {
|
||||
maxPoint.z = point.z
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println("Min point", minPoint)
|
||||
fmt.Println("Max point", maxPoint)
|
||||
|
||||
minPoint.x -= 1
|
||||
minPoint.y -= 1
|
||||
minPoint.z -= 1
|
||||
maxPoint.x += 1
|
||||
maxPoint.y += 1
|
||||
maxPoint.z += 1
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func Run(filename string) {
|
||||
file, err := os.Open(filename)
|
||||
if err != nil {
|
||||
fmt.Println("Error opening file:", err)
|
||||
return
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(file)
|
||||
scanner.Split(bufio.ScanLines)
|
||||
points := map[Point]int{}
|
||||
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
var x int
|
||||
var y int
|
||||
var z int
|
||||
fmt.Sscanf(line, "%d,%d,%d", &x, &y, &z)
|
||||
point := Point{x, y, z}
|
||||
points[point] = Lava
|
||||
}
|
||||
file.Close()
|
||||
|
||||
minPoint, maxPoint := findExtendedBoundingBox(points)
|
||||
|
||||
for x := minPoint.x; x <= maxPoint.x; x++ {
|
||||
for y := minPoint.y; y <= maxPoint.y; y++ {
|
||||
for z := minPoint.z; z <= maxPoint.z; z++ {
|
||||
point := Point{x, y, z}
|
||||
_, exists := points[point]
|
||||
if !exists {
|
||||
points[point] = Air
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println("Found", countBlankSides(points, Air), "blank sides in base solid.")
|
||||
floodSteam(minPoint, maxPoint, points)
|
||||
fmt.Println("Found", countBlankSides(points, Steam), "blank sides after filling interior.")
|
||||
|
||||
}
|
||||
171
solutions/day19/day19.go
Normal file
171
solutions/day19/day19.go
Normal file
@@ -0,0 +1,171 @@
|
||||
package day19
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
type Blueprint struct {
|
||||
blueprintNumber int
|
||||
oreRobotOreCost int
|
||||
clayRobotOreCost int
|
||||
obsRobotOreCost int
|
||||
obsRobotClayCost int
|
||||
geodeRobotOreCost int
|
||||
geodeRobotObsCost int
|
||||
}
|
||||
|
||||
type State struct {
|
||||
minute int
|
||||
ore int
|
||||
clay int
|
||||
obsidian int
|
||||
geodes int
|
||||
|
||||
oreRobots int
|
||||
clayRobots int
|
||||
obsidianRobots int
|
||||
geodeRobots int
|
||||
}
|
||||
|
||||
var initialState State = State{0, 0, 0, 0, 0, 1, 0, 0, 0}
|
||||
|
||||
func copyState(state State) State {
|
||||
return State{
|
||||
state.minute,
|
||||
state.ore,
|
||||
state.clay,
|
||||
state.obsidian,
|
||||
state.geodes,
|
||||
state.oreRobots,
|
||||
state.clayRobots,
|
||||
state.obsidianRobots,
|
||||
state.geodeRobots,
|
||||
}
|
||||
}
|
||||
|
||||
func nextStates(current State, minutes int, blueprint Blueprint) []State {
|
||||
if current.minute == minutes {
|
||||
return []State{}
|
||||
}
|
||||
|
||||
buyNothing := State{
|
||||
current.minute + 1,
|
||||
current.ore + current.oreRobots,
|
||||
current.clay + current.clayRobots,
|
||||
current.obsidian + current.obsidianRobots,
|
||||
current.geodes + current.geodeRobots,
|
||||
current.oreRobots,
|
||||
current.clayRobots,
|
||||
current.obsidianRobots,
|
||||
current.geodeRobots,
|
||||
}
|
||||
|
||||
retval := []State{buyNothing}
|
||||
|
||||
if current.ore >= blueprint.oreRobotOreCost {
|
||||
buyOre := copyState(buyNothing)
|
||||
buyOre.ore -= blueprint.oreRobotOreCost
|
||||
buyOre.oreRobots += 1
|
||||
retval = append(retval, buyOre)
|
||||
}
|
||||
|
||||
if current.ore >= blueprint.clayRobotOreCost {
|
||||
buyClay := copyState(buyNothing)
|
||||
buyClay.ore -= blueprint.clayRobotOreCost
|
||||
buyClay.clayRobots += 1
|
||||
retval = append(retval, buyClay)
|
||||
}
|
||||
|
||||
if current.ore >= blueprint.obsRobotOreCost && current.clay >= blueprint.obsRobotClayCost {
|
||||
buyObsidian := copyState(buyNothing)
|
||||
buyObsidian.ore -= blueprint.obsRobotOreCost
|
||||
buyObsidian.clay -= blueprint.obsRobotClayCost
|
||||
buyObsidian.obsidianRobots += 1
|
||||
retval = append(retval, buyObsidian)
|
||||
}
|
||||
|
||||
if current.ore >= blueprint.geodeRobotOreCost && current.obsidian >= blueprint.geodeRobotObsCost {
|
||||
buyGeode := copyState(buyNothing)
|
||||
buyGeode.ore -= blueprint.geodeRobotOreCost
|
||||
buyGeode.obsidian -= blueprint.geodeRobotObsCost
|
||||
buyGeode.geodeRobots += 1
|
||||
retval = append(retval, buyGeode)
|
||||
}
|
||||
|
||||
return retval
|
||||
}
|
||||
|
||||
func runSimulation(blueprint Blueprint, minutes int) int {
|
||||
visited := map[State]bool{}
|
||||
queue := []State{initialState}
|
||||
bestResult := 0
|
||||
|
||||
for len(queue) > 0 {
|
||||
nextState := queue[len(queue)-1]
|
||||
queue = queue[0 : len(queue)-1]
|
||||
|
||||
_, alreadyDone := visited[nextState]
|
||||
if alreadyDone {
|
||||
continue
|
||||
}
|
||||
visited[nextState] = true
|
||||
|
||||
minutesLeft := minutes - nextState.minute
|
||||
bestPossibleFuture := nextState.geodes + (minutesLeft*(2*nextState.geodeRobots+(minutesLeft-1)))/2
|
||||
if bestPossibleFuture < bestResult {
|
||||
continue
|
||||
}
|
||||
|
||||
newStates := nextStates(nextState, minutes, blueprint)
|
||||
if len(newStates) == 0 {
|
||||
if nextState.geodes > bestResult {
|
||||
bestResult = nextState.geodes
|
||||
}
|
||||
}
|
||||
queue = append(queue, newStates...)
|
||||
}
|
||||
|
||||
return bestResult
|
||||
}
|
||||
|
||||
func Run(filename string) {
|
||||
file, err := os.Open(filename)
|
||||
if err != nil {
|
||||
fmt.Println("Error opening file:", err)
|
||||
return
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(file)
|
||||
scanner.Split(bufio.ScanLines)
|
||||
blueprints := []Blueprint{}
|
||||
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
var blueprintNumber, oreRobotOre, clayRobotOre, obsRobotOre, obsRobotClay, geodeRobotOre, geodeRobotObs int
|
||||
fmt.Sscanf(line, "Blueprint %d: Each ore robot costs %d ore. Each clay robot costs %d ore. Each obsidian robot costs %d ore and %d clay. Each geode robot costs %d ore and %d obsidian.", &blueprintNumber, &oreRobotOre, &clayRobotOre, &obsRobotOre, &obsRobotClay, &geodeRobotOre, &geodeRobotObs)
|
||||
blueprints = append(blueprints, Blueprint{blueprintNumber, oreRobotOre, clayRobotOre, obsRobotOre, obsRobotClay, geodeRobotOre, geodeRobotObs})
|
||||
}
|
||||
file.Close()
|
||||
|
||||
fmt.Println("Blueprints", blueprints)
|
||||
totalQualityLevel := 0
|
||||
for _, blueprint := range blueprints {
|
||||
geodesHarvested := runSimulation(blueprint, 24)
|
||||
fmt.Println("Found", geodesHarvested, "geodes for blueprint", blueprint.blueprintNumber)
|
||||
totalQualityLevel += geodesHarvested * blueprint.blueprintNumber
|
||||
}
|
||||
fmt.Println("Total quality level of all blueprints at 24 minutes is", totalQualityLevel)
|
||||
|
||||
multipliedGeodes := 1
|
||||
if len(blueprints) > 3 {
|
||||
blueprints = blueprints[0:3]
|
||||
}
|
||||
for _, blueprint := range blueprints {
|
||||
geodesHarvested := runSimulation(blueprint, 32)
|
||||
fmt.Println("Found", geodesHarvested, "geodes for blueprint", blueprint.blueprintNumber)
|
||||
multipliedGeodes *= geodesHarvested
|
||||
}
|
||||
fmt.Println("Multiplied number of geodes of first three blueprints at 32 minutes is", multipliedGeodes)
|
||||
}
|
||||
166
solutions/day2/day2.go
Normal file
166
solutions/day2/day2.go
Normal file
@@ -0,0 +1,166 @@
|
||||
package day2
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"golang.org/x/exp/utf8string"
|
||||
)
|
||||
|
||||
type Move = int
|
||||
|
||||
const (
|
||||
Rock Move = 1
|
||||
Paper = 2
|
||||
Scissors = 3
|
||||
)
|
||||
|
||||
type Result = int
|
||||
|
||||
const (
|
||||
Loss Result = 0
|
||||
Tie = 3
|
||||
Win = 6
|
||||
)
|
||||
|
||||
func score(opponent Move, us Move) int {
|
||||
switch opponent {
|
||||
case Rock:
|
||||
switch us {
|
||||
case Rock:
|
||||
return Rock + Tie
|
||||
case Paper:
|
||||
return Paper + Win
|
||||
case Scissors:
|
||||
return Scissors + Loss
|
||||
}
|
||||
|
||||
case Paper:
|
||||
switch us {
|
||||
case Rock:
|
||||
return Rock + Loss
|
||||
case Paper:
|
||||
return Paper + Tie
|
||||
case Scissors:
|
||||
return Scissors + Win
|
||||
}
|
||||
|
||||
case Scissors:
|
||||
switch us {
|
||||
case Rock:
|
||||
return Rock + Win
|
||||
case Paper:
|
||||
return Paper + Loss
|
||||
case Scissors:
|
||||
return Scissors + Tie
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println("PANIC: Unknown combo: them", opponent, "us", us)
|
||||
return 0
|
||||
}
|
||||
|
||||
func compute_move(opponent Move, goal Result) Move {
|
||||
switch opponent {
|
||||
case Rock:
|
||||
switch goal {
|
||||
case Loss:
|
||||
return Scissors
|
||||
case Tie:
|
||||
return Rock
|
||||
case Win:
|
||||
return Paper
|
||||
}
|
||||
|
||||
case Paper:
|
||||
switch goal {
|
||||
case Loss:
|
||||
return Rock
|
||||
case Tie:
|
||||
return Paper
|
||||
case Win:
|
||||
return Scissors
|
||||
}
|
||||
|
||||
case Scissors:
|
||||
switch goal {
|
||||
case Loss:
|
||||
return Paper
|
||||
case Tie:
|
||||
return Scissors
|
||||
case Win:
|
||||
return Rock
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println("PANIC: Unknown combo: them", opponent, "goal", goal)
|
||||
return 0
|
||||
|
||||
}
|
||||
|
||||
func move(r rune) Move {
|
||||
switch r {
|
||||
case 'A':
|
||||
return Rock
|
||||
case 'B':
|
||||
return Paper
|
||||
case 'C':
|
||||
return Scissors
|
||||
case 'X':
|
||||
return Rock
|
||||
case 'Y':
|
||||
return Paper
|
||||
case 'Z':
|
||||
return Scissors
|
||||
}
|
||||
fmt.Println("PANIC: Unknown character", r)
|
||||
return 0
|
||||
}
|
||||
|
||||
func result(r rune) Result {
|
||||
switch r {
|
||||
case 'X':
|
||||
return Loss
|
||||
case 'Y':
|
||||
return Tie
|
||||
case 'Z':
|
||||
return Win
|
||||
}
|
||||
fmt.Println("PANIC: Unknown character for result", r)
|
||||
return 99
|
||||
}
|
||||
|
||||
func Run(filename string) {
|
||||
file, err := os.Open(filename)
|
||||
|
||||
if err != nil {
|
||||
fmt.Println("Error opening file:", err)
|
||||
return
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(file)
|
||||
scanner.Split(bufio.ScanLines)
|
||||
|
||||
var total_score_base int = 0
|
||||
var total_score_extended int = 0
|
||||
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
utf8 := utf8string.NewString(line)
|
||||
|
||||
opponent := move(utf8.At(0))
|
||||
us := move(utf8.At(2))
|
||||
|
||||
total_score_base += score(opponent, us)
|
||||
|
||||
target := result(utf8.At(2))
|
||||
computed_move := compute_move(opponent, target)
|
||||
total_score_extended += score(opponent, computed_move)
|
||||
}
|
||||
|
||||
file.Close()
|
||||
|
||||
fmt.Println("Total initial score is", total_score_base)
|
||||
fmt.Println("Total score with back compute is", total_score_extended)
|
||||
}
|
||||
144
solutions/day20/day20.go
Normal file
144
solutions/day20/day20.go
Normal file
@@ -0,0 +1,144 @@
|
||||
package day20
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func printNumbers(numbers []int, nexts []int) {
|
||||
current := 0
|
||||
|
||||
for nexts[current] == -1 {
|
||||
current += 1
|
||||
}
|
||||
|
||||
first := numbers[current]
|
||||
fmt.Printf("%d", first)
|
||||
current = nexts[current]
|
||||
for numbers[current] != first {
|
||||
fmt.Printf(" %d", numbers[current])
|
||||
current = nexts[current]
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
func removeIndex(idx int, prevs []int, nexts []int) (before int, after int) {
|
||||
before = prevs[idx]
|
||||
after = nexts[idx]
|
||||
|
||||
nexts[before] = after
|
||||
prevs[after] = before
|
||||
prevs[idx] = -1
|
||||
nexts[idx] = -1
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func insertAfter(idx int, after int, prevs []int, nexts []int) {
|
||||
afterNext := nexts[after]
|
||||
prevs[idx] = after
|
||||
nexts[idx] = afterNext
|
||||
prevs[afterNext] = idx
|
||||
nexts[after] = idx
|
||||
}
|
||||
|
||||
func insertBefore(idx int, before int, prevs []int, nexts []int) {
|
||||
beforePrev := prevs[before]
|
||||
prevs[idx] = beforePrev
|
||||
nexts[idx] = before
|
||||
prevs[before] = idx
|
||||
nexts[beforePrev] = idx
|
||||
}
|
||||
|
||||
func shift(count int, idx int, prevs []int, nexts []int) {
|
||||
count %= len(prevs) - 1
|
||||
for count != 0 {
|
||||
before, after := removeIndex(idx, prevs, nexts)
|
||||
if count > 0 {
|
||||
insertAfter(idx, after, prevs, nexts)
|
||||
count -= 1
|
||||
} else {
|
||||
insertBefore(idx, before, prevs, nexts)
|
||||
count += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func indexPast(count int, current int, nexts []int) int {
|
||||
for count > 0 {
|
||||
current = nexts[current]
|
||||
count -= 1
|
||||
}
|
||||
return current
|
||||
}
|
||||
|
||||
func Run(filename string, keyString string, countString string) {
|
||||
file, err := os.Open(filename)
|
||||
if err != nil {
|
||||
fmt.Println("Error opening file:", err)
|
||||
return
|
||||
}
|
||||
|
||||
key, err := strconv.Atoi(keyString)
|
||||
if err != nil {
|
||||
fmt.Println("Invalid key value", err)
|
||||
return
|
||||
}
|
||||
|
||||
roundCount, err := strconv.Atoi(countString)
|
||||
if err != nil {
|
||||
fmt.Println("Invalid round count", err)
|
||||
return
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(file)
|
||||
scanner.Split(bufio.ScanLines)
|
||||
numbers := []int{}
|
||||
nexts := []int{}
|
||||
prevs := []int{}
|
||||
zeroIndex := -1
|
||||
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
x, err := strconv.Atoi(line)
|
||||
if err != nil {
|
||||
fmt.Println("Couldn't parse number", line)
|
||||
return
|
||||
}
|
||||
numbers = append(numbers, x*key)
|
||||
nexts = append(nexts, 0)
|
||||
prevs = append(prevs, 0)
|
||||
|
||||
if x == 0 {
|
||||
zeroIndex = len(numbers) - 1
|
||||
}
|
||||
}
|
||||
|
||||
for idx := range nexts {
|
||||
nexts[idx] = (idx + 1) % len(nexts)
|
||||
if idx == 0 {
|
||||
prevs[idx] = len(prevs) - 1
|
||||
} else {
|
||||
prevs[idx] = idx - 1
|
||||
}
|
||||
}
|
||||
file.Close()
|
||||
|
||||
for round := 0; round < roundCount; round++ {
|
||||
for idx := range numbers {
|
||||
fmt.Println("Working round", round+1, "index", idx, "with value/count", numbers[idx])
|
||||
shift(numbers[idx], idx, prevs, nexts)
|
||||
//printNumbers(numbers, nexts)
|
||||
}
|
||||
}
|
||||
|
||||
idx1k := indexPast(1000, zeroIndex, nexts)
|
||||
idx2k := indexPast(1000, idx1k, nexts)
|
||||
idx3k := indexPast(1000, idx2k, nexts)
|
||||
val1k := numbers[idx1k]
|
||||
val2k := numbers[idx2k]
|
||||
val3k := numbers[idx3k]
|
||||
fmt.Println("Value 1k after 0", val1k, "2k", val2k, "3k", val3k, "sum", val1k+val2k+val3k)
|
||||
}
|
||||
221
solutions/day21/day21.go
Normal file
221
solutions/day21/day21.go
Normal file
@@ -0,0 +1,221 @@
|
||||
package day21
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
const (
|
||||
OpAdd int = iota
|
||||
OpMul
|
||||
OpSub
|
||||
OpDiv
|
||||
OpTarget
|
||||
)
|
||||
|
||||
func opString(v int) string {
|
||||
switch v {
|
||||
case OpAdd:
|
||||
return "+"
|
||||
case OpMul:
|
||||
return "*"
|
||||
case OpSub:
|
||||
return "-"
|
||||
case OpDiv:
|
||||
return "/"
|
||||
case OpTarget:
|
||||
return "=="
|
||||
default:
|
||||
return "???????"
|
||||
}
|
||||
}
|
||||
|
||||
type MonkeyState struct {
|
||||
valueSet bool
|
||||
value int
|
||||
operation int
|
||||
left string
|
||||
right string
|
||||
}
|
||||
|
||||
func step(monkeys map[string]MonkeyState) bool {
|
||||
changedSomething := false
|
||||
|
||||
for monkey, state := range monkeys {
|
||||
if !state.valueSet && monkeys[state.left].valueSet && monkeys[state.right].valueSet {
|
||||
switch state.operation {
|
||||
case OpAdd:
|
||||
state.value = monkeys[state.left].value + monkeys[state.right].value
|
||||
case OpSub:
|
||||
state.value = monkeys[state.left].value - monkeys[state.right].value
|
||||
case OpMul:
|
||||
state.value = monkeys[state.left].value * monkeys[state.right].value
|
||||
case OpDiv:
|
||||
state.value = monkeys[state.left].value / monkeys[state.right].value
|
||||
}
|
||||
state.valueSet = true
|
||||
monkeys[monkey] = state
|
||||
changedSomething = true
|
||||
}
|
||||
}
|
||||
|
||||
return changedSomething
|
||||
}
|
||||
|
||||
func contains(ls []string, x string) bool {
|
||||
for _, tester := range ls {
|
||||
if tester == x {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func Run(filename string) {
|
||||
file, err := os.Open(filename)
|
||||
if err != nil {
|
||||
fmt.Println("Error opening file:", err)
|
||||
return
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(file)
|
||||
scanner.Split(bufio.ScanLines)
|
||||
monkeys := map[string]MonkeyState{}
|
||||
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
|
||||
monkey := line[0:4]
|
||||
data := line[6:]
|
||||
value, err := strconv.Atoi(data)
|
||||
if err == nil {
|
||||
monkeys[monkey] = MonkeyState{true, value, 0, "", ""}
|
||||
} else {
|
||||
left := data[0:4]
|
||||
right := data[7:]
|
||||
switch data[5] {
|
||||
case '+':
|
||||
monkeys[monkey] = MonkeyState{false, 0, OpAdd, left, right}
|
||||
case '-':
|
||||
monkeys[monkey] = MonkeyState{false, 0, OpSub, left, right}
|
||||
case '*':
|
||||
monkeys[monkey] = MonkeyState{false, 0, OpMul, left, right}
|
||||
case '/':
|
||||
monkeys[monkey] = MonkeyState{false, 0, OpDiv, left, right}
|
||||
default:
|
||||
fmt.Println("Bad operation", string(data[5]))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
file.Close()
|
||||
|
||||
for !monkeys["root"].valueSet {
|
||||
step(monkeys)
|
||||
}
|
||||
fmt.Println("Root monkey value, part 1:", monkeys["root"].value)
|
||||
|
||||
humn := monkeys["humn"]
|
||||
humn.valueSet = false
|
||||
humn.operation = OpTarget
|
||||
monkeys["humn"] = humn
|
||||
unwoundMonkeys := []string{"humn"}
|
||||
updatedSomething := true
|
||||
for updatedSomething {
|
||||
updatedSomething = false
|
||||
for monkey, state := range monkeys {
|
||||
if contains(unwoundMonkeys, state.left) || contains(unwoundMonkeys, state.right) {
|
||||
if state.valueSet {
|
||||
state.valueSet = false
|
||||
monkeys[monkey] = state
|
||||
unwoundMonkeys = append(unwoundMonkeys, monkey)
|
||||
updatedSomething = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println("monkeys now", monkeys)
|
||||
var targetValue int
|
||||
var nextNode string
|
||||
|
||||
if monkeys[monkeys["root"].left].valueSet {
|
||||
targetValue = monkeys[monkeys["root"].left].value
|
||||
nextNode = monkeys["root"].right
|
||||
fmt.Println("Root's right is the unknown one", nextNode)
|
||||
} else {
|
||||
targetValue = monkeys[monkeys["root"].right].value
|
||||
nextNode = monkeys["root"].left
|
||||
fmt.Println("Root's left is the unknown one", nextNode)
|
||||
}
|
||||
|
||||
for !monkeys["humn"].valueSet {
|
||||
nextState := monkeys[nextNode]
|
||||
|
||||
if nextState.valueSet {
|
||||
fmt.Println("PANIC: Grounded in a node with the value set")
|
||||
return
|
||||
} else if nextState.operation == OpTarget {
|
||||
nextState.value = targetValue
|
||||
nextState.valueSet = true
|
||||
monkeys[nextNode] = nextState
|
||||
} else if monkeys[nextState.left].valueSet {
|
||||
nextState.value = targetValue
|
||||
nextState.valueSet = true
|
||||
monkeys[nextNode] = nextState
|
||||
switch nextState.operation {
|
||||
case OpAdd:
|
||||
// T = V + X
|
||||
// T - V = X
|
||||
targetValue = targetValue - monkeys[nextState.left].value
|
||||
case OpMul:
|
||||
// T = V * X
|
||||
// T / V = X
|
||||
targetValue = targetValue / monkeys[nextState.left].value
|
||||
case OpSub:
|
||||
// T = V - X
|
||||
// T + X = V
|
||||
// X = V - T
|
||||
targetValue = monkeys[nextState.left].value - targetValue
|
||||
case OpDiv:
|
||||
// T = V / X
|
||||
// TX = V
|
||||
// X = V / T
|
||||
targetValue = monkeys[nextState.left].value / targetValue
|
||||
default:
|
||||
fmt.Println("PANIC: Bad operation in back compute")
|
||||
}
|
||||
nextNode = nextState.right
|
||||
} else if monkeys[nextState.right].valueSet {
|
||||
nextState.value = targetValue
|
||||
nextState.valueSet = true
|
||||
monkeys[nextNode] = nextState
|
||||
switch nextState.operation {
|
||||
case OpAdd:
|
||||
// T = X + V
|
||||
// T - V = X
|
||||
targetValue = targetValue - monkeys[nextState.right].value
|
||||
case OpMul:
|
||||
// T = X * V
|
||||
targetValue = targetValue / monkeys[nextState.right].value
|
||||
case OpSub:
|
||||
// T = X - V
|
||||
// T + V = X
|
||||
targetValue = targetValue + monkeys[nextState.right].value
|
||||
case OpDiv:
|
||||
// T = X / V
|
||||
// TV = X
|
||||
targetValue = targetValue * monkeys[nextState.right].value
|
||||
default:
|
||||
fmt.Println("PANIC: Bad operation in back compute")
|
||||
}
|
||||
nextNode = nextState.left
|
||||
} else {
|
||||
fmt.Println("PANIC: Trouble with a double split in search")
|
||||
return
|
||||
}
|
||||
}
|
||||
fmt.Println("Actually, my value should be", monkeys["humn"].value)
|
||||
}
|
||||
635
solutions/day22/day22.go
Normal file
635
solutions/day22/day22.go
Normal file
@@ -0,0 +1,635 @@
|
||||
package day22
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
type Command int
|
||||
type Tile int
|
||||
type Direction int
|
||||
type Face int
|
||||
type Board [][]Tile
|
||||
type FaceMap map[Face]Board
|
||||
|
||||
const (
|
||||
TurnRight Command = -1
|
||||
TurnLeft = -2
|
||||
)
|
||||
|
||||
const (
|
||||
Void Tile = iota
|
||||
Empty
|
||||
Wall
|
||||
)
|
||||
|
||||
const (
|
||||
Right Direction = 0
|
||||
Down = 1
|
||||
Left = 2
|
||||
Up = 3
|
||||
)
|
||||
|
||||
const (
|
||||
TopFace Face = iota
|
||||
BottomFace
|
||||
LeftFace
|
||||
RightFace
|
||||
FrontFace
|
||||
BackFace
|
||||
)
|
||||
|
||||
func printBoard(board Board) {
|
||||
for _, row := range board {
|
||||
for _, tile := range row {
|
||||
switch tile {
|
||||
case Void:
|
||||
fmt.Printf(" ")
|
||||
case Empty:
|
||||
fmt.Printf(".")
|
||||
case Wall:
|
||||
fmt.Printf("#")
|
||||
}
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
}
|
||||
|
||||
func parseCommands(runes []rune) []Command {
|
||||
result := []Command{}
|
||||
accum := 0
|
||||
|
||||
for _, r := range runes {
|
||||
if r >= '0' && r <= '9' {
|
||||
accum = (accum * 10) + int(r-'0')
|
||||
} else {
|
||||
result = append(result, Command(accum))
|
||||
accum = 0
|
||||
if r == 'R' {
|
||||
result = append(result, TurnRight)
|
||||
} else {
|
||||
result = append(result, TurnLeft)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if accum > 0 {
|
||||
result = append(result, Command(accum))
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func parseBoardLine(runes []rune) []Tile {
|
||||
result := []Tile{}
|
||||
|
||||
for _, r := range runes {
|
||||
switch r {
|
||||
case ' ':
|
||||
result = append(result, Void)
|
||||
case '.':
|
||||
result = append(result, Empty)
|
||||
case '#':
|
||||
result = append(result, Wall)
|
||||
default:
|
||||
fmt.Println("PANIC: Illegal character in board map", string(r))
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func initialPosition(board Board) (x int, y int) {
|
||||
for idx, tile := range board[0] {
|
||||
if tile == Empty {
|
||||
x = idx
|
||||
y = 0
|
||||
return
|
||||
}
|
||||
}
|
||||
fmt.Println("PANIC: Couldn't figure out initial position")
|
||||
x = 0
|
||||
y = 0
|
||||
return
|
||||
}
|
||||
|
||||
func turn(direction Direction, turn Command) Direction {
|
||||
switch direction {
|
||||
case Up:
|
||||
switch turn {
|
||||
case TurnLeft:
|
||||
return Left
|
||||
case TurnRight:
|
||||
return Right
|
||||
}
|
||||
case Right:
|
||||
switch turn {
|
||||
case TurnLeft:
|
||||
return Up
|
||||
case TurnRight:
|
||||
return Down
|
||||
}
|
||||
case Down:
|
||||
switch turn {
|
||||
case TurnLeft:
|
||||
return Right
|
||||
case TurnRight:
|
||||
return Left
|
||||
}
|
||||
case Left:
|
||||
switch turn {
|
||||
case TurnLeft:
|
||||
return Down
|
||||
case TurnRight:
|
||||
return Up
|
||||
}
|
||||
}
|
||||
fmt.Println("PANIC: turn default WTF")
|
||||
return Down
|
||||
}
|
||||
|
||||
type State2D struct {
|
||||
x int
|
||||
y int
|
||||
direction Direction
|
||||
board Board
|
||||
}
|
||||
|
||||
func step2d(count int, state *State2D) {
|
||||
if count == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
keepGoing := true
|
||||
|
||||
switch state.direction {
|
||||
case Right:
|
||||
currentRow := state.board[state.y]
|
||||
tester := (state.x + 1) % len(currentRow)
|
||||
for {
|
||||
if currentRow[tester] == Void {
|
||||
tester = (tester + 1) % len(currentRow)
|
||||
continue
|
||||
} else if currentRow[tester] == Wall {
|
||||
keepGoing = false
|
||||
break
|
||||
} else {
|
||||
(*state).x = tester
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
case Left:
|
||||
currentRow := state.board[state.y]
|
||||
tester := state.x - 1
|
||||
for {
|
||||
if tester < 0 {
|
||||
tester = len(currentRow) - 1
|
||||
}
|
||||
if currentRow[tester] == Void {
|
||||
tester -= 1
|
||||
continue
|
||||
} else if currentRow[tester] == Wall {
|
||||
keepGoing = false
|
||||
break
|
||||
} else {
|
||||
(*state).x = tester
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
case Up:
|
||||
tester := state.y - 1
|
||||
for {
|
||||
if tester < 0 {
|
||||
tester = len(state.board) - 1
|
||||
}
|
||||
if state.board[tester][state.x] == Void {
|
||||
tester -= 1
|
||||
continue
|
||||
} else if state.board[tester][state.x] == Wall {
|
||||
keepGoing = false
|
||||
break
|
||||
} else {
|
||||
(*state).y = tester
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
case Down:
|
||||
fmt.Println("Moving down count is", count, "y value", state.y)
|
||||
tester := (state.y + 1) % len(state.board)
|
||||
for {
|
||||
if state.board[tester][state.x] == Void {
|
||||
tester = (tester + 1) % len(state.board)
|
||||
continue
|
||||
} else if state.board[tester][state.x] == Wall {
|
||||
fmt.Println("Hit wall at tester", tester)
|
||||
keepGoing = false
|
||||
break
|
||||
} else {
|
||||
(*state).y = tester
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
fmt.Println("BAD")
|
||||
}
|
||||
|
||||
if keepGoing {
|
||||
step2d(count-1, state)
|
||||
}
|
||||
}
|
||||
|
||||
func copyFace(startX int, startY int, faceSize int, board Board) Board {
|
||||
retval := Board{}
|
||||
|
||||
for y := 0; y < faceSize; y++ {
|
||||
newRow := []Tile{}
|
||||
|
||||
for x := 0; x < faceSize; x++ {
|
||||
newRow = append(newRow, board[y+startY][x+startX])
|
||||
}
|
||||
retval = append(retval, newRow)
|
||||
}
|
||||
|
||||
return retval
|
||||
}
|
||||
|
||||
func validPoint(x int, y int, board Board) bool {
|
||||
return (x >= 0) && (y >= 0) && (x < len(board[0])) && (y < len(board))
|
||||
}
|
||||
|
||||
var faceMoves map[Face]map[Direction]Face = map[Face]map[Direction]Face{
|
||||
TopFace: map[Direction]Face{
|
||||
Up: BackFace,
|
||||
Left: LeftFace,
|
||||
Right: RightFace,
|
||||
Down: FrontFace,
|
||||
},
|
||||
BottomFace: map[Direction]Face{
|
||||
Up: FrontFace,
|
||||
Left: LeftFace,
|
||||
Right: RightFace,
|
||||
Down: BackFace,
|
||||
},
|
||||
LeftFace: map[Direction]Face{
|
||||
Up: TopFace,
|
||||
Left: BackFace,
|
||||
Right: FrontFace,
|
||||
Down: BottomFace,
|
||||
},
|
||||
RightFace: map[Direction]Face{
|
||||
Up: TopFace,
|
||||
Left: FrontFace,
|
||||
Right: BackFace,
|
||||
Down: BottomFace,
|
||||
},
|
||||
FrontFace: map[Direction]Face{
|
||||
Up: TopFace,
|
||||
Left: LeftFace,
|
||||
Right: RightFace,
|
||||
Down: BottomFace,
|
||||
},
|
||||
BackFace: map[Direction]Face{
|
||||
Up: BackFace,
|
||||
Left: RightFace,
|
||||
Right: FrontFace,
|
||||
Down: FrontFace,
|
||||
},
|
||||
}
|
||||
|
||||
var faceName map[Face]string = map[Face]string{
|
||||
TopFace: "top",
|
||||
BottomFace: "bottom",
|
||||
LeftFace: "left",
|
||||
RightFace: "right",
|
||||
FrontFace: "front",
|
||||
BackFace: "back",
|
||||
}
|
||||
|
||||
var dirName map[Direction]string = map[Direction]string{
|
||||
Up: "up",
|
||||
Down: "down",
|
||||
Left: "left",
|
||||
Right: "right",
|
||||
}
|
||||
|
||||
var tileName map[Tile]string = map[Tile]string{
|
||||
Void: "void",
|
||||
Empty: "empty",
|
||||
Wall: "wall",
|
||||
}
|
||||
|
||||
type State3D struct {
|
||||
x int
|
||||
y int
|
||||
face Face
|
||||
direction Direction
|
||||
board FaceMap
|
||||
}
|
||||
|
||||
func translate(x int, y int, faceSize int, face Face, dir Direction) (resx int, resy int, resdir Direction) {
|
||||
switch face {
|
||||
case TopFace:
|
||||
resdir = Down
|
||||
switch dir {
|
||||
case Right, Left:
|
||||
resy = 0
|
||||
resx = y
|
||||
case Up, Down:
|
||||
resy = 0
|
||||
resx = x
|
||||
}
|
||||
case FrontFace:
|
||||
resdir = dir
|
||||
switch dir {
|
||||
case Up:
|
||||
resy = faceSize - 1
|
||||
resx = x
|
||||
case Down:
|
||||
resy = 0
|
||||
resx = x
|
||||
case Left:
|
||||
resy = y
|
||||
resx = faceSize - 1
|
||||
case Right:
|
||||
resy = y
|
||||
resx = 0
|
||||
}
|
||||
case LeftFace:
|
||||
switch dir {
|
||||
case Up:
|
||||
resx = 0
|
||||
resy = x
|
||||
resdir = Right
|
||||
case Down:
|
||||
resx = 0
|
||||
resy = x
|
||||
resdir = Right
|
||||
case Left:
|
||||
resy = y
|
||||
resx = faceSize - 1
|
||||
resdir = Left
|
||||
case Right:
|
||||
resy = y
|
||||
resx = 0
|
||||
resdir = Right
|
||||
}
|
||||
case RightFace:
|
||||
switch dir {
|
||||
case Up:
|
||||
resx = faceSize - 1
|
||||
resy = x
|
||||
resdir = Left
|
||||
case Down:
|
||||
resx = faceSize - 1
|
||||
resy = x
|
||||
resdir = Left
|
||||
case Left:
|
||||
resx = faceSize - 1
|
||||
resy = y
|
||||
resdir = Left
|
||||
case Right:
|
||||
resy = y
|
||||
resx = 0
|
||||
resdir = Right
|
||||
}
|
||||
case BackFace:
|
||||
switch dir {
|
||||
case Up:
|
||||
resx = x
|
||||
resy = 0
|
||||
resdir = Down
|
||||
case Down:
|
||||
resx = x
|
||||
resy = faceSize - 1
|
||||
resdir = Up
|
||||
case Left:
|
||||
resx = faceSize - 1
|
||||
resy = y
|
||||
resdir = Right
|
||||
case Right:
|
||||
resy = y
|
||||
resx = 0
|
||||
resdir = Left
|
||||
}
|
||||
case BottomFace:
|
||||
switch dir {
|
||||
case Up:
|
||||
resx = x
|
||||
resy = faceSize - 1
|
||||
resdir = Up
|
||||
case Down:
|
||||
resx = x
|
||||
resy = faceSize - 1
|
||||
resdir = Up
|
||||
case Left:
|
||||
resx = y
|
||||
resy = faceSize - 1
|
||||
resdir = Up
|
||||
case Right:
|
||||
resy = faceSize - 1
|
||||
resx = y
|
||||
resdir = Up
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func step3d(count int, state *State3D) {
|
||||
if count == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
for i := 0; i < count; i++ {
|
||||
newX := state.x
|
||||
newY := state.y
|
||||
newFace := state.face
|
||||
newDirection := state.direction
|
||||
|
||||
switch state.direction {
|
||||
case Up:
|
||||
newY -= 1
|
||||
case Down:
|
||||
newY += 1
|
||||
case Left:
|
||||
newX -= 1
|
||||
case Right:
|
||||
newX += 1
|
||||
}
|
||||
|
||||
if !validPoint(newX, newY, state.board[state.face]) {
|
||||
newFace = faceMoves[state.face][state.direction]
|
||||
newX, newY, newDirection = translate(newX, newY, len(state.board[state.face]), state.face, state.direction)
|
||||
}
|
||||
fmt.Printf("(%d, %d) going %s on %s ---> (%d, %d) going %s on %s [%s]\n", state.x, state.y, dirName[state.direction], faceName[state.face], newX, newY, dirName[newDirection], faceName[newFace], tileName[state.board[newFace][newY][state.x]])
|
||||
|
||||
if state.board[newFace][newY][newX] == Wall {
|
||||
return
|
||||
}
|
||||
|
||||
(*state).x = newX
|
||||
(*state).y = newY
|
||||
(*state).face = newFace
|
||||
(*state).direction = newDirection
|
||||
}
|
||||
}
|
||||
|
||||
func Run(filename string) {
|
||||
file, err := os.Open(filename)
|
||||
if err != nil {
|
||||
fmt.Println("Error opening file:", err)
|
||||
return
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(file)
|
||||
scanner.Split(bufio.ScanLines)
|
||||
var commands []Command
|
||||
initialBoard := Board{}
|
||||
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
runes := []rune(line)
|
||||
|
||||
if len(runes) > 0 {
|
||||
if runes[0] >= '0' && runes[0] <= '9' {
|
||||
commands = parseCommands(runes)
|
||||
} else {
|
||||
initialBoard = append(initialBoard, parseBoardLine(runes))
|
||||
}
|
||||
}
|
||||
}
|
||||
file.Close()
|
||||
|
||||
maximumRowLength := 0
|
||||
for _, row := range initialBoard {
|
||||
if len(row) > maximumRowLength {
|
||||
maximumRowLength = len(row)
|
||||
}
|
||||
}
|
||||
|
||||
board := Board{}
|
||||
|
||||
for _, row := range initialBoard {
|
||||
for len(row) < maximumRowLength {
|
||||
row = append(row, Void)
|
||||
}
|
||||
board = append(board, row)
|
||||
}
|
||||
|
||||
printBoard(board)
|
||||
|
||||
startX, startY := initialPosition(board)
|
||||
x, y := startX, startY
|
||||
fmt.Println("initial position is", x, y)
|
||||
state := State2D{x, y, Right, board}
|
||||
for _, command := range commands {
|
||||
fmt.Println("x", state.x, "y", state.y, "direction", dirName[state.direction])
|
||||
fmt.Println("Running", command)
|
||||
switch command {
|
||||
case TurnRight:
|
||||
state.direction = turn(state.direction, TurnRight)
|
||||
case TurnLeft:
|
||||
state.direction = turn(state.direction, TurnLeft)
|
||||
default:
|
||||
step2d(int(command), &state)
|
||||
}
|
||||
}
|
||||
|
||||
finalRow := state.y + 1
|
||||
finalColumn := state.x + 1
|
||||
finalPassword := 1000*finalRow + 4*finalColumn + int(state.direction)
|
||||
fmt.Println("Final row is", finalRow, "final column is", finalColumn, "final password is", finalPassword)
|
||||
|
||||
narrowestSection := len(board[0])
|
||||
for _, row := range board {
|
||||
startIdx := -1
|
||||
endIdx := -1
|
||||
|
||||
for idx, tile := range row {
|
||||
if tile != Void && startIdx == -1 {
|
||||
startIdx = idx
|
||||
}
|
||||
|
||||
if tile == Void && (startIdx != -1) && (endIdx == -1) {
|
||||
endIdx = idx
|
||||
}
|
||||
}
|
||||
|
||||
if endIdx == -1 {
|
||||
endIdx = len(row) - 1
|
||||
}
|
||||
|
||||
if (endIdx - startIdx) < narrowestSection {
|
||||
narrowestSection = endIdx - startIdx
|
||||
}
|
||||
}
|
||||
|
||||
cubeFaceSize := narrowestSection
|
||||
fmt.Println("Cube face size is", cubeFaceSize)
|
||||
|
||||
faceMap := FaceMap{}
|
||||
currentFace := TopFace
|
||||
currentSpineX := startX
|
||||
currentSpineY := startY
|
||||
|
||||
for validPoint(currentSpineX, currentSpineY, board) {
|
||||
faceMap[currentFace] = copyFace(currentSpineX, currentSpineY, cubeFaceSize, board)
|
||||
fmt.Printf("Found %s face at (%d,%d)\n", faceName[currentFace], currentSpineX, currentSpineY)
|
||||
|
||||
leftwardsFace := faceMoves[currentFace][Left]
|
||||
for leftX := currentSpineX - cubeFaceSize; validPoint(leftX, currentSpineY, board) && board[currentSpineY][leftX] != Void; leftX -= cubeFaceSize {
|
||||
faceMap[leftwardsFace] = copyFace(leftX, currentSpineY, cubeFaceSize, board)
|
||||
fmt.Printf("Found %s face at (%d,%d)\n", faceName[leftwardsFace], leftX, currentSpineY)
|
||||
fmt.Println("Moving face", faceName[leftwardsFace], "left to", faceName[faceMoves[leftwardsFace][Left]])
|
||||
leftwardsFace = faceMoves[leftwardsFace][Left]
|
||||
}
|
||||
|
||||
rightwardsFace := faceMoves[currentFace][Right]
|
||||
for rightX := currentSpineX + cubeFaceSize; validPoint(rightX, currentSpineY, board) && board[currentSpineY][rightX] != Void; rightX += cubeFaceSize {
|
||||
faceMap[rightwardsFace] = copyFace(rightX, currentSpineY, cubeFaceSize, board)
|
||||
fmt.Printf("Found %s face at (%d,%d)\n", faceName[rightwardsFace], rightX, currentSpineY)
|
||||
fmt.Println("Moving face", faceName[rightwardsFace], "right to", faceName[faceMoves[rightwardsFace][Left]])
|
||||
rightwardsFace = faceMoves[rightwardsFace][Right]
|
||||
}
|
||||
|
||||
currentSpineY += cubeFaceSize
|
||||
fmt.Println("Moving face", faceName[currentFace], "down to", faceName[faceMoves[currentFace][Down]])
|
||||
currentFace = faceMoves[currentFace][Down]
|
||||
}
|
||||
|
||||
if len(faceMap) != 6 {
|
||||
fmt.Println("COULD NOT FIND ALL SIX FACES")
|
||||
return
|
||||
}
|
||||
|
||||
for face, board := range faceMap {
|
||||
fmt.Println("Board", faceName[face])
|
||||
printBoard(board)
|
||||
}
|
||||
|
||||
fmt.Println("initial position is", x, y)
|
||||
state3 := State3D{0, 0, TopFace, Right, faceMap}
|
||||
for _, command := range commands {
|
||||
fmt.Println("x", state3.x, "y", state3.y, "face", faceName[state3.face], "direction", dirName[state3.direction])
|
||||
fmt.Println("Running", command)
|
||||
switch command {
|
||||
case TurnRight:
|
||||
state3.direction = turn(state3.direction, TurnRight)
|
||||
case TurnLeft:
|
||||
state3.direction = turn(state3.direction, TurnLeft)
|
||||
default:
|
||||
step3d(int(command), &state3)
|
||||
}
|
||||
}
|
||||
|
||||
finalRow = state3.y + 1
|
||||
finalColumn = state3.x + 1
|
||||
finalPassword = 1000*finalRow + 4*finalColumn + int(state.direction)
|
||||
fmt.Println("Final row is", finalRow, "final column is", finalColumn, "final password is", finalPassword)
|
||||
|
||||
}
|
||||
237
solutions/day23/day23.go
Normal file
237
solutions/day23/day23.go
Normal file
@@ -0,0 +1,237 @@
|
||||
package day23
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
type Direction int
|
||||
|
||||
const (
|
||||
North Direction = iota
|
||||
South
|
||||
East
|
||||
West
|
||||
)
|
||||
|
||||
type Point struct {
|
||||
x int
|
||||
y int
|
||||
}
|
||||
|
||||
type ElfLocations map[Point]bool
|
||||
|
||||
func extents(locations ElfLocations) (topLeft Point, bottomRight Point) {
|
||||
lowestX := 1000000000000000000
|
||||
highestX := -1000000000000000000
|
||||
lowestY := 1000000000000000000
|
||||
highestY := -1000000000000000000
|
||||
|
||||
for location := range locations {
|
||||
if location.x < lowestX {
|
||||
lowestX = location.x
|
||||
}
|
||||
|
||||
if location.x > highestX {
|
||||
highestX = location.x
|
||||
}
|
||||
|
||||
if location.y < lowestY {
|
||||
lowestY = location.y
|
||||
}
|
||||
|
||||
if location.y > highestY {
|
||||
highestY = location.y
|
||||
}
|
||||
}
|
||||
|
||||
topLeft = Point{lowestX, lowestY}
|
||||
bottomRight = Point{highestX, highestY}
|
||||
return
|
||||
}
|
||||
|
||||
func hasElf(x int, y int, locations ElfLocations) bool {
|
||||
_, exists := locations[Point{x, y}]
|
||||
return exists
|
||||
}
|
||||
|
||||
func printMap(locations ElfLocations) {
|
||||
topLeft, bottomRight := extents(locations)
|
||||
|
||||
fmt.Println("top left", topLeft)
|
||||
fmt.Println("bottom right", bottomRight)
|
||||
for y := topLeft.y; y <= bottomRight.y; y++ {
|
||||
for x := topLeft.x; x <= bottomRight.x; x++ {
|
||||
if hasElf(x, y, locations) {
|
||||
fmt.Printf("#")
|
||||
} else {
|
||||
fmt.Printf(".")
|
||||
}
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
}
|
||||
|
||||
func directionIsEmpty(direction Direction, x int, y int, locations ElfLocations) bool {
|
||||
switch direction {
|
||||
case North:
|
||||
return !hasElf(x-1, y-1, locations) && !hasElf(x, y-1, locations) && !hasElf(x+1, y-1, locations)
|
||||
case South:
|
||||
return !hasElf(x-1, y+1, locations) && !hasElf(x, y+1, locations) && !hasElf(x+1, y+1, locations)
|
||||
case West:
|
||||
return !hasElf(x-1, y-1, locations) && !hasElf(x-1, y, locations) && !hasElf(x-1, y+1, locations)
|
||||
case East:
|
||||
return !hasElf(x+1, y-1, locations) && !hasElf(x+1, y, locations) && !hasElf(x+1, y+1, locations)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func showDirection(dir *Direction) string {
|
||||
if dir == nil {
|
||||
return "<nil>"
|
||||
}
|
||||
|
||||
switch *dir {
|
||||
case North:
|
||||
return "north"
|
||||
case South:
|
||||
return "south"
|
||||
case East:
|
||||
return "east"
|
||||
case West:
|
||||
return "west"
|
||||
}
|
||||
|
||||
return "<???>"
|
||||
}
|
||||
|
||||
func proposeMove(x int, y int, directionPreference []Direction, locations ElfLocations) *Point {
|
||||
var firstEmpty *Direction = nil
|
||||
allEmpty := true
|
||||
|
||||
for _, dir := range directionPreference {
|
||||
if directionIsEmpty(dir, x, y, locations) {
|
||||
if firstEmpty == nil {
|
||||
firstEmpty = new(Direction)
|
||||
*firstEmpty = dir
|
||||
}
|
||||
} else {
|
||||
allEmpty = false
|
||||
}
|
||||
}
|
||||
|
||||
if allEmpty {
|
||||
return nil
|
||||
}
|
||||
|
||||
if firstEmpty == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch *firstEmpty {
|
||||
case North:
|
||||
return &Point{x, y - 1}
|
||||
case South:
|
||||
return &Point{x, y + 1}
|
||||
case East:
|
||||
return &Point{x + 1, y}
|
||||
case West:
|
||||
return &Point{x - 1, y}
|
||||
}
|
||||
|
||||
fmt.Println("PANIC: fell through?!")
|
||||
return nil
|
||||
}
|
||||
|
||||
func runRound(locations ElfLocations, preferredDirections []Direction) (newLocations ElfLocations, done bool) {
|
||||
suggestedMoves := map[Point]Point{}
|
||||
takenPoints := map[Point]int{}
|
||||
|
||||
for location := range locations {
|
||||
proposed := proposeMove(location.x, location.y, preferredDirections, locations)
|
||||
if proposed != nil {
|
||||
fmt.Println("Proposed to move", location, "to", proposed)
|
||||
suggestedMoves[location] = *proposed
|
||||
curVal := takenPoints[*proposed]
|
||||
takenPoints[*proposed] = curVal + 1
|
||||
}
|
||||
}
|
||||
|
||||
newLocations = ElfLocations{}
|
||||
done = true
|
||||
for originalLocation := range locations {
|
||||
newPlace, hasNewPlace := suggestedMoves[originalLocation]
|
||||
if hasNewPlace && takenPoints[newPlace] <= 1 {
|
||||
newLocations[newPlace] = true
|
||||
done = false
|
||||
} else {
|
||||
newLocations[originalLocation] = true
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func Run(filename string) {
|
||||
file, err := os.Open(filename)
|
||||
if err != nil {
|
||||
fmt.Println("Error opening file:", err)
|
||||
return
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(file)
|
||||
scanner.Split(bufio.ScanLines)
|
||||
elfLocations := ElfLocations{}
|
||||
y := 0
|
||||
|
||||
for scanner.Scan() {
|
||||
line := []rune(scanner.Text())
|
||||
|
||||
for x, r := range line {
|
||||
if r == '#' {
|
||||
elfLocations[Point{x, y}] = true
|
||||
}
|
||||
}
|
||||
y += 1
|
||||
}
|
||||
file.Close()
|
||||
|
||||
preferredDirections := []Direction{North, South, West, East}
|
||||
for i := 0; i < 10; i++ {
|
||||
fmt.Println("Round", i)
|
||||
printMap(elfLocations)
|
||||
newLocations, done := runRound(elfLocations, preferredDirections)
|
||||
preferredDirections = append(preferredDirections[1:], preferredDirections[0])
|
||||
if done {
|
||||
fmt.Println("No one moves after this round, so we're done.")
|
||||
break
|
||||
}
|
||||
elfLocations = newLocations
|
||||
}
|
||||
printMap(elfLocations)
|
||||
|
||||
topLeft, bottomRight := extents(elfLocations)
|
||||
emptyGround := 0
|
||||
for x := topLeft.x; x <= bottomRight.x; x++ {
|
||||
for y := topLeft.y; y <= bottomRight.y; y++ {
|
||||
if !hasElf(x, y, elfLocations) {
|
||||
emptyGround += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
fmt.Println(emptyGround, "empty spots in smallest bounding box.")
|
||||
|
||||
round := 11
|
||||
for {
|
||||
fmt.Println("Starting round", round)
|
||||
newLocations, done := runRound(elfLocations, preferredDirections)
|
||||
if done {
|
||||
fmt.Println("No one moved after round", round)
|
||||
break
|
||||
}
|
||||
elfLocations = newLocations
|
||||
preferredDirections = append(preferredDirections[1:], preferredDirections[0])
|
||||
round += 1
|
||||
}
|
||||
}
|
||||
92
solutions/day3/day3.go
Normal file
92
solutions/day3/day3.go
Normal file
@@ -0,0 +1,92 @@
|
||||
package day3
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
func in_set(target rune, set []rune) bool {
|
||||
for _, char := range set {
|
||||
if char == target {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func priority(value rune) int {
|
||||
var base_value int = 0
|
||||
|
||||
if value >= 'A' && value <= 'Z' {
|
||||
base_value = int('A') - 27
|
||||
}
|
||||
|
||||
if value >= 'a' && value <= 'z' {
|
||||
base_value = int('a') - 1
|
||||
}
|
||||
|
||||
return int(value) - base_value
|
||||
}
|
||||
|
||||
func part1_addition(runes []rune) int {
|
||||
var total int = 0
|
||||
|
||||
midpoint := len(runes) / 2
|
||||
left_runes := runes[0:midpoint]
|
||||
right_runes := runes[midpoint:]
|
||||
|
||||
for pos, char := range left_runes {
|
||||
if !in_set(char, left_runes[0:pos]) {
|
||||
if in_set(char, right_runes) {
|
||||
total += priority(char)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return total
|
||||
}
|
||||
|
||||
func score_badge(set1 []rune, set2 []rune, set3 []rune) int {
|
||||
for pos, char := range set1 {
|
||||
if !in_set(char, set1[0:pos]) {
|
||||
if in_set(char, set2) && in_set(char, set3) {
|
||||
return priority(char)
|
||||
}
|
||||
}
|
||||
}
|
||||
fmt.Println("PANIC: Couldn't find overlapping badge item")
|
||||
return 0
|
||||
}
|
||||
|
||||
func Run(filename string) {
|
||||
file, err := os.Open(filename)
|
||||
|
||||
if err != nil {
|
||||
fmt.Println("Error opening file:", err)
|
||||
return
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(file)
|
||||
scanner.Split(bufio.ScanLines)
|
||||
var total_overlap int = 0
|
||||
var total_badge int = 0
|
||||
var grouping [][]rune = [][]rune{}
|
||||
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
runes := []rune(line)
|
||||
total_overlap += part1_addition(runes)
|
||||
|
||||
grouping = append(grouping, runes)
|
||||
if len(grouping) == 3 {
|
||||
total_badge += score_badge(grouping[0], grouping[1], grouping[2])
|
||||
grouping = [][]rune{}
|
||||
}
|
||||
}
|
||||
|
||||
file.Close()
|
||||
|
||||
fmt.Println("Total initial score is", total_overlap)
|
||||
fmt.Println("Total badge score is", total_badge)
|
||||
}
|
||||
114
solutions/day4/day4.go
Normal file
114
solutions/day4/day4.go
Normal file
@@ -0,0 +1,114 @@
|
||||
package day4
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type SectionRange struct {
|
||||
start int
|
||||
end int
|
||||
}
|
||||
|
||||
func total_overlap(a SectionRange, b SectionRange) bool {
|
||||
return ((a.start >= b.start) && (a.end <= b.end)) ||
|
||||
((b.start >= a.start) && (b.end <= a.end))
|
||||
}
|
||||
|
||||
func partial_overlap(a SectionRange, b SectionRange) bool {
|
||||
return (a.start >= b.start && a.start <= b.end) ||
|
||||
(a.end >= b.end && a.end <= b.end) ||
|
||||
(b.start >= a.start && b.start <= a.end) ||
|
||||
(b.end >= a.start && b.end <= a.end)
|
||||
}
|
||||
|
||||
func range_from_string(s string) (ret_range SectionRange, err error) {
|
||||
ret_range = SectionRange{0, 0}
|
||||
items := strings.Split(s, "-")
|
||||
|
||||
if len(items) != 2 {
|
||||
err = errors.New("invalid range format")
|
||||
return
|
||||
}
|
||||
|
||||
start, start_err := strconv.Atoi(items[0])
|
||||
if start_err != nil {
|
||||
err = start_err
|
||||
return
|
||||
}
|
||||
ret_range.start = start
|
||||
|
||||
end, end_err := strconv.Atoi(items[1])
|
||||
if end_err != nil {
|
||||
err = end_err
|
||||
return
|
||||
}
|
||||
ret_range.end = end
|
||||
|
||||
err = nil
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func line_to_pair(s string) (left SectionRange, right SectionRange, err error) {
|
||||
items := strings.Split(s, ",")
|
||||
|
||||
if len(items) != 2 {
|
||||
err = errors.New("invalid range format")
|
||||
return
|
||||
}
|
||||
|
||||
left, left_err := range_from_string(items[0])
|
||||
if left_err != nil {
|
||||
err = left_err
|
||||
return
|
||||
}
|
||||
|
||||
right, right_err := range_from_string(items[1])
|
||||
if right_err != nil {
|
||||
err = right_err
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func Run(filename string) {
|
||||
file, err := os.Open(filename)
|
||||
|
||||
if err != nil {
|
||||
fmt.Println("Error opening file:", err)
|
||||
return
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(file)
|
||||
scanner.Split(bufio.ScanLines)
|
||||
var total_overlapping int = 0
|
||||
var partial_overlapping int = 0
|
||||
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
left, right, err := line_to_pair(line)
|
||||
|
||||
if err != nil {
|
||||
fmt.Println("ERROR: failed to parse line:", err)
|
||||
} else {
|
||||
if total_overlap(left, right) {
|
||||
total_overlapping += 1
|
||||
}
|
||||
|
||||
if partial_overlap(left, right) {
|
||||
partial_overlapping += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
file.Close()
|
||||
|
||||
fmt.Println("# of overlapping pairs", total_overlapping)
|
||||
fmt.Println("# of partially overlapping pairs", partial_overlapping)
|
||||
}
|
||||
282
solutions/day5/day5.go
Normal file
282
solutions/day5/day5.go
Normal file
@@ -0,0 +1,282 @@
|
||||
package day5
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
type Stack struct {
|
||||
identity rune
|
||||
then *Stack
|
||||
}
|
||||
|
||||
func newStack() *Stack {
|
||||
return nil
|
||||
}
|
||||
|
||||
func push(box rune, stack *Stack) *Stack {
|
||||
var retval *Stack = new(Stack)
|
||||
retval.identity = box
|
||||
retval.then = stack
|
||||
return retval
|
||||
}
|
||||
|
||||
func pushToEnd(box rune, stack *Stack) *Stack {
|
||||
if stack == nil {
|
||||
retval := new(Stack)
|
||||
retval.identity = box
|
||||
retval.then = nil
|
||||
return retval
|
||||
} else {
|
||||
stack.then = pushToEnd(box, stack.then)
|
||||
return stack
|
||||
}
|
||||
}
|
||||
|
||||
func pushSet(front *Stack, stack *Stack) *Stack {
|
||||
var prev *Stack = nil
|
||||
current := front
|
||||
|
||||
for current != nil {
|
||||
prev = current
|
||||
current = current.then
|
||||
}
|
||||
|
||||
if prev == nil {
|
||||
return stack
|
||||
} else {
|
||||
prev.then = stack
|
||||
}
|
||||
|
||||
return front
|
||||
}
|
||||
|
||||
func pop(stack *Stack) (newStack *Stack, value rune, err error) {
|
||||
if stack == nil {
|
||||
newStack = nil
|
||||
value = 'X'
|
||||
err = errors.New("attempt to pop from empty stack")
|
||||
return
|
||||
}
|
||||
|
||||
value = stack.identity
|
||||
newStack = stack.then
|
||||
return
|
||||
}
|
||||
|
||||
func popSet(amt int, stack *Stack) (newStack *Stack, set *Stack, err error) {
|
||||
var prev *Stack = nil
|
||||
var current *Stack = stack
|
||||
|
||||
for i := 0; i < amt; i++ {
|
||||
if current == nil {
|
||||
err = errors.New("tried to take more items than existed in stack")
|
||||
return
|
||||
}
|
||||
|
||||
prev = current
|
||||
current = current.then
|
||||
}
|
||||
|
||||
if prev != nil {
|
||||
set = stack
|
||||
prev.then = nil
|
||||
} else {
|
||||
set = nil
|
||||
}
|
||||
newStack = current
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func stackLength(stack *Stack) int {
|
||||
if stack == nil {
|
||||
return 0
|
||||
} else {
|
||||
return 1 + stackLength(stack.then)
|
||||
}
|
||||
}
|
||||
|
||||
type Workspace []*Stack
|
||||
|
||||
func addLine(line string, input *Workspace) (err error) {
|
||||
currentColumn := 0
|
||||
|
||||
fmt.Println("Workspace line's length is", len(line))
|
||||
for len(line) >= 3 {
|
||||
fmt.Println(" line length now", len(line))
|
||||
// make sure we've got enough space in the workspace
|
||||
for currentColumn+1 >= len(*input) {
|
||||
*input = append(*input, newStack())
|
||||
}
|
||||
|
||||
// see if there's actually something there
|
||||
if line[0] == '[' && unicode.IsLetter(rune(line[1])) {
|
||||
fmt.Println("Adding", rune(line[1]), "to column", currentColumn)
|
||||
(*input)[currentColumn] = pushToEnd(rune(line[1]), (*input)[currentColumn])
|
||||
} else if line[0] != ' ' {
|
||||
err = errors.New("doesn't start with square or blank")
|
||||
return
|
||||
}
|
||||
|
||||
currentColumn += 1
|
||||
if len(line) == 3 {
|
||||
return
|
||||
}
|
||||
|
||||
if len(line) > 3 {
|
||||
line = line[4:]
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func runMove(problem int, move OperatorMove, workspace *Workspace) error {
|
||||
switch problem {
|
||||
case 1:
|
||||
for i := 0; i < move.count; i++ {
|
||||
newFrom, value, err := pop((*workspace)[move.from])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
(*workspace)[move.from] = newFrom
|
||||
(*workspace)[move.to] = push(value, (*workspace)[move.to])
|
||||
}
|
||||
return nil
|
||||
|
||||
case 2:
|
||||
newFrom, poppedSet, err := popSet(move.count, (*workspace)[move.from])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
(*workspace)[move.from] = newFrom
|
||||
(*workspace)[move.to] = pushSet(poppedSet, (*workspace)[move.to])
|
||||
return nil
|
||||
|
||||
default:
|
||||
return errors.New("weird problem number found in runMove")
|
||||
}
|
||||
}
|
||||
|
||||
func printWorkspace(input Workspace) {
|
||||
maxLength := 0
|
||||
|
||||
for _, element := range input {
|
||||
current := stackLength(element)
|
||||
if current > maxLength {
|
||||
maxLength = current
|
||||
}
|
||||
}
|
||||
|
||||
for _, element := range input {
|
||||
current := stackLength(element)
|
||||
blanks := maxLength - current
|
||||
outputString := ""
|
||||
|
||||
for i := 0; i < blanks; i++ {
|
||||
outputString = outputString + " "
|
||||
}
|
||||
|
||||
node := element
|
||||
for node != nil {
|
||||
outputString = outputString + fmt.Sprintf("[%c] ", node.identity)
|
||||
node = node.then
|
||||
}
|
||||
|
||||
fmt.Println(outputString)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
type OperatorMove struct {
|
||||
count int
|
||||
from int
|
||||
to int
|
||||
}
|
||||
|
||||
func parseMove(line string) (move OperatorMove, err error) {
|
||||
words := strings.Split(line, " ")
|
||||
|
||||
if len(words) != 6 {
|
||||
err = errors.New("wrong number of words in move line")
|
||||
return
|
||||
}
|
||||
|
||||
if words[0] != "move" || words[2] != "from" || words[4] != "to" {
|
||||
err = fmt.Errorf("illegal words in move line: %s/%s/%s", words[0], words[2], words[4])
|
||||
return
|
||||
}
|
||||
|
||||
count, cerr := strconv.Atoi(words[1])
|
||||
from, ferr := strconv.Atoi(words[3])
|
||||
to, terr := strconv.Atoi(words[5])
|
||||
|
||||
if cerr != nil || ferr != nil || terr != nil {
|
||||
err = fmt.Errorf("illegal number found: %s/%s/%s", words[1], words[3], words[5])
|
||||
}
|
||||
|
||||
move = OperatorMove{count, from - 1, to - 1}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func Run(filename string, problemStr string) {
|
||||
problem, err := strconv.Atoi(problemStr)
|
||||
if err != nil || (problem < 1 || problem > 2) {
|
||||
fmt.Println("Didn't understand problem number", os.Args[2], "should be 1 or 2")
|
||||
}
|
||||
|
||||
file, err := os.Open(filename)
|
||||
if err != nil {
|
||||
fmt.Println("Error opening file:", err)
|
||||
return
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(file)
|
||||
scanner.Split(bufio.ScanLines)
|
||||
workspace := new(Workspace)
|
||||
moves := []OperatorMove{}
|
||||
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
|
||||
if strings.Contains(line, "[") {
|
||||
fmt.Println("Adding workspace line")
|
||||
addLine(line, workspace)
|
||||
} else {
|
||||
move, err := parseMove(line)
|
||||
|
||||
if err == nil {
|
||||
fmt.Println("Adding move", move)
|
||||
moves = append(moves, move)
|
||||
} else {
|
||||
fmt.Println("Skipping dead line")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
file.Close()
|
||||
|
||||
printWorkspace(*workspace)
|
||||
for count, move := range moves {
|
||||
fmt.Println("-----", count+1, "-----")
|
||||
runMove(problem, move, workspace)
|
||||
printWorkspace(*workspace)
|
||||
}
|
||||
|
||||
resultString := ""
|
||||
for _, stack := range *workspace {
|
||||
if stack == nil {
|
||||
resultString += "*"
|
||||
} else {
|
||||
resultString += fmt.Sprintf("%c", rune((*stack).identity))
|
||||
}
|
||||
}
|
||||
fmt.Println("Final string is", resultString)
|
||||
}
|
||||
58
solutions/day6/day6.go
Normal file
58
solutions/day6/day6.go
Normal file
@@ -0,0 +1,58 @@
|
||||
package day6
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func allDifferent(a string) bool {
|
||||
length := len(a)
|
||||
|
||||
for i := 0; i < length; i++ {
|
||||
for j := i + 1; j < length; j++ {
|
||||
if a[i] == a[j] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func Run(filename string, problemStr string) {
|
||||
problem, err := strconv.Atoi(problemStr)
|
||||
if err != nil || !(problem == 4 || problem == 14) {
|
||||
fmt.Println("Didn't understand problem number", os.Args[2], "should be 4 or 14")
|
||||
}
|
||||
|
||||
file, err := os.Open(filename)
|
||||
|
||||
if err != nil {
|
||||
fmt.Println("Error opening file:", err)
|
||||
return
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(file)
|
||||
scanner.Split(bufio.ScanLines)
|
||||
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
|
||||
if len(line) > 0 {
|
||||
var index int
|
||||
offset := problem - 1
|
||||
|
||||
for index = offset; index < len(line); index++ {
|
||||
if allDifferent(line[index-offset : index+1]) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println("First marker after character", index+1)
|
||||
}
|
||||
}
|
||||
|
||||
file.Close()
|
||||
}
|
||||
159
solutions/day7/day7.go
Normal file
159
solutions/day7/day7.go
Normal file
@@ -0,0 +1,159 @@
|
||||
package day7
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type FileSystem struct {
|
||||
name string
|
||||
parent *FileSystem
|
||||
subdirectories []*FileSystem
|
||||
files []FileInfo
|
||||
}
|
||||
|
||||
func newFileSystem(name string, parent *FileSystem) *FileSystem {
|
||||
return &FileSystem{name, parent, []*FileSystem{}, []FileInfo{}}
|
||||
}
|
||||
|
||||
func printFileTree(tree *FileSystem, prefix string) {
|
||||
fmt.Println(prefix, "-", tree.name, fmt.Sprintf("(dir size=%d)", totalSize(tree)))
|
||||
|
||||
for _, file := range tree.files {
|
||||
fmt.Println(prefix, " ", "-", file.name, fmt.Sprintf("(file, size=%d)", file.size))
|
||||
}
|
||||
|
||||
for _, dir := range tree.subdirectories {
|
||||
printFileTree(dir, prefix+" ")
|
||||
}
|
||||
}
|
||||
|
||||
func totalSize(tree *FileSystem) int {
|
||||
total := 0
|
||||
|
||||
for _, file := range tree.files {
|
||||
total += file.size
|
||||
}
|
||||
|
||||
for _, dir := range tree.subdirectories {
|
||||
total += totalSize(dir)
|
||||
}
|
||||
|
||||
return total
|
||||
}
|
||||
|
||||
func computePart1(tree *FileSystem) int {
|
||||
result := 0
|
||||
|
||||
for _, dir := range tree.subdirectories {
|
||||
result += computePart1(dir)
|
||||
}
|
||||
|
||||
currentSize := totalSize(tree)
|
||||
if currentSize <= 100000 {
|
||||
result += currentSize
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func computePart2(tree *FileSystem, size int) int {
|
||||
result := 70000000
|
||||
|
||||
for _, dir := range tree.subdirectories {
|
||||
subResult := computePart2(dir, size)
|
||||
if subResult < result {
|
||||
result = subResult
|
||||
}
|
||||
}
|
||||
|
||||
currentSize := totalSize(tree)
|
||||
if currentSize > size && currentSize < result {
|
||||
result = currentSize
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
type FileInfo struct {
|
||||
name string
|
||||
size int
|
||||
}
|
||||
|
||||
func parseFileInfo(line string) (result FileInfo, err error) {
|
||||
splits := strings.Split(line, " ")
|
||||
if len(splits) != 2 {
|
||||
err = errors.New("too many values from file info split")
|
||||
return
|
||||
}
|
||||
|
||||
size, err := strconv.Atoi(splits[0])
|
||||
if err != nil {
|
||||
err = errors.New(fmt.Sprintf("could not parse file size: '%s'", splits[0]))
|
||||
return
|
||||
}
|
||||
|
||||
result = FileInfo{splits[1], size}
|
||||
return
|
||||
}
|
||||
|
||||
func Run(filename string) {
|
||||
file, err := os.Open(filename)
|
||||
if err != nil {
|
||||
fmt.Println("Error opening file:", err)
|
||||
return
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(file)
|
||||
scanner.Split(bufio.ScanLines)
|
||||
|
||||
root := newFileSystem("/", nil)
|
||||
current := root
|
||||
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
|
||||
fmt.Println("line", line)
|
||||
if strings.HasPrefix(line, "$ cd /") {
|
||||
current = root
|
||||
} else if strings.HasPrefix(line, "$ cd ..") {
|
||||
current = current.parent
|
||||
} else if strings.HasPrefix(line, "$ cd ") {
|
||||
for _, subdir := range current.subdirectories {
|
||||
if subdir.name == line[5:] {
|
||||
current = subdir
|
||||
continue
|
||||
}
|
||||
}
|
||||
} else if strings.HasPrefix(line, "$ ls") {
|
||||
// don't do anything
|
||||
} else if strings.HasPrefix(line, "dir ") {
|
||||
newTree := newFileSystem(line[4:], current)
|
||||
current.subdirectories = append(current.subdirectories, newTree)
|
||||
} else {
|
||||
fileInfo, err := parseFileInfo(line)
|
||||
if err != nil {
|
||||
fmt.Println("PANIC:", err)
|
||||
return
|
||||
}
|
||||
|
||||
current.files = append((*current).files, fileInfo)
|
||||
}
|
||||
}
|
||||
|
||||
file.Close()
|
||||
|
||||
printFileTree(root, "")
|
||||
fmt.Println("example first part", computePart1(root))
|
||||
|
||||
unusedSpace := 70000000 - totalSize(root)
|
||||
fmt.Println("unused space is currently", unusedSpace)
|
||||
needToGet := 30000000 - unusedSpace
|
||||
fmt.Println("need to find a directory at least", needToGet, "bytes big")
|
||||
fmt.Println("that directory has size", computePart2(root, needToGet))
|
||||
|
||||
}
|
||||
142
solutions/day8/day8.go
Normal file
142
solutions/day8/day8.go
Normal file
@@ -0,0 +1,142 @@
|
||||
package day8
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func isVisible(treeMap [][]int, x int, y int) bool {
|
||||
treeHeight := treeMap[y][x]
|
||||
|
||||
left_test := true
|
||||
for trial := 0; trial < x; trial++ {
|
||||
left_test = left_test && treeMap[y][trial] < treeHeight
|
||||
}
|
||||
|
||||
right_test := true
|
||||
for trial := x + 1; trial < len(treeMap[y]); trial++ {
|
||||
right_test = right_test && treeMap[y][trial] < treeHeight
|
||||
}
|
||||
|
||||
up_test := true
|
||||
for trial := 0; trial < y; trial++ {
|
||||
up_test = up_test && treeMap[trial][x] < treeHeight
|
||||
}
|
||||
|
||||
down_test := true
|
||||
for trial := y + 1; trial < len(treeMap); trial++ {
|
||||
down_test = down_test && treeMap[trial][x] < treeHeight
|
||||
}
|
||||
fmt.Println("Test for x", x, "y", y, "which has height", treeHeight, "tests are", left_test, right_test, up_test, down_test)
|
||||
|
||||
return (left_test || right_test || up_test || down_test)
|
||||
}
|
||||
|
||||
func score(treeMap [][]int, x int, y int) int {
|
||||
treeHeight := treeMap[y][x]
|
||||
|
||||
if x == 0 || y == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
final := 1
|
||||
|
||||
score := 0
|
||||
for trial := x - 1; trial >= 0; trial-- {
|
||||
if treeMap[y][trial] >= treeHeight {
|
||||
score += 1
|
||||
break
|
||||
}
|
||||
score += 1
|
||||
}
|
||||
final *= score
|
||||
|
||||
score = 0
|
||||
for trial := x + 1; trial < len(treeMap[y]); trial++ {
|
||||
if treeMap[y][trial] >= treeHeight {
|
||||
score += 1
|
||||
break
|
||||
}
|
||||
score += 1
|
||||
}
|
||||
final *= score
|
||||
|
||||
score = 0
|
||||
for trial := y - 1; trial >= 0; trial-- {
|
||||
if treeMap[trial][x] >= treeHeight {
|
||||
score += 1
|
||||
break
|
||||
}
|
||||
score += 1
|
||||
}
|
||||
final *= score
|
||||
|
||||
score = 0
|
||||
for trial := y + 1; trial < len(treeMap); trial++ {
|
||||
if treeMap[trial][x] >= treeHeight {
|
||||
score += 1
|
||||
break
|
||||
}
|
||||
score += 1
|
||||
}
|
||||
final *= score
|
||||
|
||||
return final
|
||||
}
|
||||
|
||||
func Run(filename string) {
|
||||
file, err := os.Open(filename)
|
||||
if err != nil {
|
||||
fmt.Println("Error opening file:", err)
|
||||
return
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(file)
|
||||
scanner.Split(bufio.ScanLines)
|
||||
|
||||
treeMap := [][]int{}
|
||||
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
row := []int{}
|
||||
for _, c := range line {
|
||||
value, err := strconv.Atoi(string(c))
|
||||
if err != nil {
|
||||
fmt.Println("PANIC: bad character")
|
||||
return
|
||||
}
|
||||
row = append(row, value)
|
||||
}
|
||||
treeMap = append(treeMap, row)
|
||||
}
|
||||
|
||||
file.Close()
|
||||
|
||||
width := len(treeMap[0])
|
||||
height := len(treeMap)
|
||||
|
||||
visibleTrees := 2*width + 2*height - 4
|
||||
bestScore := 1
|
||||
|
||||
for y := 1; y < (height - 1); y++ {
|
||||
for x := 1; x < (width - 1); x++ {
|
||||
if isVisible(treeMap, x, y) {
|
||||
visibleTrees += 1
|
||||
//fmt.Printf("*")
|
||||
} else {
|
||||
//fmt.Printf("%d", treeMap[y][x])
|
||||
}
|
||||
|
||||
current := score(treeMap, x, y)
|
||||
if current > bestScore {
|
||||
bestScore = current
|
||||
}
|
||||
}
|
||||
//fmt.Println()
|
||||
}
|
||||
|
||||
fmt.Println("There are", visibleTrees, "visible trees")
|
||||
fmt.Println("Best score is", bestScore)
|
||||
}
|
||||
153
solutions/day9/day9.go
Normal file
153
solutions/day9/day9.go
Normal file
@@ -0,0 +1,153 @@
|
||||
package day9
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type Point struct {
|
||||
x int
|
||||
y int
|
||||
}
|
||||
|
||||
func makeSnake(length int) []*Point {
|
||||
snake := []*Point{}
|
||||
|
||||
for i := 0; i < length; i++ {
|
||||
snake = append(snake, &Point{0, 0})
|
||||
}
|
||||
|
||||
return snake
|
||||
}
|
||||
|
||||
func move(direction rune, point *Point) {
|
||||
switch direction {
|
||||
case 'U':
|
||||
point.y -= 1
|
||||
case 'R':
|
||||
point.x += 1
|
||||
case 'D':
|
||||
point.y += 1
|
||||
case 'L':
|
||||
point.x -= 1
|
||||
default:
|
||||
fmt.Println("PANIC! Bad direction", string(direction))
|
||||
}
|
||||
}
|
||||
|
||||
func abs(x int) int {
|
||||
if x >= 0 {
|
||||
return x
|
||||
} else {
|
||||
return -x
|
||||
}
|
||||
}
|
||||
|
||||
func areTouching(head *Point, tail *Point) bool {
|
||||
return abs(head.x-tail.x) <= 1 && abs(head.y-tail.y) <= 1
|
||||
}
|
||||
|
||||
func updateTail(head *Point, tail *Point) {
|
||||
if areTouching(head, tail) {
|
||||
return
|
||||
}
|
||||
|
||||
if head.x == tail.x {
|
||||
if head.y <= tail.y-2 {
|
||||
tail.y -= 1
|
||||
return
|
||||
}
|
||||
|
||||
if head.y >= tail.y+2 {
|
||||
tail.y += 1
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if head.y == tail.y {
|
||||
if head.x <= tail.x-2 {
|
||||
tail.x -= 1
|
||||
return
|
||||
}
|
||||
if head.x >= tail.x+2 {
|
||||
tail.x += 1
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
proposedTails := []Point{
|
||||
{tail.x - 1, tail.y - 1},
|
||||
{tail.x - 1, tail.y + 1},
|
||||
{tail.x + 1, tail.y - 1},
|
||||
{tail.x + 1, tail.y + 1}}
|
||||
|
||||
for _, proposed := range proposedTails {
|
||||
if areTouching(head, &proposed) {
|
||||
tail.x = proposed.x
|
||||
tail.y = proposed.y
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println("PANIC, couldn't figure out next step for", *head, "and", *tail)
|
||||
}
|
||||
|
||||
func contains(array *[]Point, point Point) bool {
|
||||
for _, val := range *array {
|
||||
if val == point {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func runMove(direction rune, amount int, snake []*Point, visitedPoints *[]Point) {
|
||||
for i := 0; i < amount; i++ {
|
||||
move(direction, snake[0])
|
||||
for t := 1; t < len(snake); t++ {
|
||||
updateTail(snake[t-1], snake[t])
|
||||
}
|
||||
lastPoint := snake[len(snake)-1]
|
||||
if !contains(visitedPoints, *lastPoint) {
|
||||
*visitedPoints = append(*visitedPoints, *lastPoint)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Run(filename string, lengthStr string) {
|
||||
file, err := os.Open(filename)
|
||||
if err != nil {
|
||||
fmt.Println("Error opening file:", err)
|
||||
return
|
||||
}
|
||||
|
||||
snakeLength, err := strconv.Atoi(lengthStr)
|
||||
if err != nil {
|
||||
fmt.Println("Error parsing snake length:", err)
|
||||
return
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(file)
|
||||
scanner.Split(bufio.ScanLines)
|
||||
|
||||
snake := makeSnake(snakeLength)
|
||||
visitedPoints := []Point{}
|
||||
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
|
||||
amount, err := strconv.Atoi(line[2:])
|
||||
if err != nil {
|
||||
fmt.Println("Could not parse argument", line)
|
||||
return
|
||||
}
|
||||
|
||||
runMove(rune(line[0]), amount, snake, &visitedPoints)
|
||||
}
|
||||
|
||||
fmt.Println("The tail visited", len(visitedPoints), "points")
|
||||
|
||||
file.Close()
|
||||
}
|
||||
Reference in New Issue
Block a user