# Task
 An `amazon` (also known as a queen+knight compound) is an imaginary chess piece that can move like a `queen` or a `knight` (or, equivalently, like a `rook`, `bishop`, or `knight`). The diagram below shows all squares which the amazon attacks from e4 (circles represent knight-like moves while crosses correspond to queen-like moves).

 ![](https://codefightsuserpics.s3.amazonaws.com/tasks/amazonCheckmate/img/amazon.png?_tm=1473934566013)

 Recently you've come across a diagram with only three pieces left on the board: a `white amazon`, `white king` and `black king`. 
 
 It's black's move. You don't have time to determine whether the game is over or not, but you'd like to figure it out in your head. 
 
 Unfortunately, the diagram is smudged and you can't see the position of the `black king`, so it looks like you'll have to check them all.

 Given the positions of white pieces on a standard chessboard, determine the number of possible black king's positions such that: 

* It's a checkmate (i.e. black's king is under amazon's 
 attack and it cannot make a valid move);

* It's a check (i.e. black's king is under amazon's attack 
 but it can reach a safe square in one move);

* It's a stalemate (i.e. black's king is on a safe square 
 but it cannot make a valid move);

* Black's king is on a safe square and it can make a valid move.

Note that two kings cannot be placed on two adjacent squares (including two diagonally adjacent ones).

# Example

 For `king = "d3" and amazon = "e4"`, the output should be `[5, 21, 0, 29]`.

 ![](https://codefightsuserpics.s3.amazonaws.com/tasks/amazonCheckmate/img/example1.png?_tm=1473934566299)

 `Red crosses` correspond to the `checkmate` positions, `orange pluses` refer to `checks` and `green circles` denote `safe squares`.

 For `king = "a1" and amazon = "g5"`, the output should be `[0, 29, 1, 29]`.

 ![](https://codefightsuserpics.s3.amazonaws.com/tasks/amazonCheckmate/img/example2.png?_tm=1473934566670)

 `Stalemate` position is marked by a `blue square`.

# Input


 - String `king`

Position of white's king in chess notation.


 - String `amazon`

Position of white's amazon in the same notation.

Constraints: `amazon ≠ king.`

# Output

An array of four integers, each equal to the number of black's king positions corresponding to a specific situation. The integers should be presented in the same order as the situations were described, `i.e. 0 for checkmates, 1 for checks, etc`.

Difficulty: interview

Starter code:

def amazon_check_mate(king, amazon):
	

Solutions:
from itertools import count

ALL_MOVES  = [(1,1), (0,1), ( 1,0), (-1,0), (0,-1), (-1,1), ( 1,-1), (-1,-1)]       # Natural directions of moves for king or queen (one step)
AMA_MOVES  = [(1,2), (2,1), (-1,2), (2,-1), (1,-2), (-2,1), (-1,-2), (-2,-1)]       # Knight moves for amazon queen


def amazon_check_mate(*args):

    def posInBoard(x,y):  return 0 <= x < 8 and 0 <= y < 8
    
    def getCoveredPos(start, king=None):                                            # Working with the amazon queen is king is provided
        covered = {start}
        for m in (AMA_MOVES if king else ALL_MOVES):                                # All "one step" moves (either for queen or king)
            pos = tuple( z+dz for z,dz in zip(start,m) )
            if posInBoard(*pos): covered.add(pos)
        
        if king:                                                                    # Get long range moves, for queen only (meaning: if king is provided!)
            for dx,dy in ALL_MOVES:
                for n in count(1):
                    pos = (start[0]+dx*n, start[1]+dy*n)
                    if not posInBoard(*pos) or pos == king: break                   # Abort if not in board or if white king is on the way
                    covered.add(pos)
        
        return covered
    
    
    K, Q      = [(ord(s[0])-97, ord(s[1])-49) for s in args]                   # King and Queen positions as tuples
    kCover    = getCoveredPos(K)                                                    # Positions protected by white king
    fullCover = getCoveredPos(Q,K) | kCover                                         # All position protected by white pieces
    freeQueen = Q not in kCover                                                     # Queen not protected by king
    counts    = [0] * 4                                                             # Indexes: 2 * "is not check" + 1 * "safe position available around"
    
    for x in range(8):
        for y in range(8):
            black = (x,y)
            
            if black in kCover or black == Q: continue                              # No adjacent kings and no king copulating with an amazon...
            
            safePosAround = any( posInBoard(*neigh) and (neigh not in fullCover or neigh == Q and freeQueen)   # Neighbour is in board and is a safe place or is the queen and isn't protected by white king
                                 for neigh in ((x+dx, y+dy) for dx,dy in ALL_MOVES) )
                                    
            counts[ 2*(black not in fullCover) + safePosAround ] += 1               # Update the correct index of "ans"
            
    return counts


from collections import namedtuple
from functools import reduce
import enum
import sys

class Outcomes:
    """Enapsulation of outcomes to which tracks results as named attributes
    and can return the array expected by the kata.
    """

    def __init__(self):
        """Initialize situational count members to 0.
        """

        self.checkmates = 0
        self.checks = 0
        self.stalemates = 0
        self.alives = 0

    def as_array(self):
        """Return the array ordered as expected by the kata definition.
        
        Returns:
            array -- integer array of counts of squares by chess condition.
        """

        return [self.checkmates, self.checks, self.stalemates, self.alives]

class GameSquareConditions:
    """Gameplay conditions of a square.
    """

    def __init__(self, is_threatened=False, is_occupied=False, is_inhibited=False):
        self.is_threatened = is_threatened
        self.is_occupied = is_occupied
        self.is_inhibited = is_inhibited

    @property
    def is_safe(self):
        """Property to return the inverse of is_threatened.
        """
        return not self.is_threatened

class GameSquareOutcome(enum.Enum):
    CHECKMATE = enum.auto()
    CHECK = enum.auto()
    STALEMATE = enum.auto()
    ALIVE = enum.auto()
    UNKNOWN = enum.auto()

@enum.unique
class MovementDirection(enum.Enum):
    """Enumeration of movement directions.
    """
    NORTH = (0, 1)
    NORTH_EAST = (1, 1)
    EAST = (1, 0)
    SOUTH_EAST = (1, -1)
    SOUTH = (0, -1)
    SOUTH_WEST = (-1, -1)
    WEST = (-1, 0)
    NORTH_WEST = (-1, 1)

    def __init__(self, x_offset, y_offset):
        self.x_offset = x_offset
        self.y_offset = y_offset

    @property
    def cartesian_offset(self):
        """Return an (x,y) tuple offset for the direction. This method assumes
        the cartesian grid lower left is 0,0 with positive x to the right and
        positive y up.
        """
        return (self.x_offset, self.y_offset)

    @property
    def reciprical(self):
        """Return the opposite direction.
        """
        if self == self.NORTH:
            return self.SOUTH
        elif self == self.NORTH_EAST:
            return self.SOUTH_WEST
        elif self == self.EAST:
            return self.WEST
        elif self == self.SOUTH_EAST:
            return self.NORTH_WEST
        elif self == self.SOUTH:
            return self.NORTH
        elif self == self.SOUTH_WEST:
            return self.NORTH_EAST
        elif self == self.WEST:
            return self.EAST
        elif self == self.NORTH_WEST:
            return self.SOUTH_EAST

        raise ValueError("Unknown direction to recipricate!")
        
    def move_from(self, x, y):
        """Apply the movement to the provided X, Y cartesian coordinates.
        
        Arguments:
            x {int} -- Starting X cartesian coordinate to move from.
            y {int} -- Starting Y cartesian coordinate to move from.

        Returns:
            Tuple (x,y) cartesian coordinates resulting from the move.
        """
        offset = self.cartesian_offset
        return (x + offset[0], y + offset[1])

class GameSquare:
    """Square on the gameboard.
    """

    def __init__(self):
        """Initialize the neighbors in all directions to None.
        """
        self.neighbors = {}
        self.condition = GameSquareConditions()
        self.outcome = GameSquareOutcome.UNKNOWN

    def has_neighbor(self, direction):
        """Check if there is a neighboring square in the given direction.
        
        Arguments:
            direction {MovementDirection} -- Direction to check.
        """
        return self.neighbor(direction) is not None

    def neighbor(self, direction):
        
        """Return the neighboring game square in the direction. If no neighbor
        has been set, None.
        
        Arguments:
            direction {MovementDirection} -- Direction to get neighbor in.

        Return:
            GameSquare or None.
        """
        return self.neighbors.get(direction, None)

    def set_neighbor(self, neighbor, direction):
        """Store the neighbor to the specified direction.

        Side effects:
        Neighbor status is also set in the reciprical direction of the
        provided neighbor. If the neighbor is already set on that
        direction, it is cleared.

        If the provided neighbor is 'self' a 
        
        Arguments:
            neighbor {GameSquare} -- Neighboring game square to connect.
            direction {MovementDirection} -- Direction to get neighbor in.
        """
        if neighbor is self:
            raise ValueError("Cannot set yourself as a neighbor!")

        existing_neighbor = self.neighbor(direction)
        if existing_neighbor is not None and existing_neighbor is not neighbor:
            raise ValueError("Another neighbor is already in that direction!")

        recip_direction = direction.reciprical
        existing_recip_neighbor = neighbor.neighbor(recip_direction)
        if existing_recip_neighbor is not None and existing_recip_neighbor is not self:
            raise ValueError("Input neighbor already has neighbor in opposite direction!")

        self.neighbors[direction] = neighbor
        neighbor.neighbors[recip_direction] = self

    def neighbor_conditions(self):
        """Return a list of all conditions for neighboring squares.
        """
        return [n.condition for n in list(self.neighbors.values())]

    def render(self, out_dest):
        """Simple grid rendering to the output stream.

        Arguments:
            out_dest -- Output stream to render to.
        """
        
        marker = " "
        if self.condition.is_threatened:
            marker = "v"
        elif self.condition.is_occupied:
            marker = "*"
        elif self.condition.is_inhibited:
            marker = "-"
        out_dest.write(marker)

    def render_outcome(self, out_dest):
        """Simple grid rendering of the outcome to the output stream.

        Arguments:
            out_dest -- Output stream to render to.
        """
        
        marker = "?"
        if self.outcome == GameSquareOutcome.CHECKMATE:
            marker = "!"
        elif self.outcome == GameSquareOutcome.CHECK:
            marker = ":"
        elif self.outcome == GameSquareOutcome.STALEMATE:
            marker = "o"
        elif self.outcome == GameSquareOutcome.ALIVE:
            marker = "."
        out_dest.write(marker)

class Gameboard:
    """Gameboard comprised of rows and columns of GameSquares.
    The origin of the gameboard is the "lower-left".
    """

    def __init__(self, num_rows, num_cols):
        """Initialize the gameboard with a num_rows x num_cols grid of 
        GameSquares.
        
        Arguments:
            num_rows {int} -- Number of rows on the gameboard
            num_cols {int} -- Number of columns on the gameboard
        """
        if num_rows < 1:
            fmt = "Gameboard initialized with number of rows < 1 ({})!"
            msg = fmt.format(num_rows)
            raise ValueError()

        if num_cols < 1:
            fmt = "Gameboard initialized with number of columns < 1 ({})!"
            msg = fmt.format(num_cols)
            raise ValueError(msg)

        self.num_rows = num_rows
        self.num_cols = num_cols

        self.board = {}
        for row in range(num_rows):
            for col in range(num_cols):
                key = (row, col)
                self.board[key] = GameSquare()

        for row in range(num_rows):
            for col in range(num_cols):
                current = self.square_at(row, col)
                for direction in MovementDirection:
                    try:
                        neighbor_x, neighbor_y = direction.move_from(row, col)
                        neighbor = self.square_at(neighbor_x, neighbor_y)
                        current.set_neighbor(neighbor, direction)
                    except KeyError:
                        pass

    def square_at(self, row, col):
        """Return the GameSquare at the specified row and column.

        If row or col are out of bounds, an KeyError is raised.
        
        Arguments:
            row {int} -- 0 based index of the row to return the square from.
            col {int} -- 0 based index of the column to return the square from.

        Return:
            GameSquare at the specified index.
        """
        key = (row, col)
        return self.board[key]

    def render(self, out_dest):
        """Simple grid rendering to the output stream. The board is rendered
        with the "top" row on "top".

        Arguments:
            out_dest -- Output stream to render to.
        """
        out_dest.write("\n\nGameboard\n")
        for y in range(self.num_cols - 1, -1, -1):
            for x in range(self.num_rows):
                out_dest.write("|")
                self.square_at(x, y).render(out_dest)
            out_dest.write("|\n")

    def render_outcome(self, out_dest):
        """Simple grid rendering to the output stream. The board is rendered
        with the "top" row on "top".

        Arguments:
            out_dest -- Output stream to render to.
        """
        out_dest.write("\n\nOutcomes\n")
        for y in range(self.num_cols - 1, -1, -1):
            for x in range(self.num_rows):
                out_dest.write("|")
                self.square_at(x, y).render_outcome(out_dest)
            out_dest.write("|\n")

class DestinationMover:
    """The DestinationMover attempts to move along a path to reach
    a GameSquare. Only the final destination is returned.
    """

    def __init__(self, *args):
        """Initialize the path to move to.

        Arguments:
            *args {MovementDirection} -- Path to move along.
        """
        self.movement_path = list(args)

    def append_path(self, *args):
        """Append the provided MovementDirections to the path.
        """

        self.movement_path = self.movement_path + list(args)

    def execute(self, origin):
        """Follow the stored movement path from the provided origin.
        
        Arguments:
            origin {GameSquare} -- Position on the gameboard to be move from.

        Return:
            List of 1 item where the move terminated, or an empty list if the
            movement path cannot be completed.
        """
        current_location = origin
        for move in self.movement_path:
            next_location = current_location.neighbor(move)
            if next_location == None:
                return []
            current_location = next_location
        return [current_location]

    def __str__(self):
        """Return a nice, printable representation of the DestinationMover.
        """
        path = "-".join(p.name for p in self.movement_path)
        return "DestinationMover: " + path

class VectorMover:
    """The VectorMover moves from an origin location in a constant direction
    and returns each GameSquare along the movement path. The mover stops a 
    GameSquare is occupied or there is no next neighbor in the direction.
    """

    def __init__(self, direction = None):
        self.direction = direction

    def execute(self, origin):
        """Follow the stored direction until there are no neighboring squares or
        if a square is occupied.
        
        Arguments:
            origin {GameSquare} -- Position on the gameboard to be move from.

        Return:
            List of squares moved along the path.
        """
        if self.direction is None:
            return []
        
        visited = []
        neighbor = origin.neighbor(self.direction)
        while neighbor is not None and not neighbor.condition.is_occupied:
            visited.append(neighbor)
            neighbor = neighbor.neighbor(self.direction)

        return visited

    def __str__(self):
        """Return a nice, printable representation of the VectorMover.
        """
        return "VectorMover: " + self.direction.name

class GamePiece:

    def __init__(self, inhibit_as_well_as_threaten = False):
        self.inhibit_as_well_as_threaten = inhibit_as_well_as_threaten
        self.location = None
        self.movers = []

    def place_on_board(self, gamesquare):
        """Set the piece on the specified gamesquare.
        
        Arguments:
            gamesquare {GameSquare} -- Location on the gameboard to place piece.
        """
        self.location = gamesquare
        self.location.condition.is_occupied = True

    def impart_force(self):
        """Update the game squares based on the force abilities of the
        piece.

        Arguments:
            location {GameSquare} -- Position on the gameboard of the piece.

        Returns a list of the updated game squares.
        """
        if self.location is None:
            return []

        result = []
        for mover in self.movers:
            result = result + mover.execute(self.location)

        for square in result:
            square.condition.is_threatened = True
            if self.inhibit_as_well_as_threaten:
                square.condition.is_inhibited = True
            
        return result

    def __str__(self):
        """Return a nice, printable representation of the GamePiece.
        """
        mover_content = "\n  ".join([str(x) for x in self.movers])
        r = "\n  ".join(['GamePiece', mover_content])
        return r

def create_knight_movers():
    """Create the DestinationMovers for the possible knight moves.
    
    Returns:
        [list(DesintationMover)] -- List of movers to execute knight moves.
    """

    knight_paths = []
    knight_paths.append((MovementDirection.NORTH,
                         MovementDirection.NORTH,
                         MovementDirection.WEST))

    knight_paths.append((MovementDirection.NORTH,
                         MovementDirection.NORTH,
                         MovementDirection.EAST))

    knight_paths.append((MovementDirection.NORTH,
                         MovementDirection.WEST,
                         MovementDirection.WEST))

    knight_paths.append((MovementDirection.NORTH,
                         MovementDirection.EAST,
                         MovementDirection.EAST))

    knight_paths.append((MovementDirection.SOUTH,
                         MovementDirection.SOUTH,
                         MovementDirection.WEST))

    knight_paths.append((MovementDirection.SOUTH,
                         MovementDirection.SOUTH,
                         MovementDirection.EAST))

    knight_paths.append((MovementDirection.SOUTH,
                         MovementDirection.WEST,
                         MovementDirection.WEST))

    knight_paths.append((MovementDirection.SOUTH,
                         MovementDirection.EAST,
                         MovementDirection.EAST))

    return [DestinationMover(*x) for x in knight_paths]

def create_rook_movers():
    """Create the VectorMovers for the possible rook moves.
    
    Returns:
        [list(VectorMover)] -- List of movers to execute rook moves.
    """
    directions = (MovementDirection.NORTH, 
                  MovementDirection.SOUTH,
                  MovementDirection.EAST,
                  MovementDirection.WEST)
    return [VectorMover(direction) for direction in directions]

def create_bishop_movers():
    """Create the VectorMovers for the possible bishop moves.
    
    Returns:
        [list(VectorMover)] -- List of movers to execute bishop moves.
    """
    directions = (MovementDirection.NORTH_EAST,
                  MovementDirection.NORTH_WEST,
                  MovementDirection.SOUTH_EAST,
                  MovementDirection.SOUTH_WEST)
    return [VectorMover(direction) for direction in directions]

def create_amazon():
    piece = GamePiece()

    for mover in create_knight_movers():
        piece.movers.append(mover)

    for mover in create_rook_movers():
        piece.movers.append(mover)

    for mover in create_bishop_movers():
        piece.movers.append(mover)

    return piece

def create_king():
    piece = GamePiece(inhibit_as_well_as_threaten=True)
    for direction in MovementDirection:
        mover = DestinationMover()
        mover.append_path(direction)
        piece.movers.append(mover)    

    return piece

def chess_location_to_game_indicies(pos_string):
    """Convert chess locations (ex A1) to index for a gameboard.

    Arguments:
            pos_string {string} -- Chess notation location on the board.

    Return:
        (x,y) tuple to index the gameboard.
    """
    first = ord(pos_string[0].upper()) - ord('A')
    second = int(pos_string[1]) - 1
    return (first, second)

def determine_square_status_and_update_outcome(my_condition, 
                                               neighbor_conditions, 
                                               outcome):
    """Determine the status of the square from the square's condition and
    the neighboring conditions. Update and return the outcome.
    
    Return the outcome type.

    Arguments:
        my_condition {GameSquareConditions} -- Condition of the square to evaluate
        neighbor_conditions {list(GameSquareConditions)} -- Conditions of nieghbors
        outcome {Outcomes} -- Cumulative outcome of the gameboard.
    """
    outcome_type = GameSquareOutcome.UNKNOWN
    if my_condition.is_occupied or my_condition.is_inhibited:
        return outcome_type

    can_move_to_safety = any([x.is_safe for x in neighbor_conditions])
    if my_condition.is_threatened:
        if can_move_to_safety:
            outcome.checks += 1
            outcome_type = GameSquareOutcome.CHECK
        else:
            outcome.checkmates += 1
            outcome_type = GameSquareOutcome.CHECKMATE

    else:
        if can_move_to_safety:
            outcome.alives += 1
            outcome_type = GameSquareOutcome.ALIVE
        else:
            outcome.stalemates += 1
            outcome_type = GameSquareOutcome.STALEMATE

    return outcome_type

def amazon_check_mate(king, amazon):
    
    outcomes = Outcomes()
    king_coords = chess_location_to_game_indicies(king)
    amazon_coords = chess_location_to_game_indicies(amazon)

    board = Gameboard(8, 8)
    king_piece = create_king()
    amazon_piece = create_amazon()

    king_piece.place_on_board(board.square_at(*king_coords))
    amazon_piece.place_on_board(board.square_at(*amazon_coords))

    king_piece.impart_force()
    amazon_piece.impart_force()
 
    for square in list(board.board.values()):
        determine_square_status_and_update_outcome(square.condition,
                                                   square.neighbor_conditions(),
                                                   outcomes)

    return outcomes.as_array()


def amazon_check_mate(k, q):
    import numpy
    board = numpy.array([[0,1,1,1,1,1, 1, 0, 1,1,1,1,1,1,0],
                     [1,0,1,1,1,1, 1, 0, 1,1,1,1,1,0,1],
                     [1,1,0,1,1,1, 1, 0, 1,1,1,1,0,1,1],
                     [1,1,1,0,1,1, 1, 0, 1,1,1,0,1,1,1],
                     [1,1,1,1,0,1, 1, 0, 1,1,0,1,1,1,1],
                     [1,1,1,1,1,0, 0, 0, 0,0,1,1,1,1,1],
                     [1,1,1,1,1,0,-1,-1,-1,0,1,1,1,1,1],
                     [0,0,0,0,0,0,-1,-2,-1,0,0,0,0,0,0],
                     [1,1,1,1,1,0,-1,-1,-1,0,1,1,1,1,1],
                     [1,1,1,1,1,0, 0, 0, 0,0,1,1,1,1,1],
                     [1,1,1,1,0,1, 1, 0, 1,1,0,1,1,1,1],
                     [1,1,1,0,1,1, 1, 0, 1,1,1,0,1,1,1],
                     [1,1,0,1,1,1, 1, 0, 1,1,1,1,0,1,1],
                     [1,0,1,1,1,1, 1, 0, 1,1,1,1,1,0,1],
                     [0,1,1,1,1,1, 1, 0, 1,1,1,1,1,1,0]])

    # Determine xy positions
    qx,qy = ord(q[0])-97, int(q[1])-1
    kx,ky = ord(k[0])-97, int(k[1])-1
    # Place king on superboard
    board[max(0,6+qy-ky):9+qy-ky, max(0,6-qx+kx):9-qx+kx]=-2
    # Crop superboard
    board = board[qy:qy+8, 7-qx:15-qx]
    
    # Fix board due to king's position
    if abs(qx-kx)>1 or abs(qy-ky)>1 : # King not securing queen
        board[board==-1] = 0; board[7-qy,qx] = 2
    if qy==ky : # Blocking horizontal
        if kx+2-(qx>kx)*4 >=0 : board[7-ky, kx+2-(qx>kx)*4::1-(qx>kx)*2] = 1
    elif qx==kx : # Blocking vertical
        if 7-ky-2+(qy>ky)*4>=0: board[7-ky-2+(qy>ky)*4::-1+(qy>ky)*2, kx] = 1
    elif kx+ky==qx+qy : # King blocking backdiag (\)
        newb = board[7-ky::1-(qx>kx)*2, kx::1-(qx>kx)*2]
        newb[newb==0] = 1
        board[7-ky::1-(qx>kx)*2, kx::1-(qx>kx)*2] = newb
    elif kx-ky==qx-qy : # King blocking forwdiag (/)
        newb = board[7-ky::-1+(qx>kx)*2, kx::1-(qx>kx)*2]
        newb[newb==0] = 1
        board[7-ky::-1+(qx>kx)*2, kx::1-(qx>kx)*2] = newb
    
    # Count answers
    ans = [0,0,0,0]
    ansb = numpy.empty(shape=(8,8),dtype=object)
    for nx in range(8):
        for ny in range(8):
            c = board[7-ny,nx]
            if   c==1  :
                pos = numpy.count_nonzero(board[max(0,6-ny):9-ny, max(0,nx-1):nx+2] > 0) - 1
                if pos : ans[3] += 1; ansb[7-ny,nx] = "o"
                else   : ans[2] += 1; ansb[7-ny,nx] = "□"
            elif c==0  :
                pos = numpy.count_nonzero(board[max(0,6-ny):9-ny, max(0,nx-1):nx+2] > 0)
                if pos : ans[1] += 1; ansb[7-ny,nx] = "+"
                else   : ans[0] += 1; ansb[7-ny,nx] = "x"
            elif c==-1 : ans[0] += 1; ansb[7-ny,nx] = "x"
            else       : ansb[7-ny,nx] = " " # 2 or -2, incorrect starting positions
    print(k,q)
    print(board)
    print(ans)
    print(ansb)
    return ans

def amazon_check_mate(king, amazon):
    if king == amazon:
        return []
    king_pos = [ ord(king[1])-ord('1'), ord(king[0])-ord('a') ]
    amazon_pos = [ ord(amazon[1])-ord('1'), ord(amazon[0])-ord('a') ]
    board = [[' '] * 8, [' '] * 8, [' '] * 8, [' '] * 8, [' '] * 8, [' '] * 8, [' '] * 8, [' '] * 8]
    board[king_pos[0]][king_pos[1]] = "K"
    board[amazon_pos[0]][amazon_pos[1]] = 'A'
    dirs = [[1,0],[0,1],[-1,0],[0,-1],[1,-1],[-1,1],[1,1],[-1,-1],[1,2],[2,1],[1,-2],[2,-1],[-1,2],[-2,1],[-2,-1],[-1,-2]]
    # king
    for i in range(8):
        row, col = king_pos[0]+dirs[i][0], king_pos[1]+dirs[i][1]
        if (0 <= row < 8) and (0 <= col < 8): 
            board[row][col] = 'B' if (board[row][col] == ' ') else 'C'
    # amazon
    for i in range(16):
        row, col = amazon_pos[0]+dirs[i][0], amazon_pos[1]+dirs[i][1]
        while (0 <= row < 8) and (0 <= col < 8) and board[row][col] in [' ','B']:
            if board[row][col] == ' ':
                board[row][col] = 'X'
            if 8 <= i:    
                break
            row, col = row+dirs[i][0], col+dirs[i][1]
    # count
    res = [0,0,0,0]
    for row in range(8):
        for col in range(8):
            if board[row][col] not in ['K','B','A','C']:
                check = (board[row][col] == 'X')
                valid_around = 0
                for i in range(8):
                    row_, col_ = row+dirs[i][0], col+dirs[i][1]
                    if (0 <= row_ < 8) and (0 <= col_ < 8) and board[row_][col_] in [' ','A']:
                        valid_around += 1
                if check:
                    res[0 if (valid_around == 0) else 1] += 1
                elif valid_around == 0:
                    res[2] += 1
                else:
                    res[3] += 1
    return res

from itertools import product

def get_pos(coord):
    x = ord(coord[0]) - ord('a')
    y = int(coord[1]) - 1
    return [y, x]

def place_pieces(board, king, amazon, connected):
    coord_king = get_pos(king)
    board[coord_king[0]][coord_king[1]] = 8
    coord_amazon = get_pos(amazon)
    board[coord_amazon[0]][coord_amazon[1]] = 9
    if coord_king[0] - 1 <= coord_amazon[0] <= coord_king[0] + 1 \
    and coord_king[1] - 1 <= coord_amazon[1] <= coord_king[1] + 1:
        connected[0] = 1
    mark_attacked_squares(board, coord_king, coord_amazon)
    
def mark_attacked_squares(board, coord_king, coord_amazon):
    mark_queen(board, coord_amazon)
    mark_knight(board, coord_amazon)
    mark_king(board, coord_king)
    board[coord_amazon[0]][coord_amazon[1]] = 9
    
def mark_king(board, coord_king):
    y = coord_king[0]
    x = coord_king[1]
    for i in product([-1, 0, 1], repeat=2):
        if 0 <= y + i[0] < 8 and 0 <= x + i[1] < 8:
            board[y + i[0]][x + i[1]] = 3
    
def mark_knight(board, coord_amazon):
    y = coord_amazon[0]
    x = coord_amazon[1]
    for i in product([-2, -1, 1, 2], repeat=2):
        if 0 <= y + i[0] < 8 and 0 <= x + i[1] < 8:
            if board[y + i[0]][x + i[1]] == 0:
                board[y + i[0]][x + i[1]] = 2

def mark_queen(board, coord_amazon):
    y = coord_amazon[0]
    x = coord_amazon[1]
    while y >= 0:
        if board[y][x] == 0:
            board[y][x] = 2
        if board[y][x] == 8:
            break
        y -= 1
    y = coord_amazon[0]
    while y < 8:
        if board[y][x] == 0:
            board[y][x] = 2
        if board[y][x] == 8:
            break 
        y += 1
    y = coord_amazon[0]
    while x >= 0:
        if board[y][x] == 0:
            board[y][x] = 2
        if board[y][x] == 8:
            break
        x -= 1
    x = coord_amazon[1]
    while x < 8:
        if board[y][x] == 0:
            board[y][x] = 2
        if board[y][x] == 8:
            break 
        x += 1
    x = coord_amazon[1]
    while x >= 0 and y >= 0:
        if board[y][x] == 0:
            board[y][x] = 2
        if board[y][x] == 8:
            break 
        x -= 1
        y -= 1
    y = coord_amazon[0]
    x = coord_amazon[1]
    while x < 8 and y >= 0:
        if board[y][x] == 0:
            board[y][x] = 2
        if board[y][x] == 8:
            break 
        x += 1
        y -= 1
    y = coord_amazon[0]
    x = coord_amazon[1]
    while x >= 0 and y < 8:
        if board[y][x] == 0:
            board[y][x] = 2
        if board[y][x] == 8:
            break 
        x -= 1
        y += 1
    y = coord_amazon[0]
    x = coord_amazon[1]
    while x < 8 and y < 8:
        if board[y][x] == 0:
            board[y][x] = 2
        if board[y][x] == 8:
            break 
        x += 1
        y += 1
    y = coord_amazon[0]
    x = coord_amazon[1]

def check_safe(board, y, x, connected):
    for i in product([-1, 0, 1], repeat=2):
        if 0 <= y + i[0] < 8 and 0 <= x + i[1] < 8:
            if not (i[0] == 0 and i[1] == 0) and \
            (board[y + i[0]][x + i[1]] == 0 or \
            (connected[0] == 0 and board[y + i[0]][x + i[1]] == 9)):
                return 1
    return 0

def count_states(board, connected):
    stalemate = check = checkmate = safe = 0
    for y in range(8):
        for x in range(8):
            if board[y][x] == 0:
                if check_safe(board, y, x, connected) == 1:
                    safe += 1
                else:
                    stalemate += 1
            elif board[y][x] == 2:
                if check_safe(board, y, x, connected) == 0:
                    checkmate += 1
                else:
                    check += 1
    return [checkmate, check, stalemate, safe]

def amazon_check_mate(king, amazon):
    board = [[0 for i in range(8)] for j in range(8)]
    connected = [1]
    connected[0] = 0
    place_pieces(board, king, amazon, connected)
    return count_states(board, connected)
    # 0 = safe
    # 8 = whiteking
    # 9 = amazon
    # 2 = attacked
    # 3 = black king can't be here


def isaban(i, j, ki, kj):
    if i < 0 or i > 7 or j < 0 or j > 7: return True
    if ki - 2 < i and i < ki + 2 and kj - 2 < j and j < kj + 2: return True
    return False


def ischeck(i, j, ki, kj, ai, aj):
    if i == ai and j == aj: return False
    # Rook
    if i == ai:
        if ai != ki or (kj < j and kj < aj) or (kj > j and kj > aj): return True
    if j == aj:
        if aj != kj or (ki < i and ki < ai) or (ki > i and ki > ai): return True
    # Bishop
    if i + j == ai + aj:
        if ai + aj != ki + kj or (ki < i and ki < ai) or (ki > i and ki > ai): return True
    if i - j == ai - aj:
        if ai - aj != ki - kj or (ki < i and ki < ai) or (ki > i and ki > ai): return True
    # Knight
    Knight = [(-2, -1), (-2, 1), (-1, -2), (-1, 2), (1, -2), (1, 2), (2, -1), (2, 1)]
    for item in Knight:
        if ai == i + item[0] and aj == j + item[1]: return True
    # Not checked
    return False


def ismate(i, j, ki, kj, ai, aj):
    if i == ai and j == aj: return False
    move = [(-1, -1), (-1, 0), (-1, 1), (0, -1), (0, 1), (1, -1), (1, 0), (1, 1)]
    for item in move:
        ti, tj = i + item[0], j + item[1]
        if not isaban(ti, tj, ki, kj):
            if ti == ai and tj == aj: return False
            if not ischeck(ti, tj, ki, kj, ai, aj):
                return False
    return True


def amazon_check_mate(king, amazon):
    ki, kj = (ord(king[0]) - ord('a'), int(king[1]) - 1)
    ai, aj = (ord(amazon[0]) - ord('a'), int(amazon[1]) - 1)
    ans = [0, 0, 0, 0]
    for i in range(8):
        for j in range(8):
            if not isaban(i, j, ki, kj):
                if ischeck(i, j, ki, kj, ai, aj):
                    if ismate(i, j, ki, kj, ai, aj):
                        if i != ai or j != aj:
                            ans[0] += 1
                    else:
                        ans[1] += 1
                else:
                    if ismate(i, j, ki, kj, ai, aj):
                        ans[2] += 1
                    else:
                        ans[3] += 1
    if not isaban(ai, aj, ki, kj): ans[3] -= 1
    return ans

from math import isclose

adjacents = [(0, 1), (0, -1), (1, 0), (-1, 0), (-1, -1), (-1, 1), (1, -1), (1, 1)]  # adjacents moves
knight = [(1, -2), (1, 2), (2, -1), (2, 1), (-2, 1), (-2, -1), (-1, 2), (-1, -2)]   # knight moves
distance = lambda x1, y1, x2, y2: ((x2 - x1) ** 2 + (y2 - y1) ** 2) ** .5           # distance between points
is_diagonal=lambda a, b, i, j : abs(a - i) == abs(b - j)                            # is point A diagonally aligned with point B?
is_ok = lambda a,b : 0<=a<8 and 0<=b<8                                              # check for corners

def amazon_check_mate(king, amazon):    
    
    board = [['-' for _ in range(8)] for _ in range(8)]
    
    king = (8 - int(king[1]), 'abcdefgh'.index(king[0]))
    amazon = (8 - int(amazon[1]), 'abcdefgh'.index(amazon[0]))
    
    board[king[0]][king[1]] = 'K'
    board[amazon[0]][amazon[1]] = 'A'
    
    get_adjacents=lambda i, j, s=0:[[board[i+k][j+l],(i+k,j+l)][s] for k, l in adjacents if is_ok(i+k,j+l)]        # all adjacents of point A

    def assign_check():                                                                                            # assign checks to king from amazon 
        for i in range(8):
            for j in range(8):
                if board[i][j] == '-' and (i == amazon[0] or j == amazon[1] or is_diagonal(*amazon,i,j)) and \
                  not isclose(distance(*amazon, *king) + distance(*king, i, j), distance(*amazon, i, j),abs_tol=10e-5) : board[i][j] = '#'      # is diagonally aligned and there is not king in between amazon and point A
        for i, j in knight:                                             
            ni, nj = amazon[0] + i, amazon[1] + j
            if is_ok(ni,nj) and board[ni][nj] != 'K' : board[ni][nj] = '#'
    
    def assign_king_check():                                                      # our king checks
        for i, j in adjacents:
            ni, nj = king[0] + i, king[1] + j
            if is_ok(ni,nj) and board[ni][nj] != 'A':board[ni][nj] = '$'
      
    def assign_checkmates():                                                      # assign checkmates from amazon
        exceptions = set(get_adjacents(*amazon,1))
        for i in range(8):
            for j in range(8):
                if board[i][j] == '#' and (i, j) not in exceptions and all(n != '-' for n in get_adjacents(i,j)): board[i][j] = '*' 
    
    def king_amazon_adj():                                                       # special case where amazon and opp. king is adjacent
        adj = get_adjacents(*amazon)
        return adj.count('#') if 'K' in adj else 0
    
    def assign_safe_not_safe():                                                  # for condition 4
        for i in range(8):
            for j in range(8):
                if board[i][j] == '-' and all(n != '-' for n in get_adjacents(i, j)) : board[i][j] = '@'
    
    def _count():                                                                 # count all the requiremets and characters used 
        assign_check()                                                              # '*' => checkmate
        assign_king_check()                                                         # '#' => check
        assign_checkmates()                                                         # '@' => king is on a safe square but it cannot make a valid move
        assign_safe_not_safe()                                                      # '-' => on safe and can make safe move 
        d = {'*': king_amazon_adj(), '#': -king_amazon_adj(), '@': 0, '-': 0}
        for i in board:
            for j in i:
                if j not in 'KA$' : d[j] += 1
        return list(d.values())
        
    return _count()                                                               # return final count

def amazon_check_mate(king, amazon):
    ranks = '87654321'
    fyles = 'abcdefgh'

    # row,col
    am = (ranks.index(amazon[1]), fyles.index(amazon[0]))
    ki = (ranks.index(king[1]), fyles.index(king[0]))
    
    amazon_attacks = set()
    knight_moves = [(1,2),(2,1),(2,-1),(1,-2),(-1,-2),(-2,-1),(-2,1),(-1,2)]
    for dr,dc in knight_moves:
        row,col = am[0]+dr, am[1]+dc
        if 0<= row <=7 and 0<= col <=7:
            amazon_attacks.add( (row,col) )
    
    rays = [(0,1),(0,-1),(1,0),(-1,0),(1,1),(1,-1),(-1,1),(-1,-1)]
    for dr,dc in rays:
        for d in range(1,8):
            row,col = am[0]+d*dr, am[1]+d*dc
            if not (0<= row <=7) or not (0<= col <=7):
                break
            amazon_attacks.add( (row,col) )
            if (row,col) == ki:
                break

    king_attacks = set()
    for dr,dc in rays:
        row,col = ki[0]+dr, ki[1]+dc
        if 0<= row <=7 and 0<= col <=7:
            king_attacks.add( (row,col) )

    attacked = amazon_attacks | king_attacks

    def has_safe_move(r,c):
        for dr,dc in rays:
            row,col = r+dr,c+dc
            if (0<= row <=7 and 0<= col <=7) and (row,col) not in attacked:
                return True
        return False
    
    checkmates, checks, stalemates, safe = 0,0,0,0
    for r in range(8):
        for c in range(8):
            if (r,c) in king_attacks or (r,c) == am or (r,c) == ki:
                continue
            if (r,c) in attacked:
                if has_safe_move(r,c):
                    checks += 1
                else:
                    checkmates += 1
            else:
                if has_safe_move(r,c):
                    safe += 1
                else:
                    stalemates += 1

    return [checkmates,checks,stalemates,safe]

import numpy as np

def amazon_check_mate(king, amazon):
    nogo_board = np.full((24, 24), True)
    nogo_board[8:16, 8:16] = False
    king_sq = (ord(king[1]) - ord('1') + 8, ord(king[0]) - ord('a') + 8)
    nogo_board[(king_sq[0] - 1):(king_sq[0] + 2), (king_sq[1] - 1):(king_sq[1] + 2)] = True
    amazon_sq = (ord(amazon[1]) - ord('1') + 8, ord(amazon[0]) - ord('a') + 8)
    for di, dj in [(2, 1), (1, 2), (-1, 2), (-2, 1), (-2, -1), (-1, -2), (1, -2), (2, -1)]:
        nogo_board[amazon_sq[0]  + di, amazon_sq[1] +dj] = True
    directions = [(0, 1), (1, 1), (1, 0), (1, -1), (0, -1), (-1, -1), (-1, 0), (-1, 1)]
    blocked = [False]*8
    for ii in range(1, 8):
        for index, dd in enumerate(directions):
            if amazon_sq[0] + ii*dd[0] == king_sq[0] and amazon_sq[1] + ii*dd[1] == king_sq[1]:
                blocked[index] = True
            elif not blocked[index]:
                nogo_board[amazon_sq[0] + ii*dd[0], amazon_sq[1] + ii*dd[1]] = True
    if abs(king_sq[0] - amazon_sq[0]) <= 1 and abs(king_sq[1] - amazon_sq[1]) <= 1:
        nogo_board[amazon_sq[0], amazon_sq[1]] = True
    print(king_sq, amazon_sq)
    answer = [0, 0, 0, 0]
    for ii in range(8, 16):
        for jj in range(8, 16):
           if (abs(ii - king_sq[0]) > 1 or abs(jj - king_sq[1]) > 1) and\
              (ii != amazon_sq[0] or jj != amazon_sq[1]):
               if nogo_board[(ii - 1):(ii + 2), (jj - 1):(jj + 2)].all():
                   answer[0] += 1
               elif nogo_board[ii, jj]:
                   answer[1] += 1
               elif nogo_board[(ii - 1):(ii + 2), (jj - 1):(jj + 2)].sum() == 8:
                   answer[2] += 1
               else:
                   answer[3] += 1
                   
    return answer

d = {'a': 0, 'b': 1, 'c': 2, 'd': 3, 'e': 4, 'f': 5, 'g': 6, 'h': 7}
import numpy as np

# for x,i in for range(7)
# iam the king, what is my situation
# en rango 1 K, es king zone

def get_area(board,x,y):
  return board[x-1 if x-1 >= 0 else 0 : x+2 if x+2<= 8 else 8 , y-1 if y-1 >= 0 else 0 : y+2 if y+2<= 8 else 8]

def get_neig(board,x,y):
  rtrn = []
  if x-1>=0:
    if y-1 >= 0: rtrn.append(board[x-1,y-1])
    rtrn.append(board[x-1,y])
    if y+1 <8 : rtrn.append(board[x-1,y+1])
  if y -1 >= 0: rtrn.append(board[x,y-1])
  if y +1 < 8 : rtrn.append(board[x,y+1])
  if x+1 <8:
    if y-1 >= 0: rtrn.append(board[x+1,y-1])
    rtrn.append(board[x+1,y])
    if y+1 <8 : rtrn.append(board[x+1,y+1])
    
  return rtrn
#   area=board[x-1 if x-1 >= 0 else 0 : x+2 if x+2<= 8 else 8 , y-1 if y-1 >= 0 else 0 : y+2 if y+2<= 8 else 8]
        

def amazon_check_mate(king, amazon):
    print(king,amazon)
    board = np.array([['O']*8 for i in range (8)])
    kingx, kingy = int(king[1])-1, d[king[0]]
    amazonx, amazony = int(amazon[1])-1, d[amazon[0]]
    board[kingx][kingy]= 'K'
    board[amazonx][amazony] = 'A'
    
    # King y alrededores
    subarray = get_area(board,kingx,kingy)
    for (x,y) in np.ndenumerate(subarray):
        if y == 'O':
          subarray[x] = "N" # kingzone - Non posible  (numpy slices are views of the 'parent' matrix, so it will modify board[[]])
        if y == 'A':
          subarray[x] = 'F' # Defended, fortified Amazon
    
    #Amazon y alrededores (the king breaks!!)
    
#     for xy in range(amazonx,len(board[0])):
    for xy in range(amazony,8):
      if board[amazonx, xy] == 'K': break
      elif board[amazonx, xy] == 'O':
        board[amazonx, xy] = 'W'
    for xy in range(amazony,-1,-1):
      if board[amazonx, xy] == 'K': break
      elif board[amazonx, xy] == 'O':
        board[amazonx, xy] = 'W'

    for yx in range(amazonx,8):
      if board[yx,amazony] == 'K': break
      elif board[yx,amazony] == 'O':
        board[yx,amazony] = 'W'
    for yx in range(amazonx,-1,-1):
      if board[yx,amazony] == 'K': break
      elif board[yx,amazony] == 'O':
        board[yx,amazony] = 'W'
    
    diag = np.diagonal(board,amazony-amazonx) #,amazony,amazonx)
    diag.setflags(write=True)
    if 'A' in diag:
      for di in range(np.where(diag=='A')[0][0] ,len(diag)):
        if diag[di] == 'K': break
        if diag[di] == 'O':
          diag[di] ='W'   # In new numpy diag is also a view, so we are modifing board.
      for di in range(np.where(diag=='A')[0][0] ,-1,-1):
        if diag[di] == 'K': break
        if diag[di] == 'O':
          diag[di] ='W'   # In new numpy diag is also a view, so we are modifing board.
    if 'F' in diag:
      for di in range(np.where(diag=='F')[0][0] ,len(diag)):
        if diag[di] == 'K': break
        if diag[di] == 'O':
          diag[di] ='W'   # In new numpy diag is also a view, so we are modifing board.
      for di in range(np.where(diag=='F')[0][0] ,-1,-1):
        if diag[di] == 'K': break
        if diag[di] == 'O':
          diag[di] ='W'   # In new numpy diag is also a view, so we are modifing board.
        
    diag2 = np.rot90(board).diagonal(-8+amazonx+amazony+1) #,amazony,amazonx)
    diag2.setflags(write=True)
    if 'A' in diag2:
      for di in range(np.where(diag2=='A')[0][0] ,len(diag2)):
        if diag2[di] == 'K': break
        if diag2[di] == 'O':
          diag2[di] ='W'   # In new numpy diag is also a view, so we are modifing board.
      for di in range(np.where(diag2=='A')[0][0] ,-1,-1):
        if diag2[di] == 'K': break
        if diag2[di] == 'O':
          diag2[di] ='W'   # In new numpy diag is also a view, so we are modifing board.
    if 'F' in diag:
      for di in range(np.where(diag2=='F')[0][0] ,len(diag2)):
        if diag2[di] == 'K': break
        if diag2[di] == 'O':
          diag2[di] ='W'   # In new numpy diag is also a view, so we are modifing board.
      for di in range(np.where(diag2=='F')[0][0] ,-1,-1):
        if diag2[di] == 'K': break
        if diag2[di] == 'O':
          diag2[di] ='W'   # In new numpy diag is also a view, so we are modifing board.
    
    #like a horse
    if amazonx - 2 >= 0:
      if amazony-1 >= 0 and board[amazonx-2,amazony-1] == 'O': board[amazonx-2,amazony-1] = 'W'
      if amazony+1 < 8 and board[amazonx-2,amazony+1] == 'O': board[amazonx-2,amazony+1] = 'W'
    if amazonx+2 < 8:
      if amazony-1 >= 0 and board[amazonx+2,amazony-1] == 'O': board[amazonx+2,amazony-1] = 'W'
      if amazony+1 < 8 and board[amazonx+2,amazony+1] == 'O': board[amazonx+2,amazony+1] = 'W'
    if amazony -2 >= 0:
      if amazonx-1 >= 0 and board[amazonx-1,amazony-2] == 'O': board[amazonx-1,amazony-2] = 'W'
      if amazonx+1 < 8 and board[amazonx+1,amazony-2] == 'O': board[amazonx+1,amazony-2] = 'W'
    if amazony +2 < 8:
      if amazonx-1 >= 0 and board[amazonx-1,amazony+2] == 'O': board[amazonx-1,amazony+2] = 'W'
      if amazonx+1 < 8 and board[amazonx+1,amazony+2] == 'O': board[amazonx+1,amazony+2] = 'W'

    
    # traverse
#     if is O and no O arround, is stalemate. = S
#     if is W and no O or A arround is checkmate = C
    for i in range(8):
      for j in range(8):
        neigs = get_neig(board,i,j)
        if board[i,j] == 'O' and neigs.count('O')==0:
          board[i,j] = 'S'
        if board[i,j] == 'W' and neigs.count('O')+neigs.count('A')+neigs.count('S')==0:
          board[i,j] = 'C'
   

    checkmate,warning,stalemate,safe = 0,0,0,0
    for p in np.nditer(board):
      if p == 'C':
        checkmate+=1
      elif p == 'W':
        warning+=1
      elif p == 'S':
        stalemate +=1
      elif p == 'O':
        safe+=1
    return [checkmate,warning,stalemate,safe]