diff --git a/P2_ONLY_random_state_agent.pth b/P2_ONLY_random_state_agent.pth new file mode 100644 index 0000000..a4725d0 Binary files /dev/null and b/P2_ONLY_random_state_agent.pth differ diff --git a/STABLE_random_state_agent.pth b/STABLE_random_state_agent.pth new file mode 100644 index 0000000..2db880a Binary files /dev/null and b/STABLE_random_state_agent.pth differ diff --git a/__pycache__/hsm.cpython-39.pyc b/__pycache__/hsm.cpython-39.pyc new file mode 100644 index 0000000..d401a03 Binary files /dev/null and b/__pycache__/hsm.cpython-39.pyc differ diff --git a/__pycache__/hsmv2.cpython-39.pyc b/__pycache__/hsmv2.cpython-39.pyc new file mode 100644 index 0000000..68541ac Binary files /dev/null and b/__pycache__/hsmv2.cpython-39.pyc differ diff --git a/__pycache__/mcts.cpython-39.pyc b/__pycache__/mcts.cpython-39.pyc new file mode 100644 index 0000000..55ef5d0 Binary files /dev/null and b/__pycache__/mcts.cpython-39.pyc differ diff --git a/ataxx/__init__.py b/ataxx/__init__.py index 5b9a81d..643b6af 100644 --- a/ataxx/__init__.py +++ b/ataxx/__init__.py @@ -14,6 +14,12 @@ FEN_4SIDES = "x5o/7/3-3/2-1-2/3-3/7/o5x x 0 1" FEN_EMPTY = "7/7/7/7/7/7/7 x 0 1" +# Additional Parameters +FEN_CENTERX = "x-3-o/1-3-1/2-1-2/3-3/2-1-2/1-3-1/o-3-x x 0 1" +FEN_ISLAND = "x5o/3-3/2-1-2/1-3-1/2-1-2/3-3/o5x x 0 1" + +BOARD_DIM = 7 + class Move: def __init__(self, fr_x, fr_y, to_x, to_y): self.fr_x = fr_x @@ -27,23 +33,23 @@ def from_san(cls, san): if san == "0000": return cls.null() elif len(san) == 2: - if san[0] not in "abcdefg": - raise Exception(F"ValueError {san}") - elif san[1] not in "1234567": - raise Exception(F"ValueError {san}") + # if san[0] not in "abcdefg": + # raise Exception(F"ValueError {san}") + # elif san[1] not in "1234567": + # raise Exception(F"ValueError {san}") to_x = ord(san[0]) - ord('a') to_y = ord(san[1]) - ord('1') return cls(to_x, to_y, to_x, to_y) elif len(san) == 4: - if san[0] not in "abcdefg": - raise Exception(F"ValueError {san}") - elif san[1] not in "1234567": - raise Exception(F"ValueError {san}") - elif san[2] not in "abcdefg": - raise Exception(F"ValueError {san}") - elif san[3] not in "1234567": - raise Exception(F"ValueError {san}") + # if san[0] not in "abcdefg": + # raise Exception(F"ValueError {san}") + # elif san[1] not in "1234567": + # raise Exception(F"ValueError {san}") + # elif san[2] not in "abcdefg": + # raise Exception(F"ValueError {san}") + # elif san[3] not in "1234567": + # raise Exception(F"ValueError {san}") fr_x = ord(san[0]) - ord('a') fr_y = ord(san[1]) - ord('1') @@ -93,9 +99,10 @@ def __str__(self): return F"{chr(ord('a')+self.fr_x)}{self.fr_y+1}{chr(ord('a')+self.to_x)}{self.to_y+1}" class Board: - def __init__(self, fen=FEN_STARTPOS): - self._board = [[GAP for x in range(7+4)] for y in range(7+4)] - self._counts = [0, 0, 0, 49] + def __init__(self, fen=FEN_STARTPOS, board_dim=BOARD_DIM): + self.board_dim = board_dim + self._board = [[GAP for x in range(self.board_dim+4)] for y in range(self.board_dim+4)] + self._counts = [0, 0, 0, self.board_dim ** 2] self.set_fen(fen) def get(self, x, y): @@ -140,11 +147,12 @@ def count(self): return self.num_black(), self.num_white(), self.num_gaps(), self.num_empty() def __str__(self): - board = " a b c d e f g\n" - board += " ╔═╦═╦═╦═╦═╦═╦═╗\n" - for y in range(6, -1, -1): - board += chr(y+49) + '║' - for x in range(0, 7): + board = " " + " ".join(chr(ord('a') + x) for x in range(self.board_dim)) + "\n" + board += " ╔" + "═╦" * (self.board_dim - 1) + "═╗\n" + + for y in range(self.board_dim - 1, -1, -1): + board += chr(y + 49) + '║' + for x in range(self.board_dim): if self.get(x, y) == BLACK: board += 'X' elif self.get(x, y) == WHITE: @@ -156,26 +164,29 @@ def __str__(self): else: board += "?" board += '║' - board += chr(y+49) + '\n' + board += chr(y + 49) + '\n' + if y > 0: - board += ' ╠═╬═╬═╬═╬═╬═╬═╣\n' - board += " ╚═╩═╩═╩═╩═╩═╩═╝\n" - board += " a b c d e f g\n" + board += ' ╠' + "═╬" * (self.board_dim - 1) + "═╣\n" + + board += " ╚" + "═╩" * (self.board_dim - 1) + "═╝\n" + board += " " + " ".join(chr(ord('a') + x) for x in range(self.board_dim)) + "\n" + if self.turn == BLACK: board += "Turn: X" elif self.turn == WHITE: board += "Turn: O" else: board += "Turn: ?" + return board - def get_fen(self): """Return a fen string for the current position""" fen = '' - for y in range(6, -1, -1): + for y in range(self.board_dim - 1, -1, -1): empty = 0 - for x in range(7): + for x in range(self.board_dim): if self.get(x, y) != EMPTY and empty > 0: fen += str(empty) empty = 0 @@ -218,28 +229,34 @@ def set_fen(self, fen): fen = FEN_STARTPOS elif fen == "empty": fen = FEN_EMPTY + elif fen == "island": + fen = FEN_ISLAND + elif fen == "foursides": + fen = FEN_4SIDES + elif fen == "centerx": + fen = FEN_CENTERX parts = fen.split() - if len(parts) < 1 or len(parts) > 4: - return False - if parts[0].count('/') != 6: - return False - if len(parts[0]) < len("7/7/7/7/7/7/7"): - return False - if len(parts[0]) > len("xxxxxxx/xxxxxxx/xxxxxxx/xxxxxxx/xxxxxxx/xxxxxxx/xxxxxxx"): - return False + # if len(parts) < 1 or len(parts) > 4: + # return False + # if parts[0].count('/') != 6: + # return False + # if len(parts[0]) < len("7/7/7/7/7/7/7"): + # return False + # if len(parts[0]) > len("xxxxxxx/xxxxxxx/xxxxxxx/xxxxxxx/xxxxxxx/xxxxxxx/xxxxxxx"): + # return False # Clear board - for x in range(7): - for y in range(7): + for x in range(self.board_dim): + for y in range(self.board_dim): self.set(x, y, EMPTY) self.turn = BLACK self.halfmove_clock = 0 self.fullmove_clock = 1 self.history = [] self._halfmove_stack = [] - self._counts = [0, 0, 0, 49] + self._counts = [0, 0, 0, self.board_dim ** 2] # Add side to move if len(parts) < 2: @@ -256,7 +273,7 @@ def set_fen(self, fen): # Set board sq = 0 for c in parts[0]: - x, y = sq%7, 7 - sq//7 - 1 + x, y = sq%self.board_dim, self.board_dim - sq//self.board_dim - 1 if c in "1234567": sq = sq + int(c) @@ -279,9 +296,8 @@ def set_fen(self, fen): return False # We need to have parsed the right number of squares - if sq != 7 * 7: - return False - + # if sq != self.board_dim ** 2: + # return False # Set turn if parts[1] in "bBxX": self.turn = BLACK @@ -305,7 +321,7 @@ def set_fen(self, fen): # Save fen self._start_fen = ' '.join(parts) - self.hash = calculate_hash(self) + self.hash = calculate_hash(self, self.board_dim) return True @@ -331,10 +347,10 @@ def makemove(self, move): return self.set(move.to_x, move.to_y, self.turn) - self.hash ^= get_sq_hash(move.to_x, move.to_y, self.turn) + self.hash ^= get_sq_hash(move.to_x, move.to_y, self.turn, self.board_dim) if move.is_double(): - self.hash ^= get_sq_hash(move.fr_x, move.fr_y, self.turn) + self.hash ^= get_sq_hash(move.fr_x, move.fr_y, self.turn, self.board_dim) self.set(move.fr_x, move.fr_y, EMPTY) for idx, (dx, dy) in enumerate(SINGLES): @@ -344,8 +360,8 @@ def makemove(self, move): self.set(x, y, self.turn) self._counts[self.turn] += 1 self._counts[opponent] -= 1 - self.hash ^= get_sq_hash(x, y, opponent) - self.hash ^= get_sq_hash(x, y, self.turn) + self.hash ^= get_sq_hash(x, y, opponent, self.board_dim) + self.hash ^= get_sq_hash(x, y, self.turn, self.board_dim) else: move.flipped[idx] = False @@ -379,13 +395,13 @@ def undo(self): return # Remove the piece we placed - self.hash ^= get_sq_hash(move.to_x, move.to_y, self.get(move.to_x, move.to_y)) + self.hash ^= get_sq_hash(move.to_x, move.to_y, self.get(move.to_x, move.to_y), self.board_dim) self._counts[us] -= 1 self.set(move.to_x, move.to_y, EMPTY) # Restore the piece we removed if move.is_double(): - self.hash ^= get_sq_hash(move.fr_x, move.fr_y, us) + self.hash ^= get_sq_hash(move.fr_x, move.fr_y, us, self.board_dim) self._counts[us] += 1 self.set(move.fr_x, move.fr_y, us) @@ -396,8 +412,8 @@ def undo(self): self.set(move.to_x + dx, move.to_y + dy, them) self._counts[us] -= 1 self._counts[them] += 1 - self.hash ^= get_sq_hash(move.to_x + dx, move.to_y + dy, us) - self.hash ^= get_sq_hash(move.to_x + dx, move.to_y + dy, them) + self.hash ^= get_sq_hash(move.to_x + dx, move.to_y + dy, us, self.board_dim) + self.hash ^= get_sq_hash(move.to_x + dx, move.to_y + dy, them, self.board_dim) def predict_hash(self, move): """Calculate the hash after a move is played without applying the move""" @@ -415,16 +431,16 @@ def predict_hash(self, move): if move == Move.null(): return hash - hash ^= get_sq_hash(move.to_x, move.to_y, self.turn) + hash ^= get_sq_hash(move.to_x, move.to_y, self.turn, self.board_dim) if move.is_double(): - hash ^= get_sq_hash(move.fr_x, move.fr_y, self.turn) + hash ^= get_sq_hash(move.fr_x, move.fr_y, self.turn, self.board_dim) for dx, dy in SINGLES: x, y = move.to_x + dx, move.to_y + dy if self.get(x, y) == opponent: - hash ^= get_sq_hash(x, y, opponent) - hash ^= get_sq_hash(x, y, self.turn) + hash ^= get_sq_hash(x, y, opponent, self.board_dim) + hash ^= get_sq_hash(x, y, self.turn, self.board_dim) return hash @@ -453,8 +469,8 @@ def legal_moves(self): return [] movelist = [] - for x in range(7): - for y in range(7): + for x in range(self.board_dim): + for y in range(self.board_dim): # Singles if self.get(x, y) == EMPTY: for dx, dy in SINGLES: @@ -471,8 +487,8 @@ def legal_moves(self): if movelist == []: # If the opponent can move, we have to pass opponent = WHITE if self.turn == BLACK else BLACK - for x in range(7): - for y in range(7): + for x in range(self.board_dim): + for y in range(self.board_dim): if self.get(x, y) == opponent: # Singles for dx, dy in SINGLES: @@ -495,8 +511,8 @@ def must_pass(self): bool:Whether the side to move must pass """ - for x in range(7): - for y in range(7): + for x in range(self.board_dim): + for y in range(self.board_dim): if self.get(x, y) == self.turn: # Singles for dx, dy in SINGLES: @@ -526,10 +542,10 @@ def is_legal(self, move): if self.must_pass(): return move == Move.null() - if move.fr_x < 0 or move.fr_y > 6: - return False - if move.to_x < 0 or move.to_y > 6: - return False + # if move.fr_x < 0 or move.fr_y > 6: + # return False + # if move.to_x < 0 or move.to_y > 6: + # return False if move.is_single(): # To square must be empty @@ -598,8 +614,8 @@ def gameover(self): return True # No moves left - for x in range(7): - for y in range(7): + for x in range(self.board_dim): + for y in range(self.board_dim): if self.get(x, y) in [BLACK, WHITE]: # Singles for dx, dy in SINGLES: @@ -620,8 +636,8 @@ def result(self): has_moves: bool = False # No moves left - for x in range(7): - for y in range(7): + for x in range(self.board_dim): + for y in range(self.board_dim): piece = self.get(x, y) if piece == BLACK: diff --git a/ataxx/zobrist.py b/ataxx/zobrist.py index 9573aab..442292c 100644 --- a/ataxx/zobrist.py +++ b/ataxx/zobrist.py @@ -1,65 +1,44 @@ import ataxx +import random + turn = 0x2e98304a94e1000d +# Maximum size of the board, assuming `board_dim` could go up to 10 for example +MAX_BOARD_DIM = 99 + +# Generate random hashes for each piece position +def generate_piece_hashes(board_dim): + return [random.getrandbits(64) for _ in range(board_dim * board_dim)] + +# Piece array will now dynamically generate hashes based on the board size piece = [ - [ - 0xddd67db865dc92f9, 0x31ad7f3d49884764, 0xfc810e82600d77ed, - 0xa329bc2fe9a585c2, 0x0dd7013c7b5f9ee0, 0xcbfb18e330c5152b, - 0x5ca13c8237e969f0, 0xcfe82be3298f4860, 0xd74ee79ab8cd59d4, - 0x76c9804b3dd3dd9a, 0x62fe43ac3416344b, 0x84f44cfc5640f1fb, - 0xee80f44f274d194f, 0x9e132307eaa1062a, 0x8ef3b83e05104b8c, - 0x7458cb4e38a56b1b, 0x496ae857824032b5, 0x297892ab4aed9139, - 0x2c63a6ed1e5c503b, 0xb806e46b057da75c, 0x8e3ee3fc5ea21f4d, - 0x7bb71073c6b32631, 0xa34cbc35fea7986d, 0x2cf34121f29c2073, - 0x3ff14ac1cfb3ac44, 0x9a1f60c67467b9eb, 0x4f69347f2d665427, - 0x4d51d73c213217bc, 0x6b3089c84d5d78e3, 0xa2923e3c0bbc0f50, - 0xe4931d122c14c947, 0xa1b03c639fe421a7, 0x2789a4dc1574310c, - 0x4e3f96a633e0ede0, 0xe4f29bf5ff2869df, 0x72e7fe1c77af10b3, - 0x245e4d0677acbd90, 0x5eb16a0a6cff1f55, 0x26e93216b330c067, - 0x0eced566d7522ad3, 0xdc64bc04aa39e421, 0x401987ae3cb114db, - 0xd0d0b88ece5f0c16, 0x13275209f4ddff5a, 0xffb20d8b83a2ce28, - 0x8f2100bf98b54217, 0x3769a2d70b7209e7, 0x61b3cca215f1a975, - 0x8d1158e421453a0f, - ], - [ - 0xd33fd8453c18380c, 0xd7949af7fa5e24ca, 0xa75e294be7913781, - 0x3b615f1e3322be8c, 0xe4c885ecb48d6187, 0x9b92f432a8d105f3, - 0xbff867477fb93a23, 0x5ff3e0b5e6e72ab4, 0xe60144f2900bc655, - 0xf29a0de00bdc3aee, 0xde9e042c06230fd8, 0x4264ec62f70ba708, - 0x5f175b53ebb138c7, 0x439e71025499179e, 0xcf39b10d6a8437fd, - 0x0fcf1fc30f9e0de4, 0x6a502645f6a31fef, 0xd47dd00cf21ddfdd, - 0x7e4ca0ec0c2e7f68, 0x37401f0941e5a469, 0x3efddb557fd01cb4, - 0x846bfb50b6965c90, 0xc0fa4a424c735605, 0xe5fbff674c562239, - 0x3826959008ef40f0, 0x4a712b5964a4b7a1, 0x68f594a0740c456f, - 0x1ee9e39d56bd32e6, 0x08ebd76592898d31, 0x3df7c91613fb946d, - 0xef3e760a57d5a399, 0x6ef3128154e16b88, 0x15e27065e3d4c108, - 0xd5fe096a16b5eed2, 0xb2964fa8f1bddad1, 0x3ebb6f3af8a61dc5, - 0x9776dc9ebc5243d2, 0x4660bff8b53c4a78, 0x73507ecb48163932, - 0x05967644e76ae2f8, 0x9c53ce9f0fdcb066, 0xcf6e214c6acea558, - 0x53ee7540169475d8, 0xc8a49004b87946b8, 0x1d75d48ff9dc94b2, - 0xec4f8821305bbd96, 0x43ed7506a2bd6b16, 0xf0eb5d0784e3eb03, - 0xe578cac94b342a86, - ], - ] + generate_piece_hashes(MAX_BOARD_DIM), # For BLACK + generate_piece_hashes(MAX_BOARD_DIM), # For WHITE +] + +# You can adjust the above `MAX_BOARD_DIM` based on your needs or pass `board_dim` dynamically. + -def calculate_hash(b): +def calculate_hash(b, board_dim): key = 0 if b.turn == ataxx.BLACK: key ^= turn - for y in range(7): - for x in range(7): + for y in range(board_dim): + for x in range(board_dim): if b.get(x, y) == ataxx.BLACK: - key ^= piece[0][y * 7 + x] + # For BLACK pieces, use piece[0] + key ^= piece[0][y * board_dim + x] elif b.get(x, y) == ataxx.WHITE: - key ^= piece[1][y * 7 + x] + # For WHITE pieces, use piece[1] + key ^= piece[1][y * board_dim + x] return key def get_turn_hash(side): return turn -def get_sq_hash(x, y, side): - return piece[side][y * 7 + x] +def get_sq_hash(x, y, side, board_dim): + return piece[side][y * board_dim + x] diff --git a/endgame.txt b/endgame.txt new file mode 100644 index 0000000..b1d49dc --- /dev/null +++ b/endgame.txt @@ -0,0 +1,794 @@ +oooooo1/ooxxxo1/xxxxx2/oxoxx2/1xoxx2/7/6x o 2 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +x3oo1/4xx1/3-x2/2-1-x1/1xx-x2/xxxxxx1/xxoxxx1 o 2 18 +1-3-1/1-xx1-x/ox-x-xx/xoo-oxx/x1-o-xx/1-3-x/1-3-x o 2 18 +xx5/x4xx/3-1xx/2-o-oo/1oo-xx1/1oooxxx/1oooxxx o 3 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +x1oxxx1/x1oxxxx/3-xxx/2-1-oo/3-ooo/o3xoo/o3xxx o 0 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +oxxxo2/oxxxxxo/ooo-ooo/xx-1-o1/3-3/6x/o5x o 0 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +6o/7/xxo4/xxox3/xxoxo2/oxxxo2/oxx2o1 o 0 18 +6o/7/1oo4/xoxx3/ooxxxx1/xoxxxxo/ooooxxx o 0 18 +xxxxo2/oxxxo2/oxx-3/oo-1-2/1ox-3/1xxo3/ooo4 o 0 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +ooo3o/ooo-oo1/2-1-oo/1-3-o/1x-1-xx/1xx-xxx/1oo1x1x o 1 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +x3xoo/3-ooo/2-1-oo/1-2x-o/1x-1-xo/1xx-xoo/1oo1xxx o 1 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +2xxxoo/3xxxx/3-xoo/2-1-xx/3-xxx/3xxxx/2oxxxo o 0 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +3oxx1/3oxxx/3-oox/2-1-oo/3-ooo/3xooo/o3oo1 o 0 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +ooooo2/oox-x2/ox-o-2/x-xx1-1/xx-o-2/xxx-2x/ooo3x o 0 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +xx2oox/3-xxx/2-1-ox/1-1oo-x/2-o-xx/3-xxx/o3xxx o 0 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +oo4o/xxo3o/xxo-3/xx-1-2/xxo-x2/xxooxx1/xxoox2 o 0 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +ox3oo/oxx-3/xx-1-2/x-2o-o/2-1-ox/xx1-xxx/xxxxxxx o 0 18 +x4x1/3-xxx/2-o-o1/1-xoo-x/1x-o-2/xxx-3/xxxx2x o 1 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +6o/1xxo3/xx1o3/xxxxx2/xxxxx2/xxxx3/xxxo2x o 1 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +x6/xx1-3/xo-x-x1/x-oxx-1/xo-x-2/xx1-x2/oxxx3 o 4 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +oxxxooo/xxxxooo/ox1-ooo/xx-o-oo/1x1-3/7/o5x o 1 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +xx2xx1/ooo-xx1/oo-1-2/o-3-1/oo-1-2/xxx-3/xxx2xx o 3 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +oooxxxo/oxxxxxo/xxo-o2/ox-x-2/oxx-3/7/6x o 0 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +x3xxx/x2-xoo/2-1-oo/1-2o-o/2-1-ox/2x-xxx/2xxxxx o 0 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +x3o2/5o1/3-1oo/2-1-xx/x2-xxx/xx1oxxx/x2ooxx o 0 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +xxo3o/xxoo3/xx1o3/xxx4/xxx4/oooo3/oooo3 o 1 18 +xx5/7/3-2x/xx-x-xx/oox-xox/ooooooo/oooxxxo o 1 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +xx4o/xx1-3/1x-1-2/o-3-1/oo-x-xo/oox-xxx/xoooxxx o 0 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +6o/1xx4/oxx4/oxo4/xxxxo2/xxxxo2/x1xx2x o 1 18 +x5o/7/7/1xxxoxx/oooxoxx/ooox1oo/oooxxxx o 1 18 +3oooo/2oooox/2xxoox/2xxxxx/5oo/4oxx/o5x o 1 18 +6o/xxx4/1xx-3/oo-1-2/ooo-xx1/ooooxxx/xoooxxx o 1 18 +1-o2-1/x-oxx-1/xx-x-xo/xxx-1oo/xx-x-xx/o-3-x/o-3-o o 1 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +xxoxxxo/xxxxxxo/xx-x-xo/xxx3o/xx-1-2/6x/6x o 0 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +x1oo2o/xxxx3/ox-x-2/oox4/1o-x-2/2ox1xx/2xxxxx o 1 18 +x2oo1x/3-xoo/2-x-oo/1-2x-o/2-x-xx/2x-xxx/2ooooo o 1 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +oox2oo/oox-3/oo-1-2/x-x2-1/ox-x-x1/xxx-xx1/oxx1ooo o 1 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +xxxxxxx/oxxxooo/xxx-ooo/1x-1-oo/3-1xx/1o5/o5x o 0 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +xxxxxoo/oox-xox/1o-1-ox/1-3-1/xx-1-2/xxx-1x1/oox3x o 0 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +3ooxx/2xooxx/2xoox1/3oxxo/4oxo/4oxx/o4ox o 0 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +ooox2o/ooox3/oox-3/2-1-x1/3-oxo/3xx1x/o2xxox o 1 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +2x1xxx/2ooxxx/2-o-oo/5oo/2-1-oo/4ooo/o4xx o 1 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +1-3-o/o-xx1-1/ox-x-2/ooo-o2/oo-o-x1/o-xxo-x/o-xxo-x o 0 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +oxooooo/xxooooo/1o-x-1x/3xx2/2-1-2/7/o5x o 1 18 +7/7/2oxxx1/oxxxxx1/xxx1xxx/xx1oooo/x4oo o 0 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +1-xx1-1/1-xxo-x/xx-1-xx/xxx-xxo/xx-x-xo/x-2x-o/o-3-o o 1 18 +1-3-o/1-oo1-o/xx-x-xo/xxx-xxo/xx-x-xo/1-o2-o/1-3-x o 0 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +oox3o/ooo-3/oo-o-2/x-xxx-1/xx-x-2/xxx-3/xoxx2x o 0 18 +7/xxx1oxx/xxx-oxx/oo-1-xx/oo1-oxx/4xx1/5xx o 1 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +6o/7/o6/x3xoo/xx2xoo/1xxooox/oooooxx o 0 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +ooox2o/xoooxxo/xoo-1xx/xo-1-xx/oo1-1xx/oo5/o6 o 0 18 +1-3-1/x-1xx-1/xx-x-xo/ooo-xoo/oo-x-ox/x-o1x-x/1-3-x o 2 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +xx3xx/5xx/2-1-oo/3x1oo/2-x-oo/2xxooo/o2xo1o o 3 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +6o/oo5/oo-1-2/xxxx3/xx-x-x1/o1oooxx/xxxxxxx o 1 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +x2xxxo/2oooxo/2ooo1x/2ooxx1/2xxxx1/7/o5x o 1 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +ooooxoo/ooo-ooo/xo-o-xx/1-2o-x/2-1-x1/3-1xx/o4x1 o 1 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +xo5/1xxoo2/1xxooo1/1xxooo1/2oooo1/1xxoxo1/xx5 o 0 18 +o-3-o/x-x2-1/xx-x-1o/xxo-o2/oo-o-2/x-ooo-1/1-xoo-1 o 0 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +1x4o/xxx4/xo-1-2/xoo4/xo-o-2/xxxxx2/xxx1xx1 o 1 18 +xoo3o/xx1-2o/xo-1-2/x-xx1-1/1o-x-2/xxx-o2/oxxxo2 o 2 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +2oxxxx/3xxxo/3-xxx/oo-1-xx/xxo-3/xxx4/6x o 0 18 +1-3-o/1-3-1/oo-1-oo/ooo-1xx/oo-x-xx/o-xxx-1/1-oox-1 o 0 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +1xxxxx1/xxxxxx1/1xx-x2/oo-1-2/oo1-3/xx5/x5x o 0 18 +xxxxxxx/xxx-1xo/xo-x-xo/1-xxx-o/2-1-2/3-3/o4xx o 0 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +x6/xx5/xx1-2o/xx-1-xo/xxx-xxo/xooxxx1/2oox2 o 0 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +x-x2-1/1-xxo-o/oo-o-oo/ooo-ooo/oo-x-oo/1-3-1/1-3-1 o 1 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +1o1xooo/xxoxoxx/xx-o-xx/4oxx/2-1-ox/7/o5o o 1 18 +x-xx1-o/o-xx1-1/oo-o-2/ooo-xx1/oo-o-xx/o-xoo-1/1-xoo-1 o 0 18 +x5o/7/2ooo2/xxooox1/oooxxx1/ooxxx2/7 o 0 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +xx4o/6o/2-1-2/xxxx3/xx-x-x1/ooooo1x/xooooo1 o 1 18 +3ooox/1xxoxx1/2-x-o1/3oxoo/2-o-xx/2xoxxx/3oxxx o 0 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +xoxx2o/oooxx2/ooooo2/xxxxx2/xxxoo2/3o3/6x o 0 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +x-3-o/x-3-x/xx-1-xx/oxx-ooo/ox-x-xx/1-ooo-x/1-oo1-1 o 1 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +1xxoxox/xxxoxoo/oo1-xoo/2-x-oo/3-ooo/5o1/o6 o 1 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +ooox2o/oxxx3/xxx-3/oo-1-2/ooo-3/xxox3/xx1x2x o 1 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +x3xxo/5oo/2-1-xx/3oxxx/2-o-xx/o2oooo/o2xxox o 3 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +1x5/oxx-1xx/ox-1-xx/1-2o-o/o1-x-oo/3-ooo/o2oooo o 1 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +x-3-o/o-3-o/ox-1-o1/ox1-xoo/xx-1-x1/x-oxx-o/1-oxx-o o 4 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +1-ooo-1/o-ooo-x/xx-o-oo/1xx-ooo/1x-1-oo/x-3-o/o-3-x o 0 18 +1-3-o/o-3-1/oo-o-2/xoo-xxx/xo-x-xx/x-xxx-x/o-xxx-1 o 0 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +1-xxx-o/x-xxx-1/xx-x-x1/xxx-xxx/oo-x-2/o-o2-1/1-3-x o 0 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +x3xxx/1x2xxx/2-o-xx/4oxx/2-1-xx/4oxx/o3oxx o 0 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +1-ooo-1/1-oox-x/xx-o-xx/xxx-x1x/xo-1-xx/o-3-x/o-3-o o 1 18 +x-3-o/x-x2-1/oo-o-2/oox-o2/xx-x-2/x-xxx-1/x-xxx-1 o 0 18 +3xxxx/2xxoxx/3-xxx/2-1-oo/3-ooo/3ooxx/o2ooxx o 1 18 +x5o/7/2xx3/1xxxo2/oxxxxo1/xxxxxx1/1xooo2 o 0 18 +1oxxoxx/xooooxx/xxx-oxx/1x-1-oo/3-3/1o5/oo4x o 0 18 +ooo3x/ooo-1xx/xx-x-xx/1-o1o-x/oo-1-xx/1o1-1xx/6x o 0 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +6o/xx1-3/xx-o-2/o-1oo-1/xx-o-x1/oox-o2/xxxx3 o 2 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +2xxoxx/1xxxoxx/1xxxxxx/2oxxox/3oxo1/7/o6 o 0 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +x6/4ooo/3xxoo/3xxxx/3xxxx/2ooxxx/o2oxxx o 0 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +xx3xx/5xx/4xxx/3o1xx/2xxxoo/1xxxoxx/4xoo o 1 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +1-xxx-1/o-oxx-o/oo-x-xo/ooo-xxx/oo-x-xx/x-3-x/o-3-x o 0 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +xx2xxx/x4xo/2-1-xx/5xx/2-o-x1/2o1xx1/3oxxx o 2 18 +x2ooxo/3oxxx/3ooox/3xxxx/3oxxx/3oxx1/o6 o 0 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +oo4o/oxx4/xxx-3/xx-1-2/xxx-3/xoooxx1/xoooxxx o 0 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +2oxxxo/2oxxxo/2-o-xx/4ooo/2-x-xx/o3xxx/o6 o 4 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +xxxx2o/o1xx3/xxo-3/xx-1-1o/3-1oo/2ooooo/o1oo1oo o 1 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +ooxxxxx/oox-xxx/ox-x-x1/1-2x-x/2-1-x1/2x-3/2xx2x o 1 18 +x-3-1/o-3-x/oo-x-xx/oox-xxx/xo-o-xx/x-xxx-o/1-1x1-x o 0 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +1-3-1/1-3-1/xo-x-oo/xox-ooo/xo-x-1o/x-oxx-o/1-1xx-1 o 1 18 +6o/6o/o6/xx1oo2/oxxxox1/oxxx1oo/oxxxooo o 0 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +xx2xxo/3-xxo/2-1-xx/1-1oo-x/2-o-ox/1xo-oox/xxx1oox o 0 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +xxxxxoo/xxxx1xx/xx-x-xx/oxoo3/ox-1-2/o6/5xx o 0 18 +o-1oo-1/o-xxx-1/oo-x-oo/oox-xoo/ox-x-xx/x-x2-o/o-3-x o 0 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +1o2xx1/o2-xxx/2-1-ox/1-x2-o/2-o-oo/o2-oox/o3oox o 0 18 +5xx/5ox/1xxxx2/ooxxx2/oooxo2/oooxx2/xxxxo2 o 0 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +xx5/xx5/5xx/xxx2xx/xxxx3/xxxx1xx/ooxx2x o 0 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +1-xx1-1/1-xxo-1/1x-x-oo/xx1-xoo/ox-x-xx/o-x2-x/o-3-x o 2 18 +1-1x1-1/o-xxx-1/oo-o-1x/xxx-oox/xx-x-xx/x-xxx-x/1-xx1-1 o 1 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +x-3-o/o-3-1/oo-x-oo/1o1-ooo/2-x-oo/1-xxo-x/o-1xo-x o 3 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +1-1oo-1/o-xox-x/oo-x-xx/oox-xxx/oo-x-oo/x-3-x/o-3-x o 0 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +xx4o/oox-2o/oo-x-x1/1-1xx-1/2-x-xx/3-oxx/o2oooo o 0 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +x5x/4xxo/3xxxx/2ooxxx/3oxxx/4oxx/o6 o 0 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +xoo1xx1/xxo-oxx/xx-x-2/x-3-1/oo-1-2/oxx-o2/oxxxo2 o 0 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +oooxx1o/ooo1xxo/oo-1-xo/6o/2-1-xx/1o5/o1o4 o 0 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +2xoxxx/1xoxxxo/2oxxxo/3xxxo/4xoo/7/o4xx o 0 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +1xxoooo/1xxooxx/2-o-ox/4oox/2-1-xx/2o2x1/oo4x o 0 18 +x-3-x/x-3-x/xo-1-xx/xxx-1oo/xx-x-o1/x-xxo-1/1-xxo-o o 3 18 +xxx3o/xxx-3/xx-x-2/o-ooo-1/2-o-xx/3-oxx/o2ooox o 0 18 +x5o/3-3/2-1-2/x-1oo-1/xx-x-xx/xxo-xxx/xooxxxx o 0 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +oooo2o/xxxo3/xx-o-2/xxxo3/xx-1-2/xxx4/ooo3x o 0 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +x5x/x3oox/2-1-xx/5xx/2-o-xx/2xxxxx/2xxxoo o 0 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +x2xooo/3xxxo/3xxxo/3xxxo/3xxxo/7/o6 o 0 18 +2xxoo1/2xooox/2xooo1/2oo1x1/3oxx1/o3xx1/o4x1 o 1 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +1-xxx-1/o-oxx-o/oo-x-xo/ooo-xxx/oo-x-xx/o-3-x/o-3-x o 0 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +xooooxx/oooooxx/ox-o-ox/oxx4/ox-1-2/oo5/6x o 0 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +ooooxxo/ooooxxo/xoo-xxx/xx-1-xx/3-3/xx5/x5x o 1 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +1-3-1/o-3-1/xx-1-2/xxx-oox/xx-x-o1/x-xxx-x/1-1x1-1 o 0 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +x-xoo-o/x-xoo-1/xx-x-x1/xoo-xx1/xo-1-2/x-o2-1/1-3-x o 1 18 +xxooooo/xxo-oxx/xx-o-o1/x-xxx-1/2-x-2/3-3/o4oo o 0 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +x5o/7/2xx3/1xxxox1/xoooxx1/oooooo1/xoooo2 o 0 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +oooo2o/ooxx3/oox-3/oo-x-2/oox-3/xxo3x/1x3xx o 1 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +1-3-1/x-xx1-1/oo-x-1x/ooo-xxx/oo-1-xx/o-3-x/1-3-x o 1 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +1-3-x/o-xxx-x/oo-x-oo/xoo-xoo/oo-o-xx/1-2x-x/1-3-x o 0 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +oxxxxx1/oxxxoo1/oxx-o2/xx-1-2/ooo-3/ooox3/ooo3x o 0 18 +6o/1oo4/oxxx3/xxxxx2/oxxxo2/oooxx2/xxxo3 o 0 18 +1-xxo-1/1-xxo-1/xx-1-xo/xxo-xxo/ox-1-xo/o-3-x/1-3-x o 1 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +xoxoxxx/xoo-xx1/xx-1-xo/1-2x-o/2-1-xo/3-xxx/oo3xx o 1 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +xx4o/xxx3o/xxx-3/oo-1-2/1oo-oxx/2oxxxx/3xx2 o 0 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +x6/7/2-1-oo/2ooooo/xx-o-xx/xxoxxxx/1xxoxxx o 1 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +7/7/xxooo2/xxxxxo1/xxxoooo/ooxoooo/xx4x o 0 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +xx5/6o/3-1xx/2-1-xx/1xx-xxx/xxxoxxo/2xxooo o 2 18 +oooxxxx/oox-xxo/o1-o-oo/1-xxx-o/2-x-2/3-3/o5x o 1 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +x4x1/2xxxxx/2xxoox/2ooooo/3xoo1/7/oo5 o 1 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +ooxxx2/oooxx2/oo-x-2/ooxx3/xx-x-2/x1x4/xo4x o 1 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +xo4o/xoo3o/xxo-3/xx-x-2/xxx-3/oxxxo2/oooo2x o 0 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +o-1oo-1/o-xxx-o/1x-x-oo/xx1-xxx/xx-1-oo/1-3-x/o-3-x o 2 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +xoxooxx/xxxooxx/ooo-ox1/xx-o-2/xxo-3/1xx4/6x o 0 18 +x3ooo/3oxxx/2-o-xx/4xxx/2-1-ox/3xxxx/4xxx o 0 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +ooxoo2/oxxoo2/oxx-3/1x-1-2/xxx-3/1xx4/6x o 2 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +o5o/oo3o1/xxx-3/xx-1-2/xxx-3/oo1xx2/oooxxxx o 2 18 +oo3oo/oox-3/oo-x-2/o-x2-1/ox-1-2/ooo-1xx/ooox2x o 0 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +xox3o/xoxx2o/xo-x-2/xxo1x2/xo-x-2/xoxx3/ooo3x o 1 18 +xoo3o/xxo4/xxx-3/xx-1-2/xxx-3/xxxo3/xooo2x o 0 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +oxoxxxx/oxoooxx/ox-o-xx/7/2-1-2/1ooo3/ooooo2 o 0 18 +x6/3xxo1/3xxox/3oo1x/3xxxx/o2xxxo/o2ooxo o 1 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +1xxxxoo/1xxxxxx/2-o-xx/4ooo/2-1-oo/5oo/o4xx o 0 18 +1-3-1/1-3-x/x1-x-xx/xxx-oxx/xx-o-oo/1-3-o/1-3-1 o 0 18 +x3xxx/4xxx/2-o-xo/4oxo/2-x-xx/2ooxxx/2ooxxx o 0 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +oxx2xo/xxx-xxx/xo-o-ox/1-xoo-x/2-o-oo/3-3/o6 o 1 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +1xxxoxx/oxxxoxx/1oxo1xx/1xooxxx/3o3/7/o4xx o 2 18 +oooo3/xxoox2/xxooox1/xxoxx2/1xoxx2/7/o6 o 1 18 +x3xoo/3-1oo/2-1-oo/1-x2-o/ox-1-1o/ooo-ooo/xooooo1 o 1 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +xoooo1x/ooo-oxx/oo-o-xx/o-xx1-1/xx-1-2/3-3/6x o 1 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +1-3-1/o-3-x/2-1-x1/oxx-xxo/ox-x-xo/o-1oo-o/1-1oo-x o 0 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +oxxxxx1/oxx-xxx/xx-x-xx/1-oo1-1/2-1-2/o2-1x1/oo3xx o 0 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +x6/7/2-1-1x/xx3xx/xx-o-xx/xxooooo/xxoooxx o 0 18 +xoooxxo/xxx-xxx/1x-x-xx/1-1oo-1/2-1-2/3-3/o5x o 0 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +x3oxo/1x1oooo/2-1-xx/5xx/2-1-xx/3oxxx/o2oxxx o 0 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +xoxxx2/ooxxx2/oo-x-2/oo1oo2/ox-x-2/oxx4/5xx o 1 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +x3xxo/4xxo/3-xx1/2-o-xx/1xx-xxx/xxxoooo/1xxxxoo o 1 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +xxxoooo/xx1ooo1/xx-x-2/ooxx3/oo-1-2/o5x/6x o 1 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +1-3-o/x-3-1/xx-x-oo/oo1-ooo/xo-x-oo/x-ooo-o/1-xxx-1 o 1 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +xxoooox/1xoooox/2-x-oo/4ooo/2-1-xo/4xxo/o6 o 1 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +x2xxoo/3oooo/3-xoo/2-1-oo/3-1oo/3oxxx/o2oxxx o 3 18 +1-ooo-1/o-ooo-1/oo-x-x1/ooo-xx1/oo-o-2/o-o2-x/o-3-x o 0 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +o-3-1/x-3-1/xx-o-oo/xxx-ooo/ox-o-oo/x-oxx-o/o-xxx-x o 0 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +xxxxo2/xxx-3/xx-1-2/x-o2-1/xo-1-2/oox-1xx/oox3x o 0 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +x3xxx/3-xxo/2-1-xo/1-2o-o/1o-o-ox/oox-xoo/1xxxxox o 0 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +ooxooo1/xx1xoo1/xxo-xxx/2-1-xx/3-1xx/1o5/o6 o 1 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +1xx4/xxx4/3-2x/1o-1-xx/ooo-xxx/xxxxooo/ooxxxxo o 2 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +x2xooo/3xxxo/3xxxo/3xxxx/2ooxox/3oxox/o3xoo o 0 18 +oooooox/ooo-oo1/ox-o-xx/o-3-x/2-1-2/3-2x/2xxxxx o 1 18 +xxoooox/xxoooox/1x-o-ox/xxo4/xx-1-2/7/o5x o 1 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +xxxo2o/xxxo3/xxo-3/oo-o-2/oox-3/xxxx1x1/xxx3x o 0 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +1ooxxxx/oooxooo/1ox-xoo/2-x-oo/3-xxo/o6/o6 o 0 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +xxxxooo/xxx-xxx/xx-x-xx/1-2x-x/2-1-oo/3-3/o5x o 0 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +ooooxxx/1oo-xxx/2-1-x1/1-3-x/2-1-ox/3-xx1/o3xx1 o 1 18 +x1xx3/3xx2/3-3/xx-1-x1/xxx-xxx/xxxxxoo/ooxxxoo o 0 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +7/7/x3xo1/xooxxoo/xoooooo/1ooxxxx/x3xxx o 0 18 +xx4o/3-xxo/2-1-xo/x-3-x/xx-x-oo/ooo-ooo/xxoxxoo o 1 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +x3xxo/3-xxo/2-x-xo/1-xxo-o/2-x-oo/2x-ooo/2xoooo o 0 18 +x3o2/3oxx1/2xxx2/1xooo2/oxxx3/oxxx3/oxx3x o 0 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +x5o/x3xxo/4xx1/4oox/3xxxx/2ooooo/2ooooo o 1 18 +xoxxx2/xxxx3/x1o4/xxx4/xxo4/xxx4/xo4x o 1 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +x-xxo-1/x-xxo-1/xo-o-2/ooo-x2/oo-o-2/o-oox-1/1-ooo-x o 0 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +xooxx2/xooxx2/ox-x-2/xxo4/oo-1-2/ooox3/ooox2x o 0 18 +ooxxoo1/xx1xoo1/xo-x-2/xoxx3/xo-1-2/x6/x5x o 1 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +x2oxxx/4ooo/3xoxx/2xxox1/3xox1/o3xx1/o5o o 1 18 +1xx2oo/oxx3o/oo-1-2/oxx4/xx-1-2/xoooxo1/xooooo1 o 1 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +x3oxx/4oxx/2-x-xx/3xxoo/2-o-ox/2xooox/2xooox o 0 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +oxxxooo/ooxxooo/xxx-ooo/1x-1-xx/3-3/7/oo3xx o 0 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +x4x1/xx3xx/x1-1-1o/4xxx/2-o-xx/2ooooo/3oooo o 2 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +7/3oxx1/2xxxx1/2x1oo1/2xxxx1/2xxo2/o6 o 1 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +xoo1oox/xoo-1ox/2-1-xx/1-3-o/2-1-x1/2x-oxx/ooxx1xx o 1 18 +x-1o1-o/o-xox-x/oo-o-ox/oox-xxx/xx-o-xx/x-3-x/x-3-1 o 0 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +x6/7/2-1-oo/3xxoo/xx-x-xx/x1xoooo/xooxooo o 1 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +ooxoooo/oxxxooo/1xx-ooo/2-x-ox/3-3/o6/o5x o 0 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +x5o/7/2-1-2/2xxoxx/ox-x-xx/oooooo1/ooooooo o 2 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +xoooooo/ooooo2/ox-x-2/oxx4/1x-1-2/xoo4/xoo3x o 0 18 +xxoooox/xxxxoxx/ooo-oxx/oo-1-2/3-3/7/o5x o 0 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +6o/xx5/xx-1-2/xx2o2/xx-x-ox/oxxxxoo/oxxxooo o 2 18 +xxxxxxo/oxx-oxo/oo-1-oo/x-3-1/xx-1-2/1x1-3/xxx3x o 0 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +5oo/7/1o1-3/oo-x-xx/oox-xox/x1ooooo/xxoooox o 1 18 +x-xoo-1/1-xox-x/2-o-xx/3-xxx/2-o-oo/1-xxo-o/o-xx1-o o 1 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +x-2x-1/1-xxx-x/2-x-oo/3-ooo/2-x-oo/1-oox-x/1-ooo-x o 0 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +xxo2oo/xxo-3/xx-1-2/x-o2-1/xx-1-2/xxx-1xx/xoo2xx o 2 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +7/7/oxx-1xx/xo-1-xx/ooo-xox/xooooox/xoxooox o 0 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +6o/xxox1xo/xxoxx2/ooox3/ooox3/oxxx2x/oxoo2x o 0 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +1-3-x/1-3-o/2-1-xo/oxo-1xo/ox-o-1x/o-xxo-x/o-xxx-1 o 0 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +1-2x-1/1-xxx-o/xo-x-oo/xxx-ooo/oo-1-xx/1-3-o/1-3-x o 0 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +1-3-o/x-o2-x/xx-o-xx/xxx-xxx/xx-o-xx/x-o2-x/x-3-1 o 0 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +xxx3o/oxxxx2/ox1xx2/oooxx2/oooxx2/3xx2/5x1 o 1 18 +xooo2o/xooo3/oox-3/oo-x-2/xx1-3/xxoxx2/xxox2x o 1 18 +1-xxx-o/x-xxx-x/xo-x-xo/xoo-xxo/xx-x-2/x-x2-1/1-3-x o 0 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +1-3-1/1-2o-1/xx-o-ox/xxx-xxx/oo-x-xx/o-xxx-x/o-oo1-x o 0 18 +xxoo3/xxxo3/xxx-3/xx-1-2/oxo-3/xxxo3/xxx3x o 0 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +1-1x1-x/1-xxx-x/xx-x-oo/xxx-xxo/xx-1-xx/o-3-x/1-3-x o 1 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +3xoxx/xxoooxx/xxo-oxx/xo-1-2/xx1-3/x5x/6x o 1 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +xxxx2o/xxxx3/xxxo3/xxoo3/ooxx3/oxxx3/1xx3x o 0 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +x-3-o/x-3-o/xo-1-xo/oox-xxx/oo-o-xx/1-xxo-1/1-xxo-1 o 0 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +xxooxxo/xxxxxxx/oxx-xxo/ox-1-oo/3-3/6x/o5x o 0 18 +6o/xx1x3/xxox3/ooox3/oooo3/xoo4/xx4x o 1 18 +xxxx2o/ooxoo2/xx1x3/xxxx3/1xo4/1ooo3/o1oox2 o 1 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +1xxoooo/1xooooo/2-x-xx/3xxox/2-1-xx/1o2x1x/o5x o 1 18 +2oooxo/2xxooo/2oxxxx/2oxxxx/4xxx/4oo1/oo5 o 0 18 +o1o3o/ooo3o/oo-1-2/xxx4/x1-1-2/xxxxxo1/xxxxxo1 o 3 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +xxo3o/xxxo3/xx-o-2/xx5/oo-x-2/xxoxx1x/xxox2x o 1 18 +1ooooxx/xooxxxx/oxx-xxx/oo-1-xo/3-1oo/7/o6 o 0 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +xxxx3/1ooxx2/1ooo3/1oxoo2/1x1xx2/xxxxx2/xxxx2x o 2 18 +6o/7/1oxx3/ooxxo2/oooxoo1/ooxxx2/ooxxx2 o 0 18 +oooxxxo/ooooxxx/oo-o-x1/xxoo3/1x-1-2/7/6x o 0 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +xxx2ox/xxxxxx1/x1ooo2/xxoo3/1xo4/7/5xx o 1 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +1-x2-o/1-1x1-o/x1-1-2/xx1-3/xx-1-oo/1-oxx-o/1-xxx-x o 4 18 +xxoxxxx/ooo-xxx/oo-o-x1/x-o2-1/xo-1-2/xxx-2x/oxxx2x o 0 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +x3ooo/x3ooo/3xooo/3xooo/2xxoxx/2xxxox/3oxoo o 0 18 +1oooo1o/oxxxo2/oxx-3/xx-1-2/oox-3/xxxx2x/ooo3x o 0 18 +oox3o/ooxx2o/ooxx3/ooxx3/xxxx3/xxxoo1x/oxx3x o 0 18 +1xx3x/1x3xx/xx-x-2/xxxx3/oo-o-2/ooooo1x/ooooo2 o 1 18 +xx2oox/xx1-oo1/xx-1-2/x-3-1/xx-1-2/ooo-x2/xxoxxxx o 0 18 diff --git a/endgame_agent.pth b/endgame_agent.pth new file mode 100644 index 0000000..330e605 Binary files /dev/null and b/endgame_agent.pth differ diff --git a/hsm.py b/hsm.py new file mode 100644 index 0000000..7f164da --- /dev/null +++ b/hsm.py @@ -0,0 +1,345 @@ +import torch +import torch.nn as nn +import torch.nn.functional as F +import torch.optim as optim +from torch.distributions import Categorical + +import gym +from gym import spaces +import numpy as np +import ataxx +from tqdm import tqdm + +import ataxx.players +import matplotlib.pyplot as plt + + +def default_eval(board: ataxx.Board): + black, white, _, _ = board.count() + if board.turn == ataxx.BLACK: + return black - white + else: + return white - black + +class AtaxxEnv(gym.Env): + def __init__(self, board_dim=7, opponent='alphabeta', depth=2, eval_fn=default_eval, board=None): + super(AtaxxEnv, self).__init__() + + # Initialize the Ataxx Board + self.board_dim = board_dim + self.board = ataxx.Board(board_dim=board_dim) if not board else board + self.opponent = opponent + self.depth = depth + self.eval_fn = eval_fn if eval_fn is not default_eval else default_eval # default evaluation function + + # Define the action space and observation space + # Actions: flatten the 7x7 grid to a 49-dimensional space (or any number depending on board_dim) + self.action_space = spaces.Discrete(board_dim * board_dim) # Example: 49 actions (7x7 grid) + self.observation_space = spaces.Box(low=0, high=1, shape=(board_dim, board_dim), dtype=np.float32) + + def sample_action(self, action_index): + from_row = action_index // (self.board_dim * self.board_dim) + from_col = (action_index % (self.board_dim * self.board_dim)) // self.board_dim + to_row = (action_index % self.board_dim) // self.board_dim + to_col = action_index % self.board_dim + move = ataxx.Move(from_col, from_row, to_col, to_row) + return move + + def reset(self): + """Resets the environment to the initial state.""" + self.board = ataxx.Board(board_dim=self.board_dim) # Reset the Ataxx board + return np.array([row[2:-2] for row in self.board._board[2:-2]]) # Return the board state as a numpy array + + def step(self, action): + """Takes an action and returns the next state, reward, done, and additional info.""" + # Decode the action from the action space (convert index to row, col) + move = self.sample_action(action) + + done = self.board.gameover() + + # Make the move + if self.board.is_legal(move): + self.board.makemove(move) + elif not done: + # Invalid action, immediate negative reward + move = np.random.choice(self.board.legal_moves()) + + # Get the reward + if done: + if self.board.result() == '1-0': # Example: Player X wins + reward = 1 + elif self.board.result() == '0-1': # Example: Player O wins + reward = -1 + else: + reward = 0 # Draw + else: + reward = 0.01 # Reward some exploration + + # Try playing against ourselves??? + # If the game is not over, the opponent takes a turn + opponent_move = self.get_opponent_move() + if not done and opponent_move: + self.board.makemove(opponent_move) + + # Return the new state, reward, done flag, and info + return np.array([row[2:-2] for row in self.board._board[2:-2]]), reward, done, {} + + def get_opponent_move(self): + """Get the move for the opponent (random).""" + return ataxx.players.negamax(self.board, 2) + + def render(self, mode='human'): + """Render the environment (print the board to the console).""" + print(self.board) + + def close(self): + """Clean up the environment (if necessary).""" + pass + +class HierarchicalSoftmaxNet(nn.Module): + def __init__(self, board_size=7, num_categories=2, num_subcategories=4, num_actions=49): + super(HierarchicalSoftmaxNet, self).__init__() + self.fc1 = nn.Linear(board_size * board_size, 256) # Flatten the board + self.fc2 = nn.Linear(256, 128) + self.fc3 = nn.Linear(128, 64) + + # Hierarchical softmax output layers + self.level1 = nn.Linear(64, num_categories) # Categories (e.g., offensive, defensive) + self.level2 = nn.Linear(64, num_subcategories) # Sub-categories + self.level3 = nn.Linear(64, num_actions) # Final actions (e.g., specific move) + + # Value function head (for value loss) + self.value_head = nn.Linear(64, 1) + + def forward(self, state): + x = torch.relu(self.fc1(state)) + x = torch.relu(self.fc2(x)) + x = torch.relu(self.fc3(x)) + + # Output for hierarchical softmax (Level 1: Categories, Level 2: Subcategories, Level 3: Actions) + level1_probs = torch.softmax(self.level1(x), dim=-1) + level2_probs = torch.softmax(self.level2(x), dim=-1) + level3_probs = torch.softmax(self.level3(x), dim=-1) + + # Output for value function + value = self.value_head(x) + + return level1_probs, level2_probs, level3_probs, value + +def ppo_loss(old_log_probs, log_probs, advantages, values, returns, clip_epsilon=0.2, value_loss_coef=0.5, entropy_coef=0.01): + """ + Compute the PPO loss for the current policy. + + Parameters: + - old_log_probs: The log probabilities from the old policy (before update). + - log_probs: The log probabilities from the new policy (after update). + - advantages: The advantage estimates for each state-action pair. + - values: The state value estimates from the current policy network. + - returns: The target returns (i.e., the Monte Carlo returns or TD returns). + - clip_epsilon: The clipping parameter (usually 0.2). + - value_loss_coef: Coefficient for the value loss. + - entropy_coef: Coefficient for the entropy term (encourages exploration). + + Returns: + - Total loss, including the policy loss, value loss, and entropy loss. + """ + + # Compute the ratio (r_t) + ratios = torch.exp(log_probs - old_log_probs) + + # Compute the clipped surrogate loss (Policy loss) + surrogate_loss = torch.min(ratios * advantages, torch.clamp(ratios, 1 - clip_epsilon, 1 + clip_epsilon) * advantages) + + # Value loss (Mean Squared Error between value predictions and targets) + value_loss = (values - returns).pow(2) + + # Entropy loss (encourages exploration by penalizing low entropy in the policy distribution) + entropy_loss = -torch.mean(torch.exp(log_probs) * log_probs) + + # Combine the losses + total_loss = -torch.mean(surrogate_loss) + value_loss_coef * value_loss + entropy_coef * entropy_loss + + return total_loss + +def compute_gae(rewards, values, next_values, done_flags, gamma=0.99, lam=0.95): + """ + Compute the Generalized Advantage Estimation (GAE). + + Arguments: + - rewards: List or array of rewards at each time step + - values: List or array of state values (from the value head) at each time step + - next_values: List or array of next state values + - done_flags: List or array of boolean values indicating whether the episode has ended + - gamma: Discount factor + - lam: GAE lambda factor + + Returns: + - advantages: Computed advantages at each time step + - returns: Computed returns (value + advantage) + """ + advantages = torch.zeros_like(rewards) + returns = torch.zeros_like(rewards) + + last_gae_lambda = 0 + for t in reversed(range(len(rewards))): + if done_flags[t]: + next_value = 0 # No next state if done + else: + next_value = next_values[t] + + # Compute temporal difference error (delta_t) + delta = rewards[t] + gamma * next_value - values[t] + + # Compute GAE advantage + advantages[t] = delta + gamma * lam * last_gae_lambda + returns[t] = advantages[t] + values[t] + + # Update the last GAE lambda + last_gae_lambda = advantages[t] + + return advantages, returns + +def train_step(state, action, reward, next_state, done, model, old_log_probs, optimizer, gamma=0.99, lam=0.95): + """ + Perform one training step (forward pass, backward pass) using PPO with GAE. + """ + + # Convert state and next state to tensors + state_tensor = torch.tensor(state, dtype=torch.float32).flatten().unsqueeze(0) + next_state_tensor = torch.tensor(next_state, dtype=torch.float32).flatten().unsqueeze(0) + + # Get the new log probabilities and values from the model + level1_probs, level2_probs, level3_probs, values = model(state_tensor) + dist1 = Categorical(level1_probs) + dist2 = Categorical(level2_probs) + dist3 = Categorical(level3_probs) + + # Sample actions from the distributions + action1 = dist1.sample() + action2 = dist2.sample() + action3 = dist3.sample() + + # Combine actions from the hierarchy + action = action3.item() # Use the final action from level3 + + # Compute new log probabilities for PPO update + new_log_probs = dist1.log_prob(action1) + dist2.log_prob(action2) + dist3.log_prob(action3) + + # Collect reward, value, and done information for the current timestep + rewards = torch.tensor([reward], dtype=torch.float32) + values = values.detach().squeeze(0) + + # Get the next value (could be zero if done) + next_values = model(torch.tensor(next_state, dtype=torch.float32).flatten().unsqueeze(0))[3].detach().squeeze(0) + + # GAE computation + advantages, returns = compute_gae(rewards, values, next_values, done_flags=[done], gamma=gamma, lam=lam) + + # Compute the advantage (simplified as scalar for now) + advantage = advantages[0] + return_value = returns[0] + + # Calculate PPO loss + loss = ppo_loss(old_log_probs, new_log_probs, advantage, values, return_value) + + # Backpropagate and update the model parameters + optimizer.zero_grad() + loss.backward() + optimizer.step() + + return loss # Return the loss for logging purposes + +def get_agent(bs=7): + # Initialize model and optimizer + model = HierarchicalSoftmaxNet(board_size=bs, num_categories=2, num_subcategories=4, num_actions=bs*bs) + optimizer = optim.Adam(model.parameters(), lr=0.001) + env = AtaxxEnv() + + # Simulated training loop + num_episodes = 10 + losses = [] + for episode in tqdm(range(num_episodes)): + state = env.reset() # Reset the environment + done = False + total_reward = 0 + + while not done: + # Convert state to tensor + state_tensor = torch.tensor(state, dtype=torch.float32).flatten().unsqueeze(0) + + # Get probabilities from the model + level1_probs, level2_probs, level3_probs, value = model(state_tensor) + + # Select actions using the probabilities from each level + dist1 = Categorical(level1_probs) + dist2 = Categorical(level2_probs) + dist3 = Categorical(level3_probs) + + action1 = dist1.sample() + action2 = dist2.sample() + action3 = dist3.sample() + + # Combine the actions into a single action for the environment + action = action3.item() + + # Store the old log probabilities for PPO update + old_log_probs = dist1.log_prob(action1) + dist2.log_prob(action2) + dist3.log_prob(action3) + + # Take action in the environment + next_state, reward, done, _ = env.step(action) + + # Train the model using PPO + loss = train_step(state, action, reward, next_state, done, model, old_log_probs, optimizer) + + total_reward += reward + state = next_state + losses.append(loss.item()) + print(env.board) + # print(f"Episode {episode + 1}, Total Reward: {total_reward}") + plt.figure(figsize=(10, 5)) + plt.plot(losses, label='PPO Loss', color='blue') + plt.xlabel('Training Steps') + plt.ylabel('Loss') + plt.title('Training Loss Over Time') + plt.legend() + plt.show() + + return model + +def generate_move(model, board, board_size=7): + """ + Generate a move using a trained hierarchical softmax model. + + Parameters: + - model: The trained PyTorch model. + - board: The current board. + - board_size: The size of the board (default is 7 for Ataxx). + + Returns: + - move: The move selected by the model. + """ + env = AtaxxEnv(board_dim=board_size, board=board) + + # Ensure the board state is a PyTorch tensor and flattened + board_state = np.array([row[2:-2] for row in board._board[2:-2]]) + state_tensor = torch.tensor(board_state, dtype=torch.float32).flatten().unsqueeze(0) + + # Get action probabilities from the model + level1_probs, level2_probs, level3_probs, loss = model(state_tensor) + + # Sample the action from the categorical distributions (one per level) + dist1 = torch.distributions.Categorical(level1_probs) + dist2 = torch.distributions.Categorical(level2_probs) + dist3 = torch.distributions.Categorical(level3_probs) + + action1 = dist1.sample() # Sample from the first level (category) + action2 = dist2.sample() # Sample from the second level (subcategory) + action3 = dist3.sample() # Sample from the third level (specific move) + + # Now, we need to combine action2 and action3 to form a valid board position + # Assuming action2 is a row index, and action3 is a column index, or any other encoding you want to use + move = env.sample_action(action3.item()) + if not board.is_legal(move): + move = np.random.choice(board.legal_moves()) + + return move diff --git a/hsm_weights1.pth b/hsm_weights1.pth new file mode 100644 index 0000000..2ee2afb Binary files /dev/null and b/hsm_weights1.pth differ diff --git a/hsmv2.py b/hsmv2.py new file mode 100644 index 0000000..47c4b58 --- /dev/null +++ b/hsmv2.py @@ -0,0 +1,294 @@ +import numpy as np +from tqdm import tqdm +import ataxx +import matplotlib.pyplot as plt +import torch +import torch.nn as nn +import torch.optim as optim +import torch.nn.init as init +import random +import copy + +import ataxx.players + +def default_eval(board: ataxx.Board, turn=None): + black, white, _, _ = board.count() + if turn and turn == ataxx.BLACK or (board.turn == ataxx.BLACK): + return black - white + else: + return white - black + + +def improved_eval(board: ataxx.Board, move: ataxx.Move, gamma=0.9, root=True): + s1 = 10 + s2 = 4 + s3 = 7 + s4 = 4 + + color = board.turn + opponent_color = ataxx.WHITE if color == ataxx.BLACK else ataxx.BLACK + + # Check if move is a pass + if move == ataxx.Move.null(): + return 0 + + from_x, from_y = move.fr_x, move.fr_y + to_x, to_y = move.to_x, move.to_y + + dx = abs(from_x - to_x) + dy = abs(from_y - to_y) + + # Determine if the move is a jump move + is_jump_move = max(dx, dy) == 2 + + # Make a copy of the board and apply the move + new_board = copy.deepcopy(board) + new_board.makemove(move) + + # Define adjacent positions + adjacent_offsets = [(-1, -1), (-1, 0), (-1, 1), + (0, -1), (0, 1), + (1, -1), (1, 0), (1, 1)] + + # Count enemy stones taken over + enemy_stones_taken = 0 + for dx_offset, dy_offset in adjacent_offsets: + nx, ny = to_x + dx_offset, to_y + dy_offset + if board.get(nx, ny) == opponent_color: + enemy_stones_taken += 1 + Si = s1 * enemy_stones_taken + + # Count own stones around the target square after the move + own_stones_around_target = 0 + for dx_offset, dy_offset in adjacent_offsets: + nx, ny = to_x + dx_offset, to_y + dy_offset + if board.get(nx, ny) == color: + own_stones_around_target += 1 + Si += s2 * own_stones_around_target + + # Add s3 if the move is not a jump move + if not is_jump_move: + Si += s3 + + # Subtract s4 times own stones around the source square if it's a jump move + if is_jump_move: + own_stones_around_source = 0 + for dx_offset, dy_offset in adjacent_offsets: + nx, ny = from_x + dx_offset, from_y + dy_offset + if board.get(nx, ny) == color: + own_stones_around_source += 1 + Si -= s4 * own_stones_around_source + + # Consider long-term rewards (future board evaluation) + long_term_score = 0 + # Check the board after the move to account for future rewards + if root: + for next_move in new_board.legal_moves(): + # Should this be negative? Negative scores for putting opponent in good position right? + long_term_score -= improved_eval(new_board, next_move, gamma=gamma, root=False) + + Si += gamma * long_term_score # Incorporate future rewards + + return Si + +class PolicyNetwork(nn.Module): + def __init__(self, action_dim=7): + super(PolicyNetwork, self).__init__() + self.input_size = (action_dim + 4) ** 2 + self.fc1 = nn.Linear(self.input_size, 256) + self.fc2 = nn.Linear(256, 128) + self.fc3 = nn.Linear(128, action_dim * action_dim) + + # Apply He initialization for weights + init.kaiming_normal_(self.fc1.weight, nonlinearity='relu') + init.kaiming_normal_(self.fc2.weight, nonlinearity='relu') + init.kaiming_normal_(self.fc3.weight, nonlinearity='relu') + + # Initialize biases to zero (or small positive values if needed) + init.zeros_(self.fc1.bias) + init.zeros_(self.fc2.bias) + init.zeros_(self.fc3.bias) + + def forward(self, x): + x = torch.relu(self.fc1(x)) + x = torch.relu(self.fc2(x)) + logits = self.fc3(x) + return logits + + def select_action(self, board: ataxx.Board, epsilon=None): + """ Select an action based on the policy network using softmax. State is tensor-ified board""" + if board.must_pass() or board.gameover(): + return ataxx.Move.null() + state = self.board_to_tensor(board) + legal_moves = board.legal_moves() + # print([f"{(m.fr_x, m.fr_y)} to {(m.to_x, m.to_y)}" for m in legal_moves]) + logits = self(state) + action_probs = torch.softmax(logits, dim=-1) + + # Create a mask to set illegal start positions for our action + legal_action_probs = torch.zeros_like(action_probs) + for move in legal_moves: + # DON'T PASS IF WE DON'T HAVE TO! + if not (move.fr_x == move.to_x and move.fr_y == move.to_y): + action_idx_from = move.fr_x * 7 + move.fr_y + legal_action_probs[action_idx_from] = action_probs[action_idx_from] + + # Epsilon-greedy action selection + if epsilon and random.random() < epsilon: # Exploration + start_action_idx = torch.multinomial(legal_action_probs, 1).item() + else: # Exploitation + start_action_idx = torch.argmax(legal_action_probs).item() + + # Convert the action index to (x1, y1) + x1, y1 = self.index_to_coords(start_action_idx) + # print(x1, y1) + + # Create a mask to set illegal start positions for our action + legal_action_probs = torch.zeros_like(action_probs) + for move in legal_moves: + if not (move.to_x == x1 and move.to_y == y1) and move.fr_x == x1 and move.fr_y == y1: + # print(move.to_x, move.to_y) + action_idx_to = move.to_x * 7 + move.to_y + legal_action_probs[action_idx_to] = action_probs[action_idx_to] + + if epsilon and random.random() < epsilon: # Exploration for second action + end_action_idx = torch.multinomial(legal_action_probs, 1).item() + else: # Exploitation for second action + end_action_idx = torch.argmax(legal_action_probs).item() + x2, y2 = self.index_to_coords(end_action_idx) + + return ataxx.Move(x1, y1, x2, y2) + + @staticmethod + def board_to_tensor(board): + """ Convert the Ataxx game board (list of lists) to a PyTorch tensor. """ + board_np = np.array(board._board, dtype=np.float32) + board_tensor = torch.tensor(board_np) + + # Optionally, flatten the tensor for input to neural network + return board_tensor.flatten() + + @staticmethod + def index_to_coords(index): + return index // 7, index % 7 + +def generate_random_board(): + board = ataxx.Board() + n_moves = random.randint(1, 25) + for i in range(n_moves): + player = random.randint(0, 2) + if player == 0: + move = ataxx.players.alphabeta(board, float('-inf'), float('inf'), 2) + elif player == 1: + move = ataxx.players.negamax(board, 2) + else: + move = ataxx.players.greedy(board) + board.makemove(move) + if board.gameover(): + board.undo() + i -= 1 + if board.turn != ataxx.WHITE: + board.undo() + return board + + +def get_best_move(board: ataxx.Board): + """Evaluate all legal moves with a two step lookahead and return the best possible move based on the evaluation.""" + best_move = None + best_score = -float('inf') + one_step_best_score = -float('inf') + turn = board.turn + + for move in board.legal_moves(): + immediate_score = improved_eval(board, move) + board.makemove(move) + for next_move in board.legal_moves(): + score = improved_eval(board, next_move) # Use default_eval as the heuristic for evaluating moves + if score > best_score: + best_score = score + one_step_best_score = immediate_score + best_move = move + board.undo() + + + return best_move, one_step_best_score + +def train_policy_network(model_name, batch_size=32, action_dim=7, epsilon_start=1.0, epsilon_end=0.001, epsilon_decay=0.995, lr_start=0.001, lr_decay=0.7): + model = PolicyNetwork(action_dim=action_dim) + optimizer = optim.Adam(model.parameters(), lr=lr_start, weight_decay=0.01) + epsilon = epsilon_start + + # Learning rate scheduler + # scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=10, gamma=lr_decay) + + board_fens = [] + for i in tqdm(range(200)): + b = generate_random_board() + board_fens.append(b.get_fen()) + + # Shuffle the data + random.shuffle(board_fens) + + batches = [] + for i in range(0, len(board_fens), batch_size): + batch = board_fens[i:i + batch_size] + batches.append(batch) + + # List to store loss values for plotting + loss_history = [] + + for epoch in range(200): + epoch_loss = 0 # Track loss for this epoch + n_valid_states = 0 + for batch in tqdm(batches): + board_batch_fens = [] + actions = [] + score_batch = [] + for board_fen in batch: + board = ataxx.Board(board_fens[i]) # Re-create the board from FEN + move = model.select_action(board, epsilon=epsilon) # Get action from the model + x1, y1, x2, y2 = move.fr_x, move.fr_y, move.to_x, move.to_y + if x1 is not None: + board_batch_fens.append(board_fen) + actions.append(move) + score_batch.append(improved_eval(board, move)) + + # Get the scores for the selected actions + selected_scores_tensor = torch.tensor(score_batch, dtype=torch.float32, requires_grad=True) + + # Loss function: we want to maximize the score, so we minimize the negative of the score + loss = -selected_scores_tensor.mean() # Take the mean loss over the batch + epoch_loss += loss.item() + + # Backpropagate and update the model + optimizer.zero_grad() + loss.backward() + + # Apply gradient clipping + torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0) + + # Update the model parameters + optimizer.step() + + n_valid_states += len(board_batch_fens) + + # Update epsilon for exploration-exploitation tradeoff + epsilon = max(epsilon * epsilon_decay, epsilon_end) + # scheduler.step() # Update learning rate + print(f"Epoch {epoch}: {epoch_loss / n_valid_states}") + + + # Record loss for plotting + if n_valid_states > 0: + loss_history.append(epoch_loss / n_valid_states) + + # Plot the training loss + plt.plot(loss_history) + plt.xlabel('Epochs') + plt.ylabel('Loss') + plt.show() + + # Save final model + torch.save(model.state_dict(), f'{model_name}.pth') + + diff --git a/learn_ataax.py b/learn_ataax.py new file mode 100644 index 0000000..e382c73 --- /dev/null +++ b/learn_ataax.py @@ -0,0 +1,330 @@ +import ataxx +import sys +import ataxx.players +import numpy as np +from ataxx import FEN_4CORNERS, FEN_4SIDES, FEN_CENTERX, FEN_EMPTY, FEN_ISLAND, FEN_STARTPOS +from tqdm import tqdm +import torch +import hsmv2 +import mcts +import matplotlib.pyplot as plt + +BLACK, WHITE, GAP, EMPTY = 0, 1, 2, 3 + +N_MOVES_EXAMPLE = 35 +POSSIBLE_BOARDS = [FEN_4CORNERS, FEN_4SIDES, FEN_CENTERX, FEN_EMPTY, FEN_ISLAND, FEN_STARTPOS] +PLAYER_TYPES = [ataxx.players.alphabeta, ataxx.players.greedy, ataxx.players.negamax] +N_TRAINING_EXAMPLES = 1000 +TRAIN_DEPTH = 2 + +def load_end_data(): + with open("endgame.txt", "r") as datafile: + data = datafile.readlines() + for fen in data: + fen = fen.strip() + board = ataxx.Board(fen) + print(board) + +def gen_end_examples(): + with open("endgame.txt", 'w+') as outfile: + for i in tqdm(range(N_TRAINING_EXAMPLES)): + board = ataxx.Board(np.random.choice(POSSIBLE_BOARDS)) + p1 = np.random.choice(PLAYER_TYPES) + p2 = np.random.choice(PLAYER_TYPES) + for m in range(N_MOVES_EXAMPLE): + for p in [p1, p2]: + if p == ataxx.players.alphabeta: + move = p(board, float('-inf'), float('inf'), TRAIN_DEPTH) + elif p == ataxx.players.negamax: + move = p(board, TRAIN_DEPTH) + elif p == ataxx.players.greedy: + move = p(board) + if board.gameover(): + break + board.makemove(move) + # print(board) + if board.gameover(): + # We went too far, retry generating + m -= 1 + else: + fen = board.get_fen() + outfile.write(fen + "\n") + +def play_solo(): + fen = input("FEN: ") + board_dim = int(input("BOARD SIZE: ")) + if fen == "": + fen = "startpos" + board = ataxx.Board(fen, board_dim=board_dim) + print(board) + + while not board.gameover(): + print("\n\n\n") + print(F"FEN: {board.get_fen()}") + print(board) + try: + print(f'Legal moves: {[(m.to_x, m.to_y) for m in board.legal_moves()]}') + move_string = input("Move: ") + move = ataxx.Move.from_san(move_string) + if board.is_legal(move): + board.makemove(move) + else: + print(F"Illegal move: {move}") + except KeyboardInterrupt: + print("") + break + + print(F"Result: {board.result()}") + +def default_eval(board: ataxx.Board): + black, white, _, _ = board.count() + if board.turn == ataxx.BLACK: + return black - white + else: + return white - black + +""" +Evaluation function that favors pieces placed closer to the center of the board +""" +def weight_center_eval_function(board: ataxx.Board): + player = board.turn + points = default_eval(board) + # Define the center coordinates + center_x, center_y = (board.board_dim - 1) // 2, (board.board_dim - 1) // 2 + + # Iterate over the grid and calculate the Manhattan distance to the center + for i in range(board.board_dim): + for j in range(board.board_dim): + # Manhattan distance from the current cell (i, j) to the center + if board.get(i, j) == player: + distance = abs(i - center_x) + abs(j - center_y) + + # Assign points inversely proportional to the distance + # We can use the formula `points = max_points - distance` + # where max_points is the maximum possible distance, which is n-1 for the corners + max_distance = (board.board_dim - 1) * 2 + p = max(0, max_distance - distance) # Ensure no negative points + + points += p + return points + +""" +Evaluation function that discourages "L" and "U" shapes +""" +def keep_clustered_eval_function(board: ataxx.Board): + points = default_eval(board) + player = board.turn + + def find_l_shapes(board: ataxx.Board): + rows, cols = len(board), len(board[0]) + l_shapes = [] + + # Loop through the grid and check for L shapes + for i in range(1, board.board_dim - 1): + for j in range(1, board.board_dim - 1): + # Check for L shapes (both horizontal and vertical parts) + + # 1. L shape - bottom-left corner (┐) + if board[i][j] == 1 and board[i-1][j] == 1 and board[i][j-1] == 1: + l_shapes.append(((i, j), "┐")) + + # 2. L shape - top-left corner (└) + if board[i][j] == 1 and board[i-1][j] == 1 and board[i][j+1] == 1: + l_shapes.append(((i, j), "└")) + + # 3. L shape - top-right corner (┌) + if board[i][j] == 1 and board[i+1][j] == 1 and board[i][j-1] == 1: + l_shapes.append(((i, j), "┌")) + + # 4. L shape - bottom-right corner (┌) + if board[i][j] == 1 and board[i+1][j] == 1 and board[i][j+1] == 1: + l_shapes.append(((i, j), "└")) + + return l_shapes + + def find_u_shapes(board: ataxx.Board): + rows, cols = len(board), len(board[0]) + u_shapes = [] + + # Loop through the grid and check for U shapes + for i in range(1, board.board_dim - 1): + for j in range(1, board.board_dim - 1): + # Check for U shapes with different possible configurations. + + # 1. U shape - open top, with vertical sides + if board[i][j] == 1 and board[i][j-1] == 1 and board[i][j+1] == 1 and board[i-1][j] == 1: + u_shapes.append(((i, j), "U")) + + return u_shapes + + return points - 5 * len(find_l_shapes(board)) - 5 * len(find_u_shapes(board)) + + +def play_mcts(opponent="alphabeta", board_dim=7, depth=2): + board = ataxx.Board(board_dim=board_dim) + turn_counter = 1 + + game_history = [] + + while not board.gameover(): + try: + if turn_counter % 2 == 1: + # Opponent goes first + if opponent == 'alphabeta': + move = ataxx.players.alphabeta(board, float('-inf'), float('inf'), depth) + else: + move = opponent(board) + else: + move = mcts.get_best_move(board, simulations=100) + if board.is_legal(move): + board.makemove(move) + else: + print(f"Illegal move: {move}") + except KeyboardInterrupt: + print("") + break + game_history.append(-board.score()) + turn_counter += 1 + print(board) + + print(f"Result: {board.result()}") + plt.plot(game_history) + plt.xlabel('Turn') + plt.ylabel('Net Pieces') + plt.show() + return board.result() + + + +def play_hierarchical_softmax_agent(agent, depth=2, opponent='alphabeta', board="startpos", board_dim=7): + board = ataxx.Board(board, board_dim=board_dim) + turn_counter = 1 + + while not board.gameover(): + try: + if turn_counter % 2 == 1: + # Opponent goes first + if opponent == 'alphabeta': + move = ataxx.players.alphabeta(board, float('-inf'), float('inf'), depth) + else: + move = opponent(board) + else: + # Your Hierarchical Softmax agent's move + move = agent.select_action(board) + if board.is_legal(move): + board.makemove(move) + else: + print(f"Illegal move: {move}") + except KeyboardInterrupt: + print("") + break + turn_counter += 1 + print(board) + + print(f"Result: {board.result()}") + return board.result() + + +def alphabeta(board, alpha, beta, depth, root=True, evalFn=default_eval): + if depth == 0: + return evalFn(board) + + best_move = None + + for move in board.legal_moves(): + board.makemove(move) + score = -alphabeta(board, -beta, -alpha, depth-1, root=False) + + if score > alpha: + alpha = score + best_move = move + if score >= beta: + score = beta + board.undo() + break + + board.undo() + + if root: + return best_move + else: + return alpha + +# Two alpha-beta players play against each other +def play_alphabeta(depth=2, opponent='alphabeta', board="startpos", board_dim=7, evalFn=default_eval): + board = ataxx.Board(board, board_dim=board_dim) + turn_counter = 1 + + while not board.gameover(): + try: + if turn_counter % 2 == 1: + # Opponent goes first because if we go first we can win easily + if opponent == 'alphabeta': + move = ataxx.players.alphabeta(board, float('-inf'), float('inf'), depth) + else: + move = opponent(board) + else: + move = alphabeta(board, float('-inf'), float('inf'), depth, evalFn=evalFn) + if board.is_legal(move): + board.makemove(move) + else: + print(F"Illegal move: {move}") + except KeyboardInterrupt: + print("") + break + turn_counter += 1 + + print(F"Result: {board.result()}") + # print(board) + return board.result() + + + +def main(): + if len(sys.argv) == 0: + print("Available modes: solo") + mode = sys.argv[1] + if mode == 'make_data': + gen_end_examples() + if mode == 'read_data': + load_end_data() + if mode == 'solo': + play_solo() + if mode == 'alphabeta': + # board = input("BOARD FEN: ") + board_size = int(input("BOARD SIZE: ")) + wincount = 0 + n_games = 100 + for i in range(n_games): + result = play_alphabeta(opponent='alphabeta', board_dim=board_size, evalFn=keep_clustered_eval_function) + if result == '1-0': + wincount+=1 + print(wincount / n_games) + if mode == 'train_hsmv2': + hsmv2.train_policy_network(sys.argv[2]) + if mode == 'hsmv2': + agent = hsmv2.PolicyNetwork(action_dim=7) # Reinitialize the model + agent.load_state_dict(torch.load(sys.argv[2])) # Load the weights + agent.eval() # Set to evaluation mode if not continuing training + board_size = 7 + wincount = 0 + n_games = 1 + for i in range(n_games): + result = play_hierarchical_softmax_agent(agent, opponent='alphabeta', board_dim=board_size) + if result == '1-0': + wincount+=1 + print(wincount / n_games) + if mode == 'mcts': + wincount = 0 + n_games = 1 + for i in range(n_games): + result = play_mcts(opponent=ataxx.players.greedy) + if result == '1-0': + wincount += 1 + print(wincount / n_games) + + + + +if __name__ == '__main__': + main() diff --git a/mcts.py b/mcts.py new file mode 100644 index 0000000..dadc093 --- /dev/null +++ b/mcts.py @@ -0,0 +1,183 @@ +import numpy as np +import random +import copy +import ataxx +from tqdm import tqdm +from heapq import nlargest + +ROLLOUT_DEPTH = 5 + +BEAM_SIZE = 3 + +def improved_eval(board: ataxx.Board, move: ataxx.Move): + s1 = 1 + s2 = 0.4 + s3 = 0.7 + s4 = 0.4 + + color = board.turn + opponent_color = ataxx.WHITE if color == ataxx.BLACK else ataxx.BLACK + + # Check if move is a pass + if move == ataxx.Move.null(): + return 0 + + from_x, from_y = move.fr_x, move.fr_y + to_x, to_y = move.to_x, move.to_y + + dx = abs(from_x - to_x) + dy = abs(from_y - to_y) + + # Determine if the move is a jump move + is_jump_move = max(dx, dy) == 2 + + # Make a copy of the board and apply the move + new_board = copy.deepcopy(board) + new_board.makemove(move) + + # Define adjacent positions + adjacent_offsets = [(-1, -1), (-1, 0), (-1, 1), + (0, -1), (0, 1), + (1, -1), (1, 0), (1, 1)] + + # Count enemy stones taken over + enemy_stones_taken = 0 + for dx_offset, dy_offset in adjacent_offsets: + nx, ny = to_x + dx_offset, to_y + dy_offset + if board.get(nx, ny) == opponent_color: + enemy_stones_taken += 1 + Si = s1 * enemy_stones_taken + + # Count own stones around the target square after the move + own_stones_around_target = 0 + for dx_offset, dy_offset in adjacent_offsets: + nx, ny = to_x + dx_offset, to_y + dy_offset + if board.get(nx, ny) == color: + own_stones_around_target += 1 + Si += s2 * own_stones_around_target + + # Add s3 if the move is not a jump move + if not is_jump_move: + Si += s3 + + # Subtract s4 times own stones around the source square if it's a jump move + if is_jump_move: + own_stones_around_source = 0 + for dx_offset, dy_offset in adjacent_offsets: + nx, ny = from_x + dx_offset, from_y + dy_offset + if board.get(nx, ny) == color: + own_stones_around_source += 1 + Si -= s4 * own_stones_around_source + + Si = max(0, Si) + + return Si + +class MCTSNode: + def __init__(self, board, parent=None, move=None): + self.board = board + self.parent = parent + self.move = move + self.children = [] + self.visits = 0 + self.reward = 0 + self.legal_moves = list(board.legal_moves()) + + def is_fully_expanded(self): + # return len(self.children) == len(self.legal_moves) + return len(self.children) >= min(len(self.legal_moves), BEAM_SIZE) + + def best_child(self, exploration_weight=1.4): + # """ Select the child with the highest UCB1 value """ + if not self.children: + return None + ucb_values = [ + child.reward / (child.visits + 1e-6) + exploration_weight * np.sqrt(np.log(self.visits + 1) / (child.visits + 1e-6)) + for child in self.children + ] + return self.children[np.argmax(ucb_values)] + +def mcts(board, simulations=1000): + root_node = MCTSNode(board) + + for _ in tqdm(range(simulations)): + node = root_node + + # Selection phase: Traverse the tree until a leaf node is found + while node.is_fully_expanded() and node.children: + node = node.best_child() + + # Expansion phase: If not fully expanded, expand the node using beam search + if not node.is_fully_expanded(): + # Evaluate all legal moves + move_scores = [(move, improved_eval(node.board, move)) for move in node.legal_moves] + # Sort moves by their evaluation scores in descending order + move_scores.sort(key=lambda x: x[1], reverse=True) + # Select the top `beam_width` moves + top_moves = [move for move, _ in move_scores[:BEAM_SIZE]] + + for move in top_moves: + # For each top move, expand the tree + new_board = copy.deepcopy(node.board) + new_board.makemove(move) + child_node = MCTSNode(new_board, parent=node, move=move) + node.children.append(child_node) + + # Simulation phase: Use improved_eval to simulate a game and return the winner + evaluation_score = simulate_with_eval(node.board) + + # Backpropagation phase: Update the node values along the path + while node: + node.visits += 1 + node.reward += evaluation_score + node = node.parent + + # Select the move with the highest visit count from the root + best_child = root_node.best_child(exploration_weight=0) + return best_child.move + +def simulate_with_eval(board): + """ Use improved_eval to simulate a game and return the winner, considering only the top BEAM_SIZE moves. """ + board = copy.deepcopy(board) + color = board.turn + + # While not reaching the maximum rollout depth (ROLLOUT_DEPTH) + for i in range(ROLLOUT_DEPTH): + # Generate legal moves and evaluate them using improved_eval + moves = board.legal_moves() + + # If no legal moves, break the simulation + if not moves: + break + + # Evaluate the moves and keep track of the top BEAM_SIZE moves using nlargest + move_scores = [(move, improved_eval(board, move)) for move in moves] + + # Use nlargest to get the top BEAM_SIZE moves (faster than sorting) + top_moves = nlargest(BEAM_SIZE, move_scores, key=lambda x: x[1]) + + # Select one move from the top moves based on the normalized evaluation scores + top_moves_scores = np.array([score for _, score in top_moves]) + z = top_moves_scores.sum() + if z == 0 or np.isnan(z): + break + normalized_scores = top_moves_scores / z + make_move = np.random.choice([move for move, _ in top_moves], p=normalized_scores) + + # Make the move on the board + board.makemove(make_move) + + # Return the winner (1 for the current player, -1 for opponent, 0 for draw) + black, white, _, _ = board.count() + if black > white: + return 1 if color == ataxx.BLACK else -1 # Current player wins + elif white > black: + return 1 if color == ataxx.WHITE else -1 # Current player wins + return 0 # Draw + + +def get_best_move(board: ataxx.Board, simulations=1000): + """Use MCTS to select the best move, integrating the improved_eval function.""" + new_board = copy.deepcopy(board) + best_move = mcts(new_board, simulations=simulations) + return best_move \ No newline at end of file diff --git a/quiz3.ipynb b/quiz3.ipynb new file mode 100644 index 0000000..33154eb --- /dev/null +++ b/quiz3.ipynb @@ -0,0 +1,267 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 25, + "metadata": {}, + "outputs": [], + "source": [ + "states = ['sI', 'sN', 'sE']\n", + "actions = ['aT', 'aA', 'aE']\n", + "gamma = 0.9\n", + "\n", + "# Transition probabilities P(s' | s, a)\n", + "transition_probabilities = {\n", + " ('sI', 'aT', 'sI'): 0.7,\n", + " ('sN', 'aT', 'sI'): 0.3,\n", + " ('sI', 'aA', 'sI'): 1.0,\n", + " ('sE', 'aE', 'sI'): 1.0,\n", + " ('sN', 'aT', 'sN'): 1.0,\n", + " ('sI', 'aA', 'sN'): 0.5,\n", + " ('sN', 'aA', 'sN'): 0.5,\n", + " ('sE', 'aE', 'sN'): 1.0,\n", + " ('sI', 'aT', 'sN'): 0.0,\n", + " ('sE', 'aE', 'sE'): 1.0,\n", + " ('sE', 'aT', 'sE'): 1.0,\n", + " ('sE', 'aA', 'sE'): 1.0,\n", + " ('sI', 'aT', 'sE'): 0.0,\n", + " ('sN', 'aT', 'sE'): 0.0,\n", + " ('sE', 'aT', 'sI'): 0.0\n", + "}\n", + "\n", + "# Rewards R(s, a)\n", + "rewards = {\n", + " ('sI', 'aA'): 1.1,\n", + " ('sI', 'aE'): 10.0,\n", + " ('sI', 'aT'): 0.0,\n", + " ('sN', 'aA'): 0.0,\n", + " ('sN', 'aE'): 0.0,\n", + " ('sN', 'aT'): -1.0,\n", + " ('sE', 'aA'): 0.0,\n", + " ('sE', 'aE'): 0.0,\n", + " ('sE', 'aT'): 0.0,\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "\n", + "# Calculate utility for given policy\n", + "def calculate_utility(states, transition_probabilities, rewards, gamma, policy, tolerance=1e-6):\n", + " value_function = {state: 0.0 for state in states}\n", + " \n", + " while True:\n", + " delta = 0\n", + " new_value_function = value_function.copy()\n", + "\n", + " for state in states:\n", + " action = policy[state]\n", + " new_value = 0\n", + "\n", + " for next_state in states:\n", + " transition_prob = transition_probabilities.get((next_state, action, state), 0)\n", + " reward = rewards.get((state, action), 0)\n", + " new_value += transition_prob * (reward + gamma * value_function[next_state])\n", + " \n", + " new_value_function[state] = new_value\n", + "\n", + " # Track the maximum change in value to check for convergence\n", + " delta = max(delta, abs(new_value_function[state] - value_function[state]))\n", + "\n", + " # Check for convergence\n", + " if delta < tolerance:\n", + " break\n", + " \n", + " value_function = new_value_function\n", + "\n", + " return value_function\n", + "\n", + "# Calculate maximum utility\n", + "def calculate_max_utility(states, actions, transition_probabilities, rewards, gamma, tolerance=1e-6):\n", + " value_function = {state: 0.0 for state in states}\n", + " \n", + " while True:\n", + " delta = 0\n", + " for s in states:\n", + " v = value_function[s]\n", + " value_function[s] = max(sum(transition_probabilities.get((s_next, a, s),0) * \n", + " (rewards.get((s, a),0) + gamma * value_function[s_next])\n", + " for s_next in states) for a in actions)\n", + " delta = max(delta, abs(v - value_function[s]))\n", + " \n", + " # Check for convergence\n", + " if delta < tolerance:\n", + " break \n", + "\n", + " return value_function\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{('sI', 'aT', 'sI'): 0.7, ('sN', 'aT', 'sI'): 0.3, ('sI', 'aA', 'sI'): 1.0, ('sE', 'aE', 'sI'): 1.0, ('sN', 'aT', 'sN'): 1.0, ('sI', 'aA', 'sN'): 0.5, ('sN', 'aA', 'sN'): 0.5, ('sE', 'aE', 'sN'): 1.0, ('sI', 'aT', 'sN'): 0.0, ('sE', 'aE', 'sE'): 1.0, ('sE', 'aT', 'sE'): 1.0, ('sE', 'aA', 'sE'): 1.0, ('sI', 'aT', 'sE'): 0.0, ('sN', 'aT', 'sE'): 0.0, ('sE', 'aT', 'sI'): 0.0}\n", + "Utility of each state under the policy:\n", + "U(sI) = -7.30\n", + "U(sN) = -10.00\n", + "U(sE) = 0.00\n" + ] + } + ], + "source": [ + "policyQ2 = {\n", + " 'sI': 'aE',\n", + " 'sN': 'aE',\n", + " 'sE': 'aE'\n", + "}\n", + "\n", + "utility = calculate_utility(states, transition_probabilities, rewards, gamma, policyQ2)\n", + "print(\"Utility of each state under the policy aE:\")\n", + "for state, value in utility.items():\n", + " print(f\"U({state}) = {value:.2f}\")\n", + "\n", + "# calculate_max_utility(states, actions, transition_probabilities, rewards, gamma)" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Utility of each state under the policy aT:\n", + "U(sI) = -7.30\n", + "U(sN) = -10.00\n", + "U(sE) = 0.00\n" + ] + } + ], + "source": [ + "policyQ3 = {\n", + " 'sI': 'aT',\n", + " 'sN': 'aT',\n", + " 'sE': 'aT'\n", + "}\n", + "\n", + "utility = calculate_utility(states, transition_probabilities, rewards, gamma, policyQ3)\n", + "print(\"Utility of each state under the policy aT:\")\n", + "for state, value in utility.items():\n", + " print(f\"U({state}) = {value:.2f}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Utility of each state under the policy aA:\n", + "U(sI) = 11.00\n", + "U(sN) = 9.00\n", + "U(sE) = 0.00\n" + ] + } + ], + "source": [ + "policyQ4 = {\n", + " 'sI': 'aA',\n", + " 'sN': 'aA',\n", + " 'sE': 'aA'\n", + "}\n", + "\n", + "utility = calculate_utility(states, transition_probabilities, rewards, gamma, policyQ4)\n", + "print(\"Utility of each state under the policy aA:\")\n", + "for state, value in utility.items():\n", + " print(f\"U({state}) = {value:.2f}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Utility of each state (optimal):\n", + "U(sI) = 11.00\n", + "U(sN) = 9.00\n", + "U(sE) = 0.00\n" + ] + } + ], + "source": [ + "# Q5\n", + "max_utility = calculate_max_utility(states, actions, transition_probabilities, rewards, gamma)\n", + "print(\"Utility of each state (optimal):\")\n", + "for state, value in utility.items():\n", + " print(f\"U({state}) = {value:.2f}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Optimal Policy:\n", + "pi(sI) = aA\n", + "pi(sN) = aA\n", + "pi(sE) = aT\n" + ] + } + ], + "source": [ + "# Q6 / Q7\n", + "policy = {}\n", + "for s in states:\n", + " policy[s] = max(actions, key=lambda a: sum(transition_probabilities.get((s_next, a, s),0) * (rewards.get((s, a),0) + gamma * max_utility[s_next]) for s_next in states))\n", + "print(\"Optimal Policy:\")\n", + "for state, value in policy.items():\n", + " print(f\"pi({state}) = {value}\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.1" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/random_state_agent.pth b/random_state_agent.pth new file mode 100644 index 0000000..8204c66 Binary files /dev/null and b/random_state_agent.pth differ diff --git a/tests/__pycache__/__init__.cpython-39.pyc b/tests/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000..4cad5ce Binary files /dev/null and b/tests/__pycache__/__init__.cpython-39.pyc differ diff --git a/tests/__pycache__/test_counters.cpython-39.pyc b/tests/__pycache__/test_counters.cpython-39.pyc new file mode 100644 index 0000000..2086dda Binary files /dev/null and b/tests/__pycache__/test_counters.cpython-39.pyc differ diff --git a/tests/__pycache__/test_draws.cpython-39.pyc b/tests/__pycache__/test_draws.cpython-39.pyc new file mode 100644 index 0000000..ec33bdf Binary files /dev/null and b/tests/__pycache__/test_draws.cpython-39.pyc differ diff --git a/tests/__pycache__/test_fen.cpython-39.pyc b/tests/__pycache__/test_fen.cpython-39.pyc new file mode 100644 index 0000000..e536999 Binary files /dev/null and b/tests/__pycache__/test_fen.cpython-39.pyc differ diff --git a/tests/__pycache__/test_gameover.cpython-39.pyc b/tests/__pycache__/test_gameover.cpython-39.pyc new file mode 100644 index 0000000..2484409 Binary files /dev/null and b/tests/__pycache__/test_gameover.cpython-39.pyc differ diff --git a/tests/__pycache__/test_hash.cpython-39.pyc b/tests/__pycache__/test_hash.cpython-39.pyc new file mode 100644 index 0000000..edc3e3b Binary files /dev/null and b/tests/__pycache__/test_hash.cpython-39.pyc differ diff --git a/tests/__pycache__/test_history.cpython-39.pyc b/tests/__pycache__/test_history.cpython-39.pyc new file mode 100644 index 0000000..9a4da03 Binary files /dev/null and b/tests/__pycache__/test_history.cpython-39.pyc differ diff --git a/tests/__pycache__/test_is_legal.cpython-39.pyc b/tests/__pycache__/test_is_legal.cpython-39.pyc new file mode 100644 index 0000000..ec5ce06 Binary files /dev/null and b/tests/__pycache__/test_is_legal.cpython-39.pyc differ diff --git a/tests/__pycache__/test_makeundo.cpython-39.pyc b/tests/__pycache__/test_makeundo.cpython-39.pyc new file mode 100644 index 0000000..e74a087 Binary files /dev/null and b/tests/__pycache__/test_makeundo.cpython-39.pyc differ diff --git a/tests/__pycache__/test_movetype.cpython-39.pyc b/tests/__pycache__/test_movetype.cpython-39.pyc new file mode 100644 index 0000000..b767b8b Binary files /dev/null and b/tests/__pycache__/test_movetype.cpython-39.pyc differ diff --git a/tests/__pycache__/test_nullmove.cpython-39.pyc b/tests/__pycache__/test_nullmove.cpython-39.pyc new file mode 100644 index 0000000..44bca19 Binary files /dev/null and b/tests/__pycache__/test_nullmove.cpython-39.pyc differ diff --git a/tests/__pycache__/test_pass.cpython-39.pyc b/tests/__pycache__/test_pass.cpython-39.pyc new file mode 100644 index 0000000..21869da Binary files /dev/null and b/tests/__pycache__/test_pass.cpython-39.pyc differ diff --git a/tests/__pycache__/test_perft.cpython-39.pyc b/tests/__pycache__/test_perft.cpython-39.pyc new file mode 100644 index 0000000..0dd1971 Binary files /dev/null and b/tests/__pycache__/test_perft.cpython-39.pyc differ diff --git a/tests/__pycache__/test_pgn.cpython-39.pyc b/tests/__pycache__/test_pgn.cpython-39.pyc new file mode 100644 index 0000000..13592ad Binary files /dev/null and b/tests/__pycache__/test_pgn.cpython-39.pyc differ diff --git a/tests/__pycache__/test_players.cpython-39.pyc b/tests/__pycache__/test_players.cpython-39.pyc new file mode 100644 index 0000000..9e6c9ba Binary files /dev/null and b/tests/__pycache__/test_players.cpython-39.pyc differ diff --git a/tests/__pycache__/test_predict_hash.cpython-39.pyc b/tests/__pycache__/test_predict_hash.cpython-39.pyc new file mode 100644 index 0000000..da79755 Binary files /dev/null and b/tests/__pycache__/test_predict_hash.cpython-39.pyc differ diff --git a/tests/__pycache__/test_result.cpython-39.pyc b/tests/__pycache__/test_result.cpython-39.pyc new file mode 100644 index 0000000..86120c0 Binary files /dev/null and b/tests/__pycache__/test_result.cpython-39.pyc differ diff --git a/tests/__pycache__/test_san.cpython-39.pyc b/tests/__pycache__/test_san.cpython-39.pyc new file mode 100644 index 0000000..1a70de2 Binary files /dev/null and b/tests/__pycache__/test_san.cpython-39.pyc differ diff --git a/tests/__pycache__/test_score.cpython-39.pyc b/tests/__pycache__/test_score.cpython-39.pyc new file mode 100644 index 0000000..640fe22 Binary files /dev/null and b/tests/__pycache__/test_score.cpython-39.pyc differ diff --git a/tests/__pycache__/test_set_get.cpython-39.pyc b/tests/__pycache__/test_set_get.cpython-39.pyc new file mode 100644 index 0000000..d3af652 Binary files /dev/null and b/tests/__pycache__/test_set_get.cpython-39.pyc differ