# Advent of code 2023 day 2: count possible qube games - in pure Bash

To paraphrase the puzzle, Santa takes a walk with an Elf, and they play a game involving a bag of colorful cubes. In each game, there is an unknown number of each colored cubes in the bag, and the Elf pulls out a number of cubes, for example:

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


### Part 1

In part 1, we want to know which games would have been possible if the bag contained only 12 red cubes, 13 green cubes, and 14 blue cubes. The answer is the sum of the eligible game ids, in the above example 1 + 2 + 5 = 8.

#!/usr/bin/env bash
#
# Solver for https://adventofcode.com/2023/day/2 part 1
# Redirect the input file to this script, for example day2part1.sh < path/to/input.txt
#

set -euo pipefail

max_red=12
max_green=13
max_blue=14

is_eligible_cubes() {
local count color
count=$1 color=$2

case $color in red) ((max_red < count)) && return 1 ;; green) ((max_green < count)) && return 1 ;; blue) ((max_blue < count)) && return 1 ;; esac return 0 } is_eligible_game() { local iterations count color iterations=$1

while [ "$iterations" ]; do [[$iterations =~ ([0-9]+)\ (red|green|blue) ]] || break
count=${BASH_REMATCH[1]} color=${BASH_REMATCH[2]}
iterations=${iterations:${#BASH_REMATCH[0]}}
iterations=${iterations#*[,;]} is_eligible_cubes "$count" "$color" || return 1 done return 0 } solve_day2_part1() { local sum game_id iterations sum=0 while read _ game_id iterations; do game_id=${game_id%:}
is_eligible_game "$iterations" && ((sum += game_id)) done echo "$sum"
}

solve_day2_part1


### Part 2

To paraphrase the change in part 2, for each game, we're looking for the maximum number of cubes per color that were drawn, multiply them together, and then sum this number for all games. With the example above this would be 48 + 12 + 1560 + 630 + 36 = 2286.

#!/usr/bin/env bash
#
# Solver for https://adventofcode.com/2023/day/2 part 2
# Redirect the input file to this script, for example day2part2.sh < path/to/input.txt
#

set -euo pipefail

solve_day2_part2() {
local sum game_id iterations count color red green blue
sum=0

while read _ game_id iterations; do
game_id=${game_id%:} red=0 green=0 blue=0 while [ "$iterations" ]; do
[[ $iterations =~ ([0-9]+)\ (red|green|blue) ]] || break count=${BASH_REMATCH[1]}
color=${BASH_REMATCH[2]} iterations=${iterations:${#BASH_REMATCH[0]}} iterations=${iterations#*[,;]}
case $color in red) ((red < count)) && red=$count ;;
green) ((green < count)) && green=$count ;; blue) ((blue < count)) && blue=$count ;;
esac
done

((sum += red * green * blue))

done

echo "$sum" } solve_day2_part2  ### Review request What would you do differently? ## 2 Answers It's going to be a really minor thing, but a review is a review and thus doesn't belong in the comments. I've learned that calling files solve_day2_part2 in an "AdventOfCode2023" folder is perfectly fine. Calling the function solve_day2_part2, completely destroys the possibility for re-use. Ever. There's not even a year mentioned in the function itself, so the function name isn't guaranteed to be unique in your codebase. The following line is for the file, not the function: # Solver for https://adventofcode.com/2023/day/2 part 2  And thus does not really tell us what the function does. There could be lots of functions in that file and you could've copied a 2018 solution into your file for all we know. For a one-off hobby project, the damage is minimal. But this code shouldn't pass a professional review. Considering you can fix that by using a better function name... I'd say it's worth considering. What would you do differently? Well, choosing a language better suited to the task springs to mind, maybe python. In particular, I find this inconsistency distracting when reading such scripts:  [[$iterations =~ ...(red|green|blue) ]] || break
...
((sum += red * green * blue))


We have the usual \$ dollar sigil for variable interpolation. And then we switch to an entirely different language which eschews that sigil. And the identifiers are perfect, wouldn't change them, yet we have this jarring disconnect between RGB literals above then RGB variables below. Coding up arithmetic in bash asks the Author to attend to too many details, like whether the cost of forking off a child to evaluate an expression is "mandatory" (e.g. sort, wc) or "too much" as we see here.

# part 1

I like your shebang, quite flexible. The set is conservative, very nice.

I confess I found the is_eligible_cubes predicate a bit confusing, but that's because 0 denotes success, so we kind of see the boolean sense flipped. IDK, maybe assign FAIL = 1 and return that? What you wrote is just fine.

In a similar vein, I had some trouble seeing why we wouldn't want max_red <= count. I find it more natural to mention the variable count before the constant max_red that we're comparing against. Somehow I find I more rapidly parse count <= max_red than the equivalent count > max_red or max_red < count. I think it's because the real number line is conventionally depicted left-to-right, and I'm focused on the small (finite) "good" region while ignoring the infinite set of "bad" counts that disappears off into the distance toward the right.

At the call site further down it looks beautiful, very intuitive.

  while read _ game_id iterations; do


Sorry, I'm not getting why you chose an identifier of iterations. That's not a term from the business domain. The problem statement suggests that some form of "subsets of cubes" would lead to a more descriptive identifier.

# part 2

No notes (beyond what I mentioned above).

Looks great, very clear.

• "Well, choosing a language better suited to the task springs to mind, maybe python." << I disagree. After doing a few of these tasks, one should easily be convinced that it makes much more sense to choose "a language I want to improve with" or "a language I find fun", rather than "a language that is best-suited for this task".
– Stef
Commented Dec 3, 2023 at 15:22
• @Stef Agreed, it's not uncommon to do such challenges in languages you either know nothing about or want to push to its limits.
– Mast
Commented Dec 3, 2023 at 17:00
• As someone who does a lot of shell programming for the past 25 years and counting, I'd highlight that on many systems (embedded, constrained by security paperwork, ancient, etc.) some, hopefully POSIX-compatible, shell interpreter is the only one you have (and often not bash, but some ksh or busybox derivative). A Python or Perl or PHP footprint may be larger than the whole storage in such box or SBC or boot-handling environment. So being proficient with shells (and being open to use of dumb syntax and maybe monkey-patch it with better code if you detect a more capable interpreters) is useful. Commented Dec 3, 2023 at 17:34
• Coming to think of it, many such scripts can be made for distributability - as systems automation or build recipes, etc. and you don't know about capabilities of the target system it ends up on. Availability of a baseline shell is something you can almost count on, and that you have a chance to write it so that it would behave similarly for some three decades worth of distros and platforms. Commented Dec 3, 2023 at 17:37
• Hmmm, I sympathize, though the quoted "game" doesn't sound too bad. We draw a batch of cubes from an urn, make an observation, and replace them. So maybe a "draw"? Or a "sample"?
– J_H
Commented Dec 3, 2023 at 21:32