Simplified Chess Engine

The main things to deal with in this problem are:

  1. Maintaing the state of the board,
  2. Finding the valid moves for pieces, and
  3. Updating the state of the board.

We utilize a collection of dictionaries and functions to help us with these tasks are outlined below.

The board is stored as a list of length 16, where index 0 is referring to the upper left-hand square of the simplified $4 \times 4$ board, and index 15 is the lower right-hand square.

Note that gathering the input is done here in a different fashion than is done on HackerRank.

import sys

# Create piece and board dictionaries.
# Used for translating the input of the problem into more
# usable values for the remaining functions.  
PIECE_DICT = {'Q' : 4, 'R' : 3, 'B' : 2, 'N' : 1}
BOARD_DICT = {'A': {'1' : 0, '2' : 1, '3' : 2, '4' : 3},
         'B': {'1' : 4, '2' : 5, '3' : 6, '4' : 7},
         'C': {'1' : 8, '2' : 9, '3' : 10, '4' : 11},
         'D': {'1' : 12, '2' : 13, '3' : 14, '4' : 15},
        }

MOVE_LIST = ['r','d','l','u','dr','dl','ur','ul']
MOVE_DICT = dict((x,[-1]*16) for x in MOVE_LIST)

def get_init_board(n_white, n_black):
    """
    Initialize board
    """
    board = [0] * 16
    for i in range(n_white + n_black):
        pc, row, col = sys.stdin.readline().split()
        piece = PIECE_DICT[pc] * (-1)**(0+(i>=n_white))
        position = BOARD_DICT[row][col]
        board[position] = piece
    return board

def precompute_moves():
    """
    Determine valid move directions for each position on the board
    """
    for i in range(16):
        okright = (i % 4 != 3)
        okleft = (i % 4 != 0)
        okup = (i >= 4)
        okdown = (i <= 11)
        # Rightward moves
        if okright:
            MOVE_DICT['r'][i] = i + 1
        # Leftward moves
        if okleft:
            MOVE_DICT['l'][i] = i - 1
        # Downward moves
        if okdown:
            MOVE_DICT['d'][i] = i + 4
        # Upward moves
        if okup:
            MOVE_DICT['u'][i] = i - 4
        # Right-down diagonal moves
        if okright and okdown:
            MOVE_DICT['dr'][i] = i + 5
        # Left-down diagonal moves
        if okleft and okdown:
            MOVE_DICT['dl'][i] = i + 3
        # Right-up diagonal moves
        if okright and okup:
            MOVE_DICT['ur'][i] = i - 3
        # Left-up diagonal moves
        if okleft and okup:
            MOVE_DICT['ul'][i] = i - 5

def n_moves(board,i,rv):
    """
    Determine valid knight moves
    """
    for s in range(4):
        j = MOVE_DICT[MOVE_LIST[s]][i]
        if j == -1:
            continue
        j = MOVE_DICT[MOVE_LIST[s]][j]
        if j == -1:
            continue
        for t in range(2):
            tt = (s + 2 * t + 1) % 4
            k = MOVE_DICT[MOVE_LIST[tt]][j]
            if k == -1:
                continue
            if board[k] <= 0:
                x = board[:]
                x[i] = 0
                x[k] = 1
                rv.append(x)

def dir_moves(board,i,direction,rv):
    """
    Determine valid single-directional moves (for all pieces ex. knights)
    """
    pos = i
    while True:
        pos = MOVE_DICT[direction][pos]
        if pos == -1:
            return
        if board[pos] > 0:
            return
        x = board[:]
        x[pos] = x[i]
        x[i] = 0
        rv.append(x)
        if board[pos] < 0:
            return

def b_moves(board,i,rv):
    # Bishops only move diagonally (move categories 4-7)
    for d in MOVE_LIST[4:]:
        dir_moves(board,i,d,rv)

def r_moves(board,i,rv):
    # Rooks only move horizontally (move categories 0-3)
    for d in MOVE_LIST[0:4]:
        dir_moves(board,i,d,rv)

def q_moves(board,i,rv):
    for d in MOVE_LIST:
        dir_moves(board,i,d,rv)

def new_boards(board):
    rv = []
    for i in range(16):
        if board[i] == 1:
            n_moves(board,i,rv)
        if board[i] == 2:
            b_moves(board,i,rv)
        if board[i] == 3:
            r_moves(board,i,rv)
        if board[i] == 4:
            q_moves(board,i,rv)
    return rv

def is_win(board):
    for p in board:
        if p == -4:
            return False
    return True

memodict = {}
def board_value(board, depth):
    state = tuple(board + [depth])
    if state in memodict:
        return memodict[state]
    if depth == 0:
        return 0
    bb = new_boards(board)
    if len(bb) == 0:
        return 0
    for x in bb:
        if is_win(x):
            memodict[state] = 1
            return 1
    best = -1
    for x in bb:
        y = [-p for p in x]
        v = -board_value(y, depth - 1)
        if v > best:
            best = v
        if best == 1:
            break
    memodict[state] = best
    return best

if __name__ == '__main__':
    for _ in range(int(sys.stdin.readline())):
        n_white, n_black, depth = (int(x) for x in sys.stdin.readline().split())
        board = get_init_board(n_white, n_black)
        precompute_moves()
        value = board_value(board, depth)
        if value == 1:
            print("YES")
        else:
            print("NO")