def test_generate_moves_one_valid(self): config = board.GameConfiguration(6, 2) b = board.Board(config, [1, 2, 3]) got = list(b.generate_moves(board.Roll(dice=[5, 4], prob=0))) self.assertEqual(2, len(got)) self.assertIn([board.Move(2, 5), board.Move(2, 4)], got) self.assertIn([board.Move(2, 4), board.Move(2, 5)], got)
def test_initialize_3marker_2spots(self): config = board.GameConfiguration(3, 2) self.assertEqual(config.num_valid_boards, 10) # 5 total bits 00111 is the min self.assertEqual(config.min_board_id, 7) # 5 total bits 11100 is the max, adding 1 for exclusive self.assertEqual(config.max_board_id, 28 + 1)
def test_is_valid_counts(self, num_markers, num_spots): config = board.GameConfiguration(num_markers, num_spots) count_valid = 0 for idx in range(config.min_board_id, config.max_board_id): if config.is_valid_id(idx): count_valid += 1 self.assertEqual(config.num_valid_boards, count_valid)
def test_id_round_trip(self, num_markers, num_spots): config = board.GameConfiguration(num_markers, num_spots) for idx in range(config.min_board_id, config.max_board_id): if not config.is_valid_id(idx): continue b = board.Board.from_id(config, idx) self.assertEqual(b.get_id(), idx)
def test_pretty_print(self): config = board.GameConfiguration(6, 2) # Board state is 1 off, 2 on 1 spot, 3 on 2 spot # 11101101 b = board.Board.from_id(config, 0xED) self.assertEqual(b.pretty_string(), "0 1 o \n" + "1 2 oo \n" + "2 3 ooo \n")
def test_next_valid_board(self, num_markers, num_spots): config = board.GameConfiguration(num_markers, num_spots) b = board.Board.from_id(config, config.min_board_id) id_gen = config.generate_valid_ids() next(id_gen) while True: msg = "old b: %s; " % str(b) try: next_b = b.next_valid_board() msg += "next b: %s; " % str(next_b) except StopIteration: try: next_idx = next(id_gen) self.fail( "Generator not finished with next_valid_board was %s" % msg) except StopIteration: break try: next_idx = next(id_gen) msg += "next idx: %d; " % next_idx except StopIteration: self.fail("Generator ran out before board; %s" % msg) self.assertEqual(next_b.get_id(), next_idx, msg) b = next_b
def test_to_string(self): config = board.GameConfiguration(15, 6) self.assertEqual( 'AQAAAAAAAAAAAA', gnubg_interface.board_id_to_gnubg_id_str( config, board.Board(config, [14, 1, 0, 0, 0, 0, 0]).get_id()))
def test_e2e_6_3(self): config = board.GameConfiguration(6, 3) store = strategy.DistributionStore(config) store.compute() def test_move_count_distribution(board_init, expected_move_count): print("board_init: %s" % board_init) b = board.Board(config, board_init) np.testing.assert_allclose(store.distribution_map[b.get_id()].dist, expected_move_count) # This is an end to end test. We'll pull a few specific cases # that are relatively easy to understand. # The end game state test_move_count_distribution([6, 0, 0, 0], [1]) # Only two pieces left, has to finish on one roll test_move_count_distribution([4, 2, 0, 0], [0, 1]) # Only three pieces left. If doubles, finish in 1 move otherwise, 2. test_move_count_distribution([3, 3, 0, 0], [0, 1 / 6, 5 / 6]) # Only two pieces left but on 2nd spot. Any of the 1-2, 1-3, # 1-4, 1-5, 1-6 rolls mean you will finish in 2 instead of 1. test_move_count_distribution([4, 0, 2, 0], [0, 26 / 36, 10 / 36]) # Only two pieces left but on 1st and 3rd spot. Any roll # except 1-2 and you go out in 1 roll test_move_count_distribution([4, 1, 0, 1], [0, 34 / 36, 2 / 36])
def test_generate_moves_unfinished(self): # This is where you don't have to use all the moves to finish config = board.GameConfiguration(2, 2) b = board.Board(config, [1, 0, 1]) got = list(b.generate_moves(board.Roll(dice=[5, 4], prob=0))) self.assertEqual(2, len(got)) self.assertIn([board.Move(2, 4)], got) self.assertIn([board.Move(2, 5)], got)
def test_generate_moves_valid_with_holes(self): config = board.GameConfiguration(3, 4) # Making sure we don't generate the move that is the 1 spot # going 4 spaces.` b = board.Board(config, [0, 1, 0, 2, 0]) got = list(b.generate_moves(board.Roll(dice=[4, 4], prob=0))) self.assertEqual(1, len(got)) self.assertIn([board.Move(3, 4), board.Move(3, 4)], got)
def test_spot_counts_init_errors(self): config = board.GameConfiguration(6, 2) with self.assertRaises(ValueError): b = board.Board(config, [5, 1]) with self.assertRaises(ValueError): b = board.Board(config, [3, 1, 1, 1]) with self.assertRaises(ValueError): b = board.Board(config, [1, 1, 1])
def test_generate_moves_opposite_order(self): # This is the case where you have to try applying the dice in the # opposite order in order to get a different board as outcome. config = board.GameConfiguration(2, 4) b = board.Board(config, [0, 0, 1, 0, 1]) got = list(b.generate_moves(board.Roll(dice=[4, 3], prob=0))) self.assertEqual(2, len(got)) self.assertIn([board.Move(4, 3), board.Move(2, 4)], got) self.assertIn([board.Move(4, 4), board.Move(2, 3)], got)
def test_apply_move(self): config = board.GameConfiguration(6, 2) b = board.Board(config, [1, 2, 3]) self.assertEqual(b.apply_move(board.Move(2, 6)), board.Board(config, [2, 2, 2])) self.assertEqual(b.apply_move(board.Move(2, 1)), board.Board(config, [1, 3, 2])) self.assertEqual(b.apply_move(board.Move(1, 1)), board.Board(config, [2, 1, 3]))
def test_round_trip(self): config = board.GameConfiguration(15, 6) for board_id in config.generate_valid_ids(): gnubg_str = gnubg_interface.board_id_to_gnubg_id_str( config, board_id) new_board_id = gnubg_interface.gnubg_id_str_to_board_id( config, gnubg_str) self.assertEqual(board_id, new_board_id, msg='gnbg_str={}'.format(gnubg_str))
def test_generate_moves_valid(self, roll): # For a random board position, we'll just check that all # generated moves are valid. config = board.GameConfiguration(6, 4) b = board.Board(config, [0, 2, 3, 0, 1]) for moves in b.generate_moves(roll): try: b.apply_moves(moves) except Exception as e: print("%s with moves %s" % (roll, moves)) raise e
def test_pretty_print_with_moves(self): config = board.GameConfiguration(6, 4) b = board.Board(config, [2, 1, 1, 1, 1]) m1 = board.Move(spot=3, count=2) m2 = board.Move(spot=3, count=3) m3 = board.Move(spot=3, count=6) m4 = board.Move(spot=4, count=1) self.assertEqual( b.pretty_string([m1, m2, m3, m4]), "0 2 oo x + \n" + "1 1 o x | | \n" + "2 1 o | | | \n" + "3 1 o 2 3 6 x \n" + "4 1 o 1 \n")
def test_apply_move_errors(self): config = board.GameConfiguration(6, 2) b = board.Board(config, [1, 2, 3]) with self.assertRaisesRegex(ValueError, "Invalid spot"): b.apply_move(board.Move(0, 6)) b = board.Board(config, [3, 0, 3]) with self.assertRaisesRegex(ValueError, "No marker"): b.apply_move(board.Move(1, 1)) b = board.Board(config, [1, 2, 3]) with self.assertRaisesRegex(ValueError, "Overflow count"): b.apply_move(board.Move(1, 6))
def create_distribution_store_from_gnubg(gnubg_dir): """Creates a database in our format from the gnubg database. This uses a really dumb and inefficient strategy of calling 'bearoffdump' many times as separate processes. But it allows us to not tweak the gnubg code and not have to deal with the vagaries of their format (just some annoying text parsing) Args: gnubg_dir: source directory of gnubg with everyting compiled Returns: strategy.DistributionStore """ progress_interval = 500 config = board.GameConfiguration(15, 6) store = strategy.DistributionStore(config) start_time = time.time() # gnubg uses a 1 based index as an argument to # bearoffdump. However, it doesn't have the end state as a valid # index so we add that manually. store.distribution_map[config.min_board_id] = ( strategy.MoveCountDistribution([1])) for idx in range(1, config.num_valid_boards): completed_process = subprocess.run([ os.path.join(gnubg_dir, 'bearoffdump'), os.path.join(gnubg_dir, 'gnubg_os0.bd'), '-n', str(idx) ], universal_newlines=True, stdout=subprocess.PIPE, check=True) try: b, mcd = parse_gnubg_dump(config, completed_process.stdout) except ValueError as err: raise ValueError('For gnubg index {}: {}'.format(idx, err)) store.distribution_map[b.get_id()] = mcd if idx % progress_interval == 0: frac_complete = idx / config.num_valid_boards this_time = time.time() print("%d/%d %.1f%%, %fs elapsed, %fs estimated total" % (idx, config.num_valid_boards, frac_complete * 100, this_time - start_time, (this_time - start_time) / frac_complete), flush=True) return store
def test_generate_moves_four_dice(self): config = board.GameConfiguration(9, 4) b = board.Board(config, [0, 0, 3, 3, 3]) got = list(b.generate_moves(board.Roll(dice=[2, 2, 2, 2], prob=0))) self.assertEqual(78, len(got)) # Since this generates an annoying large number of moves, # we're not going to test all of them, just a couple. self.assertIn([ board.Move(3, 2), board.Move(2, 2), board.Move(2, 2), board.Move(2, 2) ], got) self.assertIn([ board.Move(3, 2), board.Move(3, 2), board.Move(3, 2), board.Move(2, 2) ], got)
def test_round_trip_save_load(self): config = board.GameConfiguration(3, 2) store = strategy.DistributionStore(config) store.compute() with tempfile.TemporaryFile() as tmp: store.save_hdf5(tmp) tmp.seek(0) loaded_store = strategy.DistributionStore.load_hdf5(tmp) self.assertEqual(store.config.num_markers, loaded_store.config.num_markers) self.assertEqual(store.config.num_spots, loaded_store.config.num_spots) self.assertEqual(len(store.distribution_map), len(loaded_store.distribution_map)) for board_id, mcd in store.distribution_map.items(): np.testing.assert_allclose( mcd.dist, loaded_store.distribution_map[board_id].dist)
def test_next_valid_id(self, num_markers, num_spots): config = board.GameConfiguration(num_markers, num_spots) board_id = config.min_board_id while True: try: next_board_id = config.next_valid_id(board_id) except StopIteration: next_board_id = -1 expected_board_id = board_id + 1 while not (config.is_valid_id(expected_board_id)): expected_board_id += 1 if expected_board_id >= config.max_board_id: expected_board_id = -1 break self.assertEqual(next_board_id, expected_board_id, "previous board idx: %d" % board_id) if next_board_id == -1: break board_id = next_board_id
def test_from_string(self): config = board.GameConfiguration(15, 6) # index 1 self.assertEqual( board.Board(config, [14, 1, 0, 0, 0, 0, 0]).get_id(), gnubg_interface.gnubg_id_str_to_board_id(config, 'AQAAAAAAAAAAAA')) # index 2 self.assertEqual( board.Board(config, [14, 0, 1, 0, 0, 0, 0]).get_id(), gnubg_interface.gnubg_id_str_to_board_id(config, 'AgAAAAAAAAAAAA')) # index 99 self.assertEqual( board.Board(config, [11, 2, 0, 0, 2, 0, 0]).get_id(), gnubg_interface.gnubg_id_str_to_board_id(config, 'YwAAAAAAAAAAAA')) # index 33333 self.assertEqual( board.Board(config, [1, 1, 11, 0, 0, 1, 1]).get_id(), gnubg_interface.gnubg_id_str_to_board_id(config, '/R8FAAAAAAAAAA')) # index 50000 (everything is on the board) self.assertEqual( board.Board(config, [0, 1, 0, 3, 8, 3, 0]).get_id(), gnubg_interface.gnubg_id_str_to_board_id(config, 'uX8HAAAAAAAAAA'))
def test_total_pips(self): config = board.GameConfiguration(6, 3) self.assertEqual(0, board.Board(config, [6, 0, 0, 0]).total_pips()) self.assertEqual(5, board.Board(config, [1, 5, 0, 0]).total_pips()) self.assertEqual(14, board.Board(config, [0, 1, 2, 3]).total_pips())
def testParse(self): input = """ Bearoff database: gnubg_os0.bd Position number : 30 Information about database: * On disk 1-sided bearoff database evaluator - generated by GNU Backgammon - up to 15 chequers on 6 points (54264 positions) per player - database includes gammon distributions Dump of position#: 30 GNU Backgammon Position ID: EwAAAAAAAAAAAA +13-14-15-16-17-18------19-20-21-22-23-24-+ | | | O O | OOO | | | O | OOO | | | | OO | | | | OO | | | | OO v| |BAR| | | | | | XXX | | | | XXX | | | | XXX | | | | XXX | | | | XXX +12-11-10--9--8--7-------6--5--4--3--2--1-+ Player Opponent Position 0 30 Bearing off Bearing at least one chequer off Rolls Player Opponent Player Opponent 0 100.000 0.000 100.000 100.000 1 0.000 13.889 0.000 0.000 2 0.000 86.111 Average rolls Bearing off Saving gammon Player Opponent Player Opponent Mean 0.000 1.861 0.000 0.000 Std dev 0.000 0.346 0.000 0.000 Effective pip count: Player Opponent EPC 0.000 15.199 Wastage 0.000 10.199 EPC = 8.167 * Average rolls Wastage = EPC - pips """ config = board.GameConfiguration(15, 6) b, mcd = gnubg_interface.parse_gnubg_dump(config, input) expected_b = board.Board(config, [12, 2, 0, 1, 0, 0, 0]) self.assertEqual(b, expected_b, msg='expected={}, got={}'.format(expected_b, b)) expected_mcd = strategy.MoveCountDistribution([0.0, .13889, .86111]) np.testing.assert_allclose(expected_mcd.dist, mcd.dist, atol=1e-5)
def test_generate_moves_second_dependent(self): config = board.GameConfiguration(2, 4) b = board.Board(config, [0, 0, 1, 0, 1]) got = list(b.generate_moves(board.Roll(dice=[4, 4], prob=0))) self.assertEqual(1, len(got)) self.assertIn([board.Move(4, 4), board.Move(2, 4)], got)
# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # https://www.apache.org/licenses/LICENSE-2.0 # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import argparse import board import strategy parser = argparse.ArgumentParser() parser.add_argument("num_markers") parser.add_argument("num_spots") args = parser.parse_args() num_markers = int(args.num_markers) num_spots = int(args.num_spots) config = board.GameConfiguration(num_markers, num_spots) store = strategy.DistributionStore(config) store.compute() fn = "data/bgend_store_%d_%d.hdf5" % (num_markers, num_spots) store.save_hdf5(fn)
def test_generator(self, num_markers, num_spots): config = board.GameConfiguration(num_markers, num_spots) self.assertEqual(config.num_valid_boards, sum(1 for _ in config.generate_valid_ids()))
def test_spot_counts_init(self): config = board.GameConfiguration(6, 2) b = board.Board(config, [1, 2, 3]) self.assertListEqual(list(b.spot_counts), [1, 2, 3])
def test_id_init(self): config = board.GameConfiguration(6, 2) # Board state is 1 off, 2 on 1 spot, 3 on 2 spot # 11101101 b = board.Board.from_id(config, 0xED) self.assertListEqual(list(b.spot_counts), [1, 2, 3])