Reversi

Game logic

Game Logic code
def init_game(game_settings):
    state = {
        'player_turn_idx': 0,
        # Initial board has 4 pieces already placed
        'game_board': [-1, -1, -1, -1, -1, -1, -1, -1,
                    -1, -1, -1, -1, -1, -1, -1, -1,
                    -1, -1, -1, -1, -1, -1, -1, -1,
                    -1, -1, -1, 0, 1, -1, -1, -1,
                    -1, -1, -1, 1, 0, -1, -1, -1,
                    -1, -1, -1, -1, -1, -1, -1, -1,
                    -1, -1, -1, -1, -1, -1, -1, -1,
                    -1, -1, -1, -1, -1, -1, -1, -1],
        # First move can be in these four places
        # key is valid index to place piece, directions are directions to modify board after placing piece
        'valid_moves': [
            {
                'index': 20, 
                'valid_directions': ['BELOW']
            },
            {
                'index': 29, 
                'valid_directions': ['LEFT']
            },
            {
                'index': 34, 
                'valid_directions': ['RIGHT']
            },
            {
                'index': 43, 
                'valid_directions': ['ABOVE']
            }]
    }
    return state

def place_piece(state, index):
    """
    Places a piece in the game state's user_board parameter.
    If an index is outside the user_boards range or its not the user's turn,
    an exception is thrown.

    state: a reversi game model state created from init_game
    index: the index in user_board that the piece will be placed
    """
    assert type(index) == int, repr(index) + " is not an integer."
    if index < 0 or index > 63:
        raise InvalidActionError("Invalid index.")
    if state['game_board'][index] != -1:
        raise InvalidActionError("A piece has already been placed here.")
    if len(state['valid_moves']) == 0:
        raise InvalidActionError("The game is done.")


    offset_dict = {
        'ABOVE': -8, 
        'BELOW': 8,
        'LEFT': -1, 
        'RIGHT': 1,
        'ABOVE_LEFT': -9, 
        'ABOVE_RIGHT': -7,
        'BELOW_LEFT': 7, 
        'BELOW_RIGHT': 9}

    player_piece = state['player_turn_idx']
    opp_piece = (player_piece + 1) % 2

    valid_move = None
    for pair in state['valid_moves']:
        if index == pair['index']:
            valid_move = pair # Use to modify board
            break
    if valid_move == None:
        raise InvalidActionError("This is not a valid place to put a piece.")
    state['game_board'][valid_move['index']] = player_piece
    state = _modify_board(state, player_piece, valid_move, offset_dict)

    state = _modify_valid_moves(state, opp_piece, player_piece, offset_dict)
    if len(state['valid_moves']) == 0: # If opponent no moves, then you go again (if possible)
        state = _modify_valid_moves(state, player_piece, opp_piece, offset_dict)
    else: # Opponent has valid move
        state['player_turn_idx'] = opp_piece
    # At this point, valid_moves will only be 0 if both users have no moves
    return state

def get_game_result(state):
    """ Returns if a the game represented by state is won, ongoing, or a tie """
    if len(state['valid_moves']) != 0:
        return {
            "game_result": "NoWinnerYet"
        }
    else:
        board = state['game_board']
        num_user1_pieces = sum([x == 0 for x in board])
        num_user2_pieces = sum([x == 1 for x in board])
        if num_user1_pieces == num_user2_pieces: # Board is filled
            return {
                "game_result": "Draw"
            }
        else:
            if num_user1_pieces > num_user2_pieces:
                return {
                    "game_result": "Winner",
                    "winner_idx": 0
                }
            else:
                return {
                    "game_result": "Winner",
                    "winner_idx": 1
                }

def _modify_board(state, player_piece, valid_move, offset_dict):
    """ Changes the color of the pieces in a certain direction for a valid move
    If this function is called, we know that placing a piece at index is valid. """
    for direction in valid_move['valid_directions']:
        increase_interval = offset_dict[direction]
        iterator = increase_interval + valid_move['index']
        while (state['game_board'][iterator] != player_piece):
            state['game_board'][iterator] = player_piece
            iterator += increase_interval
    return state

def _modify_valid_moves(state, player_piece, opp_piece, offset_dict):
    """ Modify state['valid_moves'] for the next move. """
    # TODO implement BFS starting at index's key
    del state['valid_moves'][:] # Empty valid moves list
    for board_pos in range(64):
        if state['game_board'][board_pos] == -1:
            valid_directions_for_index = []
            for direction_enum in offset_dict:
                if _validate_move_generic(state, player_piece, opp_piece, board_pos, offset_dict[direction_enum]):
                    valid_directions_for_index.append(direction_enum)

            if len(valid_directions_for_index) > 0:
                state['valid_moves'].append({
                    'index': board_pos, 
                    'valid_directions': valid_directions_for_index
                })
    return state

def _validate_move_generic(state, player_piece, opp_piece, board_pos, offset_amount):
    """ Check if placing a piece at board_pos is valid when looking at one
    of the eight directions. """
    if _is_out_of_bound(board_pos, offset_amount) or state['game_board'][board_pos + offset_amount] != opp_piece:
        return False
    iterator = board_pos + (offset_amount * 2)
    while _is_within_iterator_bound(board_pos, iterator, offset_amount):
        if state['game_board'][iterator] == player_piece:
            return True
        elif state['game_board'][iterator] == -1:
            return False # Placement of piece will not surround an opponent piece
        iterator += offset_amount
    return False

def _is_out_of_bound(board_pos, offset_amount):
    """ Determine if index is on the boundary of the board. If so, there is no
    need to validate the move in certain directions.
    Assume index is already between 0 and 63, inclusive. """
    if offset_amount == -8: return True if board_pos <= 7 else False
    if offset_amount == 8: return True if board_pos >= 56 else False
    if offset_amount == -1: return True if board_pos % 8 == 0 else False
    if offset_amount == 1: return True if (board_pos - 7) % 8 == 0 else False
    if offset_amount == -9:
        return True if board_pos % 8 == 0 or board_pos <= 7 else False
    if offset_amount == 7:
        return True if board_pos % 8 == 0 or board_pos >= 56 else False
    if offset_amount == -7:
        return True if (board_pos - 7) % 8 == 0 or board_pos <= 7 else False
    if offset_amount == 9:
        return True if (board_pos - 7) % 8 == 0 or board_pos >= 56 else False

def _is_within_iterator_bound(board_pos, iterator, offset_amount):
    if offset_amount == -8: return iterator >= 0
    if offset_amount == 8: return iterator <= 63
    if offset_amount == -1: return iterator >= (board_pos // 8) * 8
    if offset_amount == 1: return iterator <= ((board_pos // 8) * 8) + 7
    if offset_amount == -7: return iterator >= 2 and iterator % 8 != 0
    if offset_amount == -9: return iterator >= 0 and (iterator - 7) % 8 != 0
    if offset_amount == 7: return iterator <= 61 and (iterator - 7) % 8 != 0
    if offset_amount == 9: return iterator <= 63 and iterator % 8 != 0

Game state schema

message State {
  // Required field to indicate the player who should be making the next move
  // Values = 0 or 1    
  int32 player_turn_idx = 1;

  // An array of size 64 stored in row major order, so index 0 is top left, 
  // index 7 is top right, index 56 is bottom left, and index 63 is bottom right.
  // Values can be -1, 0, or 1, where -1 represents an empty spot, 0 is a piece
  // made by player_idx 0 and 1 is a piece made by player_idx 1.
  repeated int32 game_board = 2;

  // Precomputed array of all the valid moves. Players should select an index from
  // this list to make a move. 
  repeated ValidMove valid_moves = 3;
}
/*
Each ValidMove is an index that the current player can place his piece on.
It also contains information about the all directions that will flip the 
opponent pieces. 
*/
message ValidMove {
  int32 index = 1;
  repeated direction valid_directions = 2;
}
// The 8 directions to flip opponent disks.
enum direction {
  ABOVE = 0;
  BELOW = 1;
  LEFT = 2;
  RIGHT = 3;
  ABOVE_LEFT = 4;
  ABOVE_RIGHT = 5;
  BELOW_LEFT = 6;
  BELOW_RIGHT = 7;
}

Action schema

place_piece(int index)

Start building now

Last updated