@@ -165,6 +165,18 @@ def result(self) -> str:
165165 return "1/2-1/2" if self .winner is None else ("1-0" if self .winner else "0-1" )
166166
167167
168+ class InvalidMoveError (ValueError ):
169+ """Raised when the attempted move is invalid in the current position"""
170+
171+
172+ class IllegalMoveError (ValueError ):
173+ """Raised when the attempted move is illegal in the current position"""
174+
175+
176+ class AmbiguousMoveError (ValueError ):
177+ """Raised when the attempted move is ambiguous in the current position"""
178+
179+
168180Square = int
169181SQUARES = [
170182 A1 , B1 , C1 , D1 , E1 , F1 , G1 , H1 ,
@@ -551,23 +563,29 @@ def from_uci(cls, uci: str) -> Move:
551563 """
552564 Parses a UCI string.
553565
554- :raises: :exc:`ValueError ` if the UCI string is invalid.
566+ :raises: :exc:`InvalidMoveError ` if the UCI string is invalid.
555567 """
556568 if uci == "0000" :
557569 return cls .null ()
558570 elif len (uci ) == 4 and "@" == uci [1 ]:
559- drop = PIECE_SYMBOLS .index (uci [0 ].lower ())
560- square = SQUARE_NAMES .index (uci [2 :])
571+ try :
572+ drop = PIECE_SYMBOLS .index (uci [0 ].lower ())
573+ square = SQUARE_NAMES .index (uci [2 :])
574+ except ValueError :
575+ raise InvalidMoveError (f"invalid uci: { uci !r} " )
561576 return cls (square , square , drop = drop )
562577 elif 4 <= len (uci ) <= 5 :
563- from_square = SQUARE_NAMES .index (uci [0 :2 ])
564- to_square = SQUARE_NAMES .index (uci [2 :4 ])
565- promotion = PIECE_SYMBOLS .index (uci [4 ]) if len (uci ) == 5 else None
578+ try :
579+ from_square = SQUARE_NAMES .index (uci [0 :2 ])
580+ to_square = SQUARE_NAMES .index (uci [2 :4 ])
581+ promotion = PIECE_SYMBOLS .index (uci [4 ]) if len (uci ) == 5 else None
582+ except ValueError :
583+ raise InvalidMoveError (f"invalid uci: { uci !r} " )
566584 if from_square == to_square :
567- raise ValueError (f"invalid uci (use 0000 for null moves): { uci !r} " )
585+ raise InvalidMoveError (f"invalid uci (use 0000 for null moves): { uci !r} " )
568586 return cls (from_square , to_square , promotion = promotion )
569587 else :
570- raise ValueError (f"expected uci string to be of length 4 or 5: { uci !r} " )
588+ raise InvalidMoveError (f"expected uci string to be of length 4 or 5: { uci !r} " )
571589
572590 @classmethod
573591 def null (cls ) -> Move :
@@ -2305,14 +2323,14 @@ def find_move(self, from_square: Square, to_square: Square, promotion: Optional[
23052323 Castling moves are normalized to king moves by two steps, except in
23062324 Chess960.
23072325
2308- :raises: :exc:`ValueError ` if no matching legal move is found.
2326+ :raises: :exc:`IllegalMoveError ` if no matching legal move is found.
23092327 """
23102328 if promotion is None and self .pawns & BB_SQUARES [from_square ] and BB_SQUARES [to_square ] & BB_BACKRANKS :
23112329 promotion = QUEEN
23122330
23132331 move = self ._from_chess960 (self .chess960 , from_square , to_square , promotion )
23142332 if not self .is_legal (move ):
2315- raise ValueError (f"no matching legal move for { move .uci ()} ({ SQUARE_NAMES [from_square ]} -> { SQUARE_NAMES [to_square ]} ) in { self .fen ()} " )
2333+ raise IllegalMoveError (f"no matching legal move for { move .uci ()} ({ SQUARE_NAMES [from_square ]} -> { SQUARE_NAMES [to_square ]} ) in { self .fen ()} " )
23162334
23172335 return move
23182336
@@ -2938,14 +2956,14 @@ def variation_san(self, variation: Iterable[Move]) -> str:
29382956
29392957 The board will not be modified as a result of calling this.
29402958
2941- :raises: :exc:`ValueError ` if any moves in the sequence are illegal.
2959+ :raises: :exc:`IllegalMoveError ` if any moves in the sequence are illegal.
29422960 """
29432961 board = self .copy (stack = False )
29442962 san = []
29452963
29462964 for move in variation :
29472965 if not board .is_legal (move ):
2948- raise ValueError (f"illegal move { move } in position { board .fen ()} " )
2966+ raise IllegalMoveError (f"illegal move { move } in position { board .fen ()} " )
29492967
29502968 if board .turn == WHITE :
29512969 san .append (f"{ board .fullmove_number } . { board .san_and_push (move )} " )
@@ -2966,7 +2984,11 @@ def parse_san(self, san: str) -> Move:
29662984
29672985 The returned move is guaranteed to be either legal or a null move.
29682986
2969- :raises: :exc:`ValueError` if the SAN is invalid, illegal or ambiguous.
2987+ :raises:
2988+ :exc:`ValueError` (or specifically an exception specified below) if the SAN is invalid, illegal or ambiguous.
2989+ - :exc:`InvalidMoveError` if the SAN is invalid.
2990+ - :exc:`IllegalMoveError` if the SAN is illegal.
2991+ - :exc:`AmbiguousMoveError` if the SAN is ambiguous.
29702992 """
29712993 # Castling.
29722994 try :
@@ -2975,7 +2997,7 @@ def parse_san(self, san: str) -> Move:
29752997 elif san in ["O-O-O" , "O-O-O+" , "O-O-O#" , "0-0-0" , "0-0-0+" , "0-0-0#" ]:
29762998 return next (move for move in self .generate_castling_moves () if self .is_queenside_castling (move ))
29772999 except StopIteration :
2978- raise ValueError (f"illegal san: { san !r} in { self .fen ()} " )
3000+ raise IllegalMoveError (f"illegal san: { san !r} in { self .fen ()} " )
29793001
29803002 # Match normal moves.
29813003 match = SAN_REGEX .match (san )
@@ -2984,9 +3006,9 @@ def parse_san(self, san: str) -> Move:
29843006 if san in ["--" , "Z0" , "0000" , "@@@@" ]:
29853007 return Move .null ()
29863008 elif "," in san :
2987- raise ValueError (f"unsupported multi-leg move: { san !r} " )
3009+ raise InvalidMoveError (f"unsupported multi-leg move: { san !r} " )
29883010 else :
2989- raise ValueError (f"invalid san: { san !r} " )
3011+ raise InvalidMoveError (f"invalid san: { san !r} " )
29903012
29913013 # Get target square. Mask our own pieces to exclude castling moves.
29923014 to_square = SQUARE_NAMES .index (match .group (4 ))
@@ -3016,7 +3038,7 @@ def parse_san(self, san: str) -> Move:
30163038 if move .promotion == promotion :
30173039 return move
30183040 else :
3019- raise ValueError (f"missing promotion piece type: { san !r} in { self .fen ()} " )
3041+ raise IllegalMoveError (f"missing promotion piece type: { san !r} in { self .fen ()} " )
30203042 else :
30213043 from_mask &= self .pawns
30223044
@@ -3031,12 +3053,12 @@ def parse_san(self, san: str) -> Move:
30313053 continue
30323054
30333055 if matched_move :
3034- raise ValueError (f"ambiguous san: { san !r} in { self .fen ()} " )
3056+ raise AmbiguousMoveError (f"ambiguous san: { san !r} in { self .fen ()} " )
30353057
30363058 matched_move = move
30373059
30383060 if not matched_move :
3039- raise ValueError (f"illegal san: { san !r} in { self .fen ()} " )
3061+ raise IllegalMoveError (f"illegal san: { san !r} in { self .fen ()} " )
30403062
30413063 return matched_move
30423064
@@ -3047,7 +3069,11 @@ def push_san(self, san: str) -> Move:
30473069
30483070 Returns the move.
30493071
3050- :raises: :exc:`ValueError` if neither legal nor a null move.
3072+ :raises:
3073+ :exc:`ValueError` (or specifically an exception specified below) if neither legal nor a null move.
3074+ - :exc:`InvalidMoveError` if the SAN is invalid.
3075+ - :exc:`IllegalMoveError` if the SAN is illegal.
3076+ - :exc:`AmbiguousMoveError` if the SAN is ambiguous.
30513077 """
30523078 move = self .parse_san (san )
30533079 self .push (move )
@@ -3075,8 +3101,11 @@ def parse_uci(self, uci: str) -> Move:
30753101
30763102 The returned move is guaranteed to be either legal or a null move.
30773103
3078- :raises: :exc:`ValueError` if the move is invalid or illegal in the
3104+ :raises:
3105+ :exc:`ValueError` (or specifically an exception specified below) if the move is invalid or illegal in the
30793106 current position (but not a null move).
3107+ - :exc:`InvalidMoveError` if the UCI is invalid.
3108+ - :exc:`IllegalMoveError` if the UCI is illegal.
30803109 """
30813110 move = Move .from_uci (uci )
30823111
@@ -3087,7 +3116,7 @@ def parse_uci(self, uci: str) -> Move:
30873116 move = self ._from_chess960 (self .chess960 , move .from_square , move .to_square , move .promotion , move .drop )
30883117
30893118 if not self .is_legal (move ):
3090- raise ValueError (f"illegal uci: { uci !r} in { self .fen ()} " )
3119+ raise IllegalMoveError (f"illegal uci: { uci !r} in { self .fen ()} " )
30913120
30923121 return move
30933122
@@ -3097,8 +3126,11 @@ def push_uci(self, uci: str) -> Move:
30973126
30983127 Returns the move.
30993128
3100- :raises: :exc:`ValueError` if the move is invalid or illegal in the
3129+ :raises:
3130+ :exc:`ValueError` (or specifically an exception specified below) if the move is invalid or illegal in the
31013131 current position (but not a null move).
3132+ - :exc:`InvalidMoveError` if the UCI is invalid.
3133+ - :exc:`IllegalMoveError` if the UCI is illegal.
31023134 """
31033135 move = self .parse_uci (uci )
31043136 self .push (move )
0 commit comments