81 lines
2.8 KiB
Python
81 lines
2.8 KiB
Python
|
# get input
|
||
|
with open("input.txt") as f:
|
||
|
lines = f.read().split('\n\n')
|
||
|
raw_numbers = lines[0]
|
||
|
raw_boards = lines[1:]
|
||
|
|
||
|
# define a structure for our bingo game
|
||
|
class Board:
|
||
|
def __init__(self, points):
|
||
|
self.unmarked = set()
|
||
|
self.marked = set()
|
||
|
for point in points:
|
||
|
self.unmarked.add(point) # all points are unmarked at start of game
|
||
|
def __repr__(self):
|
||
|
return f"\nmarked = {self.marked}\nunmarked = {sorted(self.unmarked)}\n"
|
||
|
def is_bingo(self):
|
||
|
# we need to count rows and columns (diagonals don't count)
|
||
|
i_vals = []
|
||
|
j_vals = []
|
||
|
for point in self.marked:
|
||
|
i, j, _ = point
|
||
|
i_vals.append(i)
|
||
|
j_vals.append(j)
|
||
|
from collections import Counter
|
||
|
i_count = Counter(i_vals)
|
||
|
j_count = Counter(j_vals)
|
||
|
# bingo is called if there are 5 in a row or column;
|
||
|
# luckily, diagonals don't count
|
||
|
return ( ( 5 in i_count.values() ) or ( 5 in j_count.values() ) )
|
||
|
def score(self, last_number):
|
||
|
score = 0
|
||
|
# "start by finding the sum of all unmarked numbers"
|
||
|
for point in self.unmarked:
|
||
|
_, _, value = point
|
||
|
score += value
|
||
|
# "then, multiply that sum by the number that was just called"
|
||
|
score *= last_number
|
||
|
return score
|
||
|
|
||
|
# parse input
|
||
|
numbers = [int(number) for number in raw_numbers.split(',')]
|
||
|
boards = set()
|
||
|
for board in raw_boards:
|
||
|
points = set(
|
||
|
(i,j,int(value))
|
||
|
for i, row in enumerate(board.split('\n'))
|
||
|
for j, value in enumerate(row.split())
|
||
|
)
|
||
|
boards.add(Board(points))
|
||
|
|
||
|
# game loop
|
||
|
def part1(numbers, boards):
|
||
|
for number in numbers: # call a number
|
||
|
for board in boards: # then check the boards
|
||
|
for point in board.unmarked: # point by point.
|
||
|
_, _, value = point
|
||
|
if value == number: # if there is a match,
|
||
|
board.unmarked.remove(point) # then it is no longer unmarked
|
||
|
board.marked.add(point) # and it should be tracked as marked.
|
||
|
break # we also don't need to continue checking, since numbers are unique.
|
||
|
if board.is_bingo(): # once bingo is called,
|
||
|
return board.score(number) # we can calculate our score with that last number.
|
||
|
|
||
|
print(part1(numbers, boards))
|
||
|
|
||
|
# game loop 2 -- this time we wanna lose
|
||
|
def part2(numbers, boards):
|
||
|
for number in numbers: # call a number
|
||
|
for board in boards.copy(): # then check the boards
|
||
|
for point in board.unmarked: # point by point.
|
||
|
_, _, value = point
|
||
|
if value == number: # if there is a match,
|
||
|
board.unmarked.remove(point) # then it is no longer unmarked
|
||
|
board.marked.add(point) # and it should be tracked as marked.
|
||
|
break # we also don't need to continue checking, since numbers are unique.
|
||
|
if len(boards) == 1 and board.is_bingo(): # if this is the last bingo'd board,
|
||
|
return board.score(number) # then we can calculate our score.
|
||
|
elif board.is_bingo(): # otherwise we still have more boards to check
|
||
|
boards.remove(board) # so we just throw it away.
|
||
|
|
||
|
print(part2(numbers, boards))
|