def _place_tile(state, tile, brand): if not 0 <= tile.x < int(os.environ['WIDTH']): raise models.RuleViolation('x coordinate is off the board!') if not 0 <= tile.y < int(os.environ['HEIGHT']): raise models.RuleViolation('y coordinate is off the board!') if state.grid[tile.x][tile.y] is not None: raise models.RuleViolation('Space already has tile on it!') neighbors = get_unique_neighbors(state.grid, tile) locked_neighbors = [neighbor for neighbor in neighbors if neighbor.is_locked()] if len(locked_neighbors) > 1: raise models.RuleViolation('Cannot place tile adjacent to multiple locked chains!') if len(neighbors) == 0: if brand: raise models.RuleViolation('Cannot specify brand for single tile!') _create_chain(state, tile) return models.PlaceTileResult(state, [], None, None, tile) if len(neighbors) == 1: _grow_chain(state, neighbors[0], brand, tile) return models.PlaceTileResult(state, [], None, brand, tile) acquired_chains, acquiree, new_brand = _merge_chains(state, neighbors, brand, tile) return models.PlaceTileResult(state, acquired_chains, acquiree, new_brand, tile)
def _merge_chains(state, current_chains, brand, tile): new_chain = models.Chain([tile]) chains = current_chains + [new_chain] branded_chains = [chain for chain in chains if chain.brand] if not any(branded_chains): return _combine_chains(state, chains, brand) max_branded_chain_length_example = max(branded_chains, key=lambda c: len(c.tiles)) max_branded_chain_length = len(max_branded_chain_length_example.tiles) largest_brands = [chain.brand for chain in branded_chains if len(chain.tiles) == max_branded_chain_length] largest_brand_count = len(largest_brands) if largest_brand_count == 1: if brand: raise models.RuleViolation('Cannot choose a brand when there is uniquely one largest!') return _combine_chains(state, chains, largest_brands[0]) if not brand: raise models.RuleViolation('Must choose a brand!') if brand not in largest_brands: raise models.RuleViolation('Must choose one of the largest brands!') return _combine_chains(state, chains, brand)
def buy_stock(state, player_id, brand, amount): if amount < 0: raise models.RuleViolation('Cannot buy negative stock!') if amount == 0: return state available_stock_count = state.stock_availability[brand] player_cash_amount = state.money_by_player[player_id] if amount > available_stock_count: raise models.RuleViolation('Insufficient stock available to fulfill this order!') price_per_stock = _calculate_price_from_state_and_brand(state, brand) total_price = price_per_stock * amount if total_price > player_cash_amount: raise models.RuleViolation('You cannot afford this order!') state.stock_availability[brand] -= amount _perform_money_change(state, player_id, -total_price) state.stock_by_player[player_id][brand] += amount action_display.record_buy_action(state, brand, amount, price_per_stock) return state
def trade_stock(state, player_id, brand_to_send, brand_to_receive, send_count): if send_count < 0: raise models.RuleViolation('Cannot trade negative stock!') if send_count % 2 != 0: raise models.RuleViolation('Cannot trade an odd number of stock!') if send_count == 0: return state global_stock_to_receive_count = state.stock_availability[brand_to_receive] receive_count = send_count / 2 if receive_count > global_stock_to_receive_count: raise models.RuleViolation('Insufficient stock available to complete trade!') state.stock_by_player[player_id][brand_to_send] -= send_count state.stock_by_player[player_id][brand_to_receive] += receive_count state.stock_availability[brand_to_send] += send_count state.stock_availability[brand_to_receive] -= receive_count action_display.record_trade_action( state, brand_to_send, send_count, brand_to_receive, receive_count) return state
def _calculate_price_from_state_and_brand(state, brand): chain = grid.find_chain(state.grid, brand) if not chain: raise models.RuleViolation('No chains of this brand found!') return calculate_price_from_chain(chain)
def _grow_chain(state, chain, brand, tile): if brand in state.cost_by_brand: raise models.RuleViolation('Cannot use brand already in use!') if brand is not None and chain.brand is not None: raise models.RuleViolation('Cannot rebrand chain!') if brand is None and chain.brand is None: raise models.RuleViolation('Cannot grow chain without branding it!') if brand: chain.brand = brand chain.tiles.append(tile) state.grid[tile.x][tile.y] = chain return state
def start_game(): game_id = request.json['game_id'] game_state = persistance.get_game_state(game_id) if game_state.is_started: raise models.RuleViolation('Cannot start already started game!') player_count_min = int(os.environ['PLAYER_COUNT_MIN']) player_count_max = int(os.environ['PLAYER_COUNT_MAX']) tile_hand_size = int(os.environ['TILE_HAND_SIZE']) player_count = len(game_state.player_order) if not player_count_min <= player_count <= player_count_max: raise models.RuleViolation( f'Cannot start game with {player_count} players!') shuffle(game_state.player_order) starting_player = game_state.player_order[0] initial_tiles = tiles.generate_initial_tiles() board_starting_tiles = tiles.draw_tiles(initial_tiles, player_count) for tile in board_starting_tiles: grid.place_tile(game_state, tile) game_state.is_started = True game_state.current_turn_player = starting_player game_state.current_action_player = starting_player tiles_by_player_id = { player_id: tiles.draw_tiles(initial_tiles, tile_hand_size) for player_id in game_state.player_order } game_state.tiles_remaining = len(initial_tiles) persistance.initialize_global_tiles(game_id, initial_tiles) persistance.initialize_player_tiles(game_id, tiles_by_player_id) persistance.update_game_state(game_id, game_state.to_dict()) return 'OK'
def place_tile(): game_id = request.json['game_id'] id_token = request.json['id_token'] x = int(request.json['x']) y = int(request.json['y']) raw_brand = request.json['brand'] brand = None if raw_brand == '' else models.Brand(raw_brand) player_id = firebase_admin.auth.verify_id_token(id_token)['uid'] state = persistance.get_game_state(game_id) if state.current_action_player != player_id: raise models.RuleViolation( "Stop trying to take other player's turns! You cheat!") if state.current_action_type != models.ActionType.PLACE_TILE: raise models.RuleViolation( 'It is your turn, but it is not time to place a tile!') player_tiles = persistance.get_player_tiles(game_id, player_id) if not any(tile.x == x and tile.y == y for tile in player_tiles): raise models.RuleViolation( 'You do not have that tile! Stop trying to cheat!') if not state.is_started: raise models.RuleViolation('Cannot take turn until game has begun!') tile = models.Tile(x, y) place_tile_result = grid.place_tile(state, tile, brand) stock.apply_majority_bonuses(state, place_tile_result.acquired_chains) stock.award_founder_share(state, player_id, place_tile_result.new_brand) grid.set_brand_lists(state) stock.set_price_table(state) turns.transition_from_place(state, place_tile_result, game_id) persistance.delete_player_tile(game_id, player_id, models.Tile(x, y)) # this should all happen atomically, but as a stopgap make sure this happens last persistance.update_game_state(game_id, state.to_dict()) return 'OK'
def sell_stock(state, player_id, brand, cost_per_stock, sell_count): if sell_count < 0: raise models.RuleViolation('Cannot sell negative stock!') if sell_count == 0: return state player_stock_count = state.stock_by_player[player_id][brand] if player_stock_count < sell_count: raise models.RuleViolation('Cannot sell more stock than you have!') total_price = cost_per_stock * sell_count state.stock_by_player[player_id][brand] -= sell_count state.stock_availability[brand] += sell_count _perform_money_change(state, player_id, total_price) action_display.record_sell_action(state, player_id, brand, sell_count, cost_per_stock) return state
def resolve_acquisition(): game_id = request.json['game_id'] id_token = request.json['id_token'] sell_count = int(request.json['sell_count']) trade_count = int(request.json['trade_count']) user_id = firebase_admin.auth.verify_id_token(id_token)['uid'] state = persistance.get_game_state(game_id) if state.current_action_player != user_id: raise models.RuleViolation( "Stop trying to take other player's turns! You cheat!") if state.current_action_type != models.ActionType.RESOLVE_ACQUISITION: raise models.RuleViolation( 'It is your turn, but it is not time to resolve an acquisition!') if not state.is_started: raise models.RuleViolation('Cannot take turn until game has begun!') acquiree = models.Brand(state.current_action_details['acquiree']) player_acquiree_stock_count = state.stock_by_player[user_id][acquiree] if sell_count + trade_count > player_acquiree_stock_count: raise models.RuleViolation( 'Cannot trade and sell more stock than you current have!') acquirer = models.Brand(state.current_action_details['acquirer']) cost_at_acquisition_time = state.current_action_details[ 'acquiree_cost_at_acquisition_time'] stock.sell_stock(state, user_id, acquiree, cost_at_acquisition_time, sell_count) stock.trade_stock(state, user_id, acquiree, acquirer, trade_count) turns.transition_from_resolve(state, game_id) persistance.update_game_state(game_id, state.to_dict()) return 'OK'
def join_game(): game_id = request.json['game_id'] user_id = request.json['user_id'] game_state = persistance.get_game_state(game_id) if user_id in game_state.player_order: raise models.RuleViolation('Player is already in this game!') if game_state.is_started: raise models.RuleViolation('Cannot join a game in progress!') user_data = persistance.get_user_data(user_id) game_state.player_order.append(user_id) game_state.money_by_player[user_id] = 6000 game_state.stock_by_player[user_id] = {brand: 0 for brand in models.Brand} game_state.user_data_by_id[user_id] = user_data persistance.update_game_state(game_id, game_state.to_dict()) return 'OK'
def buy_stock(): max_purchase_amount = int(os.environ['MAX_STOCK_PURCHASE_AMOUNT']) game_id = request.json['game_id'] id_token = request.json['id_token'] purchase_order = request.json['purchase_order'] user_id = firebase_admin.auth.verify_id_token(id_token)['uid'] state = persistance.get_game_state(game_id) if state.current_action_player != user_id: raise models.RuleViolation( "Stop trying to take other player's turns! You cheat!") if state.current_action_type != models.ActionType.BUY_STOCK: raise models.RuleViolation( 'It is your turn, but it is not time to buy stock!') if not state.is_started: raise models.RuleViolation('Cannot take turn until game has begun!') total_stock_purchased = 0 for raw_brand, raw_amount in purchase_order.items(): parsed_amount = int(raw_amount) total_stock_purchased += parsed_amount brand = models.Brand(raw_brand) stock.buy_stock(state, user_id, brand, parsed_amount) if total_stock_purchased > max_purchase_amount: raise models.RuleViolation('Too many stock in purchase order!') turn_transitioned_state = turns.transition_from_buy(state, game_id) persistance.update_game_state(game_id, turn_transitioned_state.to_dict()) return 'OK'