2021-12-4

Created on Monday, December 23, 2024.

Day 4 - Let's play bingo

How curious. My first job after graduating university was to program touchscreen bingo systems, so I'll be fascinated to see how accurate this is. My first note is that the bingo cards are 5x5, whereas good british cards are 9x3 or 4x4. So it's american bingo then.

This one is going to be the first opportunity for us to consider some form of data structure I think. Previously we've worked on sets of numbers, and there's been no real need for a more complex data structure at all.

But in this case, we're going to load a stream of random numbers, and then we're going to have to store a number of boards of 5x5 bingo cards. We're then going to have to step through the number list, and for each number called, we're going to go through our boards and mark the number (dab them in Bingo parlance FYI), and then we're also going to have to check for a winning card, one in this case that has a line of 5 in a row. In this case, a board is winning if there's a row or a column of marked numbers.

We also need that board to keep track of which number is marked and which are not in order to calculate the final sum for the board. This feels like it calls for a class.

We can have a board class. It can keep the 25 numbers on the board in one list, and a list of booleans indicating whether or not a number is marked seperately. We could use a multidimensional list, so something like numbers = [[1,2,3,4,5],[6,7,8,9,10],[11,12,13,14,15]], but I suspect it's easier to use a bit of math on a simple list of numbers. If we want to check a column, we can simply look at every 5th number, offset by the column number, so at numebr 0,5,10 for 1st column or number 1,6,11 for the second column. This is easy enough to do.

Oh, finally we need to be able to parse the data into our boards. That's a lot more complex than previous parsing, so we're going to want to consume the input file in two chunks. Firstly the first line is just the list of numbers called. Then we can skip a line, and line 3 onwards can be seen as a set of 5 lines with a board, and skip a line. We can repeat that to get all the boards.

Onwards

## Import ipytest and get it setup for use in Python Notebook
import pytest
import ipytest
ipytest.autoconfig()
class Board:
    def __init__(self, numbers):
        self.numbers = numbers
        self.marked = [False for i in numbers]
    
    def dab(self, number):
        if number in self.numbers:
            self.marked[self.numbers.index(number)] = True
            
    def is_winning(self):
        for num in range(5):
            if not False in self.marked[num*5:num*5+5]:
                return True
            if not False in self.marked[num::5]:
                return True
        return False
    
    def score(self):
        return sum([n for m,n in zip(self.marked, self.numbers) if not m])
    
    def __eq__(self, other):
        if isinstance(other,Board):
            return self.numbers == other.numbers
        return NotImplemented
    
    def __repr__(self):
        return f"Board(numbers={self.numbers}, marked={self.marked})"
    
    def __str__(self):
        s = "\n"
        for y in range(5):
            for x in range(5):
                if self.marked[y*5+x]: 
                    s+=f"*{self.numbers[y*5+x]:2}*"
                else:
                    s+=f" {self.numbers[y*5+x]:2} "
            s+="\n"
        return s
    
test_board = Board([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25])
assert test_board.score() == 325 
test_board.dab(3)
assert test_board.marked[0] == False
assert test_board.marked[3] == False
assert test_board.is_winning() == False
test_board.dab(8)
test_board.dab(13)
test_board.dab(18)
test_board.dab(23)
assert test_board.is_winning() == True
assert test_board.score() == 260 

That's a Board class, it lets us mark numbers, it keeps track of the marked numbers.

There's two clever bits of code in here that you might not have seen before though.

Firstly, there's a range that uses a step. When you access a number inside an list you use list[n]. But sometimes you want a range rather than just one number. You can use list[n:m] to get you all the list from index n to index m-1 (it doesn't include the last index for ... reasons, just trust me). You can shorten this if you want all the way from the beginning or all the way to the end, so list[n:] and list[:m] can be handy (we'll use this shortly on the input lines).

But there's another thing, the step. By default, when you say list[m:n] it means every digit from m to n. But you can also tell it to step by more than one, so list[m:n:2] will get you every other number from a list. In our case we use self.marked[num::5] to get us every 5th number starting from number (0 to 5). That's basically a column.

Secondly, I've used another list comprehension feature, the filter clause. For a list comprehension [item for item in list] you can also put an if statement on it, for example [item for item in list if item > 5]. We use that in the score to combine the list of numebrs and the list of marked with zip to give us a list of tuples like [(False, 1), (False,2), (True, 3)...]. Instead of just item, I've used m,n to unpack the marked and number values, and then the if clause is returning only numbers that are not marked. We then pass that list to sum, and viola, our scoring mechanism.

Final thing, I want to compare boards for testing, so I've implemented the special method __eq__. This is called when you do a == b, and it does two things, if b is another Board, and if both a and b's list of numbers are the same, then it'll return true. If b is any other class, it will return NotImplemented, and python will just compare object identity instead. Finally I'm implemented repr which is called when the test framework prints objects.

So let's try this weird parsing thing next.

We want to open our file, but now we're going to read it in a unique way. We're going to read the first line and store those as numbers, and then we're going to read a card. We want to store our cards as a single long list, so we'll read five lines, strip them of their linebreaks, and then join them together, with a space between each line. We can then split them up by spaces and get 25 numbers out.

Let's go

def parse_file(lines):
    called_numbers = [int(num) for num in lines[0].split(",")]
    boards = []
    current_board = ""
    lines = lines[1:]
    for n in range(len(lines)//6):
        boardnums = " ".join([line for line in lines[6*n:(6*n)+6]])
        boards.append(Board([int(num) for num in boardnums.split()]))
    return called_numbers, boards

test_file = """7,4,9,5,11,17,23,2,0,14,21,24,10,16,13,6,15,25,12,22,18,20,8,19,3,26,1

22 13 17 11  0
 8  2 23  4 24
21  9 14 16  7
 6 10  3 18  5
 1 12 20 15 19

 3 15  0  2 22
 9 18 13 17  5
19  8  7 25 23
20 11 10 24  4
14 21 16 12  6

14 21 17 24  4
10 16 15  9 19
18  8 23 26 20
22 11 13  6  5
 2  0 12  3  7"""

test_calls, test_boards = parse_file(test_file.split('\n'))
assert [7,4,9,5,11,17,23,2,0,14,21,24,10,16,13,6,15,25,12,22,18,20,8,19,3,26,1] == test_calls
assert 3 == len(test_boards)
assert test_boards[0] == Board([22,13,17,11,0,8,2,23,4,24,21,9,14,16,7,6,10,3,18,5,1,12,20,15,19])
assert test_boards[2] == Board([14,21,17,24,4,10,16,15,9,19,18,8,23,26,20,22,11,13,6,5,2,0,12,3,7])

Yuck, that parsing was more complex than I thought. We're joining together the lines, in 6's. We're including the blank line because the split will eat all the extra whitespace and it's easier to take them in chunks of 6 than to work out how to handle the first or last board being different.

Ok, let's try dabbing our test boards and see if we can get teh same result as the example. We're going to write a function that takes the call numbers and keeps dabbing until one of the boards tells it it's won, at which point it will return the winning board and we can then calculate it's score.

def dab_until_win(boards, numbers):
    for number in numbers:
        print(f"Calling {number}\n")
        for board in boards:
            board.dab(number)
            if board.is_winning():
                print(f"Win! Board: {board}\n")
                return board, number
    return None

test_calls, test_boards = parse_file(test_file.split('\n'))
winning_test_board, winning_test_number = dab_until_win(test_boards, test_calls)
assert winning_test_board == Board([14,21,17,24,4,10,16,15,9,19,18,8,23,26,20,22,11,13,6,5,2,0,12,3,7])
assert winning_test_number == 24
assert winning_test_board.score() == 188
print(24*188)

Calling 7

Calling 4

Calling 9

Calling 5

Calling 11

Calling 17

Calling 23

Calling 2

Calling 0

Calling 14

Calling 21

Calling 24

Win! Board: 14211724 4 10 16 15 * 9* 19 18 8 23 26 20 22 11 13 6 * 5* * 2** 0* 12 3 * 7*

4512

Blimey, wasn't expecting that to work first time!

Ok, let's try this bad boy on some real data and see what it tells us.

data = [line.strip() for line in open("day4.txt").readlines()]
calls, boards = parse_file(data)
winning_board, winning_number = dab_until_win(boards, calls)

print(f"winning board: {winning_board} at {winning_number} with score {winning_board.score()} equals {winning_board.score()*winning_number}")

Calling 83

Calling 5

Calling 71

Calling 61

Calling 88

Calling 55

Calling 95

Calling 6

Calling 0

Calling 97

Calling 20

Calling 16

Calling 27

Calling 7

Calling 79

Calling 25

Calling 81

Calling 29

Calling 22

Calling 52

Calling 43

Calling 21

Win! Board: 39 55**88 78 72 * 6* 82 52 1 60 41 23 97 44 11 3 15 21 93 38 24 90 * 7* 80 2

winning board: 39 55**88 78 72 * 6* 82 52 1 60 41 23 97 44 11 3 15 21 93 38 24 90 * 7* 80 2 at 21 with score 796 equals 16716

That worked, although I'll admit now to 1 minor bug when I first wrote this. First of all, I was still calling dab_until_win on the test data, which gave me the wrong total. Seocndly, once I fixed that, I discovered I was trying to parse the day3 data, which was resulting in some very odd errors.

Oh well, onto ...

Part 2 Find out who loses

This is really interesting, because we're after a really specific condition. As numbers are called, a board might be winning because it has a single line. But when the next number is called, it might now win on a column, so it has two lines. I was expecting something like this, keep playing until there's 2 lines or even a special pattern (I've seen christmas tree patterns on 5x5 bingo cards, as well as 4 corners plus center, and so on).

But in this case, even if a card has won, we keep playing until every card has won apart from one of them. Even then we need to keep playing until that card finally wins. We probably don't care about the other cards at that point, so I'm super tempted to start removing them from the list, that way we can wait until there's only 1 card left and then when it wins, we can return the card and the number as before. Lets try that, it's just a new version of dab_until_win for us.

def dab_until_last_win(boards, numbers):
    for number in numbers:
        winning_boards = []
        for board in boards:
            board.dab(number)
            if board.is_winning():
                print(f"Win on {number} for board: {board}")
                if len(boards) == 1:
                    return board, number
                winning_boards.append(board)
        for board in winning_boards:
            boards.remove(board)
    return None

test_calls, test_boards = parse_file(test_file.split('\n'))
winning_test_board, winning_test_number = dab_until_last_win(test_boards, test_calls)
assert winning_test_board == Board([3, 15, 0, 2, 22, 9, 18, 13, 17, 5, 19, 8, 7, 25, 23, 20, 11, 10, 24, 4, 14, 21, 16, 12, 6])
assert winning_test_number == 13
assert winning_test_board.score() == 148
print(f"winning board: {winning_test_board} at {winning_test_number} with score {winning_test_board.score()} equals {winning_test_board.score()*winning_test_number}")

Win on 24 for board: 14211724 4 10 16 15 * 9* 19 18 8 23 26 20 22 11 13 6 * 5* * 2** 0* 12 3 * 7*

Win on 16 for board: 22 13 1711 0 8 * 223 424* 21 91416* 7* 6 10 3 18 * 5* 1 12 20 15 19

Win on 13 for board: 3 15 * 0** 2* 22 * 9* 18 1317 5 19 8 * 7* 25 23 20 111024* 4* 142116 12 6

winning board: 3 15 * 0** 2* 22 * 9* 18 1317 5 19 8 * 7* 25 23 20 111024* 4* 142116 12 6 at 13 with score 148 equals 1924

Phew.

I ran into a problem I'd forgotten about there, removing items from a list while iterating through a list does really odd things to the iteration. So I had to rework the process to iterate through the boards, adding winning boards to a list of boards to delete.

The second thing that I struggled with at this point was that I had made a mistake in the is_winning code. The original cost checked for rows without multiplying. Since I'd made the point of mentioning it, this was a stupid mistake to make. More importantly, because somehow nothing so far had needed to know about the completion of rows 2-5, I didn't notice.

Finally, I added late a __str__ function,which is used to render the boards as strings. I used stars around numbers to indicate those that had been marked, making it easier to tell how well a board had been filled. This is what tipped me off to my is_winning bug, when I discovered a board being marked as winning despite not having any lines completed!

data = [line.strip() for line in open("day4.txt").readlines()]
calls, boards = parse_file(data)
winning_board, winning_number = dab_until_last_win(boards, calls)

print(f"winning board: {winning_board} at {winning_number} with score {winning_board.score()} equals {winning_board.score()*winning_number}")

Win on 21 for board: 39 55**88 78 72 * 6* 82 52 1 60 41 23 97 44 11 3 15 21 93 38 24 90 * 7* 80 2

Win on 59 for board: 74 99 * 6* 4 20 9581275988 63 69 30 25 87 92 96 89 42 18 11 77 91 8 46

Win on 96 for board: 94 99 67 88 82 96* 52153**52* 41 15 49 35 89 54 39 66 24 51 9 * 6* 62 33 70

Win on 96 for board: 80 47 53**81 36 75 35 87 90 89 19 * 5* 56 28 26 8 44 77 31 20 6196279979

Win on 51 for board: 78 51 69 47 16 48 55 58 70 37 * 759* 66 * 5* 76 94 52 82 22 10 13 *8395* 24 79

Win on 51 for board: 1 66 8 * 6** 7* 47 96**25 77 72 23 22 31 42 24 5227535199 21 65 35 84 * 5*

Win on 57 for board: 52**18 32 56 61 40 * 5* 48 64 62 22**57 19 26 91 31 * 39527* 87 74 83 75 99 73

Win on 50 for board: 96 45 75 97 94 68 35 9 30 67 25**88 40 46 37 82 79 90 76 55 5059582221

Win on 32 for board: 44 23 73 56 88 4 96 90 * 0**32* 40 86 47 87 50 28 30 42 39 17 10 12 16 8 14

Win on 74 for board: 20 72 10 30 17 2514747158 34 51 45 43 76 38 75 50 98 42 2 12 67 66 82

Win on 63 for board: 39 8 68 87 21 78 5927 0**14 25* 3965163* 92 35 19 *5799* 83 75 69 37 72

Win on 63 for board: 576396* 327* *8874* 9 60 99 48 30 1 18**15 23 77 89 24 55 37 58 67 91 10

Win on 76 for board: 90 21 75 77**97 80 29 54 16 10 55 98 65 19 * 7* 9676202888 94 83 91 26 86

Win on 76 for board: 66 276179 73 37 88 47 84 72 501899* 776* 97 11 *5343* 30 42 56 98 39 63

Win on 65 for board: 85 10 21 33 80 78 86 77 41 36 98 58**53 82 72 75 2065 3 46 521674 45 99

Win on 62 for board: 976217* 5**79* 1 99 98 80 84 44 16 2 40 94 68 95 49 32 8 38 35 23 89 * 3*

Win on 62 for board: 1 22 72 43**58 78 975261**62 27 48 81 2 63 33 37 4 82 18 65**28 70 31 59

Win on 67 for board: 15 85 8 65**79 95**51 39 75 96 18 45 68 81**71 6728216120 70 29 92 74 36

Win on 67 for board: 296277* 3* 89 54 12 55 44 34 66 78 83 98 22 17 10 67 82 75 431684 41 19

Win on 48 for board: 4853599943 77 24 625027 28 8 10 86 18 96* 9* 92 66 67 20**55 87 52 31

Win on 48 for board: * 995* 75 14 1 94 90 40 84 24 43 72 93 4 87 *48505320** 6* 65 11 38 25 46

Win on 12 for board: 82 24 952179 85 84 38 89 50 * 7* 10 * 52520* 99 37 48 86 12 68 93* 6* 66 43

Win on 12 for board: 49 1 79 39 82 * 796* 13 33 85 * 353321250* 36 30 *2755**95* 16 24 2 66 77

Win on 12 for board: 70 19 79* 7* 24 357193 42 76 1788622512 54 * 0* 11 32**58 38 64 29 75 80

Win on 12 for board: 90 76**12 80 58 60 45 35 10 33 79 19 65 54 21 635177**15 92 34 53* 7**59* 44

Win on 12 for board: 76* 3* 47 40 72 41 * 7* 68 * 558* *1232816293* 91 80 17 78 61 *2295* 94 38 33

Win on 12 for board: 96**48 64 89 * 6* 76**12 47 8 30 39 55**95 11 62 68 255063 31 59**17 46 52 78

Win on 12 for board: 957616* 5* 56 552852**88 73 * 699* 75 90 18 *1225**22* 44 57 62 37 36 30 48

Win on 8 for board: 25* 88129**95* 65 56 86 34 17 38 66 85 43 26 39 12 70 32 19 49 68 10 4 13

Win on 68 for board: 35**16 40 94 65 60 28 46 51**61 45 53 36 89 80 33 93**12 39 42 13 68**57 64 26

Win on 31 for board: 16**95 72 67**17 71* 3* 38 90 14 34 * 855* 49 33 54 *792027* 80 *9631**18* 70 61

Win on 31 for board: 2979 87631 30 2012 0**61 14 37 49 45 74 64 17 1 91 51 87 67* 3**77* 47

Win on 31 for board: 80 57 2 73 46 22**50 87 60 89 95**74 98 93**62 86 61 10 69 * 9* 4831538884

Win on 31 for board: 59* 89748* 73 40 31**29 49 85 41 68 11 * 9* 45 87 74**77 75 91 67**27 70 90 16

Win on 31 for board: 81 46 31 56 30 94 22**58 69 41 42 91 20* 0*14* 71 11 17 37 12 * 7* 73 79 9* 26

Win on 31 for board: 75 22 42 59**55 23 33 90 2 52 94 13 78 * 016* 39 72 67 45 31 11 53 783*28*

Win on 19 for board: 93* 8* 11 2 39 74 40 95 69 57 86 213188**63 5216192022 72 * 7**25* 90 77

Win on 36 for board: 883681 42 10 85 99**29 70 86 64 15 37 96**61 66 76 87 17**62 91 16 60 13 65

Win on 36 for board: 55* 3* 33 66 69 5197365750 56 743584 44 45 92 18 42 52 85 13 27 70 20

Win on 98 for board: 35**98 24 31 41 79**63 92 70 11 36* 3* 72 50**93 90 21 40 38 77 * 014* 42 *9967*

Win on 30 for board: 99 69 61**29 86 6788 5**20 2 70 60 27 82 * 6* 956530* 985* 23 *5859* 87 66

Win on 30 for board: 94 * 958* 91 38 84 7223063* 23 26 49 *9348* 79 75 *9996*67* 90 19 66 57 47

Win on 91 for board: 45 75 85**35 72 9925916828 29**52 1 80 98 62 46 63**22 44 82 86 57 24 58

Win on 91 for board: 77**63 37 68 73 34 302291 10 16 80 89 98 45 46 36 90 95**83 54 525761**55

Win on 89 for board: 746527**53 39 67 66 76 13 31 75 51 11 49 59 181271* 9**89* 98 24 73 26 43

Win on 89 for board: 36 78 46 84**14 56 53**51 92 89 39 997722**32 65 38 42 76* 7* 62**31 1 87 95

Win on 89 for board: 26 51**31 44 25 21* 6* 70 12**71 67 69 13 63**79 8174 88930 164888 72 66

Win on 66 for board: 33 89**48 4 20 46 66 45 76* 7* 127743 60 15 54 589195 69 11 * 83231**18*

Win on 66 for board: 25 75 23 2 38 66**52 42 62**16 93**63 78 31**65 * 09177* 4 14 615953**17 10

Win on 66 for board: 43 33 52**89 40 53 94 87 90 19 98**51 64 63**62 6665579318 80 795999 73

Win on 75 for board: 41 22 47 34 55 74**57 42 85 33 40 21**52 78 * 7* 51**58 37 4 49 53**75 11 48**76

Win on 75 for board: 589363**52 23 77 60 1 38 87 7589852591 64 39 96 49 66 14 45 84 13 29

Win on 75 for board: 70 30**59 94 86 40 87 20 69 25 46 44 41 17**79 7599 391 8 71 39 73 88 37

Win on 75 for board: 62 90 596725 41 45 * 79117* 10 297543 82 12 78 95 37 32 286676 2 49

Win on 75 for board: 48 64 21 23 92 299975 2 53 41 97**74 39 89 666322 45 73 206830**35 78

Win on 75 for board: 56 68**71 11 63 129357 94 84 91 13 293175 54 49 51 73 * 5* 81* 7* 60 53**89

Win on 75 for board: 83**29 90 48 46 97**21 2 65**15 89**28 60 69 26 7775 93596 82 49 66* 5**16*

Win on 47 for board: 5232146647 53* 7** 9* 69 11 193657 54 65 17 26 76**51 42 13 * 8* 44 63 39

Win on 47 for board: 96 73 49 36 56 * 6* 45 308176 10 95 70 88**98 4347746684 778368 54 28

Win on 47 for board: 42 36 34 77 69 2155475289 61 90 * 3* 23 41 45 802927**99 79 86 87 93**74

Win on 4 for board: * 3* 86 40 61**65 * 4* 82 28 46 32 31* 5* 33 96**98 306268**75 70 * 9**18* 92 19 72

Win on 4 for board: 72 15 46 71**75 41 166814**43 97**25 78 26 39 595788* 4**52* 20 49 * 3* 23 29

Win on 4 for board: 33 78 3135 6 8543 7 87 18 6893 48096 98 13 61**77 23 10 29 34 36* 5*

Win on 23 for board: 26 * 6* 49 44 74 94 34 73 70 64 1491238831 90 556275**43 * 4* 1 635719

Win on 23 for board: 51**80 38 15 44 5323836163 27 33 79 40 32 84 2 82 20**93 72 92 48 39 98

Win on 60 for board: 6057229523 81* 4* 34 36**14 77 1 45 24 19 33 88* 82874* 2 17 37 32 94

Win on 70 for board: 34 82 45 65 44 7089952079 881862**68 37 85**17 54 86 69 97**25 13 42 67

Win on 87 for board: * 631* 78 64 89 76 13 83 56 34 *952997* 49 37 *6677**74* 73 90 87 41 62 39 85

Win on 87 for board: 36 73 27 72 * 8* 75748755 7 2 67 34 84**51 94 182362 11 65 41 * 32953*

Win on 87 for board: 856362 11 38 53**29 2 * 8* 13 87 64 31 69 58 888417* 3* 26 * 53223* 33 39

Win on 90 for board: 90* 6982580* 41 *813087* 33 11 *217962* 92 *2760* 46 56 88 * 4* 69 70 13 84

Win on 90 for board: 40 904357 26 10 52**27 64 72 * 383* 11 54 42 39 *20871581* 49 28**58 33 29

Win on 90 for board: 35* 6289021* 72 74 78 43 3* *4717* 13 41 96 *681276*81* 11 70 34 33 25 54

Win on 90 for board: 21 33 * 720* 78 81 46 77 42 79 *8428* 82 93**68 906360* 0* 34 35**70 40 29 54

Win on 13 for board: 32 73 * 0* 37 86 78 42 133053 44 995112**96 45 57**63 34 58 41 91* 7* 49 52

Win on 13 for board: 23**84 34 35**19 297181**32 92 22 49 54 * 6* 56 64 94 53**89 2 74**68 11 13**47

Win on 13 for board: 2 30 11 55**52 51 92 73 54 96 892267 56 17 49 50* 995* 45 *23741375** 7*

Win on 13 for board: 42 277013* 5* 77 38 50* 3* 44 29 56 361597 68**20 94 12 54 64 832555**80

Win on 13 for board: 64 13 45 * 7* 72 663518**68 86 38 30**89 11 29 37 762314**67 366187 26 46

Win on 13 for board: 1350194858 28 42 832029 * 596* 92 90 3* *8793* 56 23 78 *9857* 0* 72 62

Win on 38 for board: 46 17**28 56 50 64 65**43 73 22 3231892038 13 49 18**55 72 83 41 78 94 57

Win on 56 for board: 40 146843 37 123529 82 48 472897 44 93 95**56 33 96**27 388588 49 * 6*

Win on 56 for board: 34 256759**66 68**27 69 91 33 * 456* 46 *9921* 51**13 24 41 12 906519 26 55

Win on 56 for board: 67 24 * 98948* 56* 7* 44 47**68 123835 54 14 95**58 78 13**28 97* 5* 37 99 42

Win on 34 for board: 16 2 675674 50 41 86 38 39 329659 40 * 8* 17 82 49 55**89 348881 73 94

Win on 46 for board: 795162 33 * 5* 15 39 214890 8829 7 92 98 87 49 84* 614* 72 *8546**71* 26

Win on 46 for board: 63 78 55* 760* *951438* 10 45 * 316* 72 53 37 1 897075 44 * 5** 66613**46*

Win on 46 for board: 656795 33 60 55 49 64 92 * 7* 56**75 73 35**99 * 8* 72 80* 0**46* 41 25 2 69 * 4*

Win on 24 for board: * 838* 40 *6724* 45 * 921 789* 82 96 72 92 * 4* 86 49 *8079**22* 26 11 84 78 70

Win on 24 for board: 3832249879 48 49 * 41790* 122095**99 10 94 23**30 92 97 841857 11 53

Win on 41 for board: 11 326396**81 77 82 * 03015* 88314146 6 175576 42 87 249370**66 40

Win on 41 for board: 24**41 73 90**46 559163 86 44 * 074* 72 *4776* 34**13 33 65**62 49 75 10 15**27

Win on 92 for board: 6046 4**56 49 2 36* 851* 54 71 82 97 1 18 45 69 37 * 6* 26 *85612792**77*

Win on 49 for board: * 6659122* 86 82 72 *604187* 2 71 91284* *5190434980* *1520* 54 *66*29*

Win on 73 for board: 7355873584 376341 54 39 58 42 856668 96**24 86 72 27 40 28* 4**80* 33

Win on 73 for board: 6367735313 28 54 19 72 93 484155 64 33 837065 26 22 11 86 351618

Win on 26 for board: 45 716680 69 53 39 299299 23* 0* 72 36**52 75**70 33 2 14 22772126 3

Win on 26 for board: * 0* 78 44 49**14 72 883031**81 348755**27 11 58 64 76 40 62 4718383526

Win on 26 for board: 39 64 352310 73**25 1 45 93 503795 86 78 52* 6* 2 * 013* *26892762**80*

Win on 40 for board: 45 979623**62 795960**87 64 75 2 304750 858156 11 38 172640* 7**66*

winning board: 45 979623**62 795960**87 64 75 2 304750 858156 11 38 172640* 7**66* at 40 with score 122 equals 4880

Next

2021-12-5

Previous

2021-12-3