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: MEDIUM_HARD
Tags: String algorithms

Data structures

Ad-hoc
Skill types: Data structures

Starter code:
def pawn_move_tracker(moves):
	

Solutions:
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]


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

	def canMoveTo(self, cell, board):
		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)

	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'
			elif 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'
			elif 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')
	(comingFrom, mayBeDouble) = ({'P': -1, 'p': 1}, {'P': '4', 'p': '5'})
	pawns = {'P': {y + '2' for y in COLS}, 'p': {y + '7' for y in COLS}}
	for (t, m) in enumerate(moves):
		(player, other, toMove) = ('Pp'[t % 2], 'pP'[t % 2], set())
		toMove.add(getSourcePos(m, 1))
		if m[1] == mayBeDouble[player] and m[1] != 'x':
			toMove.add(getSourcePos(m, 2))
		toMove &= pawns[player]
		if m[1] == 'x' and m[2:] not in pawns[other] or (m[1] != 'x' and m in pawns[other]) or m in pawns[player] or (len(toMove) != 1):
			return '{} is invalid'.format(m)
		if m[1] == 'x':
			m = m[2:]
			pawns[other].remove(m)
		pawns[player].remove(toMove.pop())
		pawns[player].add(m)
	board = {pos: k for (k, s) in pawns.items() for pos in s}
	return [[board.get(y + x, '.') for y in COLS] for x in ROWS]


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]


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)):
		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'
		piece2 = 'p' if white_turn == True else 'P'
		back1 = row + 1 if white_turn == True else row - 1
		back2 = row + 2 if white_turn == True else row - 2
		(current_row, current_col) = (back1, col)
		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'
		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'
		elif board[back1][col] != piece1 or board[row][col] != '.':
			return f'{moves[i]} is invalid'
		board[current_row][current_col] = '.'
		board[row][col] = piece1
	return board