def get_move(self) -> chess.engine.PlayResult: """ Parses new move expected format <from_square><to_square> or a game end offer Prints the current position and waits for an input move. Input move should be in format <from_square><to_square> ie. e2e4 to move pawn to e4. The move should be legal in the current position :returns: Move played in engine plays format """ print(self._board) while True: txt = input("Set move:") txt = txt.replace(" ", "") draw = False if txt == "resign": self._resigned = True return engine.PlayResult(None, None, resigned=True) if "draw" in txt: draw = True txt = txt.replace("draw", "") try: move = chess.Move.from_uci(txt) if move in self._board.legal_moves: break else: print("not a legal move") except ValueError: print("Not parsed") self._board.push(move) return engine.PlayResult(move, None, draw_offered=draw)
def _hw_detect_move_player(self): """ Detect move from player We compare the expected occupancy with the actual occupancy. If two squares are different then we set the square where the clients piece used to be as the from square and the other square as the too square. Using this we create a candidate move (from_square, to_square). If this is a legal move then we will place this move in the output field. Note that the chess library will detect any other side effects from the move like captures, promotions and en passant. The internal board will be updated correctly and the diff will be marked on the board. """ draw_offer = False while not self.game_is_over(): occupancy = self._hwi.get_occupancy() diff = self._diff_occupancy_board(occupancy) self._hwi.mark_squares(diff) offers = self._hwi.game_end_offers() if offers is HardwareInterface.Offer.RESIGN: self._resigned = True return engine.PlayResult(None, None, resigned=True) if offers is HardwareInterface.Offer.DRAW: draw_offer = True # Detect all changed squares squares = [] for file in range(8): for rank in range(8): if diff[file][rank]: squares.append(chess.square(file, rank)) if len(squares) == 2: # Try to convert changed squares to legal move with self._board_lock: # Promotion not implemented if self._board.piece_at(squares[0]) is not None and \ self._board.piece_at(squares[0]).color == self.color: move = chess.Move(squares[0], squares[1]) elif self._board.piece_at(squares[1]) is not None and \ self._board.piece_at(squares[1]).color == self.color: move = chess.Move(squares[1], squares[0]) else: continue if self._board.piece_at(move.from_square).piece_type == chess.PAWN and \ ((self.color == chess.WHITE and chess.square_rank(move.to_square) == 7) or (self.color == chess.BLACK and chess.square_rank(move.to_square) == 0)): move.promotion = chess.QUEEN move_is_legal = move in self._board.legal_moves # save result to not use two locks if move_is_legal and move.promotion is not None: move.promotion = self._hwi.promotion_piece() if move_is_legal: with self._board_lock: self._board.push(move) with self._playResult_lock: self._output_playResult = engine.PlayResult(move, None, draw_offered=draw_offer) return self._update_display() sleep(SLEEP_TIME)
def test__hw_play_move_opponent_move_promote(self): """ Test promotion move """ self.hc._board.set_fen( "4k3/P7/8/8/8/8/8/4K3 w - - 0 1") # Lone pawn a7 self.hc._input_playResult = engine.PlayResult( chess.Move(chess.square(0, 6), chess.square(0, 7), chess.QUEEN), None) # New move a8Q # Configure occupancy for first, second and third call set_occupancy(self.hwi_mock, self.hc._board) occ1 = deepcopy(self.hwi_mock.get_occupancy.return_value) occ2 = deepcopy(self.hwi_mock.get_occupancy.return_value) occ2[0][6] = False occ2[0][7] = True self.hwi_mock.get_occupancy.side_effect = [occ1, occ2] # Itr 1 no move, itr 2 promoted piece, itr 3 terminate with patch.object(HardwareClient.HardwareClient, 'game_is_over') as game_over_mock: game_over_mock.side_effect = [False, False, True] self.hc._hw_play_move_opponent() self.assertIsNone(self.hc._input_playResult, "Input move has not been processed correctly") # Check that in first iterations a7 and a8 are marked marking2 = [[False] * 8 for _ in range(8)] marking1 = deepcopy(marking2) marking1[0][6] = True marking1[0][7] = True self.hwi_mock.mark_squares.assert_has_calls( [call(marking1), call(marking2)], "Incorrect squares have been marked")
def test__hw_play_move_opponent_move_castle(self): """ Test castle input move """ self.hc._board.set_fen("4k3/8/8/8/8/8/8/4K2R w K - 0 1") self.hc._input_playResult = engine.PlayResult( chess.Move(chess.square(4, 0), chess.square(6, 0)), None) # New move O-O # Configure occupancy for first, second and third call set_occupancy(self.hwi_mock, self.hc._board) occ1 = deepcopy(self.hwi_mock.get_occupancy.return_value) occ2 = deepcopy(self.hwi_mock.get_occupancy.return_value) occ2[4][0] = False occ2[6][0] = True self.hwi_mock.get_occupancy.side_effect = [occ1, occ2] # Itr 1 no move, itr 2 move, itr 4 terminate with patch.object(HardwareClient.HardwareClient, 'game_is_over') as game_over_mock: game_over_mock.side_effect = [False, False, True] self.hc._hw_play_move_opponent() self.assertIsNone(self.hc._input_playResult, "Input move has not been processed correctly") # Check that in first iteration e1 and g1 are marked and next iteration f1 and h1 are marked marking1 = [[False] * 8 for _ in range(8)] marking2 = deepcopy(marking1) marking1[4][0] = True marking1[6][0] = True marking2[7][ 0] = True # Rook move still needs to be played but king move is sufficient to finish method marking2[5][0] = True self.hwi_mock.mark_squares.assert_has_calls( [call(marking1), call(marking2)], "Incorrect squares have been marked")
def test__hw_play_move_opponent_move_normal(self): """ Test normal input move """ self.hc._input_playResult = engine.PlayResult( chess.Move(chess.square(0, 1), chess.square(0, 3)), None) # New move pawn a4 # Configure occupancy for first and second call set_occupancy(self.hwi_mock, self.hc._board) occ1 = deepcopy(self.hwi_mock.get_occupancy.return_value) occ2 = deepcopy(self.hwi_mock.get_occupancy.return_value) occ2[0][1] = False occ2[0][3] = True self.hwi_mock.get_occupancy.side_effect = [occ1, occ2] # Play move itr 1 no move, itr 2 move made, itr 3 terminate with patch.object(HardwareClient.HardwareClient, 'game_is_over') as game_over_mock: game_over_mock.side_effect = [False, False, True] self.hc._hw_play_move_opponent() self.assertIsNone(self.hc._input_playResult, "Input move has not been processed correctly") # Check that in first iteration a2 and a4 are marked and next iteration no squares are marked marking2 = [[False] * 8 for _ in range(8)] marking1 = deepcopy(marking2) marking1[0][1] = True marking1[0][3] = True self.hwi_mock.mark_squares.assert_has_calls( [call(marking1), call(marking2)], "Incorrect squares have been marked")
def test_get_move(self): """ Test get_move method""" play_result = engine.PlayResult( chess.Move(chess.square(0, 1), chess.square(0, 3)), None) self.hc._output_playResult = play_result self.assertEqual(self.hc.get_move(), play_result, "Output move not returned") self.assertEqual(self.hc._output_playResult, None, "Internal output field not cleared")
def test__hw_control_case_3(self): """ Test _hw_control wait for opponent move to be played on hardware""" self.hc._board.turn = chess.BLACK self.hc.color = chess.WHITE self.hc._input_playResult = engine.PlayResult( chess.Move(chess.square(0, 1), chess.square(0, 3)), None) with patch.object(HardwareClient.HardwareClient, 'game_is_over') as game_over_mock: game_over_mock.side_effect = [False, True] with patch.object(HardwareClient.HardwareClient, '_hw_play_move_opponent') as target_mock: self.hc._hw_control() self.assertTrue(target_mock.called, "Expected method was not called")
def test_set_move(self): """ Test set_move method """ input_pr = engine.PlayResult( chess.Move(chess.square(0, 1), chess.square(0, 3)), None) self.hc.set_move(input_pr) internal_pr = self.hc._input_playResult self.assertNotEqual( internal_pr, input_pr, "HC should not reuse input object for threadsafety") # Move overrides __eq__ check if move is a new object input_pr.move.to_square = chess.square(0, 2) self.assertNotEqual( internal_pr.move, input_pr.move, "HC should not reuse input object for threadsafety")
def test__hw_play_move_opponent_move_capture(self): """ Test capture input move """ self.hc._board.push(chess.Move(chess.square(1, 1), chess.square(1, 3))) # b4 self.hc._board.push(chess.Move(chess.square(0, 6), chess.square(0, 4))) # a5 self.hc._input_playResult = engine.PlayResult( chess.Move(chess.square(1, 3), chess.square(0, 4)), None) # New move capture a5 # Configure occupancy for first, second and third call set_occupancy(self.hwi_mock, self.hc._board) occ1 = deepcopy(self.hwi_mock.get_occupancy.return_value) occ2 = deepcopy(self.hwi_mock.get_occupancy.return_value) occ3 = deepcopy(self.hwi_mock.get_occupancy.return_value) occ2[1][3] = False occ2[0][4] = False occ3[1][3] = False occ3[0][4] = True self.hwi_mock.get_occupancy.side_effect = [occ1, occ2, occ3] # Itr 1 no move, itr 2 remove captured piece, itr3 add new piece, itr 4 terminate with patch.object(HardwareClient.HardwareClient, 'game_is_over') as game_over_mock: game_over_mock.side_effect = [False, False, False, True] self.hc._hw_play_move_opponent() self.assertIsNone(self.hc._input_playResult, "Input move has not been processed correctly") # Check that in first two iterations b4 and a5 are marked and next iteration no squares are marked marking3 = [[False] * 8 for _ in range(8)] marking1 = deepcopy(marking3) marking1[1][3] = True marking1[0][4] = True marking2 = deepcopy(marking1) self.hwi_mock.mark_squares.assert_has_calls( [call(marking1), call(marking2), call(marking3)], "Incorrect squares have been marked")
def test__hw_play_move_opponent_move_None(self): """ Test if method does not crash when input is not a move """ self.hc._input_playResult = engine.PlayResult(None, None, resigned=True) self.hc._hw_play_move_opponent()
def test__hw_wait_move_opponent(self): """ Test if method terminates once input is given """ self.hc._input_playResult = engine.PlayResult( chess.Move(chess.square(0, 1), chess.square(0, 3)), None) self.hc._hw_wait_move_opponent()