{-# Language QuasiQuotes #-}
{-|
Module      : Main
Description : Day 4 solution
Copyright   : (c) Eric Mertens, 2021
License     : ISC
Maintainer  : emertens@gmail.com

<https://adventofcode.com/2021/day/4>

Today we played Bingo and picked the first and last winning cards

-}
module Main (main) where

import Advent (format)
import Data.List (partition, transpose)

type Board = [[Int]]

-- | >>> :main
-- 49686
-- 26878
main :: IO ()
IO ()
main =
 do ([Int]
calls, [[[Int]]]
boards) <- [format|2021 4 %u&,%n(%n(( *%u)+%n)+)*|]
    let outcomes :: [Int]
outcomes = [Int] -> [[[Int]]] -> [Int]
play [Int]
calls [[[Int]]]
boards
    Int -> IO ()
forall a. Show a => a -> IO ()
print ([Int] -> Int
forall a. HasCallStack => [a] -> a
head [Int]
outcomes)
    Int -> IO ()
forall a. Show a => a -> IO ()
print ([Int] -> Int
forall a. HasCallStack => [a] -> a
last [Int]
outcomes)

-- | Given the called numbers and initial boards return a list of
-- winning scores in order of winning.
play :: [Int] -> [Board] -> [Int]
play :: [Int] -> [[[Int]]] -> [Int]
play [] [[[Int]]]
_ = []
play (Int
c:[Int]
calls) [[[Int]]]
boards =
  case ([[Int]] -> Bool) -> [[[Int]]] -> ([[[Int]]], [[[Int]]])
forall a. (a -> Bool) -> [a] -> ([a], [a])
partition [[Int]] -> Bool
isWinner (([[Int]] -> [[Int]]) -> [[[Int]]] -> [[[Int]]]
forall a b. (a -> b) -> [a] -> [b]
map (Int -> [[Int]] -> [[Int]]
mark Int
c) [[[Int]]]
boards) of
    ([[[Int]]]
winners, [[[Int]]]
losers) -> ([[Int]] -> Int) -> [[[Int]]] -> [Int]
forall a b. (a -> b) -> [a] -> [b]
map (Int -> [[Int]] -> Int
score Int
c) [[[Int]]]
winners [Int] -> [Int] -> [Int]
forall a. [a] -> [a] -> [a]
++ [Int] -> [[[Int]]] -> [Int]
play [Int]
calls [[[Int]]]
losers

-- | Mark off a called number on a board.
mark :: Int -> Board -> Board
mark :: Int -> [[Int]] -> [[Int]]
mark Int
c = ([Int] -> [Int]) -> [[Int]] -> [[Int]]
forall a b. (a -> b) -> [a] -> [b]
map ((Int -> Int) -> [Int] -> [Int]
forall a b. (a -> b) -> [a] -> [b]
map (\Int
x -> if Int
x Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
c then -Int
1 else Int
x))

-- | Compute the final score for a board given the last call and unmarked numbers.
score :: Int -> Board -> Int
score :: Int -> [[Int]] -> Int
score Int
c [[Int]]
b = Int
c Int -> Int -> Int
forall a. Num a => a -> a -> a
* [Int] -> Int
forall a. Num a => [a] -> a
forall (t :: * -> *) a. (Foldable t, Num a) => t a -> a
sum ((Int -> Bool) -> [Int] -> [Int]
forall a. (a -> Bool) -> [a] -> [a]
filter (-Int
1 Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
/=) ([[Int]] -> [Int]
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat [[Int]]
b))

-- | Predicate for boards with a completed row or column
isWinner :: Board -> Bool
isWinner :: [[Int]] -> Bool
isWinner [[Int]]
b = [[Int]] -> Bool
f [[Int]]
b Bool -> Bool -> Bool
|| [[Int]] -> Bool
f ([[Int]] -> [[Int]]
forall a. [[a]] -> [[a]]
transpose [[Int]]
b)
  where f :: [[Int]] -> Bool
f = ([Int] -> Bool) -> [[Int]] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any ((Int -> Bool) -> [Int] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all (-Int
1 Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
==))