import scala.io.Source object Day4 extends App: val input = Source .fromURL(getClass.getResource("day4Input.txt")) .mkString final case class Bingo(boards: Array[Board], playedNumbers: Seq[Int]): def playNumber(number: Int) = Bingo( boards.map(board => if (board.isWinner) board else board.mark(number)), playedNumbers.appended(number) ) def winningBoards: Seq[Board] = boards .filter(_.isWinner) .map(board => (board, board.lastMarked.map(playedNumbers.indexOf(_)))) .sortBy(_._2) .map(_._1) val gameOver = boards.forall(_.isWinner) object Bingo: def parse(input: Array[String]): Bingo = Bingo(input.map(Board.parse), Seq()) final case class Board( val rows: Array[Array[Cell]], lastMarked: Option[Int] = None ): val isWinner: Boolean = rows.exists(_.forall(_.marked)) || rows.transpose.exists(_.forall(_.marked)) def mark(number: Int): Board = Board( rows.map(_.map(_.mark(number))), Some(number) ) def score: Int = rows.flatMap(_.filterNot(_.marked).map(_.value)).sum * lastMarked.getOrElse(0) object Board: def parse(input: String) = Board( input .split('\n') .map( _.split(' ') .filter(_.nonEmpty) .map(Cell.parse) ) ) final case class Cell(value: Int, marked: Boolean = false): def mark(number: Int): Cell = if (number == value) Cell(value, true) else this object Cell: def parse(input: String) = Cell(input.toInt) val numbersAndBoardsInput = input.split("\n\n") val drawnNumbers = numbersAndBoardsInput.head.split(',').map(_.toInt) val initialState = Bingo.parse(numbersAndBoardsInput.tail) val finalState = drawnNumbers.foldLeft(initialState)(_.playNumber(_)) val firstWinningBoard = finalState.winningBoards.head println(s"Part 1: ${firstWinningBoard.score}") val lastWinningBoard = finalState.winningBoards.last println(s"Part 2: ${lastWinningBoard.score}")