'''
Author: Caleb Pitts
Date: 3/15/19
Summary:
- Contains computation methods that board.py uses to
manage valid move seeks and piece placement.
- Methods use Numba with jit decorator that precompiles
types and makes runtime faster than normal python.
'''
from numba import jit
import numpy as np
import math
# def dummy_jit(*args, **kwargs):
# def dumdum(f):
# return f
# return dumdum
#
# jit = dummy_jit
#### METHODS FOR check_shifted() ####
[docs]@jit("UniTuple(int64, 2)(UniTuple(int64, 2), UniTuple(int64, 2), double)", nopython=True) # "int(int64, ...)"
def rotate_by_deg(index, offset_point, angle):
''' Rotates each point on piece around the index by the given angle
'''
ox, oy = index
px, py = offset_point
new_x = ox + math.cos(angle) * (px - ox) - math.sin(angle) * (py - oy)
new_y = oy + math.sin(angle) * (px - ox) + math.cos(angle) * (py - oy)
return int(round(new_x, 1)), int(round(new_y, 1))
[docs]@jit("UniTuple(int64, 2)(UniTuple(int64, 2), int64, int64)", nopython=True)
def flip_piece_x(index, x, y):
''' Takes the difference between index x and point x, then applies reverse
difference to the index point. y stays the same
'''
return index[0] - (index[0] - x) * -1, y
[docs]@jit("UniTuple(int64, 2)(UniTuple(int64, 2), int64, int64)", nopython=True)
def flip_piece_y(index, x, y):
''' Takes the difference between index y and point y, then applies reverse
difference to the index point. x stays the same
'''
return x, index[1] + (y - index[1]) * -1
[docs]@jit("UniTuple(int64, 2)(UniTuple(int64, 2), int64, int64, unicode_type)", nopython=True)
def rotate_piece(index, x_offset, y_offset, piece_orientation):
''' Description: Orients piece around the index point
Parameters:
index: int tuple that specifies the index coordinate on the board (the coord the piece will rotate around)
offset: int tuple that specifies the offset from the index coord for the current cell
piece_orientation: string specifying what new orientation you want the point at
Returns:
2 ints x and y that are the new rotated piece coords
'''
piece_orientation = piece_orientation[:-1] # Takes out last character specifying the shift id (not needed in this method)
x_offset += index[0] # calculates the actual x coord on board
y_offset += index[1] # calculates the actual y coord on board
if piece_orientation == "north":
return rotate_by_deg(index, (x_offset, y_offset), math.radians(270))
elif piece_orientation == "northwest":
new_x, new_y = rotate_by_deg(index, (x_offset, y_offset), math.radians(270))
return flip_piece_x(index, new_x, new_y)
elif piece_orientation == "south":
return rotate_by_deg(index, (x_offset, y_offset), math.radians(90))
elif piece_orientation == "southeast":
new_x, new_y = rotate_by_deg(index, (x_offset, y_offset), math.radians(90))
return flip_piece_x(index, new_x, new_y)
elif piece_orientation == "west":
return rotate_by_deg(index, (x_offset, y_offset), math.radians(180))
elif piece_orientation == "southwest":
new_x, new_y = rotate_by_deg(index, (x_offset, y_offset), math.radians(180))
return flip_piece_y(index, new_x, new_y)
elif piece_orientation == "northeast":
new_x, new_y = rotate_by_deg(index, (x_offset, y_offset), math.radians(0))
return flip_piece_y(index, new_x, new_y)
else: # Default orientation (East)
return rotate_by_deg(index, (x_offset, y_offset), math.radians(0))
[docs]@jit("boolean(int64[:, ::1], int64, int64, int64)", nopython=True)
def is_valid_adjacents(board_contents, y, x, player_color):
''' Description: Invalid coord if left, right, bottom, or top cell is the same color as the current player.
Parameters:
board_contents: 20 by 20 numpy matrix representing the current state of the board
x: int x coord of the cell
y: int y coord of the cell
player_color: int representing current player color
Returns:
bool indicating whether the cell is a valid adjacent
'''
valid_adjacent = True
# Excludes top board edge from top cell check
if y != 0:
if board_contents[y - 1][x] == player_color:
valid_adjacent = False
# Excludes left board edge from left cell check
if x != 0:
if board_contents[y][x - 1] == player_color:
valid_adjacent = False
# Excludes bottom board edge from bottom cell check
if y != 19:
if board_contents[y + 1][x] == player_color:
valid_adjacent = False
# Excludes right board edge from right cell check
if x != 19:
if board_contents[y][x + 1] == player_color:
valid_adjacent = False
return valid_adjacent
[docs]@jit("boolean(int64[:, ::1], int64, int64, int64)", nopython=True)
def is_valid_cell(board_contents, x, y, player_color):
''' Description: If the cell x, y is empty, has no adjacent cells that are the same color,
and is not out of bounds of the 20x20 board, then the cell is valid
to put a part of a piece on it.
Parameters:
board_contents: 20 by 20 numpy matrix representing the current state of the board
x: int x coord of the cell
y: int y coord of the cell
player_color: int representing current player color
Returns:
bool indicating whether the cell is a valid cell
'''
# Out of bounds check
if x < 0 or x >= 20 or y < 0 or y >= 20:
return False
# Checks if cell is empty and a valid adjacent
if (board_contents[y][x] == 0 and is_valid_adjacents(board_contents, y, x, player_color)):
return True
else:
return False
[docs]@jit("int64[:](int64[:, ::1], int64, UniTuple(int64, 2), unicode_type, int64[:, :, ::1])", nopython=True)
def check_shifted(board_contents, player_color, index, orientation, shifted_offsets):
''' Description: Shifts entire piece N times were N is how many cells the piece takes up.
All shifted offsets are checked for the current orientation to see whether
the shifted set of offsets is a valid move.
Parameters:
board_contents: 20 by 20 numpy matrix representing the current state of the board
played_color: int representing current player color
index: int tuple that specifies the index coordinate on the board (the coord the piece will rotate around)
orientation: string specifying which orientation is being checked
shifted_offsets: list of a list of tuples where each element in the main list represents a different set of coords
for a shifted piece.
Returns:
Returns the list of ints representing the shifted offsets ids where the piece can be
placed at that set of shifted offsets
'''
shifted_ids = np.zeros(shifted_offsets.shape[0], np.int64)
num_items = 0
for shifted_id in range(shifted_offsets.shape[0]): # Shift piece N times where N is the number of cells in the piece
valid_placement = True
for offset_id in range(shifted_offsets.shape[1]):
offset = shifted_offsets[shifted_id, offset_id, :]
if offset[0] == 0 and offset[1] == 0: # No need to rotate coord since its the index and there is no offset
if not is_valid_cell(board_contents, index[0], index[1], player_color):
valid_placement = False
else:
new_piece = rotate_piece(index, offset[0], offset[1], orientation)
new_x = new_piece[0]
new_y = new_piece[1]
if not is_valid_cell(board_contents, new_x, new_y, player_color):
valid_placement = False
if valid_placement:
shifted_ids[num_items] = shifted_id
num_items += 1
return shifted_ids[:num_items]
#### METHODS FOR get_all_shifted_offsets() ####
[docs]@jit("int64[:, ::1](int64[:, ::1], unicode_type)", nopython=True)
def rotate_default_piece(offsets, orientation):
''' Description: Rotates the initial default piece orientation for shifting.
Parameters:
offsets: numpy array of tuples indicated all corresponding offsets to a specific piece type
orientation: string indicating the orientation to rotate the offset pieces
Returns:
numpy list of all offsets for given orientation
'''
orientation_offsets_to_shift = np.zeros((len(offsets), 2), np.int64)
for index in range(len(offsets)):
if offsets[index][0] == 0 and offsets[index][1] == 0:
orientation_offsets_to_shift[index, :] = (0, 0)
else:
new_coord = rotate_piece((0, 0), offsets[index][0], offsets[index][1], orientation + "!") # adding dummy character to end since rotate ignores last character of orientation
orientation_offsets_to_shift[index, :] = (new_coord[0], new_coord[1])
return orientation_offsets_to_shift
[docs]@jit("int64[:, ::1](int64[:, ::1], int64)", nopython=True)
def shift_offsets(offsets, offset_id):
''' Description: Shifts the offsets so that the offset that corresponds to the offset_id is the new index
Parameters:
offsets: numpy array of tuples containing the coords of offsets needing do be shifted
offset_id: identifies which coord becomes the new index in the list of offsets
Returns:
numpy array of tuples containing the newly shifted offsets
'''
shifted_offsets = np.zeros((len(offsets), 2), np.int64)
if offset_id == 0: # No need to shift the offsets for the default piece shape defined in the global space
return offsets
new_origin_y_diff = offsets[offset_id][0]
new_origin_x_diff = offsets[offset_id][1]
for index in range(len(offsets)):
shifted_offsets[index, :] = (offsets[index][0] - new_origin_y_diff, offsets[index][1] - new_origin_x_diff)
return shifted_offsets
[docs]@jit("int64[:, :, ::1](int64[:, ::1], unicode_type)", nopython=True)
def get_all_shifted_offsets(offsets, orientation):
''' Description: Compiles a list of all shifted offsets for a piece at a specific orientation.
Returns a numpy array, which is a list of a list of tuples which each contain
a shifted offset.
Parameters:
offsets: numpy array of tuples containing the coords of offsets needing do be shifted
orientation: string specifying the orientation that the shifts should take place
Returns:
list of all shifted orientations a piece can make at a given orientation
'''
orientation_offsets_to_shift = rotate_default_piece(offsets, orientation)
shifted_offsets = np.zeros((len(offsets), len(orientation_offsets_to_shift), 2), np.int64)
for offset_id in range(len(orientation_offsets_to_shift)):
shifted_offsets[offset_id] = shift_offsets(orientation_offsets_to_shift, offset_id)
return shifted_offsets