LordDarkula/chess_py

View on GitHub
chess_py/pieces/pawn.py

Summary

Maintainability
B
4 hrs
Test Coverage
# -*- coding: utf-8 -*-

"""
Class stores Pawn on the board

| rank
| 7 8 ║♜ ♞ ♝ ♛ ♚ ♝ ♞ ♜
| 6 7 ║♟ ♟ ♟ ♟ ♟ ♟ ♟ ♟
| 5 6 ║… … … … … … … …
| 4 5 ║… … … … … … … …
| 3 4 ║… … … … … … … …
| 2 3 ║… … … … … … … …
| 1 2 ║♙ ♙ ♙ ♙ ♙ ♙ ♙ ♙
| 0 1 ║♖ ♘ ♗ ♕ ♔ ♗ ♘ ♖
| ----╚═══════════════
| ——---a b c d e f g h
| -----0 1 2 3 4 5 6 7
| ------file

| Copyright © 2016 Aubhro Sengupta. All rights reserved.
"""
import itertools

from .bishop import Bishop
from .piece import Piece
from .queen import Queen
from .rook import Rook
from .knight import Knight
from ..core import color
from ..core.algebraic import notation_const
from ..core.algebraic.move import Move


class Pawn(Piece):
    def __init__(self, input_color, location):
        """
        Initializes a Pawn that is capable of moving

        :type: input_color: Color
        :type: location: Location
        """
        self.just_moved_two_steps = False
        super(Pawn, self).__init__(input_color, location)

    def _symbols(self):
        return {color.white: "♟", color.black: "♙"}

    def __str__(self):
        return "P"

    def on_home_row(self, location=None):
        """
        Finds out if the piece is on the home row.

        :return: bool for whether piece is on home row or not
        """
        location = location or self.location
        return (self.color == color.white and location.rank == 1) or \
               (self.color == color.black and location.rank == 6)

    def would_move_be_promotion(self, location=None):
        """
        Finds if move from current get_location would result in promotion

        :type: location: Location
        :rtype: bool
        """
        location = location or self.location
        return (location.rank == 1 and self.color == color.black) or \
                (location.rank == 6 and self.color == color.white)

    def square_in_front(self, location=None):
        """
        Finds square directly in front of Pawn

        :type: location: Location
        :rtype: Location
        """
        location = location or self.location
        return location.shift_up() if self.color == color.white else location.shift_down()

    def two_squares_in_front(self, location):
        """
        Finds square two squares in front of Pawn

        :type: location: Location
        :rtype: get_location
        """
        return self.square_in_front(self.square_in_front(location))

    def create_promotion_moves(self, status, location=None):
        location = location or self.square_in_front()
        def create_each_move(piece):
            return Move(end_loc=location,
                        piece=self,
                        status=status,
                        start_loc=self.location,
                        promoted_to_piece=piece)

        yield create_each_move(Queen)
        yield create_each_move(Rook)
        yield create_each_move(Bishop)
        yield create_each_move(Knight)

    def forward_moves(self, position):
        """
        Finds possible moves one step and two steps in front
        of Pawn.

        :type: position: Board
        :rtype: list
        """
        if position.is_square_empty(self.square_in_front(self.location)):
            """
            If square in front is empty add the move
            """
            if self.would_move_be_promotion():
                for move in self.create_promotion_moves(notation_const.PROMOTE):
                    yield move
            else:
                yield self.create_move(end_loc=self.square_in_front(self.location),
                                       status=notation_const.MOVEMENT)

            if self.on_home_row() and \
                    position.is_square_empty(self.two_squares_in_front(self.location)):
                """
                If pawn is on home row and two squares in front of the pawn is empty
                add the move
                """
                yield self.create_move(
                    end_loc=self.square_in_front(self.square_in_front(self.location)),
                    status=notation_const.MOVEMENT
                )

    def _one_diagonal_capture_square(self, capture_square, position):
        """
        Adds specified diagonal as a capture move if it is one
        """
        if self.contains_opposite_color_piece(capture_square, position):

            if self.would_move_be_promotion():
                for move in self.create_promotion_moves(status=notation_const.CAPTURE_AND_PROMOTE,
                                                        location=capture_square):
                    yield move

            else:
                yield self.create_move(end_loc=capture_square,
                                       status=notation_const.CAPTURE)

    def capture_moves(self, position):
        """
        Finds out all possible capture moves

        :rtype: list
        """
        try:
            right_diagonal = self.square_in_front(self.location.shift_right())
            for move in self._one_diagonal_capture_square(right_diagonal, position):
                yield move
        except IndexError:
            pass

        try:
            left_diagonal = self.square_in_front(self.location.shift_left())
            for move in self._one_diagonal_capture_square(left_diagonal, position):
                yield move
        except IndexError:
            pass

    def on_en_passant_valid_location(self):
        """
        Finds out if pawn is on enemy center rank.

        :rtype: bool
        """
        return (self.color == color.white and self.location.rank == 4) or \
               (self.color == color.black and self.location.rank == 3)

    def _is_en_passant_valid(self, opponent_pawn_location, position):
        """
        Finds if their opponent's pawn is next to this pawn

        :rtype: bool
        """
        try:
            pawn = position.piece_at_square(opponent_pawn_location)
            return pawn is not None and \
                isinstance(pawn, Pawn) and \
                pawn.color != self.color and \
                position.piece_at_square(opponent_pawn_location).just_moved_two_steps
        except IndexError:
            return False

    def add_one_en_passant_move(self, direction, position):
        """
        Yields en_passant moves in given direction if it is legal.

        :type: direction: function
        :type: position: Board
        :rtype: gen
        """
        try:
            if self._is_en_passant_valid(direction(self.location), position):
                yield self.create_move(
                    end_loc=self.square_in_front(direction(self.location)),
                    status=notation_const.EN_PASSANT
                )
        except IndexError:
            pass

    def en_passant_moves(self, position):
        """
        Finds possible en passant moves.

        :rtype: list
        """

        # if pawn is not on a valid en passant get_location then return None
        if self.on_en_passant_valid_location():
            for move in itertools.chain(self.add_one_en_passant_move(lambda x: x.shift_right(), position),
                                        self.add_one_en_passant_move(lambda x: x.shift_left(), position)):
                yield move

    def possible_moves(self, position):
        """
        Finds out the locations of possible moves given board.Board position.
        :pre get_location is on board and piece at specified get_location on position

        :type: position: Board
        :rtype: list
        """
        for move in itertools.chain(self.forward_moves(position),
                                    self.capture_moves(position),
                                    self.en_passant_moves(position)):
            yield move