Today's puzzle was quick and easy. For the first part, we're to take a list of "games" as input where each game has an id and a set of semicolon-separated sets of plays and report the sum of the game ids where the sets match a certain condition. The example input looks like:

```
Game 1: 3 blue, 4 red; 1 red, 2 green, 6 blue; 2 green
Game 2: 1 blue, 2 green; 3 green, 4 blue, 1 red; 1 green, 1 blue
Game 3: 8 green, 6 blue, 20 red; 5 blue, 4 red, 13 green; 5 green, 1 red
Game 4: 1 green, 3 red, 6 blue; 3 green, 6 red; 3 green, 15 blue, 14 red
Game 5: 6 red, 1 blue, 3 green; 2 blue, 1 red, 2 green
```

I decided to make a struct to represent each game:

```
(struct game (id sets)
#:transparent)
```

And to parse each set into a hash from colors to the number of blocks:

```
(define (parse-set set-str)
(for/hasheq ([reveal-str (in-list (string-split set-str ","))])
(match-define (regexp #rx"([0-9]+) ([a-z]+)"
(list _
(app string->number blocks)
(app string->symbol color)))
reveal-str)
(values color blocks)))
```

The `parse-set`

procedure splits the input on commas and extracts the
block count and color of each reveal using a regular expression. The
`for/hasheq`

form then collects the results of each iteration into a
hash. The `match`

form's `app`

syntax comes in handy when you want
to transform a matched value before binding it.

```
> (parse-set "")
'#hasheq()
> (parse-set "10 red, 3 blue")
'#hasheq((blue . 3) (red . 10))
> (parse-set "10 red, 3 blue, 5 green")
'#hasheq((blue . 3) (green . 5) (red . 10))
```

To parse a game, we do a similar kind of pattern matching to extract
the game id and the set of reveals to pass to `parse-set`

:

```
(define (parse-game line)
(match-define (regexp #rx"Game ([^:]+): (.+)"
(list _ (app string->number id) sets-str))
line)
(define sets
(map parse-set (string-split sets-str ";")))
(game id sets))
```

With the id and parsed sets in hand, we construct an instance of the game struct.

```
> (parse-game "Game 5: 10 red, 3 blue; 10 green, 5 red")
(game 5 '(#hasheq((blue . 3) (red . 10))
#hasheq((green . 10) (red . 5))))
```

The goal of part one is to sum up the game ids where the games would be valid. A valid game is defined as any game where every set of reveals had fewer than 12 red cubes, 13 green cubes and 14 blue cubes. So, I defined a generic procedure for determining if a game was valid:

```
(define (game-ok? g proc)
(for/and ([s (in-list (game-sets g))])
(proc s)))
```

The `for/and`

form returns `#t`

when all of the iterations are
truthy and `#f`

otherwise. Next, we define a procedure representing the
valid condition for part 1:

```
(define (part1-ok? s)
(and
(<= (hash-ref s 'red 0) 12)
(<= (hash-ref s 'green 0) 13)
(<= (hash-ref s 'blue 0) 14)))
```

And, with that, we can put everything together:

```
(call-with-input-file "day02.txt"
(lambda (in)
(for*/sum ([line (in-lines in)]
[game (in-value (parse-game line))]
#:when (game-ok? game part1-ok?))
(game-id game))))
```

We use the `sum`

variant of `for*`

to sum up the valid game ids. The
`in-value`

form generates a sequence of one element representing
each game for each line and the body of the loop is skipped whenever
`game-ok?`

is false.

In part two, the problem is flipped on its head and we're asked to
find the minimum constraint that would make each game valid. That's
just a matter of iterating over every set in each game and keeping
track of the *maximum* number of blocks of each color:

```
(define (game-minimums g)
(for*/fold ([minimums (hasheq 'red 0 'green 0 'blue 0)])
([s (in-list (game-sets g))]
[c (in-list '(red green blue))])
(hash-update minimums c (λ (blocks) (max blocks (hash-ref s c 0))))))
```

For example:

```
> (game-minimums (parse-game "Game 5: 10 red, 3 blue; 10 green, 5 red"))
'#hasheq((blue . 3) (green . 10) (red . 10))
```

To compute the puzzle solution, we have to sum up the result of multiplying the number of colors in every game (its "power"). So, we define a procedure to compute a game's power:

```
(define (game-power g)
(apply * (hash-values (game-minimums g))))
```

Put it all together:

```
(call-with-input-file "day02.txt"
(lambda (in)
(for*/sum ([line (in-lines in)]
[game (in-value (parse-game line))])
(game-power game))))
```

And that's it for day two!