mirror of
https://github.com/Threnklyn/advent-of-code-go.git
synced 2026-05-18 19:13:27 +02:00
8 9 10 11
This commit is contained in:
@@ -0,0 +1,144 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "embed"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/alexchao26/advent-of-code-go/cast"
|
||||||
|
"github.com/alexchao26/advent-of-code-go/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed input.txt
|
||||||
|
var input string
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// do this in init (not main) so test file has same input
|
||||||
|
input = strings.TrimRight(input, "\n")
|
||||||
|
if len(input) == 0 {
|
||||||
|
panic("empty input.txt file")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var part int
|
||||||
|
flag.IntVar(&part, "part", 1, "part 1 or 2")
|
||||||
|
flag.Parse()
|
||||||
|
fmt.Println("Running part", part)
|
||||||
|
|
||||||
|
if part == 1 {
|
||||||
|
ans := part1(input)
|
||||||
|
util.CopyToClipboard(fmt.Sprintf("%v", ans))
|
||||||
|
fmt.Println("Output:", ans)
|
||||||
|
} else {
|
||||||
|
ans := part2(input)
|
||||||
|
util.CopyToClipboard(fmt.Sprintf("%v", ans))
|
||||||
|
fmt.Println("Output:", ans)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func part1(input string) int {
|
||||||
|
grid := parseInput(input)
|
||||||
|
|
||||||
|
// may be visible from multiple angles
|
||||||
|
visibleCoords := map[[2]int]string{}
|
||||||
|
for r := 1; r < len(grid)-1; r++ {
|
||||||
|
// from left
|
||||||
|
highestFromLeft := -1
|
||||||
|
for c := 0; c < len(grid[0])-1; c++ {
|
||||||
|
height := grid[r][c]
|
||||||
|
if height > highestFromLeft {
|
||||||
|
visibleCoords[[2]int{r, c}] = "L"
|
||||||
|
highestFromLeft = height
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// from right
|
||||||
|
highestFromRight := -1
|
||||||
|
for c := len(grid[0]) - 1; c > 0; c-- {
|
||||||
|
height := grid[r][c]
|
||||||
|
if height > highestFromRight {
|
||||||
|
visibleCoords[[2]int{r, c}] = "R"
|
||||||
|
highestFromRight = height
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for c := 1; c < len(grid[0])-1; c++ {
|
||||||
|
// from top
|
||||||
|
highestFromTop := -1
|
||||||
|
for r := 0; r < len(grid)-1; r++ {
|
||||||
|
height := grid[r][c]
|
||||||
|
if height > highestFromTop {
|
||||||
|
visibleCoords[[2]int{r, c}] = "T"
|
||||||
|
highestFromTop = height
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// from bottom
|
||||||
|
highestFromBottom := -1
|
||||||
|
for r := len(grid) - 1; r > 0; r-- {
|
||||||
|
height := grid[r][c]
|
||||||
|
if height > highestFromBottom {
|
||||||
|
visibleCoords[[2]int{r, c}] = "B"
|
||||||
|
highestFromBottom = height
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return len(visibleCoords) + 4 // plus 4 for corners
|
||||||
|
}
|
||||||
|
|
||||||
|
func part2(input string) int {
|
||||||
|
// multiply the four scores together... score = how many trees any tree can see
|
||||||
|
// because trees on the edge will have a zero, just ignore them
|
||||||
|
grid := parseInput(input)
|
||||||
|
|
||||||
|
bestScore := 0
|
||||||
|
// iterate through every eligible tree
|
||||||
|
for r := 1; r < len(grid)-1; r++ {
|
||||||
|
for c := 1; c < len(grid[0])-1; c++ {
|
||||||
|
score := visible(grid, r, c, -1, 0)
|
||||||
|
score *= visible(grid, r, c, 1, 0)
|
||||||
|
score *= visible(grid, r, c, 0, -1)
|
||||||
|
score *= visible(grid, r, c, 0, 1)
|
||||||
|
|
||||||
|
if score > bestScore {
|
||||||
|
bestScore = score
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return bestScore
|
||||||
|
}
|
||||||
|
|
||||||
|
func visible(grid [][]int, r, c, dr, dc int) int {
|
||||||
|
count := 0
|
||||||
|
startingHeight := grid[r][c]
|
||||||
|
r += dr
|
||||||
|
c += dc
|
||||||
|
for r >= 0 && r < len(grid) && c >= 0 && c < len(grid[0]) {
|
||||||
|
height := grid[r][c]
|
||||||
|
if height < startingHeight {
|
||||||
|
count++
|
||||||
|
} else {
|
||||||
|
count++
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
r += dr
|
||||||
|
c += dc
|
||||||
|
}
|
||||||
|
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseInput(input string) (ans [][]int) {
|
||||||
|
for _, line := range strings.Split(input, "\n") {
|
||||||
|
var row []int
|
||||||
|
for _, n := range strings.Split(line, "") {
|
||||||
|
row = append(row, cast.ToInt(n))
|
||||||
|
}
|
||||||
|
ans = append(ans, row)
|
||||||
|
}
|
||||||
|
return ans
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var example = `30373
|
||||||
|
25512
|
||||||
|
65332
|
||||||
|
33549
|
||||||
|
35390`
|
||||||
|
|
||||||
|
func Test_part1(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input string
|
||||||
|
want int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "example",
|
||||||
|
input: example,
|
||||||
|
want: 21,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "actual",
|
||||||
|
input: input,
|
||||||
|
want: 1690,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := part1(tt.input); got != tt.want {
|
||||||
|
t.Errorf("part1() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_part2(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input string
|
||||||
|
want int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "example",
|
||||||
|
input: example,
|
||||||
|
want: 8,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "actual",
|
||||||
|
input: input,
|
||||||
|
want: 535680,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := part2(tt.input); got != tt.want {
|
||||||
|
t.Errorf("part2() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,253 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "embed"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/alexchao26/advent-of-code-go/cast"
|
||||||
|
"github.com/alexchao26/advent-of-code-go/mathy"
|
||||||
|
"github.com/alexchao26/advent-of-code-go/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed input.txt
|
||||||
|
var input string
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// do this in init (not main) so test file has same input
|
||||||
|
input = strings.TrimRight(input, "\n")
|
||||||
|
if len(input) == 0 {
|
||||||
|
panic("empty input.txt file")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var part int
|
||||||
|
flag.IntVar(&part, "part", 1, "part 1 or 2")
|
||||||
|
flag.Parse()
|
||||||
|
fmt.Println("Running part", part)
|
||||||
|
|
||||||
|
if part == 1 {
|
||||||
|
ans := part1(input)
|
||||||
|
util.CopyToClipboard(fmt.Sprintf("%v", ans))
|
||||||
|
fmt.Println("Output:", ans)
|
||||||
|
} else {
|
||||||
|
ans := part2(input)
|
||||||
|
util.CopyToClipboard(fmt.Sprintf("%v", ans))
|
||||||
|
fmt.Println("Output:", ans)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func part1(input string) int {
|
||||||
|
// tail follows head logically if in the same row or column
|
||||||
|
// if not in same row or column, always moves diagonally
|
||||||
|
insts := parseInput(input)
|
||||||
|
|
||||||
|
// start stacked at 0,0
|
||||||
|
var head, tail [2]int
|
||||||
|
|
||||||
|
// "normal" grid mapping...
|
||||||
|
diffs := map[string][2]int{
|
||||||
|
"U": {1, 0},
|
||||||
|
"D": {-1, 0},
|
||||||
|
"L": {0, -1},
|
||||||
|
"R": {0, 1},
|
||||||
|
}
|
||||||
|
|
||||||
|
visited := map[[2]int]bool{
|
||||||
|
{0, 0}: true,
|
||||||
|
}
|
||||||
|
for _, inst := range insts {
|
||||||
|
for inst.val > 0 {
|
||||||
|
// move head
|
||||||
|
diff := diffs[inst.dir]
|
||||||
|
head[0] += diff[0] // row
|
||||||
|
head[1] += diff[1] // col
|
||||||
|
|
||||||
|
// update tail
|
||||||
|
// if diff to row or col is > 1
|
||||||
|
|
||||||
|
rowDiff := head[0] - tail[0]
|
||||||
|
colDiff := head[1] - tail[1]
|
||||||
|
|
||||||
|
// if either row or col diff is > 1, then that dimension HAS to move
|
||||||
|
// additionally, if the other diff is not zero, it needs to be
|
||||||
|
// adjusted to move diagonally
|
||||||
|
// note: the nested if blocks screwed me in part 2 because a longer
|
||||||
|
// rope can make coordinates off by 2 rows AND 2 cols
|
||||||
|
if mathy.AbsInt(rowDiff) > 1 {
|
||||||
|
/* 0 1 2
|
||||||
|
H . T
|
||||||
|
diff = head - tail = -2
|
||||||
|
want to make tail (2) to (1), so add diff / 2
|
||||||
|
|
||||||
|
T . H
|
||||||
|
diff = 2 - 0 = 2
|
||||||
|
tail (0) + 2/2 = 1, checks out still
|
||||||
|
*/
|
||||||
|
tail[0] += rowDiff / 2
|
||||||
|
// account for diagonal adjustment, same math... add col diff
|
||||||
|
if colDiff != 0 {
|
||||||
|
tail[1] += colDiff
|
||||||
|
}
|
||||||
|
} else if mathy.AbsInt(colDiff) > 1 {
|
||||||
|
tail[1] += colDiff / 2
|
||||||
|
// account for diagonal adjustment, same math... add col diff
|
||||||
|
if rowDiff != 0 {
|
||||||
|
tail[0] += rowDiff
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// update where the tail has been...
|
||||||
|
visited[tail] = true
|
||||||
|
inst.val-- // one step at a time
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// return spots TAIL visited at least once, map[[2]int]bool
|
||||||
|
return len(visited)
|
||||||
|
}
|
||||||
|
|
||||||
|
type inst struct {
|
||||||
|
dir string
|
||||||
|
val int
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseInput(input string) (ans []inst) {
|
||||||
|
for _, line := range strings.Split(input, "\n") {
|
||||||
|
ans = append(ans, inst{
|
||||||
|
dir: line[:1],
|
||||||
|
val: cast.ToInt(line[2:]),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return ans
|
||||||
|
}
|
||||||
|
|
||||||
|
func part2(input string) int {
|
||||||
|
// oof, quite the refactor...
|
||||||
|
insts := parseInput(input)
|
||||||
|
|
||||||
|
rope := initRope(10)
|
||||||
|
|
||||||
|
visited := map[[2]int]bool{}
|
||||||
|
for _, inst := range insts {
|
||||||
|
for inst.val > 0 {
|
||||||
|
rope.moveOneSpace(inst.dir)
|
||||||
|
|
||||||
|
// update where the tail has been...
|
||||||
|
visited[rope.tail.coords] = true
|
||||||
|
|
||||||
|
inst.val-- // one step at a time
|
||||||
|
|
||||||
|
fmt.Println(inst, rope, len(visited))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return len(visited)
|
||||||
|
}
|
||||||
|
|
||||||
|
type node struct {
|
||||||
|
coords [2]int // row, col still
|
||||||
|
next *node
|
||||||
|
}
|
||||||
|
|
||||||
|
type rope struct {
|
||||||
|
head, tail *node
|
||||||
|
}
|
||||||
|
|
||||||
|
func initRope(length int) rope {
|
||||||
|
head := &node{}
|
||||||
|
itr := head
|
||||||
|
|
||||||
|
// start at 1 to account for head already being created
|
||||||
|
for i := 1; i < length; i++ {
|
||||||
|
itr.next = &node{}
|
||||||
|
itr = itr.next
|
||||||
|
}
|
||||||
|
|
||||||
|
return rope{
|
||||||
|
head: head,
|
||||||
|
tail: itr,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r rope) moveOneSpace(dir string) {
|
||||||
|
// "normal" grid mapping...
|
||||||
|
diffs := map[string][2]int{
|
||||||
|
"U": {1, 0},
|
||||||
|
"D": {-1, 0},
|
||||||
|
"L": {0, -1},
|
||||||
|
"R": {0, 1},
|
||||||
|
}
|
||||||
|
|
||||||
|
diff := diffs[dir]
|
||||||
|
r.head.coords[0] += diff[0]
|
||||||
|
r.head.coords[1] += diff[1]
|
||||||
|
|
||||||
|
// update rest of rope too
|
||||||
|
r.head.updateTrailer()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r rope) String() string {
|
||||||
|
str := ""
|
||||||
|
i := 0
|
||||||
|
for itr := r.head; itr != nil; itr = itr.next {
|
||||||
|
str += fmt.Sprintf("%d:[%d,%d]->", i, itr.coords[0], itr.coords[1])
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
|
||||||
|
// recursively updates the node behind itself as it follows
|
||||||
|
func (n *node) updateTrailer() {
|
||||||
|
if n.next == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rowDiff := n.coords[0] - n.next.coords[0]
|
||||||
|
colDiff := n.coords[1] - n.next.coords[1]
|
||||||
|
|
||||||
|
// if either row or col diff is > 1, then that dimension HAS to move
|
||||||
|
// additionally, if the other diff is not zero, it needs to be
|
||||||
|
// adjusted to move diagonally
|
||||||
|
if mathy.AbsInt(rowDiff) > 1 && mathy.AbsInt(colDiff) > 1 {
|
||||||
|
n.next.coords[0] += rowDiff / 2
|
||||||
|
n.next.coords[1] += colDiff / 2
|
||||||
|
} else if mathy.AbsInt(rowDiff) > 1 {
|
||||||
|
// see part1 for math logic
|
||||||
|
n.next.coords[0] += rowDiff / 2
|
||||||
|
n.next.coords[1] += colDiff
|
||||||
|
} else if mathy.AbsInt(colDiff) > 1 {
|
||||||
|
n.next.coords[1] += colDiff / 2
|
||||||
|
n.next.coords[0] += rowDiff
|
||||||
|
} else {
|
||||||
|
// no need to continue updating children if movement is over
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// go to next node
|
||||||
|
n.next.updateTrailer()
|
||||||
|
}
|
||||||
|
|
||||||
|
func reImplPart1(input string) int {
|
||||||
|
// oof, quite the refactor...
|
||||||
|
insts := parseInput(input)
|
||||||
|
|
||||||
|
rope := initRope(2)
|
||||||
|
|
||||||
|
visited := map[[2]int]bool{}
|
||||||
|
for _, inst := range insts {
|
||||||
|
for inst.val > 0 {
|
||||||
|
rope.moveOneSpace(inst.dir)
|
||||||
|
|
||||||
|
// update where the tail has been...
|
||||||
|
visited[rope.tail.coords] = true
|
||||||
|
|
||||||
|
inst.val-- // one step at a time
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// return spots TAIL visited at least once, map[[2]int]bool
|
||||||
|
return len(visited)
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var example = `R 4
|
||||||
|
U 4
|
||||||
|
L 3
|
||||||
|
D 1
|
||||||
|
R 4
|
||||||
|
D 1
|
||||||
|
L 5
|
||||||
|
R 2`
|
||||||
|
|
||||||
|
func Test_part1(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input string
|
||||||
|
want int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "example",
|
||||||
|
input: example,
|
||||||
|
want: 13,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "actual",
|
||||||
|
input: input,
|
||||||
|
want: 6236,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := part1(tt.input); got != tt.want {
|
||||||
|
t.Errorf("part1() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("reimplementation_"+tt.name, func(t *testing.T) {
|
||||||
|
if got := reImplPart1(tt.input); got != tt.want {
|
||||||
|
t.Errorf("reImplPart1() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var largerExample = `R 5
|
||||||
|
U 8
|
||||||
|
L 8
|
||||||
|
D 3
|
||||||
|
R 17
|
||||||
|
D 10
|
||||||
|
L 25
|
||||||
|
U 20`
|
||||||
|
|
||||||
|
func Test_part2(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input string
|
||||||
|
want int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "example",
|
||||||
|
input: example,
|
||||||
|
want: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "larger_example",
|
||||||
|
input: largerExample,
|
||||||
|
want: 36,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "actual",
|
||||||
|
input: input,
|
||||||
|
want: 2449,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := part2(tt.input); got != tt.want {
|
||||||
|
t.Errorf("part2() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,167 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "embed"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/alexchao26/advent-of-code-go/cast"
|
||||||
|
"github.com/alexchao26/advent-of-code-go/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed input.txt
|
||||||
|
var input string
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// do this in init (not main) so test file has same input
|
||||||
|
input = strings.TrimRight(input, "\n")
|
||||||
|
if len(input) == 0 {
|
||||||
|
panic("empty input.txt file")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var part int
|
||||||
|
flag.IntVar(&part, "part", 1, "part 1 or 2")
|
||||||
|
flag.Parse()
|
||||||
|
fmt.Println("Running part", part)
|
||||||
|
|
||||||
|
if part == 1 {
|
||||||
|
ans := part1(input)
|
||||||
|
util.CopyToClipboard(fmt.Sprintf("%v", ans))
|
||||||
|
fmt.Println("Output:", ans)
|
||||||
|
} else {
|
||||||
|
ans := part2(input)
|
||||||
|
util.CopyToClipboard(fmt.Sprintf("%v", ans))
|
||||||
|
fmt.Println("Output:", ans)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func part1(input string) int {
|
||||||
|
instructions := parseInput(input)
|
||||||
|
|
||||||
|
X := 1
|
||||||
|
sum := 0
|
||||||
|
|
||||||
|
i := 0 // what the current instruction is
|
||||||
|
for cycle := 1; cycle <= 220; cycle++ {
|
||||||
|
// "during" equates to the start of the cycle...
|
||||||
|
if (cycle-20)%40 == 0 {
|
||||||
|
sum += X * cycle
|
||||||
|
}
|
||||||
|
|
||||||
|
switch instructions[i].name {
|
||||||
|
case "addx":
|
||||||
|
// decrement cycles on that instruction
|
||||||
|
// IF it hits zero add V
|
||||||
|
// AND move to next step
|
||||||
|
instructions[i].cycles--
|
||||||
|
if instructions[i].cycles == 0 {
|
||||||
|
X += instructions[i].val
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
case "noop":
|
||||||
|
// just increment to next instruction
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sum
|
||||||
|
}
|
||||||
|
|
||||||
|
func part2(input string) string {
|
||||||
|
instructions := parseInput(input)
|
||||||
|
|
||||||
|
X := 1 // doubles as sprite's center coordinate
|
||||||
|
|
||||||
|
// 6 rows by 40 wide screen, starts all off
|
||||||
|
CRT := [6][40]string{}
|
||||||
|
for i, rows := range CRT {
|
||||||
|
for j := range rows {
|
||||||
|
CRT[i][j] = "."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
i := 0 // what the current instruction is
|
||||||
|
for cycle := 1; i < len(instructions); cycle++ {
|
||||||
|
// if (cycle-20)%40 == 0 {
|
||||||
|
// sum += X * cycle
|
||||||
|
// }
|
||||||
|
|
||||||
|
/*
|
||||||
|
X = horizontal position of middle of (3 pixel wide) sprite
|
||||||
|
axis draws left to right, top to bottom, 40 wide x 6 high
|
||||||
|
1---40
|
||||||
|
41---80
|
||||||
|
...
|
||||||
|
201---240
|
||||||
|
|
||||||
|
draws 1 pixel per cycle
|
||||||
|
light up pixels IF the pixel being drawn is the same as one of the sprite's 3 pixels
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
// calculate which pixel is being drawn... ZERO INDEXED
|
||||||
|
pixelRow := (cycle - 1) / 40
|
||||||
|
pixelCol := (cycle - 1) % 40
|
||||||
|
|
||||||
|
// see if the spite's horizontal location overlaps that pixelCol
|
||||||
|
spriteLeft, spriteRight := X-1, X+1
|
||||||
|
if spriteLeft <= pixelCol && spriteRight >= pixelCol {
|
||||||
|
CRT[pixelRow][pixelCol] = "#"
|
||||||
|
}
|
||||||
|
|
||||||
|
switch instructions[i].name {
|
||||||
|
case "addx":
|
||||||
|
// decrement cycles on that instruction
|
||||||
|
// IF it hits zero add V
|
||||||
|
// AND move to next step
|
||||||
|
instructions[i].cycles--
|
||||||
|
if instructions[i].cycles == 0 {
|
||||||
|
X += instructions[i].val
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
case "noop":
|
||||||
|
// just increment to next instruction
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
log := ""
|
||||||
|
for _, rows := range CRT {
|
||||||
|
for _, cell := range rows {
|
||||||
|
log += cell
|
||||||
|
}
|
||||||
|
log += "\n"
|
||||||
|
}
|
||||||
|
fmt.Println(log)
|
||||||
|
return log
|
||||||
|
}
|
||||||
|
|
||||||
|
type instruction struct {
|
||||||
|
name string
|
||||||
|
val int
|
||||||
|
cycles int
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseInput(input string) (ans []instruction) {
|
||||||
|
for _, l := range strings.Split(input, "\n") {
|
||||||
|
switch l[:4] {
|
||||||
|
case "addx":
|
||||||
|
ans = append(ans, instruction{
|
||||||
|
name: "addx",
|
||||||
|
val: cast.ToInt(l[5:]),
|
||||||
|
cycles: 2,
|
||||||
|
})
|
||||||
|
case "noop":
|
||||||
|
ans = append(ans, instruction{
|
||||||
|
name: "noop",
|
||||||
|
cycles: 1,
|
||||||
|
})
|
||||||
|
default:
|
||||||
|
panic("input line: " + l)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ans
|
||||||
|
}
|
||||||
@@ -0,0 +1,216 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var example = `addx 15
|
||||||
|
addx -11
|
||||||
|
addx 6
|
||||||
|
addx -3
|
||||||
|
addx 5
|
||||||
|
addx -1
|
||||||
|
addx -8
|
||||||
|
addx 13
|
||||||
|
addx 4
|
||||||
|
noop
|
||||||
|
addx -1
|
||||||
|
addx 5
|
||||||
|
addx -1
|
||||||
|
addx 5
|
||||||
|
addx -1
|
||||||
|
addx 5
|
||||||
|
addx -1
|
||||||
|
addx 5
|
||||||
|
addx -1
|
||||||
|
addx -35
|
||||||
|
addx 1
|
||||||
|
addx 24
|
||||||
|
addx -19
|
||||||
|
addx 1
|
||||||
|
addx 16
|
||||||
|
addx -11
|
||||||
|
noop
|
||||||
|
noop
|
||||||
|
addx 21
|
||||||
|
addx -15
|
||||||
|
noop
|
||||||
|
noop
|
||||||
|
addx -3
|
||||||
|
addx 9
|
||||||
|
addx 1
|
||||||
|
addx -3
|
||||||
|
addx 8
|
||||||
|
addx 1
|
||||||
|
addx 5
|
||||||
|
noop
|
||||||
|
noop
|
||||||
|
noop
|
||||||
|
noop
|
||||||
|
noop
|
||||||
|
addx -36
|
||||||
|
noop
|
||||||
|
addx 1
|
||||||
|
addx 7
|
||||||
|
noop
|
||||||
|
noop
|
||||||
|
noop
|
||||||
|
addx 2
|
||||||
|
addx 6
|
||||||
|
noop
|
||||||
|
noop
|
||||||
|
noop
|
||||||
|
noop
|
||||||
|
noop
|
||||||
|
addx 1
|
||||||
|
noop
|
||||||
|
noop
|
||||||
|
addx 7
|
||||||
|
addx 1
|
||||||
|
noop
|
||||||
|
addx -13
|
||||||
|
addx 13
|
||||||
|
addx 7
|
||||||
|
noop
|
||||||
|
addx 1
|
||||||
|
addx -33
|
||||||
|
noop
|
||||||
|
noop
|
||||||
|
noop
|
||||||
|
addx 2
|
||||||
|
noop
|
||||||
|
noop
|
||||||
|
noop
|
||||||
|
addx 8
|
||||||
|
noop
|
||||||
|
addx -1
|
||||||
|
addx 2
|
||||||
|
addx 1
|
||||||
|
noop
|
||||||
|
addx 17
|
||||||
|
addx -9
|
||||||
|
addx 1
|
||||||
|
addx 1
|
||||||
|
addx -3
|
||||||
|
addx 11
|
||||||
|
noop
|
||||||
|
noop
|
||||||
|
addx 1
|
||||||
|
noop
|
||||||
|
addx 1
|
||||||
|
noop
|
||||||
|
noop
|
||||||
|
addx -13
|
||||||
|
addx -19
|
||||||
|
addx 1
|
||||||
|
addx 3
|
||||||
|
addx 26
|
||||||
|
addx -30
|
||||||
|
addx 12
|
||||||
|
addx -1
|
||||||
|
addx 3
|
||||||
|
addx 1
|
||||||
|
noop
|
||||||
|
noop
|
||||||
|
noop
|
||||||
|
addx -9
|
||||||
|
addx 18
|
||||||
|
addx 1
|
||||||
|
addx 2
|
||||||
|
noop
|
||||||
|
noop
|
||||||
|
addx 9
|
||||||
|
noop
|
||||||
|
noop
|
||||||
|
noop
|
||||||
|
addx -1
|
||||||
|
addx 2
|
||||||
|
addx -37
|
||||||
|
addx 1
|
||||||
|
addx 3
|
||||||
|
noop
|
||||||
|
addx 15
|
||||||
|
addx -21
|
||||||
|
addx 22
|
||||||
|
addx -6
|
||||||
|
addx 1
|
||||||
|
noop
|
||||||
|
addx 2
|
||||||
|
addx 1
|
||||||
|
noop
|
||||||
|
addx -10
|
||||||
|
noop
|
||||||
|
noop
|
||||||
|
addx 20
|
||||||
|
addx 1
|
||||||
|
addx 2
|
||||||
|
addx 2
|
||||||
|
addx -6
|
||||||
|
addx -11
|
||||||
|
noop
|
||||||
|
noop
|
||||||
|
noop`
|
||||||
|
|
||||||
|
func Test_part1(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input string
|
||||||
|
want int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "example",
|
||||||
|
input: example,
|
||||||
|
want: 13140,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "actual",
|
||||||
|
input: input,
|
||||||
|
want: 15880,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := part1(tt.input); got != tt.want {
|
||||||
|
t.Errorf("part1() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_part2(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "example",
|
||||||
|
input: example,
|
||||||
|
want: `##..##..##..##..##..##..##..##..##..##..
|
||||||
|
###...###...###...###...###...###...###.
|
||||||
|
####....####....####....####....####....
|
||||||
|
#####.....#####.....#####.....#####.....
|
||||||
|
######......######......######......####
|
||||||
|
#######.......#######.......#######.....
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "actual",
|
||||||
|
input: input,
|
||||||
|
want: `###..#.....##..####.#..#..##..####..##..
|
||||||
|
#..#.#....#..#.#....#.#..#..#....#.#..#.
|
||||||
|
#..#.#....#....###..##...#..#...#..#....
|
||||||
|
###..#....#.##.#....#.#..####..#...#.##.
|
||||||
|
#....#....#..#.#....#.#..#..#.#....#..#.
|
||||||
|
#....####..###.#....#..#.#..#.####..###.
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := part2(tt.input); got != tt.want {
|
||||||
|
t.Errorf("part2() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,243 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "embed"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/alexchao26/advent-of-code-go/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed input.txt
|
||||||
|
var input string
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// do this in init (not main) so test file has same input
|
||||||
|
input = strings.TrimRight(input, "\n")
|
||||||
|
if len(input) == 0 {
|
||||||
|
panic("empty input.txt file")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var part int
|
||||||
|
flag.IntVar(&part, "part", 1, "part 1 or 2")
|
||||||
|
flag.Parse()
|
||||||
|
fmt.Println("Running part", part)
|
||||||
|
|
||||||
|
if part == 1 {
|
||||||
|
ans := part1(true)
|
||||||
|
util.CopyToClipboard(fmt.Sprintf("%v", ans))
|
||||||
|
fmt.Println("Output:", ans)
|
||||||
|
} else {
|
||||||
|
ans := part2(true)
|
||||||
|
util.CopyToClipboard(fmt.Sprintf("%v", ans))
|
||||||
|
fmt.Println("Output:", ans)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func part1(useRealInput bool) int {
|
||||||
|
monkeys := initInput()
|
||||||
|
if !useRealInput {
|
||||||
|
monkeys = initExample()
|
||||||
|
}
|
||||||
|
|
||||||
|
inspectedCounts := make([]int, len(monkeys))
|
||||||
|
for round := 0; round < 20; round++ {
|
||||||
|
for i, monkey := range monkeys {
|
||||||
|
for _, item := range monkey.items {
|
||||||
|
newItemVal := monkey.operation(item) / 3
|
||||||
|
|
||||||
|
if newItemVal%monkey.testDivisibleBy == 0 {
|
||||||
|
monkeys[monkey.trueMonkey].items = append(
|
||||||
|
monkeys[monkey.trueMonkey].items, newItemVal)
|
||||||
|
} else {
|
||||||
|
monkeys[monkey.falseMonkey].items = append(
|
||||||
|
monkeys[monkey.falseMonkey].items, newItemVal)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
inspectedCounts[i] += len(monkey.items)
|
||||||
|
|
||||||
|
// empty out this monkey's items
|
||||||
|
monkeys[i].items = []int{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Ints(inspectedCounts)
|
||||||
|
return inspectedCounts[len(inspectedCounts)-1] * inspectedCounts[len(inspectedCounts)-2]
|
||||||
|
}
|
||||||
|
|
||||||
|
// oh my god i figured out a math-y remainder theorem-y thing myself!
|
||||||
|
func part2(useRealInput bool) int {
|
||||||
|
monkeys := initInput()
|
||||||
|
if !useRealInput {
|
||||||
|
monkeys = initExample()
|
||||||
|
}
|
||||||
|
|
||||||
|
// the worry levels will always increase now that they're not being divided
|
||||||
|
// by 3, and we care about remainders because that's what all the tests are
|
||||||
|
// BUT we can't just mod by any monkey's testBy number, because they're all
|
||||||
|
// throwing the items around,
|
||||||
|
// so find a shared common denominator that can be used to keep the numbers
|
||||||
|
// under overflow
|
||||||
|
bigMod := 1
|
||||||
|
for _, m := range monkeys {
|
||||||
|
bigMod *= m.testDivisibleBy
|
||||||
|
}
|
||||||
|
|
||||||
|
inspectedCounts := make([]int, len(monkeys))
|
||||||
|
for round := 0; round < 10000; round++ {
|
||||||
|
|
||||||
|
for i, monkey := range monkeys {
|
||||||
|
for _, item := range monkey.items {
|
||||||
|
newItemVal := monkey.operation(item)
|
||||||
|
newItemVal %= bigMod
|
||||||
|
|
||||||
|
if newItemVal%monkey.testDivisibleBy == 0 {
|
||||||
|
monkeys[monkey.trueMonkey].items = append(
|
||||||
|
monkeys[monkey.trueMonkey].items, newItemVal)
|
||||||
|
} else {
|
||||||
|
monkeys[monkey.falseMonkey].items = append(
|
||||||
|
monkeys[monkey.falseMonkey].items, newItemVal)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
inspectedCounts[i] += len(monkey.items)
|
||||||
|
|
||||||
|
// empty out this monkey's items
|
||||||
|
monkeys[i].items = []int{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Ints(inspectedCounts)
|
||||||
|
return inspectedCounts[len(inspectedCounts)-1] * inspectedCounts[len(inspectedCounts)-2]
|
||||||
|
}
|
||||||
|
|
||||||
|
type monkey struct {
|
||||||
|
items []int
|
||||||
|
operation func(int) int
|
||||||
|
testDivisibleBy int
|
||||||
|
trueMonkey, falseMonkey int // indices
|
||||||
|
}
|
||||||
|
|
||||||
|
// faster to manually type this than write a parser (and potentially debug)
|
||||||
|
func initInput() []monkey {
|
||||||
|
return []monkey{
|
||||||
|
{
|
||||||
|
items: []int{50, 70, 89, 75, 66, 66},
|
||||||
|
operation: func(old int) int {
|
||||||
|
return old * 5
|
||||||
|
},
|
||||||
|
testDivisibleBy: 2,
|
||||||
|
trueMonkey: 2,
|
||||||
|
falseMonkey: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
items: []int{85},
|
||||||
|
operation: func(old int) int {
|
||||||
|
return old * old
|
||||||
|
},
|
||||||
|
testDivisibleBy: 7,
|
||||||
|
trueMonkey: 3,
|
||||||
|
falseMonkey: 6,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
items: []int{66, 51, 71, 76, 58, 55, 58, 60},
|
||||||
|
operation: func(old int) int {
|
||||||
|
return old + 1
|
||||||
|
},
|
||||||
|
testDivisibleBy: 13,
|
||||||
|
trueMonkey: 1,
|
||||||
|
falseMonkey: 3,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
items: []int{79, 52, 55, 51},
|
||||||
|
operation: func(old int) int {
|
||||||
|
return old + 6
|
||||||
|
},
|
||||||
|
testDivisibleBy: 3,
|
||||||
|
trueMonkey: 6,
|
||||||
|
falseMonkey: 4,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
items: []int{69, 92},
|
||||||
|
operation: func(old int) int {
|
||||||
|
return old * 17
|
||||||
|
},
|
||||||
|
testDivisibleBy: 19,
|
||||||
|
trueMonkey: 7,
|
||||||
|
falseMonkey: 5,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
items: []int{71, 76, 73, 98, 67, 79, 99},
|
||||||
|
operation: func(old int) int {
|
||||||
|
return old + 8
|
||||||
|
},
|
||||||
|
testDivisibleBy: 5,
|
||||||
|
trueMonkey: 0,
|
||||||
|
falseMonkey: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
items: []int{82, 76, 69, 69, 57},
|
||||||
|
operation: func(old int) int {
|
||||||
|
return old + 7
|
||||||
|
},
|
||||||
|
testDivisibleBy: 11,
|
||||||
|
trueMonkey: 7,
|
||||||
|
falseMonkey: 4,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
items: []int{65, 79, 86},
|
||||||
|
operation: func(old int) int {
|
||||||
|
return old + 5
|
||||||
|
},
|
||||||
|
testDivisibleBy: 17,
|
||||||
|
trueMonkey: 5,
|
||||||
|
falseMonkey: 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func initExample() []monkey {
|
||||||
|
return []monkey{
|
||||||
|
{
|
||||||
|
items: []int{79, 98},
|
||||||
|
operation: func(num int) int {
|
||||||
|
return num * 19
|
||||||
|
},
|
||||||
|
testDivisibleBy: 23,
|
||||||
|
trueMonkey: 2,
|
||||||
|
falseMonkey: 3,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
items: []int{54, 65, 75, 74},
|
||||||
|
operation: func(num int) int {
|
||||||
|
return num + 6
|
||||||
|
},
|
||||||
|
testDivisibleBy: 19,
|
||||||
|
trueMonkey: 2,
|
||||||
|
falseMonkey: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
items: []int{79, 60, 97},
|
||||||
|
operation: func(num int) int {
|
||||||
|
return num * num
|
||||||
|
},
|
||||||
|
testDivisibleBy: 13,
|
||||||
|
trueMonkey: 1,
|
||||||
|
falseMonkey: 3,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
items: []int{74},
|
||||||
|
operation: func(num int) int {
|
||||||
|
return num + 3
|
||||||
|
},
|
||||||
|
testDivisibleBy: 17,
|
||||||
|
trueMonkey: 0,
|
||||||
|
falseMonkey: 1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var example = ``
|
||||||
|
|
||||||
|
func Test_part1(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
useRealInput bool
|
||||||
|
want int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "example",
|
||||||
|
useRealInput: false,
|
||||||
|
want: 10605,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "actual",
|
||||||
|
useRealInput: true,
|
||||||
|
want: 151312,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := part1(tt.useRealInput); got != tt.want {
|
||||||
|
t.Errorf("part1() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_part2(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
useRealInput bool
|
||||||
|
want int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "example",
|
||||||
|
useRealInput: false,
|
||||||
|
want: 2713310158,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "actual",
|
||||||
|
useRealInput: true,
|
||||||
|
want: 51382025916,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := part2(tt.useRealInput); got != tt.want {
|
||||||
|
t.Errorf("part2() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user