--- /dev/null
+#!/bin/bash
+
+set -e
+set -u
+
+filename=${1:-example.txt}
+
+bold=$(tput bold)
+normal=$(tput sgr0)
+
+# each board is a 5*5 grid, so that's handy, we'll store all the boards in one
+# array, then create a second array that shows which matches we have, we can
+# select any given board by limiting to a range of 25 values from the array
+
+exec 3<"$filename"
+
+# first line is the draws, so read that in to an array too
+OLDIFS="$IFS"
+IFS=","
+read -u 3 -a draw
+
+read -u 3 line
+
+IFS=" "
+line_count=0
+
+declare -a boards
+declare -a checked
+
+while read -u 3 -a row; do
+ ((line_count+=1))
+ boards+=("${row[@]}")
+ if [ $line_count -eq 5 ]; then
+ read -u 3 line || true
+ line_count=0
+ fi
+done
+
+for x in ${boards[@]}; do
+ checked+=(0)
+done
+
+display_board() {
+ local board_number=$1
+ offset=$((25*$board_number))
+
+ count=0
+
+ for val in "${boards[@]:$offset:25}"; do
+ if [ ${checked[$((offset+$count))]} -eq 1 ]; then
+ echo -n "${bold}"
+ fi
+ ((count+=1))
+ printf "%2d " $val
+ echo -n "${normal}"
+ if [ $((count%5)) -eq 0 ]; then
+ echo
+ fi
+ done
+ echo
+}
+
+check_board() {
+ local board_number=$1
+ local drawn_number=$2
+ offset=$((25*$board_number))
+
+ count=0
+ for val in "${boards[@]:$offset:25}"; do
+ if [ $val -eq $drawn_number ]; then
+ checked[$((offset+$count))]=1
+ fi
+ ((count+=1))
+ done
+
+ row=0
+ col=0
+ declare -a row_matches=(0 0 0 0 0)
+ declare -a col_matches=(0 0 0 0 0)
+
+ for val in "${checked[@]:$offset:25}"; do
+ if [ $val -eq 1 ]; then
+ ((col_matches[$col]+=1))
+ ((row_matches[$row]+=1))
+ fi
+ ((col+=1))
+ if [ $col -eq 5 ]; then
+ col=0
+ ((row+=1))
+ fi
+ done
+
+ for row in "${row_matches[@]}"; do
+ if [ $row -eq 5 ]; then
+ return 0
+ fi
+ done
+
+ for col in "${col_matches[@]}"; do
+ if [ $col -eq 5 ]; then
+ return 0
+ fi
+ done
+
+ return 1
+}
+
+get_unmarked_count() {
+ local board_number=$1
+ local offset=$((board_number*25))
+ local count=0
+
+ for ((i=0; i<25; i++)); do
+ if [ ${checked[$(($offset+$i))]} -eq 0 ]; then
+ ((count+=${boards[$(($offset+$i))]}))
+ fi
+ done
+
+ echo $count
+}
+
+declare -a completed_boards=()
+
+in_array() {
+ local value=$1
+ local -n array=$2
+
+ for val in "${array[@]}"; do
+ if [ $val -eq $value ]; then
+ return 0
+ fi
+ done
+
+ return 1
+}
+
+first_board=-1
+first_board_last_number=-1
+last_board_last_number=-1
+
+for number in "${draw[@]}"; do
+ for (( board=0; board<$((${#boards[@]} / 25)); board++ )); do
+ if ( ! in_array $board completed_boards ); then
+ check_board $board $number && { if [ $first_board -eq -1 ]; then first_board=$board; first_board_last_number=$number; fi; completed_boards+=( $board ); } || true
+ if [ ${#completed_boards[@]} -eq $((${#boards[@]} / 25)) ]; then
+ last_board_last_number=$number
+ break 2
+ fi
+ fi
+ done
+done
+
+echo "Board matched: $first_board"
+echo "Last number drawn: $first_board_last_number"
+echo
+display_board $first_board
+
+unmarked=$(get_unmarked_count $first_board)
+
+echo "$unmarked * $first_board_last_number = "$((unmarked*$first_board_last_number))
+
+echo "Last board matched: "${completed_boards[-1]}
+echo "Last number drawn: $last_board_last_number"
+echo
+display_board ${completed_boards[-1]}
+
+unmarked=$(get_unmarked_count ${completed_boards[-1]})
+
+echo "$unmarked * $last_board_last_number = "$((unmarked*$last_board_last_number))