Solver Code (src/solvers/day04.ts):

/**
 * Solver for Day 4 of Advent of Code 2025
 *
 * To use this solver:
 * 1. Implement the solve function below
 * 2. The function receives the puzzle input as a string
 * 3. Return the solution (can be a string, number, or object)
 */

const getSurroundingRolls = (
  grid: Array<Array<string>>,
  row: number,
  col: number,
): number => {
  // 8 directions: up, down, left, right, and 4 diagonals
  const directions = [
    [-1, -1],
    [-1, 0],
    [-1, 1], // top row
    [0, -1],
    [0, 1], // middle row (skip center)
    [1, -1],
    [1, 0],
    [1, 1], // bottom row
  ]

  let surroundingRolls = 0
  for (const [dr, dc] of directions) {
    const newRow = row + dr
    const newCol = col + dc

    // Check bounds
    if (
      newRow >= 0 &&
      newRow < grid.length &&
      newCol >= 0 &&
      newCol < grid[0].length &&
      grid[newRow][newCol] === '@'
    ) {
      surroundingRolls++
    }
  }
  return surroundingRolls
}

export function solve(input: string): Promise<string | number | object> {
  const grid = input.split('\n').map((line) => line.split(''))
  const rows = grid.length
  const cols = grid[0].length
  let rollsPt1 = 0

  for (let rowIndex = 0; rowIndex < rows; rowIndex++) {
    const row = grid[rowIndex]
    for (let colIndex = 0; colIndex < cols; colIndex++) {
      const cell = row[colIndex]
      if (cell === '@') {
        const surroundingRolls = getSurroundingRolls(grid, rowIndex, colIndex)
        if (surroundingRolls < 4) {
          rollsPt1++
          grid[rowIndex][colIndex] = 'x'
        }
      }
    }
  }
  // Part 2: Iteratively remove rolls until no more can be removed
  // Part 2 total includes part 1, so start with rollsPt1
  let totalRollsRemoved = rollsPt1
  let rollsRemovedThisIteration = 1 // Start with 1 to enter the loop

  while (rollsRemovedThisIteration > 0) {
    rollsRemovedThisIteration = 0

    for (let rowIndex = 0; rowIndex < rows; rowIndex++) {
      const row = grid[rowIndex]
      for (let colIndex = 0; colIndex < cols; colIndex++) {
        const cell = row[colIndex]
        if (cell === '@') {
          const surroundingRolls = getSurroundingRolls(grid, rowIndex, colIndex)
          if (surroundingRolls < 4) {
            rollsRemovedThisIteration++
            totalRollsRemoved++
            grid[rowIndex][colIndex] = 'x'
          }
        }
      }
    }
  }

  return Promise.resolve({
    part1: rollsPt1,
    part2: totalRollsRemoved,
  })
}

How to add your solver:

Create a file at src/solvers/day04.ts with the following structure:

export async function solve(input: string): Promise<string | number | object> {
  // Your solution here
  // The input parameter contains the puzzle input as a string
  
  // Example:
  const lines = input.trim().split('\n');
  
  // Process and return your answer
  return 'Your answer here';
}

Then, import it in src/solvers/index.ts and add it to the solvers object:

import * as day04 from './day04'

export const solvers = {
  // ... existing solvers
  '04': day04,
}

The solver function will receive the puzzle input as a string and should return the solution (string, number, or object).