From 9f1bd56c0aacee8e4864f5155562cb026fea2632 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoffer=20M=C3=BCller=20Madsen?= Date: Sat, 12 May 2018 15:18:52 +0200 Subject: [PATCH 1/2] fix bear_off bug; addtional tests and additional fixes --- quack/quack.c | 52 ++-- test.py | 648 +++++++++++++++++++++++++++----------------------- 2 files changed, 385 insertions(+), 315 deletions(-) diff --git a/quack/quack.c b/quack/quack.c index 0c72ae5..213c49c 100644 --- a/quack/quack.c +++ b/quack/quack.c @@ -73,29 +73,49 @@ int no_block_at_to_idx(int to_state, int player) { else return 1; } -int can_bear_off(int board[], int player, int from_idx) { + +int can_bear_off(int board[], int player, int from_idx, int to_idx) { int* checker_idxs = idxs_with_checkers_of_player(board, player); - if (player == 1) { - for (int i = 1; i <= checker_idxs[0]; i++) { - if ( !((checker_idxs[i] >= from_idx) && - (checker_idxs[i] >= 19)) ) { - free(checker_idxs); - return 0; + int moving_backmost_checker = 1; + int bearing_directly_off = 0; + int all_checkers_in_last_quadrant = 1; + + /* Check if bearing directly off */ + if (player == 1 && to_idx == 25) bearing_directly_off = 1; + else if (player == -1 && to_idx == 0) bearing_directly_off = 1; + + for (int i = 1; i <= checker_idxs[0]; i++) { + if (player == 1 ) { + /* Check if all checkers are in last quardrant */ + if (checker_idxs[i] < 19) { + all_checkers_in_last_quadrant = 0; + break; } - } - } else { - for (int i = 1; i <= checker_idxs[0]; i++) { - if ( !((checker_idxs[i] <= from_idx) && - (checker_idxs[i] <= 6)) ) { - free(checker_idxs); - return 0; + + /* Check if moving backmost checker */ + if (checker_idxs[i] < from_idx) { + moving_backmost_checker = 0; + if (!bearing_directly_off) break; + } + } else { + if (checker_idxs[i] > 6) { + all_checkers_in_last_quadrant = 0; + break; + } + + if (checker_idxs[i] > from_idx) { + moving_backmost_checker = 0; + if (!bearing_directly_off) break; } } } free(checker_idxs); - return 1; + + if (all_checkers_in_last_quadrant && + (bearing_directly_off || moving_backmost_checker)) return 1; + else return 0; } @@ -124,7 +144,7 @@ int is_move_valid(int board[], int player, int face_value, int move[]) { && bear_in_if_checker_on_bar(board, player, from_idx) && checkers_at_from_idx(from_state, player) && no_block_at_to_idx(to_state, player) - && (!bearing_off || can_bear_off(board, player, from_idx)) + && (!bearing_off || can_bear_off(board, player, from_idx, to_idx)) ; } diff --git a/test.py b/test.py index 759c2c5..90cea23 100644 --- a/test.py +++ b/test.py @@ -141,6 +141,56 @@ class TestIsMoveValid(unittest.TestCase): # TODO: More tests for bearing off are needed + def test_bear_off_non_backmost(self): + board = ( 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 1, + 0 ) + self.assertEqual(Board.is_move_valid(board, 1, 2, (23, 25)), True) + self.assertEqual(Board.is_move_valid(board, 1, 1, (24, 25)), True) + self.assertEqual(Board.is_move_valid(board, 1, 2, (24, 26)), False) + + def test_bear_off_quadrant_limits_white(self): + board = ( 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, + 1, 1, 1, 1, 1, 1, + 0 ) + self.assertEqual(Board.is_move_valid(board, 1, 2, (23, 25)), False) + self.assertEqual(Board.is_move_valid(board, 1, 1, (24, 25)), False) + + def test_bear_off_quadrant_limits_black(self): + board = ( 0, + -1, -1, -1, -1, -1, -1, + -1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0 ) + self.assertEqual(Board.is_move_valid(board, -1, 2, (2, 0)), False) + self.assertEqual(Board.is_move_valid(board, -1, 1, (1, 0)), False) + + def test_bear_off_quadrant_limits_white_2(self): + board = ( 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 1, + 0 ) + self.assertEqual(Board.is_move_valid(board, 1, 1, (24, 25)), True) + + def test_bear_off_quadrant_limits_black_2(self): + board = ( 0, + -1, 0, 0, 0, 0, -1, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0 ) + self.assertEqual(Board.is_move_valid(board, -1, 1, (1, 0)), True) + + class TestNumOfChecker(unittest.TestCase): def test_simple_1(self): board = ( 0, @@ -552,372 +602,372 @@ class TestLegalMoves(unittest.TestCase): self.assertEqual(Board.calculate_legal_states(board, -1, (4,3)), expected_board_set) -# class TestBoardFlip(unittest.TestCase): -# def test_flip_board(self): -# board = (0, -# -14, -1, 0, 0, 0, 0, -# 0, 0, 0, 0, 0, 0, -# 0, 0, 0, 0, 0, 0, -# 0, 0, 0, 0, 0, 1, -# 0) +class TestBoardFlip(unittest.TestCase): + def test_flip_board(self): + board = (0, + -14, -1, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, + 0) -# expected_board = ( 0, -# -1, 0, 0, 0, 0, 0, -# 0, 0, 0, 0, 0, 0, -# 0, 0, 0, 0, 0, 0, -# 0, 0, 0, 0, 1, 14, -# 0 ) + expected_board = ( 0, + -1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 14, + 0 ) -# self.assertEqual(Board.flip(board), expected_board) + self.assertEqual(Board.flip(board), expected_board) -# def test_flip_board_bar(self): -# board = (2, -# 0, 0, 0, 0, 0, 0, -# 0, 0, 0, 0, 0, 0, -# 0, 0, 0, 0, 0, 0, -# 0, 0, 0, 0, 0, 0, -# -7) + def test_flip_board_bar(self): + board = (2, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + -7) -# expected_board = (7, -# 0, 0, 0, 0, 0, 0, -# 0, 0, 0, 0, 0, 0, -# 0, 0, 0, 0, 0, 0, -# 0, 0, 0, 0, 0, 0, -# -2) + expected_board = (7, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + -2) -# self.assertEqual(Board.flip(board), expected_board) + self.assertEqual(Board.flip(board), expected_board) -# def test_flip_board_extensive(self): -# board = (4, -# -5, -1, 0, 4, 3, 0, -# 0, -1, 0, -5, 0, 0, -# 0, 3, 0, 0, 0, 0, -# 0, 0, 0, -1, 0, 1, -# -2) + def test_flip_board_extensive(self): + board = (4, + -5, -1, 0, 4, 3, 0, + 0, -1, 0, -5, 0, 0, + 0, 3, 0, 0, 0, 0, + 0, 0, 0, -1, 0, 1, + -2) -# expected_board = (2, -# -1, 0, 1, 0, 0, 0, -# 0, 0, 0, 0, -3, 0, -# 0, 0, 5, 0, 1, 0, -# 0, -3, -4, 0, 1, 5, -# -4) + expected_board = (2, + -1, 0, 1, 0, 0, 0, + 0, 0, 0, 0, -3, 0, + 0, 0, 5, 0, 1, 0, + 0, -3, -4, 0, 1, 5, + -4) -# self.assertEqual(Board.flip(board), expected_board) + self.assertEqual(Board.flip(board), expected_board) -# def test_inverse(self): -# board = (4, -# -5, -1, 0, 4, 3, 0, -# 0, -1, 0, -5, 0, 0, -# 0, 3, 0, 0, 0, 0, -# 0, 0, 0, -1, 0, 1, -# -2) + def test_inverse(self): + board = (4, + -5, -1, 0, 4, 3, 0, + 0, -1, 0, -5, 0, 0, + 0, 3, 0, 0, 0, 0, + 0, 0, 0, -1, 0, 1, + -2) -# self.assertEqual(Board.flip(Board.flip(board)), board) + self.assertEqual(Board.flip(Board.flip(board)), board) -# def test_tesauro_initial(self): -# board = Board.initial_state + def test_tesauro_initial(self): + board = Board.initial_state -# expected = (1,1,0,0, -# 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, + expected = (1,1,0,0, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, -# 1,1,1,1, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 1,1,1,1, -# 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, -# 1,1,1,0, -# 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 1,1,1,0, + 0,0,0,0, -# 1,1,1,1, -# 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, + 1,1,1,1, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, -# 0.0, -# 0, + 0.0, + 0, -# 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, -# 1,1,1,1, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 1,1,1,1, -# 0,0,0,0, -# 1,1,1,0, -# 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, + 0,0,0,0, + 1,1,1,0, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, -# 1,1,1,1, -# 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, + 1,1,1,1, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, -# 1,1,0,0, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 1,1,0,0, -# 0.0, -# 0, + 0.0, + 0, -# 1, -# 0 -# ) + 1, + 0 + ) -# import numpy as np -# self.assertTrue((Board.board_features_tesauro(board, 1) == -# np.array(expected).reshape(1, 198)).all()) + import numpy as np + self.assertTrue((Board.board_features_tesauro(board, 1) == + np.array(expected).reshape(1, 198)).all()) -# def test_tesauro_bars(self): -# board = list(Board.initial_state) -# board[1] = 0 -# board[0] = 2 -# board[24] = 0 -# board[25] = -2 + def test_tesauro_bars(self): + board = list(Board.initial_state) + board[1] = 0 + board[0] = 2 + board[24] = 0 + board[25] = -2 -# board = tuple(board) + board = tuple(board) -# expected = (0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, + expected = (0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, -# 1,1,1,1, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 1,1,1,1, -# 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, -# 1,1,1,0, -# 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 1,1,1,0, + 0,0,0,0, -# 1,1,1,1, -# 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, + 1,1,1,1, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, -# 1.0, -# 0, + 1.0, + 0, -# 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, -# 1,1,1,1, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 1,1,1,1, -# 0,0,0,0, -# 1,1,1,0, -# 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, + 0,0,0,0, + 1,1,1,0, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, -# 1,1,1,1, -# 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, + 1,1,1,1, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, -# 1.0, -# 0, + 1.0, + 0, -# 1, -# 0 -# ) + 1, + 0 + ) -# import numpy as np -# self.assertTrue((Board.board_features_tesauro(board, 1) == -# np.array(expected).reshape(1, 198)).all()) + import numpy as np + self.assertTrue((Board.board_features_tesauro(board, 1) == + np.array(expected).reshape(1, 198)).all()) -# def test_tesauro_home(self): -# board = list(Board.initial_state) + def test_tesauro_home(self): + board = list(Board.initial_state) -# board[1] = 0 -# board[24] = 0 + board[1] = 0 + board[24] = 0 -# board = tuple(board) + board = tuple(board) -# expected = (0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, + expected = (0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, -# 1,1,1,1, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 1,1,1,1, -# 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, -# 1,1,1,0, -# 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 1,1,1,0, + 0,0,0,0, -# 1,1,1,1, -# 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, + 1,1,1,1, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, -# 0.0, -# 2, + 0.0, + 2, -# 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, -# 1,1,1,1, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 1,1,1,1, -# 0,0,0,0, -# 1,1,1,0, -# 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, + 0,0,0,0, + 1,1,1,0, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, -# 1,1,1,1, -# 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, + 1,1,1,1, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, -# 0.0, -# 2, + 0.0, + 2, -# 1, -# 0 -# ) + 1, + 0 + ) -# import numpy as np -# self.assertTrue((Board.board_features_tesauro(board, 1) == -# np.array(expected).reshape(1, 198)).all()) + import numpy as np + self.assertTrue((Board.board_features_tesauro(board, 1) == + np.array(expected).reshape(1, 198)).all()) -# def test_tesauro_black_player(self): -# board = Board.initial_state + def test_tesauro_black_player(self): + board = Board.initial_state -# expected = (1,1,0,0, -# 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, + expected = (1,1,0,0, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, -# 1,1,1,1, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 1,1,1,1, -# 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, -# 1,1,1,0, -# 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 1,1,1,0, + 0,0,0,0, -# 1,1,1,1, -# 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, + 1,1,1,1, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, -# 0.0, -# 0, + 0.0, + 0, -# 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, -# 1,1,1,1, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 1,1,1,1, -# 0,0,0,0, -# 1,1,1,0, -# 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, + 0,0,0,0, + 1,1,1,0, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, -# 1,1,1,1, -# 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, + 1,1,1,1, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, -# 0,0,0,0, -# 1,1,0,0, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 1,1,0,0, -# 0.0, -# 0, + 0.0, + 0, -# 0, -# 1 -# ) + 0, + 1 + ) -# import numpy as np -# self.assertTrue((Board.board_features_tesauro(board, -1) == -# np.array(expected).reshape(1, 198)).all()) + import numpy as np + self.assertTrue((Board.board_features_tesauro(board, -1) == + np.array(expected).reshape(1, 198)).all()) if __name__ == '__main__': From d932663519006e158dc3dbf4adbe9dd93f2e72b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoffer=20M=C3=BCller=20Madsen?= Date: Sun, 13 May 2018 22:26:24 +0200 Subject: [PATCH 2/2] add explanation of ply speedup --- report_docs.txt | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 report_docs.txt diff --git a/report_docs.txt b/report_docs.txt new file mode 100644 index 0000000..e9d68ca --- /dev/null +++ b/report_docs.txt @@ -0,0 +1,28 @@ + Alexander og jeg skrev noget af vores bachelorprojekt om til C her i fredags. + Man skal virkelig passe på sine hukommelsesallokeringer. + Ja, helt klart. + Jeg fandt et memory leak, der lækkede 100 MiB hukommelse i sekundet. + Hvilken del blev C-ificeret? + Damned + Årsagen var at vi gav et objekt med tilbage til Python uden at dekrementere dets ref-count, så fortolkeren stadig troede at nogen havde brug for det. + Den del af spillogikken, der tjekker om træk er gyldige. + Det bliver kaldt ret mange tusinde gange pr. spil, så vi tænkte at der måske kunne være lidt optimering at hente i at omskrive det til C. + Ok, så I har ikke selv brugt alloc og free. Det er alligevel noget. + Metoden selv blev 7 gange hurtigere! + Wow! + Jo. Det endte vi også med at gøre. + Vi havde brug for lister af variabel størrelse. Det endte med en struct med et "size" felt og et "list" felt. + Inkluderer det speedup, frem og tilbagen mellem C og python? + Det burde det gøre, ja! + Gjorde det nogen stor effekt for hvor hurtigt I kan evaluere? + Jeg tror ikke at der er særligt meget "frem og tilbage"-stads. Det ser ud til at det kode man skriver bliver kastet ret direkte ind i fortolkeren. + Det gjorde en stor forskel for når vi laver 1-ply. + "ply" er hvor mange træk man kigger fremad. + Så kun at kigge på det umiddelbart næste træk er 0-ply, hvilket er det vi har gjort indtil nu + 1-ply var for langsomt. Det tog ca. 6-7 sekunder at evaluere ét træk. + Alexander lavede lidt omskrivninger, så TensorFlow udregnede det hurtigere og fik det ned på ca. 3-4 sekunder *pr. spil*. + Så skrev vi noget af det om til C, og nu er vi så på ca. 2 sekunder pr. spil med 1-ply, hvilket er ret vildt. + Det er så godt at Python-fortolkeren kan udvides med C! + caspervk, kan I optimere jeres bachelorprojekt med et par C-moduler? + Det er en hel lille sektion til rapporten det der. + Yeah. Kopierer bare det her verbatim ind.