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)
Last updated