Not much to talk about in today’s Advent of Code challenge: I just read the statement and implemented as it went. As usual, this post is a literate Haskell program.

```
import Data.List.Split
import qualified Data.Set as Set
```

Space cards have a semi-infinite rank, no suit and no duplicates. I’ll represent a deck of them as a simple list.

`type Deck = [Int]`

Reading the input would probably be easier with Emacs shortcuts, but heck this is a literate Haskell program, I might just as well code it up.

```
parseInput :: String -> (Deck,Deck)
= (read <$> d1,read <$> d2)
parseInput i where ["Player 1:":d1,"Player 2:":d2] = linesBy null (lines i)
```

Ok, let’s tackle the actual problem. The winner’s score is computed as a weighted sum, implemented with `Prelude`

functions.

```
score :: [Int] -> Int
= sum $ zipWith (*) s [n,n-1..]
score s where n = length s
```

`λ> score [3,2,10,6,8,5,9,4,7,1] 306`

Deciding which player wins a round is the determining aspect of the day’s two parts. I’ll extract that as a type and function.

```
data Side = P1 | P2 deriving Show
type Rule = Deck -> Deck -> Side
simpleGameRule :: Rule
:_) (h2:_) = case compare h1 h2 of
simpleGameRule (h1LT -> P2
GT -> P1
```

Just as the statement says: winner is the one who shows the highest-ranked card. We take note that this function would be partial if the input could contain duplicates.

Now to implement the full game logic.

```
game :: Rule -> Deck -> Deck -> (Side,Int)
= go Set.empty where
game rule | (d1,d2) `Set.member` cl = (P1,score d1)
go cl d1 d2 = (P2,score d2)
go _ [] d2 = (P1,score d1)
go _ d1 [] @(h1:t1) d2@(h2:t2) = case rule d1 d2 of
go cl d1P1 -> go cl' (t1 ++ [h1,h2]) t2
P2 -> go cl' t1 (t2 ++ [h2,h1])
where cl' = Set.insert (d1,d2) cl
```

Pretty self-describing. I’ve included the part 2 amendment for game loop avoidance^{1} that’s unneeded in part 1, but doesn’t hurt either. It’s a bit unfortunate that the same full pattern-matching is needed for both the distinctive round winner decision and for moving the cards to their new place, but it’ll have to do.

This solves part 1 flawlessly.

`λ> uncurry (game simpleGameRule) $ parseInput sample (P2,306)`

Part 2 is simply a matter of transcribing the loop detection above, and the new, recursive game rule.

```
recursiveGameRule :: Rule
@(h1:t1) d2@(h2:t2)
recursiveGameRule d1| length t1 < h1 || length t2 < h2 = simpleGameRule d1 d2
| otherwise = fst $ game recursiveGameRule (take h1 t1) (take h2 t2)
```

This solves part 2.

`λ> uncurry (game recursiveeGameRule) $ parseInput sample (P2,291)`

Here’s `main`

for completeness.

```
main :: IO ()
= do
main <- parseInput <$> readFile "day22.in"
(d1,d2) print $ game simpleGameRule d1 d2
print $ game recursiveGameRule d1 d2
```

This concludes today’s puzzle solution.

I call the accumulator

`cl`

for “closed” as a matter of habit from writing graph search functions with an “open” and a “closed” node set.↩︎