...
 
Commits (7)
......@@ -9,7 +9,7 @@
# --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,fixme,duplicate-code
disable=locally-enabled,locally-disabled,redefined-builtin,fixme,duplicate-code,too-few-public-methods
[REPORTS]
......
......@@ -75,7 +75,7 @@ En Pyssant has a few core data types::
>>> white_pawn = Piece(Type.PAWN, Side.WHITE)
>>> white_pawn
Piece(type=<Type.PAWN: 'p'>, side=<Side.WHITE: 1>)
Piece(type='p', side='w')
>>> a1 = Square('a1')
>>> a1.up().up()
'a3'
......@@ -90,7 +90,7 @@ partial `Forsyth-Edwards Notation (FEN)
>>> DictBoard.from_fen('rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR')
rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR
>>> board[a1]
Piece(type=<Type.ROOK: 'r'>, side=<Side.WHITE: 1>)
Piece(type='r', side='w')
>>> print(board['a3'])
None
>>> board.put('a3', white_pawn)
......@@ -207,10 +207,10 @@ Mate <https://en.wikipedia.org/wiki/Fool%27s_mate>`_::
3 . . . . . P . .
2 P P P P P . . P
1 R N B Q K B N R
>>> game.is_gameover()
<Gameover.CHECKMATE: 1>
>>> bool(game.is_gameover())
True
>>> game.winner()
<Side.BLACK: 0>
'b'
>>> assert len(game.history) == 4
You can also export (and import) the game as `Portable Game Notation
......@@ -223,7 +223,7 @@ You can also export (and import) the game as `Portable Game Notation
1. f3 e5 2. g4 Qh4# 0-1
>>> new_game = Game.from_pgn(pgn)
>>> new_game.winner()
<Side.BLACK: 0>
'b'
The simplest way to play a complete game of chess::
......
......@@ -72,7 +72,7 @@ class Board(metaclass=ABCMeta):
file_position += 1
else:
piece = Piece(
Type(char.lower()),
char.lower(),
Side.WHITE if char.isupper() else Side.BLACK)
board = board.put(
'{}{}'.format(files[file_position], rank),
......@@ -174,9 +174,9 @@ class Board(metaclass=ABCMeta):
if counter:
new_chunk.append(str(counter))
counter = 0
new_chunk.append(piece.type.value.upper()
new_chunk.append(piece.type.upper()
if piece.side == Side.WHITE
else piece.type.value)
else piece.type)
else:
counter += 1
if counter:
......@@ -264,7 +264,7 @@ for _char, _square in zip(_INITIAL_STRINGBOARD, ALL_SQUARES):
if _char == ' ':
_piece = None
else:
_piece = Piece(Type(_char.lower()), Side.WHITE if _char.isupper() else
_piece = Piece(_char.lower(), Side.WHITE if _char.isupper() else
Side.BLACK)
_INITIAL_DICTBOARD[_square] = _piece
_INITIAL_LISTBOARD.append(_piece)
......@@ -380,7 +380,7 @@ class BytesBoard(Board):
letter = bytes((i,))
if letter == b' ':
return None
rtype = Type(letter.lower().decode('ascii'))
rtype = letter.lower().decode('ascii')
rside = Side.WHITE if letter.isupper() else Side.BLACK
return Piece(rtype, rside)
......@@ -452,7 +452,7 @@ class StringBoard(Board):
letter = self._board[ALGEBRAIC_TO_INDEX_MAP[square]]
if letter == ' ':
return None
rtype = Type(letter.lower())
rtype = letter.lower()
rside = Side.WHITE if letter.isupper() else Side.BLACK
return Piece(rtype, rside)
......
......@@ -28,7 +28,6 @@ Safe to assume that all submodules depend on this module.
import re
from collections import namedtuple
from enum import Enum
from functools import lru_cache
from types import MethodType
from typing import Sequence, Set, Union
......@@ -45,7 +44,7 @@ def stripped_san(san: str) -> str:
return re.sub(SAN_END_PATTERN, '', san)
class Type(Enum):
class Type:
"""Type of piece."""
KING = 'k'
QUEEN = 'q'
......@@ -55,10 +54,10 @@ class Type(Enum):
PAWN = 'p'
class Side(Enum):
class Side:
"""Colours corresponding to sides."""
WHITE = 1
BLACK = 0
WHITE = 'w'
BLACK = 'b'
class Piece(namedtuple('Piece', ['type', 'side'])):
......@@ -84,11 +83,11 @@ class Piece(namedtuple('Piece', ['type', 'side'])):
def __str__(self):
if self.side == Side.WHITE:
return self.type.value.upper()
return self.type.value.lower()
return self.type.upper()
return self.type.lower()
class Direction(Enum):
class Direction:
"""General four directions."""
# (file, rank)
UP = (0, 1) # pylint: disable=invalid-name
......@@ -147,15 +146,15 @@ class Square(str):
:return: One square in the given direction.
:raise IndexError: Destination out of bounds.
"""
offset = direction.value
offset = direction
dest = '{}{}'.format(
chr(ord(self.file) + offset[0]),
self.rank + offset[1])
try:
return self.__class__('{}{}'.format(
chr(ord(self.file) + offset[0]),
self.rank + offset[1]
))
return self.__class__(dest)
except ValueError:
raise IndexError('Cannot go to {} from {}'.format(direction.name,
repr(self)))
raise IndexError('Cannot go to {} from {}'.format(
dest, repr(self)))
def up(self) -> 'Square': # pylint: disable=invalid-name
""":return: One square up.
......@@ -200,7 +199,7 @@ class Square(str):
"""
balance = (0, 0) # Total offset from origin.
for direction in path:
offset = direction.value
offset = direction
balance = tuple(map(sum, zip(balance, offset)))
file_ = chr(ord(self.file) + balance[0])
rank = self.rank + balance[1]
......@@ -209,7 +208,7 @@ class Square(str):
return False
class MoveFlag(Enum):
class MoveFlag:
"""Flags associated with a move."""
NON_CAPTURE = 0
STANDARD_CAPTURE = 1
......@@ -302,7 +301,7 @@ class Move(namedtuple('Move', ['origin', 'destination', 'piece', 'captured',
if self.piece.type != Type.PAWN:
disambiguator = self._disambiguate_san(position, ruleset)
# TODO: Make following line nicer.
piece = self.piece.type.value.upper()
piece = self.piece.type.upper()
result += '{}{}'.format(piece, disambiguator)
if self.captured:
if self.piece.type == Type.PAWN:
......@@ -310,7 +309,7 @@ class Move(namedtuple('Move', ['origin', 'destination', 'piece', 'captured',
result += 'x'
result += self.destination
if MoveFlag.PROMOTION in self.flags:
result += self.promotion.value.upper()
result += self.promotion.upper()
new_position = ruleset.do_move(position, self)
if ruleset.is_check(new_position):
......@@ -362,9 +361,9 @@ class Move(namedtuple('Move', ['origin', 'destination', 'piece', 'captured',
or move.destination != destination
or (file and move.origin.file != file)
or (rank and str(move.origin.rank) != rank)
or (piece and move.piece.type != Type(piece.lower()))
or (piece and move.piece.type != piece.lower())
or (promotion
and move.promotion != Type(promotion.lower()))
and move.promotion != promotion.lower())
or (not piece and move.piece.type != Type.PAWN)):
continue
if not promotion and move.promotion:
......@@ -412,7 +411,7 @@ class HistoryRecord(namedtuple('HistoryRecord', ['position', 'move'])):
return super().__new__(cls, position, move)
class Gameover(Enum):
class Gameover:
"""How a game has ended. There is no value for 'game has not ended'."""
CHECKMATE = 1
STALEMATE = 2
......
......@@ -75,9 +75,9 @@ class Castling(namedtuple('Castling', ['white', 'black'])):
def __getitem__(self, key):
if key == Side.WHITE:
return self.white
key = 0
elif key == Side.BLACK:
return self.black
key = 1
return super().__getitem__(key)
......
......@@ -39,7 +39,7 @@ def opponent(side: Side) -> Side:
:param side: Proponent side.
"""
return Side(not side.value)
return 'w' if side == 'b' else 'b'
def validate_fen_board(fen: str) -> bool:
......
......@@ -241,7 +241,9 @@ class TestMoves:
def test_castling_regular(self, b):
"""Test if castling moves are returned by :func:`moves`."""
for side in Side:
for name, side in Side.__dict__.items():
if name.startswith('_'):
continue
position = Position(board=b.CASTLING_BOARD, side_to_play=side)
legal_moves = list(moves(position))
home_rank = 8 if side == Side.BLACK else 1
......@@ -259,7 +261,9 @@ class TestMoves:
def test_castling_king_in_check(self, b):
"""Cannot castle when king is in check."""
for side in Side:
for name, side in Side.__dict__.items():
if name.startswith('_'):
continue
board = b.CASTLING_BOARD.put(
'e4', Piece(Type.ROOK, opponent(side))
)
......@@ -271,7 +275,9 @@ class TestMoves:
def test_castling_path_in_check(self, b):
"""Cannot castle when path (or destination) is in check."""
for side in Side:
for name, side in Side.__dict__.items():
if name.startswith('_'):
continue
square_map = {
'e2': MoveFlag.QUEENSIDE_CASTLING,
'e3': MoveFlag.QUEENSIDE_CASTLING,
......@@ -302,7 +308,9 @@ class TestMoves:
def test_en_passant(self, b):
"""En passant moves are correctly registered as legal moves."""
for side in Side:
for name, side in Side.__dict__.items():
if name.startswith('_'):
continue
squares = ('f6', 'h6') if side == Side.WHITE else ('a3', 'c3')
for square in squares:
position = Position(
......
......@@ -149,7 +149,7 @@ def test_san_promotion(san_position):
"""Return promotion notation."""
types = [Type.QUEEN, Type.ROOK, Type.BISHOP, Type.KNIGHT]
for type_ in types:
san = 'h8{}'.format(type_.value.upper())
san = 'h8{}'.format(type_.upper())
move = Move('h7', 'h8', promotion=type_)
move = move.expand(san_position, rules)
assert move.san(san_position, rules) == san
......
......@@ -67,7 +67,7 @@ def test_put_a1(board):
def test_correct_initial_board(board):
"""The board matches an initial chess board."""
pieces = [Type(char) for char in 'rnbqkbnrpppppppp']
pieces = [char for char in 'rnbqkbnrpppppppp']
# Black and empty squares
for i, (_, piece) in enumerate(board.all_pieces()):
if i < 16:
......@@ -108,8 +108,12 @@ def test_clear(board):
def test_put_all_pieces(board):
"""Board.put can put all pieces of both sides on all squares."""
for square in ALL_SQUARES:
for side in Side:
for type_ in Type:
for side_name, side in Side.__dict__.items():
if side_name.startswith('_'):
continue
for type_name, type_ in Type.__dict__.items():
if type_name.startswith('_'):
continue
piece = Piece(type_, side)
assert board.put(square, piece)[square] == piece
......