Implement Move.from_san

parent e0c44876
Pipeline #12562210 passed with stages
in 29 minutes and 6 seconds
......@@ -19,7 +19,7 @@ jobs=4
# --enable=similarities". If you want to run only the classes checker, but have
# no Warning level messages displayed, use"--disable=all --enable=classes
# --disable=W"
disable=locally-enabled,locally-disabled,redefined-builtin
disable=locally-enabled,locally-disabled,redefined-builtin,fixme
[FORMAT]
......
......@@ -306,6 +306,33 @@ class Move(namedtuple('Move', ['origin', 'destination', 'piece', 'captured',
return result
@classmethod
def from_san(cls, position, ruleset, san):
"""Return a :class:`Move` object from a Standard Algebraic Notation
string.
:param position: Chess position _before_ the move is performed.
:type position: :class:`Position`
:param ruleset: Game ruleset.
:type ruleset: Anything that matches the public interface of
:mod:`~en_pyssant.rules`
:param str san: Standard Algebraic Notation.
:return: A move.
:rtype: :class:`Move`
:raise ValueError: *san* is invalid.
"""
# A very cheeky implementation. Generate all moves, generate their
# notations, and compare against input *san*. If they match, we have
# found our move.
#
# TODO: Rework this more nicely.
for move in ruleset.moves(position):
move_san = move.san(position, ruleset)
if move_san == san:
return move
raise ValueError('{!r} is not a valid move'.format(san))
def _disambiguate_san(self, position, ruleset):
"""Find the *origin* coordinates (either file or rank or both) that
might be necessary to disambiguate a SAN move.
......
......@@ -551,78 +551,101 @@ class TestMove:
def test_san_simple_pawn_move(self, b):
"""Return solely the destination of a simple pawn move."""
san = 'a3'
position = Position(board=b.INITIAL_BOARD)
move = Move('a2', 'a3')
move = expand_move(rules.moves(position), move)
assert move.san(position, rules) == 'a3'
assert move.san(position, rules) == san
assert move == Move.from_san(position, rules, san)
def test_san_simple_pawn_move_push(self, b):
"""Return solely the destination of a simple pawn push."""
san = 'a4'
position = Position(board=b.INITIAL_BOARD)
move = Move('a2', 'a4')
move = expand_move(rules.moves(position), move)
assert move.san(position, rules) == 'a4'
assert move.san(position, rules) == san
assert move == Move.from_san(position, rules, san)
def test_san_simple_knight_move(self, b):
"""Return the type followed by destination."""
san = 'Na3'
position = Position(board=b.INITIAL_BOARD)
move = Move('b1', 'a3')
move = expand_move(rules.moves(position), move)
assert move.san(position, rules) == 'Na3'
assert move.san(position, rules) == san
assert move == Move.from_san(position, rules, san)
def test_san_en_passant_capture(self, b):
"""Return pawn capture notation."""
san = 'gxh6'
position = Position(board=b.EN_PASSANT_BOARD, en_passant_target='h6')
move = Move('g5', 'h6')
move = expand_move(rules.moves(position), move)
assert move.san(position, rules) == 'gxh6'
assert move.san(position, rules) == san
assert move == Move.from_san(position, rules, san)
def test_san_kingside_castling_move(self, b):
"""Return kingside castling notation."""
san = 'O-O'
position = Position(board=b.CASTLING_BOARD)
move = Move('e1', 'g1')
move = expand_move(rules.moves(position), move)
assert move.san(position, rules) == 'O-O'
assert move.san(position, rules) == san
assert move == Move.from_san(position, rules, san)
def test_san_queenside_castling_move(self, b):
"""Return queenside castling notation."""
san = 'O-O-O'
position = Position(board=b.CASTLING_BOARD)
move = Move('e1', 'c1')
move = expand_move(rules.moves(position), move)
assert move.san(position, rules) == 'O-O-O'
assert move.san(position, rules) == san
assert move == Move.from_san(position, rules, san)
def test_san_check(self, san_position):
"""Return check notation."""
san = 'Rxc7+'
move = Move('c2', 'c7')
move = expand_move(rules.moves(san_position), move)
assert move.san(san_position, rules) == 'Rxc7+'
assert move.san(san_position, rules) == san
assert move == Move.from_san(san_position, rules, san)
def test_san_checkmate(self, san_position):
"""Return checkmate notation."""
san = 'Rxa2#'
move = Move('c2', 'a2')
move = expand_move(rules.moves(san_position), move)
assert move.san(san_position, rules) == 'Rxa2#'
assert move.san(san_position, rules) == san
assert move == Move.from_san(san_position, rules, san)
def test_san_disambiguous_file_move(self, san_position):
"""Return file disambiguator notation."""
san = 'B7xg6'
move = Move('f7', 'g6')
move = expand_move(rules.moves(san_position), move)
assert move.san(san_position, rules) == 'B7xg6'
assert move.san(san_position, rules) == san
assert move == Move.from_san(san_position, rules, san)
def test_san_disambiguous_rank_move(self, san_position):
"""Return rank disambiguator notation."""
san = 'Bhxg6'
move = Move('h5', 'g6')
move = expand_move(rules.moves(san_position), move)
assert move.san(san_position, rules) == 'Bhxg6'
assert move.san(san_position, rules) == san
assert move == Move.from_san(san_position, rules, san)
def test_san_doubly_disambiguous_move(self, san_position):
"""Return rank and file disambiguator notation."""
san = 'Bf5xg6'
move = Move('f5', 'g6')
move = expand_move(rules.moves(san_position), move)
assert move.san(san_position, rules) == 'Bf5xg6'
assert move.san(san_position, rules) == san
assert move == Move.from_san(san_position, rules, san)
def test_san_castlemate_move(self, b):
"""Return both castling and checkmate notation."""
san = 'O-O#'
position = Position(board=b.CASTLEMATE_BOARD,
castling={
Side.WHITE: CastlingAvailability(True, True),
......@@ -630,16 +653,18 @@ class TestMove:
})
move = Move('e1', 'g1')
move = expand_move(rules.moves(position), move)
assert move.san(position, rules) == 'O-O#'
assert move.san(position, rules) == san
assert move == Move.from_san(position, rules, san)
def test_san_promotion(self, san_position):
"""Return promotion notation."""
types = [Type.QUEEN, Type.ROOK, Type.BISHOP, Type.KNIGHT]
for type_ in types:
san = 'h8{}'.format(type_.value.upper())
move = Move('h7', 'h8', promotion=type_)
move = expand_move(rules.moves(san_position), move)
assert move.san(san_position, rules) == 'h8{}'.format(
type_.value.upper())
assert move.san(san_position, rules) == san
assert move == Move.from_san(san_position, rules, san)
def side_effect(*args, **kwargs):
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment