definit_game(game_settings): state ={'phase':"PLACE_SHIP",'player_info': [{"ships": [],"sunken_ship_sizes": [],"shots_at_this_player": []},{"ships": [],"sunken_ship_sizes": [],"shots_at_this_player": []} ],'player_turn_idx':0}return statedefplace_ships(state,ships):""" <ships> should be an array of 2+3+3+4+5 = 17 coordinates expressed as object {'x': x, 'y': y}. Elements 0~1 represent the first ship of size 2. Elements 2~4 represent the second ship of size 3, etc..., until elements 12~16 represent the fifth ship of size 5. """ BOARD_WIDTH =10 BOARD_HEIGHT =10 ships = [(int(coord["x"]),int(coord["y"])) for coord in ships] player_idx = state["player_turn_idx"] other_player_idx = (player_idx +1) %2iflen(state['player_info'][player_idx]['ships'])>0:raiseInvalidActionError("Ships already placed.")if__are_ships_valid(ships):__place_ships(state, player_idx, ships)iflen(state['player_info'][other_player_idx]['ships'])>0: state['phase']="FIRE_SHOT" state["player_turn_idx"]= other_player_idxreturn statedeffire_shot(state,x,y): BOARD_WIDTH =10 BOARD_HEIGHT =10 x =int(x) y =int(y)if state['phase']=="PLACE_SHIP":raiseInvalidActionError("Still waiting for ships to be placed.")if x <0or x >= BOARD_WIDTH or y <0or y >= BOARD_HEIGHT:raiseInvalidActionError("Out of bound.") player_idx = state["player_turn_idx"] other_player_idx = (player_idx +1) %2 other_player_info = state["player_info"][other_player_idx] target_positions = [(pos['x'], pos['y']) for ship in other_player_info['ships']for pos in ship['positions']] shots_fired =set([(shot['x'], shot['y']) for shot in other_player_info['shots_at_this_player']])if (x, y) in shots_fired:raiseInvalidActionError("Already placed a shot at this location.")if (x, y) in target_positions: did_hit =Trueelse: did_hit =False state['player_info'][other_player_idx]['shots_at_this_player'].append({'x': x,'y': y, 'did_hit': did_hit }) shots_fired.add((x, y)) state['player_info'][other_player_idx]['sunken_ship_sizes'] =__get_sunken_ships(shots_fired, state['player_info'][other_player_idx]['ships']) state["player_turn_idx"]= other_player_idxreturn statedef__get_sunken_ships(shots,ships): sunken_ships = []for ship in ships: is_sunk =Truefor pos in ship['positions']:if (pos['x'], pos['y']) notin shots: is_sunk =Falsecontinueif is_sunk: sunken_ships.append(len(ship['positions']))return sunken_shipsdef__place_ships(state,player_idx,ships): ship_models = [__make_ship_model(ships[0:2]),__make_ship_model(ships[2:5]),__make_ship_model(ships[5:8] ),__make_ship_model(ships[8:12]),__make_ship_model(ships[12:17])] state['player_info'][player_idx]['ships'] = ship_modelsdef__make_ship_model(coordinates): ship_positions = [{'x': coord[0],'y': coord[1]}for coord in coordinates]return{'positions': ship_positions}def__are_ships_valid(ships):iflen(ships)!=17:raiseInvalidActionError("Ships should be a list of 17 coordinates representing ships of sizes 2, 3, 3, 4, 5.")iflen(set(ships))!=17:raiseInvalidActionError("Ships are overlapping.")if (__is_valid_ship(ships[0:2])and__is_valid_ship(ships[2:5])and__is_valid_ship(ships[5:8])and__is_valid_ship(ships[8:12])and__is_valid_ship(ships[12:17])):returnTruereturnFalsedef__is_valid_ship(ship):""" Verify if <ship> is a list of adjacent tuples in a straight line that are within the bounds of the board. """ BOARD_WIDTH =10 BOARD_HEIGHT =10for x, y in ship:if x <0or x >= BOARD_WIDTH or y <0or y >= BOARD_HEIGHT:raiseInvalidActionError("Out of bound.") xs = [coordinate[0]for coordinate in ship] ys = [coordinate[1]for coordinate in ship] diff_x =max(xs)-min(xs) diff_y =max(ys)-min(ys)if diff_x ==0:# vertical ship adjacent_coordinates =sorted(ys)elif diff_y ==0: adjacent_coordinates =sorted(xs)else:raiseInvalidActionError("Ship has to be vertical or horizontal.")for i inrange(len(adjacent_coordinates) -1):if adjacent_coordinates[i+1]- adjacent_coordinates[i]!=1:raiseInvalidActionError("Ship coordinates aren't consecutive.")returnTruedefget_game_result(state):iflen(state['player_info'][0]['sunken_ship_sizes'])==5:iflen(state['player_info'][1]['sunken_ship_sizes'])==5:return{"game_result":"Draw"}else:return{"game_result":"Winner","winner_idx":1}eliflen(state['player_info'][1]['sunken_ship_sizes'])==5and state['player_turn_idx']==0:return{"game_result":"Winner","winner_idx":0}return{"game_result":"NoWinnerYet"}defget_player_states(state): player_states = []for i inrange(2): player_info = [{"ships": state['player_info'][0]["ships"] if i ==0else [],"sunken_ship_sizes": state['player_info'][0]["sunken_ship_sizes"],"shots_at_this_player": state['player_info'][0]["shots_at_this_player"]},{"ships": state['player_info'][1]["ships"] if i ==1else [],"sunken_ship_sizes": state['player_info'][1]["sunken_ship_sizes"],"shots_at_this_player": state['player_info'][1]["shots_at_this_player"]} ] player_states.append({'player_info': player_info,'phase': state['phase'],'player_turn_idx': state['player_turn_idx'] })return player_states
Game state schema
message State {enum Phase { PLACE_SHIP =0; FIRE_SHOT =1; }// Required field to indicate the player who should be making the next move// Values = 0 or 1int32 player_turn_idx =1;// This will be length 2 for the two players. The player can only see their own ships// but will have information on the other fields such as the shots and sunken ships.repeatedPlayerInfo player_info =2;// The game starts off with PLACE_SHIP phase. Once both players placed their ships,// the phase changes to FIRE_SHOT until one of the player's ships are all sunk.Phase phase =3;}
message PlayerInfo {
// Initially length 0. Once the ships are placed, this will be length 5.
// The ships will have Coordinate lengths of 2, 3, 3, 4, 5.
repeated Ship ships = 1;
// Opponents fire shots at this player. Once a ship is sunk, it gets added
// to this list, which stores the length of the ship that was sunk. This
// array will be size 0 to 5, and the game is over when all ships are sunk.
repeated int32 sunken_ship_sizes = 2;
// Stores all the shots at this player.
repeated Shot shots_at_this_player = 3;
}
message Shot {
int32 x = 1;
int32 y = 2;
bool did_hit = 3;
}
message Coordinate {
int32 x = 1;
int32 y = 2;
}
Action schema
/*
Takes in a list of length 2+3+3+4+5 = 17 coordinates
Elements 0~1 represent the first ship of size 2.
Elements 2~4 represent the second ship of size 3, etc...,
until elements 12~16 represent the fifth ship of size 5.
*/
place_ships(list<coordinate> ships)
// The action needed for the second phase of the game
fire_shot(int x, int y)