A chess board is normally played with 16 pawns and 16 other pieces, for this kata a variant will be played with only the pawns.  All other pieces will not be on the board.  
For information on how pawns move, refer [here](http://www.chesscorner.com/tutorial/basic/pawn/pawn.htm)
  
  
Write a function that can turn a list of pawn moves into a visual representation of the resulting board.  
A chess move will be represented by a string,
```
"c3"
```
This move represents a pawn moving to `c3`.  If it was white to move, the move would represent a pawn from `c2` moving to `c3`.  If it was black to move, a pawn would move from `c4` to `c3`, because black moves in the other direction.  
The first move in the list and every other move will be for white's pieces.
  
The letter represents the column, while the number represents the row of the square where the piece is moving  
    
Captures are represented differently from normal moves:

```
"bxc3"
```
represents a pawn on the column represented by 'b' (the second column) capturing a pawn on `c3`.

For the sake of this kata a chess board will be represented by a list like this one:  
```
[[".",".",".",".",".",".",".","."],
 ["p","p","p","p","p","p","p","p"],
 [".",".",".",".",".",".",".","."],
 [".",".",".",".",".",".",".","."],
 [".",".",".",".",".",".",".","."],
 [".",".",".",".",".",".",".","."],
 ["P","P","P","P","P","P","P","P"],
 [".",".",".",".",".",".",".","."]]
```
Here is an example of the board with the squares labeled:
```
[["a8","b8","c8","d8","e8","f8","g8","h8"],
 ["a7","b7","c7","d7","e7","f7","g7","h7"],
 ["a6","b6","c6","d6","e6","f6","g6","h6"],
 ["a5","b5","c5","d5","e5","f5","g5","h5"],
 ["a4","b4","c4","d4","e4","f4","g4","h4"],
 ["a3","b3","c3","d3","e3","f3","g3","h3"],
 ["a2","b2","c2","d2","e2","f2","g2","h2"],
 ["a1","b1","c1","d1","e1","f1","g1","h1"]]
```
White pawns are represented by capital `'P'` while black pawns are lowercase `'p'`.  
  
      
A few examples
```
If the list/array of moves is: ["c3"]
>>>
[[".",".",".",".",".",".",".","."],
 ["p","p","p","p","p","p","p","p"],
 [".",".",".",".",".",".",".","."],
 [".",".",".",".",".",".",".","."],
 [".",".",".",".",".",".",".","."],
 [".",".","P",".",".",".",".","."],
 ["P","P",".","P","P","P","P","P"],
 [".",".",".",".",".",".",".","."]]
```
add a few more moves,
```
If the list/array of moves is: ["d4", "d5", "f3", "c6", "f4"]
>>>
[[".",".",".",".",".",".",".","."],
 ["p","p",".",".","p","p","p","p"],
 [".",".","p",".",".",".",".","."],
 [".",".",".","p",".",".",".","."],
 [".",".",".","P",".","P",".","."],
 [".",".",".",".",".",".",".","."],
 ["P","P","P",".","P",".","P","P"],
 [".",".",".",".",".",".",".","."]]
```
now to add a capture...
```
If the list/array of moves is: ["d4", "d5", "f3", "c6", "f4", "c5", "dxc5"]
>>>
[[".",".",".",".",".",".",".","."],
 ["p","p",".",".","p","p","p","p"],
 [".",".",".",".",".",".",".","."],
 [".",".","P","p",".",".",".","."],
 [".",".",".",".",".","P",".","."],
 [".",".",".",".",".",".",".","."],
 ["P","P","P",".","P",".","P","P"],
 [".",".",".",".",".",".",".","."]]
```
If an invalid move (a move is added that no pawn could perform, a capture where there is no piece, a move to a square where there is already a piece, etc.) is found in the list of moves, return '(move) is invalid'.
```python
If the list/array of moves is: ["e6"]
>>>
"e6 is invalid"
```

```python
If the list/array of moves is: ["e4", "d5", "exf5"]
>>>
"exf5 is invalid"
```
The list passed to `pawn_move_tracker / PawnMoveTracker.movePawns` will always be a list of strings in the form (regex pattern): `[a-h][1-8]` or `[a-h]x[a-h][1-8]`.  

Notes:

* In the case of a capture, the first lowercase letter will always be adjacent to the second in the alphabet, a move like `axc5` will never be passed.
* A pawn can move two spaces on its first move
* There are no cases with the 'en-passant' rule.

Difficulty: interview

Starter code:

def pawn_move_tracker(moves):
	

Solutions:
LETTERS        = 'abcdefgh'                                                    # Defining some constants
NUMBERS        = '87654321'
W, B = WB      = 'Pp'
EMPTY, CAPTURE = '.x'
WHITEHOME      = '12'
BLACKHOME      = '87'
JUMP           = '54'

def pawn_move_tracker(moves):
    board = {letter + number :                                                 # Representing board as
             B if number == BLACKHOME[1] else                                  # a dictionary for easy
             W if number == WHITEHOME[1] else EMPTY                            # access
             for letter in LETTERS for number in NUMBERS}
    whitemove = True                                                           # Move side switcher
    for move in moves:
        target    = move[-2:]                                                  # Finding target
        mover     = move[0] + str(int(move[-1]) + 1 - whitemove * 2)           # Finding mover
        if  move[-1] in JUMP[whitemove] and board[mover] == EMPTY:             # Mover for the jump
            mover = move[0] + str(int(move[-1]) + 2 - whitemove * 4)
        if (move[-1] in (BLACKHOME, WHITEHOME)[whitemove]           or         # Is the move valid?
            board[target] != (EMPTY, WB[whitemove])[move[1] == CAPTURE] or
            board[mover]  != WB[not whitemove]):
            return "{} is invalid".format(move)
        whitemove = not whitemove                                                  # Switching side
        board[mover]  = EMPTY                                                      # Empty the source cell
        board[target] = WB[whitemove]                                              # Fill the target
    return [[board[letter + number] for letter in LETTERS] for number in NUMBERS]  # Return representation

def pawn_move_tracker(moves):
    board = [[c] * 8 for c in '.p....P.']
    for turn, move in enumerate(moves, 1):
        capture, white_turn = len(move) == 4, turn % 2
        direction = 2 * white_turn - 1
        c0, _, c, r = (move * 2)[:4]
        c0, c, r = ord(c0) - ord('a'), ord(c) - ord('a'), 8 - int(r)
        long_move = not capture and r == 3 + white_turn
        for i in range(1, 2 + long_move):
            r0 = r + direction * i
            if board[r0][c0][0] == 'pP'[white_turn] \
                and board[r][c][0] == '.pP'[capture and 2 - white_turn]:
                board[r0][c0], board[r][c] = '.', board[r0][c0]
                break
        else: return "{} is invalid".format(move)
    return board

def pawn_move_tracker(moves):
    board_state = [
        [".",".",".",".",".",".",".","."],
        ["p","p","p","p","p","p","p","p"],
        [".",".",".",".",".",".",".","."],
        [".",".",".",".",".",".",".","."],
        [".",".",".",".",".",".",".","."],
        [".",".",".",".",".",".",".","."],
        ["P","P","P","P","P","P","P","P"],
        [".",".",".",".",".",".",".","."]
    ]

    board = Board(board_state)
    next_board = board
    player = 'P'

    for move in moves:
        next_board = board.play(move, player)
        if(next_board == None):
            return move + ' is invalid'

        player = 'P' if player == 'p' else 'p'

    return board.get_state()

class Pawn:
    def __init__(self, cell, player):
        self.cell = cell
        self.moved = False
        self.player = player
        self.is_white = player == 'P'
        self.is_black = player == 'p'
        self.forward_direction = -1 if self.is_white else 1;

    def moveTo(self, cell):
        self.moved = True
        self.cell.remove_pawn()
        cell.add_pawn(self)
        self.cell = cell
        return cell

    def belongsTo(self, player):
        return self.player == player

    # returns true if this pawn can move to the given cell in the context of a chess board
    def canMoveTo(self, cell, board):
        # can't move ahead if cell is occupied
        if cell.is_occupied():
            return False

        steps_away = cell.row - self.cell.row

        if steps_away == self.forward_direction * 1:
            return True

        if steps_away == self.forward_direction * 2:
            cell_ahead = board.cellAt(self.cell.row + self.forward_direction, self.cell.col)
            return not self.moved and not cell_ahead.is_occupied()

    def canCapture(self, cell):
        cell_has_enemy = cell.is_occupied() and cell.pawn.player != self.player
        cell_is_ahead = cell.row - self.cell.row == self.forward_direction
        cell_is_adjacent = abs(cell.col - self.cell.col)
        return cell_has_enemy and cell_is_ahead and cell_is_adjacent

class Cell:
    def __init__(self, row, col, occupant):
        self.row = row
        self.col = col
        self.occupant = occupant

        if(occupant != '.'):
           self.pawn = Pawn(self, occupant)
        else:
           self.pawn = None

    def is_occupied(self):
        return not self.pawn == None

    def add_pawn(self, pawn):
        self.pawn = pawn
        self.occupant = pawn.player

    def remove_pawn(self):
        self.pawn = None
        self.occupant = '.'

class Board:
    def __init__(self, board_array):
        self.board_state = []

        for i, row in enumerate(board_array):
            board_row = []
            for j, occupant in enumerate(row):
                board_row.append(Cell(i,j, occupant))

            self.board_state.append(board_row)

    # returns all pawns in a given column. At most 2 pawns per column.
    def pawnsInColumn(self, col):
        cells = [row[col] for row in self.board_state]
        return [cell.pawn for cell in cells if cell.is_occupied()]

    def cellAt(self, row, col):
        if(row < 0 or row > 7 or col < 0 or col > 7):
            return None
        return self.board_state[row][col]

    def play(self, move, player):
        if(len(move) == 2):
           return self.playMove(move, player)
        else:           
           return self.playCapture(move, player)

    def playMove(self, move, player):
        row, col = toCoords(move)
        cell = self.cellAt(row, col)
        pawns = self.pawnsInColumn(col)

        for pawn in pawns:
            if pawn.belongsTo(player) and pawn.canMoveTo(cell, self):
                pawn.moveTo(cell)
                return self
        return None

    def playCapture(self, move, player):
        col = toCol(move[0])
        capture_row, capture_col = toCoords(move[2:])
        cell = self.cellAt(capture_row, capture_col)
        pawns = self.pawnsInColumn(col)

        for pawn in pawns:
            if pawn.belongsTo(player) and pawn.canCapture(cell):
                pawn.moveTo(cell)
                return self
        return None

    def get_state(self):
        return list([list([cell.occupant for cell in row]) for row in self.board_state])

def toCoords(move):
    col = toCol(move[0])
    row = 8 - int(move[1])
    return [row, col]

def toCol(move):
    return (ord(move) - 97)


def pawn_move_tracker(moves):
    board = [
    [".",".",".",".",".",".",".","."],
    ["p","p","p","p","p","p","p","p"],
    [".",".",".",".",".",".",".","."],
    [".",".",".",".",".",".",".","."],
    [".",".",".",".",".",".",".","."],
    [".",".",".",".",".",".",".","."],
    ["P","P","P","P","P","P","P","P"],
    [".",".",".",".",".",".",".","."]
    ]
    turn = 0
    for p in moves:
        if len(p) == 2:
            x,y = 'abcdefgh'.index(p[0]), 8-int(p[1])
            if board[y][x]!='.': return p+' is invalid'
            if turn == 0:
                if y<7 and board[y+1][x] == 'P': board[y][x]='P'; board[y+1][x]='.'
                elif y==4 and board[y+1][x] == '.' and board[y+2][x] == 'P': board[y][x]='P'; board[y+2][x]='.'
                else: return p+' is invalid'
            else:
                if y>0 and board[y-1][x] == 'p': board[y][x]='p'; board[y-1][x]='.'
                elif y==3 and board[y-1][x] == '.' and board[y-2][x] == 'p': board[y][x]='p'; board[y-2][x]='.'
                else: return p+' is invalid'
        else:
            x,y,fx,fy = 'abcdefgh'.index(p[2]), 8-int(p[3]), 'abcdefgh'.index(p[0]), 8-int(p[3])+(1-turn*2)
            if turn == 0:
                if board[y][x]=='p' and board[fy][fx]=='P': board[y][x]='P'; board[fy][fx]='.'
                else: return p+' is invalid'
            else:
                if board[y][x]=='P' and board[fy][fx]=='p': board[y][x]='p'; board[fy][fx]='.'
                else: return p+' is invalid'
        turn ^= 1
    return board

def pawn_move_tracker(moves):

    def getSourcePos(m, delta):  return m[0] + str(int(m[-1]) + delta * comingFrom[player])
    
    ROWS, COLS = "87654321", "abcdefgh"                                                          # constants...
    comingFrom, mayBeDouble = {'P': -1, 'p': 1}, {'P': '4', 'p': '5'}                            # constants...
    
    pawns = {'P': {y+"2" for y in COLS}, 'p': {y+"7" for y in COLS}}                             # All pawns at their initial position
    for t,m in enumerate(moves):
        player, other, toMove = "Pp"[t%2], "pP"[t%2], set()
        
        toMove.add(getSourcePos(m, 1))                                                           # Single move or capturing pawn
        if m[1] == mayBeDouble[player] and m[1]!='x':  toMove.add(getSourcePos(m, 2))            # Double move if m in row 4 or 5, according to "player" (only if not capturing move for m)
        toMove &= pawns[player]                                                                  # Extract matching pawn(s)
        
        if ( m[1] == 'x' and m[2:] not in pawns[other] or m[1] != 'x' and m in pawns[other]      # Invalid if capturing non-existing enemy or moving to a position occupied by enemy...
          or m in pawns[player] or len(toMove) != 1):                                            # ... or moving to a position occupied by friend or no pawn to move or too many.
            return "{} is invalid".format(m)
        
        if m[1] == 'x':
            m = m[2:]; pawns[other].remove(m)                                                    # Reassign final position (needed if capture) and remove captured enemy piece
        pawns[player].remove(toMove.pop())                                                       # Move current pawn (remove/add) 
        pawns[player].add(m)
            
    board = {pos: k for k,s in pawns.items() for pos in s}                                       # Dict of all remaining pawns and their position
    return [[board.get(y+x, '.') for y in COLS] for x in ROWS]                                   # final board

import re


def pawn_move_tracker(moves):
    turn = 0
    white = ['a2', 'b2', 'c2', 'd2', 'e2', 'f2', 'g2', 'h2']
    black = ['a7', 'b7', 'c7', 'd7', 'e7', 'f7', 'g7', 'h7']

    try:
        for move in moves:
            if re.match('[a-h][1-8]', move):
                white, black = motion(white, black, move, turn)
            elif re.match('[a-h]x[a-h][1-8]', move):
                white, black = eat(white, black, move, turn)
            turn = (turn + 1) % 2
    except ValueError as move:
        return '{0} is invalid'.format(move)

    return pawns_to_board(white, black)


def motion(white, black, move, turn):
    column = move[0]
    row = move[1]
    piece = None
    if turn == 0:
        try:
            search_row = str(int(row) - 1)
            piece = white.index(column + search_row)

        except ValueError:
            try:
                if row == '4':
                    piece = white.index(column + '2')
                else:
                    raise ValueError
            except ValueError:
                raise ValueError(move)

        try:
            black.index(move)
        except ValueError:
            white[piece] = move
        else:
            raise ValueError(move)

    elif turn == 1:
        try:
            search_row = str(int(row) + 1)
            piece = black.index(column + search_row)

        except ValueError:
            try:
                if row == '5':
                    piece = black.index(column + '7')
                else:
                    raise ValueError
            except ValueError:
                raise ValueError(move)

        try:
            white.index(move)
        except ValueError:
            black[piece] = move
        else:
            raise ValueError(move)

    return [white, black]


def eat(white, black, move, turn):
    column = move[0]
    row = move[3]
    piece = None
    dead = None
    if turn == 0:
        try:
            search_row = str(int(row) - 1)
            piece = white.index(column + search_row)
        except ValueError:
            raise ValueError(move)

        else:
            try:
                dead = black.index(move[2:4])
            except ValueError:
                raise ValueError(move)
            else:
                white[piece] = move[2:4]
                del black[dead]

    elif turn == 1:
        try:
            search_row = str(int(row) + 1)
            piece = black.index(column + search_row)
        except ValueError:
            raise ValueError(move)

        else:
            try:
                dead = white.index(move[2:4])
            except ValueError:
                raise ValueError(move)
            else:
                black[piece] = move[2:4]
                del white[dead]

    return [white, black]


def pawns_to_board(white, black):
    board = [['.', '.', '.', '.', '.', '.', '.', '.'],
             ['.', '.', '.', '.', '.', '.', '.', '.'],
             ['.', '.', '.', '.', '.', '.', '.', '.'],
             ['.', '.', '.', '.', '.', '.', '.', '.'],
             ['.', '.', '.', '.', '.', '.', '.', '.'],
             ['.', '.', '.', '.', '.', '.', '.', '.'],
             ['.', '.', '.', '.', '.', '.', '.', '.'],
             ['.', '.', '.', '.', '.', '.', '.', '.']]
    for i in white:
        r, c = coords(i)
        board[r][c] = 'P'
    for i in black:
        r, c = coords(i)
        board[r][c] = 'p'

    return board


def coords(pos):
    col = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'].index(pos[0])
    row = 8 - int(pos[1])
    return [row, col]

LETTERS        = 'abcdefgh'
NUMBERS        = '87654321'
W, B = WB      = 'Pp'
EMPTY, CAPTURE = '.x'
WHITEHOME      = '12'
BLACKHOME      = '87'
JUMP           = '54'

def pawn_move_tracker(moves):
    board = {letter + number :
             B if number == BLACKHOME[1] else W if number == WHITEHOME[1] else EMPTY
             for letter in LETTERS for number in NUMBERS}
    whitemove = True
    for move in moves:
        target    = move[-2:]
        mover     = move[0] + str(int(move[-1]) + 1 - whitemove * 2)
        if  move[-1] in JUMP[whitemove] and board[mover] == EMPTY:
            mover = move[0] + str(int(move[-1]) + 2 - whitemove * 4)
        if (move[-1] in (BLACKHOME, WHITEHOME)[whitemove]           or
            board[target] != (EMPTY, WB[whitemove])[move[1] == CAPTURE] or
            board[mover]  != WB[not whitemove]):
            return "{} is invalid".format(move)
        whitemove = not whitemove
        board[mover]  = EMPTY
        board[target] = WB[whitemove]
    return [[board[letter + number] for letter in LETTERS] for number in NUMBERS]


import numpy as np
def pawn_move_tracker(moves):
    
    print(moves)
    board = [
    [".",".",".",".",".",".",".","."],
    ["p","p","p","p","p","p","p","p"],
    [".",".",".",".",".",".",".","."],
    [".",".",".",".",".",".",".","."],
    [".",".",".",".",".",".",".","."],
    [".",".",".",".",".",".",".","."],
    ["P","P","P","P","P","P","P","P"],
    [".",".",".",".",".",".",".","."]
    ]
    
    board = np.asarray(board)
    white = True
    cols = {'a':0,'b':1,'c':2,'d':3,'e':4,'f':5,'g':6,'h':7}
    

    for move in moves:
        if white:
            diff, piece, start = 1, 'P', 6
        else:
            diff, piece, start = -1, 'p', 1
            
        if 'x' in move:
            splt = move.split('x')
            new_c, new_r = cols[splt[1][0]], 8 - int(splt[1][1]) 
            old_c, old_r = cols[splt[0]], new_r + diff
            if board[old_r,old_c] != piece or board[new_r, new_c] in ['.',piece]:
                return move + ' is invalid'
            else:
                board[old_r,old_c]  = '.'
                board[new_r, new_c] = piece
        else:
            col, new_row = cols[move[0]], 8 - int(move[1]) 
            if board[new_row,col] != '.':
                return move + ' is invalid'
            
            x = np.where(board == piece)
            
            pwns = list((zip(x[0],x[1])))
            if (new_row + diff, col) in pwns:
                board[new_row + diff, col] = '.'
                board[new_row, col] = piece
            elif new_row + 2*diff == start:
                if (new_row + 2*diff, col) in pwns:
                    board[new_row + 2*diff, col] = '.'
                    board[new_row, col] = piece
            else:
                return move + ' is invalid'
                
                
                
        if white:
            white = False
        else:
            white = True

    
    return np.ndarray.tolist(board)

def pawn_move_tracker(moves):
    white_pawns = [(x, 2) for x in range(1,9)]
    black_pawns = [(x, 7) for x in range(1,9)]
    all_pawns = (white_pawns, black_pawns)
    turn = 0
    for move in moves:
        pawns = all_pawns[turn]
        opponent = all_pawns[turn^1]
        direction = 1 - 2 * turn
        capture = 'x' in move
        if capture:
            sx, dx, dy = column(move[0]), column(move[2]), int(move[3])
            if (dx, dy) in opponent: opponent.remove((dx, dy))
            else: return f'{move} is invalid'
        else:
            sx = dx = column(move[0])
            dy = int(move[1])
            if (dx, dy) in opponent or (dx, dy-direction) in opponent:
                return f'{move} is invalid'
        sy = dy - direction
        if not capture and dy == (4,5)[turn] and not (sx,sy) in pawns:
            sy -= direction
        if (sx, sy) not in pawns:
            return f'{move} is invalid'
        pawns.remove((sx, sy))
        pawns.append((dx, dy))
        turn ^= 1
    char = lambda p: 'P' if p in white_pawns else 'p' if p in black_pawns else '.'
    return [[char((x, y)) for x in range(1, 9)] for y in range(8, 0, -1)]

def column(c):
    return ' abcdefgh'.index(c)


def pawn_move_tracker(moves):
    board = [
    [".",".",".",".",".",".",".","."],
    ["p","p","p","p","p","p","p","p"],
    [".",".",".",".",".",".",".","."],
    [".",".",".",".",".",".",".","."],
    [".",".",".",".",".",".",".","."],
    [".",".",".",".",".",".",".","."],
    ["P","P","P","P","P","P","P","P"],
    [".",".",".",".",".",".",".","."]]
        
    for i in range(len(moves)):
        # Flip row to work with array indexing
        row = abs(int(moves[i][1])-8) if moves[i][1] != 'x' else abs(int(moves[i][3])-8)
        col = ord(moves[i][0])-97 if moves[i][1] != 'x' else ord(moves[i][2])-97
        white_turn = bool((i % 2) == 0)
        piece1 = "P" if white_turn == True else "p" #piece to move
        piece2 = "p" if white_turn == True else "P" #piece to take
        # Back is down if white's turn, and up if black's turn
        back1 = row + 1 if white_turn == True else row - 1
        back2 = row + 2 if white_turn == True else row - 2
        # Most likely current position, updated if necessary
        current_row, current_col = back1, col
        
        # If capturing enemy pawn
        if 'x' in moves[i] and board[row][col] == piece2:
            current_col = ord(moves[i][0]) - 97
            if board[back1][current_col] != piece1:
                return f"{moves[i]} is invalid"
        # If moving 2 squares from starting position
        elif board[back1][col] == "." and board[back2][col] == piece1:
            if (white_turn == True and back2 == 6) or (white_turn == False and back2 == 1):
                current_row = back2
            else:
                return f"{moves[i]} is invalid"
        # Standard move forward, ensure move is valid
        elif board[back1][col] != piece1 or board[row][col] != ".":
            return f"{moves[i]} is invalid"
        
        # Update board
        board[current_row][current_col] = "."
        board[row][col] = piece1
                
    return board