Blokus
Game logic
Game Logic code
def init_game(game_settings, ref_data):
player_count = int(game_settings["Player Count"])
required_positions = [
{
"positions": [{"x": 0, "y": 0}]
},
{
"positions": [{"x": 19, "y": 0}]
},
{
"positions": [{"x": 19, "y": 19}]
},
{
"positions": [{"x": 0, "y": 19}]
}
]
return {
"player_hands": [list(range(len(ref_data["piece_templates"]))) for i in range(4)],
"game_board": [-1]*(20*20),
"players_unavailable_spots": [{
"unavailable_spots": [0]*(20*20)
} for i in range(4)],
"scores": [0]*player_count,
"skips": 0,
"required_positions": required_positions,
"player_turn_idx": 0,
"player_idx_to_play": 0,
"player_idx_to_play_fourth_color": 0 if player_count == 3 else None
}
def place_piece(state, ref_data, piece_idx, orientation_idx, offset_x, offset_y):
player_idx = state["player_idx_to_play"]
if piece_idx not in state["player_hands"][player_idx]:
raise InvalidActionError("piece_idx not in the player's hand")
if orientation_idx < 0 or orientation_idx >= len(ref_data["piece_templates"][piece_idx]["all_orientations"]):
raise InvalidActionError("orientation_idx out of bound")
piece = ref_data["piece_templates"][piece_idx]["all_orientations"][orientation_idx]
board = state["game_board"]
positions, sides, corners = _convert_pos_to_coordinate_tuples(piece, offset_x, offset_y)
required_positions = state["required_positions"][player_idx]["positions"]
required_positions = set([(pos["x"], pos["y"]) for pos in required_positions])
is_required_position_met = False
for x, y in positions:
if x < 0 or y < 0 or x >= 20 or y >= 20:
raise InvalidActionError("Placing this piece here goes out of the board")
if board[_coordinate_to_index(x, y)] != -1:
raise InvalidActionError("Piece overlaps with another on the board")
if (x, y) in required_positions:
is_required_position_met = True
if not is_required_position_met:
raise InvalidActionError("Piece must be placed in one of the required positions (check if it touches the corner of another one of your piece)")
for x, y in sides:
if x < 0 or y < 0 or x >= 20 or y >= 20:
continue
if board[_coordinate_to_index(x, y)] == player_idx:
raise InvalidActionError("The side of this piece touches another one of your own pieces")
# Place the piece and update all the other helper fields
for x, y in positions:
index = _coordinate_to_index(x, y)
board[index] = player_idx
for unavailable_spots in state["players_unavailable_spots"]:
unavailable_spots["unavailable_spots"][index] = 1
for x, y in sides:
if x < 0 or y < 0 or x >= 20 or y >= 20:
continue
state["players_unavailable_spots"][player_idx]["unavailable_spots"][_coordinate_to_index(x, y)] = 1
for x, y in corners:
if x >= 0 and y >= 0 and x < 20 and y < 20:
state["required_positions"][player_idx]["positions"].append({"x": x, "y": y})
state["player_hands"][player_idx].remove(piece_idx)
state = _sanitize_required_positions(state, player_idx)
state["game_board"] = board
state["skips"] = 0
state = _update_score(state, len(positions))
state = _update_player_turns(state)
return state
def pass_turn(state, ref_data):
state["skips"] += 1
state = _update_player_turns(state)
return state
def get_game_result(state, ref_data):
if state["skips"] == len(state["scores"]):
reversed_scores = state["scores"][::-1] # if scores are equal, last player to move wins
return {
"game_result": "Winner",
"winner_idx": len(reversed_scores) - 1 - reversed_scores.index(max(reversed_scores))
}
return {
"game_result": "NoWinnerYet"
}
def _convert_pos_to_coordinate_tuples(piece, offset_x, offset_y):
positions = [(pos["x"] + offset_x, pos["y"] + offset_y) for pos in piece["pos"]]
sides = [(side["x"] + offset_x, side["y"] + offset_y) for side in piece["side"]]
corners = [(corner["x"] + offset_x, corner["y"] + offset_y) for corner in piece["corner"]]
return positions, sides, corners
def _coordinate_to_index(x, y):
return x + y*20
def _sanitize_required_positions(state, player_idx):
required_positions = state["required_positions"]
players_unavailable_spots = state["players_unavailable_spots"]
board = state["game_board"]
new_required_positions = []
for i in range(4):
required = required_positions[i]
unavailable_spots = players_unavailable_spots[i]["unavailable_spots"]
new_required = []
for pos in required["positions"]:
index = _coordinate_to_index(pos["x"], pos["y"])
if unavailable_spots[index] == 1:
continue # this spot is no longer available, so it is not required
if board[index] == -1:
new_required.append(pos)
new_required_positions.append({
"positions": new_required
})
state["required_positions"] = new_required_positions
return state
def _update_score(state, score):
num_of_players = len(state["scores"])
if num_of_players == 2 or num_of_players == 4:
state["scores"][state["player_idx_to_play"] % num_of_players] += score
elif num_of_players == 3:
if state["player_idx_to_play"] != 3:
state["scores"][state["player_idx_to_play"]] += score
return state
def _update_player_turns(state):
state["player_idx_to_play"] = (state["player_idx_to_play"] + 1) % 4 # colors always increment
num_of_players = len(state["scores"])
if num_of_players == 2 or num_of_players == 4:
state["player_turn_idx"] = state["player_idx_to_play"] % num_of_players
elif num_of_players == 3:
if state["player_idx_to_play"] == 3:
state["player_turn_idx"] = state["player_idx_to_play_fourth_color"]
state["player_idx_to_play_fourth_color"] = (state["player_idx_to_play_fourth_color"] + 1) % 3
else:
state["player_turn_idx"] = state["player_idx_to_play"]
return state
Game state schema
message State {
repeated int32 player_hands = 1;
repeated int32 game_board = 2;
repeated UnavailableSpots players_unavailable_spots = 3;
int32 player_turn_idx = 4;
repeated int32 scores = 5;
int32 skips = 6;
repeated RequiredPositions required_positions = 7;
int32 player_idx_to_play = 8;
int32 player_idx_to_play_fourth_color = 9;
}
message Orientation {
repeated Position pos = 1;
repeated Position side = 2;
repeated Position corner = 3;
}
message PieceTemplate {
repeated Orientation all_orientations = 1;
}
message RefData {
repeated PieceTemplate piece_templates = 1;
}
message RequiredPositions {
repeated Position positions = 1;
}
message Position {
int32 x = 1;
int32 y = 1;
}
message UnavailableSpots {
repeated int32 unavailable_spots = 1;
}
Action schema
place_piece(int piece_idx, int orientation_idx, int offset_x, int offset_y)
pass_turn()
Last updated