diff --git a/cmd/01/direction/direction_test.go b/cmd/01/direction/direction_test.go new file mode 100644 index 0000000..568924b --- /dev/null +++ b/cmd/01/direction/direction_test.go @@ -0,0 +1,17 @@ +package direction_test + +import ( + "testing" + + "github.com/StevanFreeborn/advent-of-code-2025/cmd/01/direction" +) + +func TestDirectionConstants(t *testing.T) { + if direction.Left != "L" { + t.Errorf("Expected Left to be 'L', got '%s'", direction.Left) + } + + if direction.Right != "R" { + t.Errorf("Expected Right to be 'R', got '%s'", direction.Right) + } +} diff --git a/cmd/04/main.go b/cmd/04/main.go index ba53e3e..1395edf 100644 --- a/cmd/04/main.go +++ b/cmd/04/main.go @@ -1,78 +1,38 @@ package main import ( + "fmt" + "github.com/StevanFreeborn/advent-of-code-2025/internal/file" + "github.com/StevanFreeborn/advent-of-code-2025/internal/grid" + "github.com/StevanFreeborn/advent-of-code-2025/internal/move" + "github.com/StevanFreeborn/advent-of-code-2025/internal/position" ) const PaperRollCharacter = "@" -const EmptyCharacter = "." - -type move struct { - NumberOfRows int - NumberOfColumns int -} - -var ( - up = move{NumberOfRows: -1, NumberOfColumns: 0} - down = move{NumberOfRows: 1, NumberOfColumns: 0} - right = move{NumberOfRows: 0, NumberOfColumns: 1} - left = move{NumberOfRows: 0, NumberOfColumns: -1} - upRight = move{NumberOfRows: -1, NumberOfColumns: 1} - upLeft = move{NumberOfRows: -1, NumberOfColumns: -1} - downRight = move{NumberOfRows: 1, NumberOfColumns: 1} - downLeft = move{NumberOfRows: 1, NumberOfColumns: -1} -) - -var moves = []move{ - up, - down, - right, - left, - upRight, - upLeft, - downRight, - downLeft, -} func SolvePartOne(filePath string) int { input := file.ReadAllLines(filePath) - numberOfRows := len(input) - numberOfColumns := len(input[0]) + grid := grid.From(input) total := 0 - for row := range numberOfRows { - for column := range numberOfColumns { - value := string(input[row][column]) + for row := range grid.NumberOfRows() { + for column := range grid.NumberOfColumns() { + currentPosition := position.From(row, column) + value := grid.GetValueAt(currentPosition) if value != PaperRollCharacter { continue } - numberOfPaperRolls := 0 + sameNeighbors := grid.GetSameNeighborsOf(currentPosition, move.AllDirections) - for _, move := range moves { - neighborRow := row + move.NumberOfRows - neighborColumn := column + move.NumberOfColumns - - if neighborRow > numberOfRows-1 || neighborRow < 0 { - continue - } - - if neighborColumn > numberOfColumns-1 || neighborColumn < 0 { - continue - } - - neighborValue := string(input[neighborRow][neighborColumn]) - - if neighborValue != PaperRollCharacter { - continue - } - - numberOfPaperRolls++ + if row == 0 && column == 3 { + fmt.Println(sameNeighbors) } - if numberOfPaperRolls < 4 { + if len(sameNeighbors) < 4 { total++ } } diff --git a/internal/grid/grid.go b/internal/grid/grid.go index 3c07a5f..d95047b 100644 --- a/internal/grid/grid.go +++ b/internal/grid/grid.go @@ -1,104 +1,97 @@ package grid +import ( + "fmt" + + "github.com/StevanFreeborn/advent-of-code-2025/internal/move" + "github.com/StevanFreeborn/advent-of-code-2025/internal/position" +) + type Grid interface { - Positions() map[Position]string - Walk() + NumberOfRows() int + NumberOfColumns() int + InBounds(position.Position) bool + GetValueAt(position.Position) string + GetSameNeighborsOf(position.Position, []move.Move) []position.Position } type grid struct { - positions map[Position]string + numberOfRows int + numberOfColumns int + positions map[position.Position]string } -func NewGrid(input []string) Grid { +func From(input []string) Grid { numberOfRows := len(input) numberOfColumns := len(input[0]) - positions := map[Position]string{} + positions := map[position.Position]string{} for row := range numberOfRows { for column := range numberOfColumns { value := string(input[row][column]) - pos := NewPosition(row, column) + pos := position.From(row, column) positions[pos] = value } } return grid{ - positions: positions, + numberOfRows: numberOfRows, + numberOfColumns: numberOfColumns, + positions: positions, } } -func (g grid) Positions() map[Position]string { - positions := map[Position]string{} +func (g grid) NumberOfRows() int { + return g.numberOfRows +} - for key, value := range g.positions { - newPosition := NewPosition(key.Row(), key.Column()) - positions[newPosition] = value +func (g grid) NumberOfColumns() int { + return g.numberOfColumns +} + +func (g grid) InBounds(pos position.Position) bool { + if pos.Row() < 0 || pos.Row() >= g.numberOfRows { + return false } - return positions -} - -type Position interface { - Column() int - Row() int -} - -type position struct { - column int - row int -} - -func NewPosition(row int, column int) Position { - return position{ - row: row, - column: column, + if pos.Column() < 0 || pos.Column() >= g.numberOfColumns { + return false } + + return true } -func (p position) Row() int { - return p.row +func (g grid) GetValueAt(pos position.Position) string { + return g.positions[pos] } -func (p position) Column() int { - return p.column -} +func (g grid) GetSameNeighborsOf(pos position.Position, moves []move.Move) []position.Position { + similarNeighbors := []position.Position{} + originalValue := g.GetValueAt(pos) -type Move interface { - NumberOfRows() int - NumberOfColumns() int -} + fmt.Println("Moves to check:", moves) + for _, m := range moves { + postionRow := pos.Row() + positionColumn := pos.Column() -type move struct { - numberOfRows int - numberOfColumns int -} + fmt.Println("Checking move:", m, "from position:", pos) + fmt.Println("Move rows:", m.NumberOfRows(), "columns:", m.NumberOfColumns()) + neighborRow := postionRow + m.NumberOfRows() + neighborColumn := positionColumn + m.NumberOfColumns() + neighborPos := position.From(neighborRow, neighborColumn) -func (m move) NumberOfRows() int { - return m.numberOfColumns -} + if g.InBounds(neighborPos) == false { + continue + } -func (m move) NumberOfColumns() int { - return m.numberOfColumns -} + neighborValue := g.GetValueAt(neighborPos) -var ( - Up = move{numberOfRows: -1, numberOfColumns: 0} - Down = move{numberOfRows: 1, numberOfColumns: 0} - Right = move{numberOfRows: 0, numberOfColumns: 1} - Left = move{numberOfRows: 0, numberOfColumns: -1} - UpRight = move{numberOfRows: -1, numberOfColumns: 1} - UpLeft = move{numberOfRows: -1, numberOfColumns: -1} - DownRight = move{numberOfRows: 1, numberOfColumns: 1} - DownLeft = move{numberOfRows: 1, numberOfColumns: -1} -) + if neighborValue != originalValue { + continue + } -var AllMoves = []Move{ - Up, - Down, - Right, - Left, - UpRight, - UpLeft, - DownRight, - DownLeft, + similarNeighbors = append(similarNeighbors, neighborPos) + } + + return similarNeighbors } diff --git a/internal/grid/grid.txt b/internal/grid/grid.txt deleted file mode 100644 index c24e925..0000000 --- a/internal/grid/grid.txt +++ /dev/null @@ -1,10 +0,0 @@ -.......... -.......... -.......... -.......... -.......... -.......... -.......... -.......... -.......... -.......... diff --git a/internal/grid/grid_test.go b/internal/grid/grid_test.go index 06fe566..bdb6f61 100644 --- a/internal/grid/grid_test.go +++ b/internal/grid/grid_test.go @@ -1 +1,152 @@ package grid_test + +import ( + "testing" + + "github.com/StevanFreeborn/advent-of-code-2025/internal/grid" + "github.com/StevanFreeborn/advent-of-code-2025/internal/move" + "github.com/StevanFreeborn/advent-of-code-2025/internal/position" +) + +func TestFrom(t *testing.T) { + expectedNumberOfRows := 3 + expectedNumberOfColumns := 3 + expectedPositions := map[position.Position]string{ + position.From(0, 0): "@", + position.From(0, 1): ".", + position.From(0, 2): ".", + position.From(1, 0): ".", + position.From(1, 1): "@", + position.From(1, 2): ".", + position.From(2, 0): ".", + position.From(2, 1): ".", + position.From(2, 2): "@", + } + + input := []string{ + "@..", + ".@.", + "..@", + } + + g := grid.From(input) + + if g.NumberOfRows() != expectedNumberOfRows { + t.Errorf("expected number of rows to be %d, got %d", expectedNumberOfRows, g.NumberOfRows()) + } + + if g.NumberOfColumns() != expectedNumberOfColumns { + t.Errorf("expected number of columns to be %d, got %d", expectedNumberOfColumns, g.NumberOfColumns()) + } + + for pos, expectedValue := range expectedPositions { + value := g.GetValueAt(pos) + + if value != expectedValue { + t.Errorf("expected value at position (%d, %d) to be %s, got %s", pos.Row(), pos.Column(), expectedValue, value) + } + } +} + +func TestInBounds(t *testing.T) { + tests := []struct { + pos position.Position + expected bool + }{ + {position.From(0, 0), true}, + {position.From(1, 1), true}, + {position.From(2, 2), true}, + {position.From(-1, 0), false}, + {position.From(0, -1), false}, + {position.From(3, 0), false}, + {position.From(0, 3), false}, + } + + input := []string{ + "@..", + ".@.", + "..@", + } + + g := grid.From(input) + + for _, test := range tests { + result := g.InBounds(test.pos) + + if result != test.expected { + t.Errorf("expected InBounds(%v) to be %v, got %v", test.pos, test.expected, result) + } + } +} + +func TestGetValueAt(t *testing.T) { + tests := []struct { + pos position.Position + expected string + }{ + {position.From(0, 0), "@"}, + {position.From(0, 1), "."}, + {position.From(1, 1), "@"}, + {position.From(2, 2), "@"}, + } + + input := []string{ + "@..", + ".@.", + "..@", + } + + g := grid.From(input) + + for _, test := range tests { + result := g.GetValueAt(test.pos) + + if result != test.expected { + t.Errorf("expected GetValueAt(%v) to be %s, got %s", test.pos, test.expected, result) + } + } +} + +func TestGetSameNeighborsOf(t *testing.T) { + input := []string{ + "@..", + ".@.", + "..@", + } + + g := grid.From(input) + + tests := []struct { + pos position.Position + expected []position.Position + }{ + { + position.From(1, 1), + []position.Position{ + position.From(0, 0), + position.From(2, 2), + }, + }, + { + position.From(0, 0), + []position.Position{ + position.From(1, 1), + }, + }, + } + + for _, test := range tests { + result := g.GetSameNeighborsOf(test.pos, move.AllDirections) + + if len(result) != len(test.expected) { + t.Errorf("expected GetSameNeighborsOf(%v) to return %d neighbors, got %d", test.pos, len(test.expected), len(result)) + continue + } + + for i, expectedPos := range test.expected { + if result[i] != expectedPos { + t.Errorf("expected neighbor at index %d to be %v, got %v", i, expectedPos, result[i]) + } + } + } +} diff --git a/internal/move/move.go b/internal/move/move.go new file mode 100644 index 0000000..715e1b9 --- /dev/null +++ b/internal/move/move.go @@ -0,0 +1,36 @@ +// Package move provides types and functions to represent and manipulate moves on a 2D grid. +package move + +type Move interface { + NumberOfRows() int + NumberOfColumns() int +} + +type move struct { + numberOfRows int + numberOfColumns int +} + +func (m move) NumberOfRows() int { + return m.numberOfRows +} + +func (m move) NumberOfColumns() int { + return m.numberOfColumns +} + +var ( + Up = move{numberOfRows: -1, numberOfColumns: 0} + Down = move{numberOfRows: 1, numberOfColumns: 0} + Right = move{numberOfRows: 0, numberOfColumns: 1} + Left = move{numberOfRows: 0, numberOfColumns: -1} + cardinal = []Move{Up, Down, Right, Left} + + UpRight = move{numberOfRows: -1, numberOfColumns: 1} + UpLeft = move{numberOfRows: -1, numberOfColumns: -1} + DownRight = move{numberOfRows: 1, numberOfColumns: 1} + DownLeft = move{numberOfRows: 1, numberOfColumns: -1} + ordinal = []Move{UpRight, UpLeft, DownRight, DownLeft} + + AllDirections = append(cardinal, ordinal...) +) diff --git a/internal/move/move_test.go b/internal/move/move_test.go new file mode 100644 index 0000000..419dad5 --- /dev/null +++ b/internal/move/move_test.go @@ -0,0 +1,91 @@ +package move_test + +import ( + "testing" + + "github.com/StevanFreeborn/advent-of-code-2025/internal/move" +) + +func TestNumberOfRows(t *testing.T) { + tests := []struct { + move move.Move + expected int + }{ + {move.Up, -1}, + {move.Down, 1}, + {move.Right, 0}, + {move.Left, 0}, + {move.UpRight, -1}, + {move.UpLeft, -1}, + {move.DownRight, 1}, + {move.DownLeft, 1}, + } + + for _, test := range tests { + if test.move.NumberOfRows() != test.expected { + t.Errorf("expected number of rows for move to be %d, got %d", test.expected, test.move.NumberOfRows()) + } + } +} + +func TestNumberOfColumns(t *testing.T) { + tests := []struct { + move move.Move + expected int + }{ + {move.Up, 0}, + {move.Down, 0}, + {move.Right, 1}, + {move.Left, -1}, + {move.UpRight, 1}, + {move.UpLeft, -1}, + {move.DownRight, 1}, + {move.DownLeft, -1}, + } + + for _, test := range tests { + if test.move.NumberOfColumns() != test.expected { + t.Errorf("expected number of columns for move to be %d, got %d", test.expected, test.move.NumberOfColumns()) + } + } +} + +func TestAllDirectionsLength(t *testing.T) { + expectedLength := 8 + + if len(move.AllDirections) != expectedLength { + t.Errorf("expected AllDirections length to be %d, got %d", expectedLength, len(move.AllDirections)) + } +} + +func TestDefinedMoves(t *testing.T) { + definedMoves := []struct { + name string + move move.Move + expectedRows int + expectedColumns int + }{ + {"Up", move.Up, -1, 0}, + {"Down", move.Down, 1, 0}, + {"Right", move.Right, 0, 1}, + {"Left", move.Left, 0, -1}, + {"UpRight", move.UpRight, -1, 1}, + {"UpLeft", move.UpLeft, -1, -1}, + {"DownRight", move.DownRight, 1, 1}, + {"DownLeft", move.DownLeft, 1, -1}, + } + + for _, dm := range definedMoves { + if dm.move == nil { + t.Errorf("expected move %s to be defined, got nil", dm.name) + } + + if dm.move.NumberOfRows() != dm.expectedRows { + t.Errorf("expected move %s to have %d rows, got %d", dm.name, dm.expectedRows, dm.move.NumberOfRows()) + } + + if dm.move.NumberOfColumns() != dm.expectedColumns { + t.Errorf("expected move %s to have %d columns, got %d", dm.name, dm.expectedColumns, dm.move.NumberOfColumns()) + } + } +} diff --git a/internal/position/position.go b/internal/position/position.go new file mode 100644 index 0000000..e296062 --- /dev/null +++ b/internal/position/position.go @@ -0,0 +1,27 @@ +// Package position provides types and functions to represent and manipulate positions and moves on a 2D grid. +package position + +type Position interface { + Row() int + Column() int +} + +type position struct { + row int + column int +} + +func From(row int, column int) Position { + return position{ + row: row, + column: column, + } +} + +func (p position) Row() int { + return p.row +} + +func (p position) Column() int { + return p.column +} diff --git a/internal/position/position_test.go b/internal/position/position_test.go new file mode 100644 index 0000000..546d010 --- /dev/null +++ b/internal/position/position_test.go @@ -0,0 +1,22 @@ +package position_test + +import ( + "testing" + + "github.com/StevanFreeborn/advent-of-code-2025/internal/position" +) + +func TestFrom(t *testing.T) { + row := 3 + column := 5 + + pos := position.From(row, column) + + if pos.Row() != row { + t.Errorf("expected row to be %d, got %d", row, pos.Row()) + } + + if pos.Column() != column { + t.Errorf("expected column to be %d, got %d", column, pos.Column()) + } +}