class TestSingleNetworkGrid(unittest.TestCase): GRAPH_SIZE = 10 def setUp(self): ''' Create a test network grid and populate with Mock Agents. ''' G = nx.complete_graph(TestSingleNetworkGrid.GRAPH_SIZE) self.space = NetworkGrid(G) self.agents = [] for i, pos in enumerate(TEST_AGENTS_NETWORK_SINGLE): a = MockAgent(i, None) self.agents.append(a) self.space.place_agent(a, pos) def test_agent_positions(self): ''' Ensure that the agents are all placed properly. ''' for i, pos in enumerate(TEST_AGENTS_NETWORK_SINGLE): a = self.agents[i] assert a.pos == pos def test_get_neighbors(self): assert len(self.space.get_neighbors( 0, include_center=True)) == TestSingleNetworkGrid.GRAPH_SIZE assert len(self.space.get_neighbors( 0, include_center=False)) == TestSingleNetworkGrid.GRAPH_SIZE - 1 def test_move_agent(self): initial_pos = 1 agent_number = 1 final_pos = TestSingleNetworkGrid.GRAPH_SIZE - 1 _agent = self.agents[agent_number] assert _agent.pos == initial_pos assert _agent in self.space.G.node[initial_pos]['agent'] assert _agent not in self.space.G.node[final_pos]['agent'] self.space.move_agent(_agent, final_pos) assert _agent.pos == final_pos assert _agent not in self.space.G.node[initial_pos]['agent'] assert _agent in self.space.G.node[final_pos]['agent'] def test_is_cell_empty(self): assert not self.space.is_cell_empty(0) assert self.space.is_cell_empty(TestSingleNetworkGrid.GRAPH_SIZE - 1) def test_get_cell_list_contents(self): assert self.space.get_cell_list_contents([0]) == [self.agents[0]] assert self.space.get_cell_list_contents( list(range(TestSingleNetworkGrid.GRAPH_SIZE))) == [ self.agents[0], self.agents[1], self.agents[2] ] def test_get_all_cell_contents(self): assert self.space.get_all_cell_contents() == [ self.agents[0], self.agents[1], self.agents[2] ]
class TestSingleNetworkGrid(unittest.TestCase): GRAPH_SIZE = 10 def setUp(self): ''' Create a test network grid and populate with Mock Agents. ''' G = nx.complete_graph(TestSingleNetworkGrid.GRAPH_SIZE) self.space = NetworkGrid(G) self.agents = [] for i, pos in enumerate(TEST_AGENTS_NETWORK_SINGLE): a = MockAgent(i, None) self.agents.append(a) self.space.place_agent(a, pos) def test_agent_positions(self): ''' Ensure that the agents are all placed properly. ''' for i, pos in enumerate(TEST_AGENTS_NETWORK_SINGLE): a = self.agents[i] assert a.pos == pos def test_get_neighbors(self): assert len(self.space.get_neighbors(0, include_center=True)) == TestSingleNetworkGrid.GRAPH_SIZE assert len(self.space.get_neighbors(0, include_center=False)) == TestSingleNetworkGrid.GRAPH_SIZE - 1 def test_move_agent(self): initial_pos = 1 agent_number = 1 final_pos = TestSingleNetworkGrid.GRAPH_SIZE - 1 _agent = self.agents[agent_number] assert _agent.pos == initial_pos assert _agent in self.space.G.node[initial_pos]['agent'] assert _agent not in self.space.G.node[final_pos]['agent'] self.space.move_agent(_agent, final_pos) assert _agent.pos == final_pos assert _agent not in self.space.G.node[initial_pos]['agent'] assert _agent in self.space.G.node[final_pos]['agent'] def test_is_cell_empty(self): assert not self.space.is_cell_empty(0) assert self.space.is_cell_empty(TestSingleNetworkGrid.GRAPH_SIZE - 1) def test_get_cell_list_contents(self): assert self.space.get_cell_list_contents([0]) == [self.agents[0]] assert self.space.get_cell_list_contents(list(range(TestSingleNetworkGrid.GRAPH_SIZE))) == [self.agents[0], self.agents[1], self.agents[2]] def test_get_all_cell_contents(self): assert self.space.get_all_cell_contents() == [self.agents[0], self.agents[1], self.agents[2]]
class SPQRisiko(Model): """A SPQRisiko model with some number of players""" def __init__(self, n_players, points_limit, strategy, goal): super().__init__() self.players_goals = ["BE", "LA", "PP" ] # Definition of acronyms on `strategies.py` self.current_turn = 0 self.journal = [] # Keep track of main events self.reinforces_by_goal = {} self.tris_by_goal = {} # How many agent players wiil be self.n_players = n_players if n_players <= constants.MAX_PLAYERS else constants.MAX_PLAYERS # How many computer players will be self.n_computers = constants.MAX_PLAYERS - n_players # Creation of player, goals and computer agents goals = [] if goal == "Random": for i, player in enumerate(range(n_players)): goals.append(self.players_goals[i % 3]) else: goals = [goal for i in range(self.n_players)] self.players = [ Player(i, computer=False, strategy=self.get_strategy_setup(strategy, i), goal=goals[i], model=self) for i in range(self.n_players) ] for player in self.players: self.log("{} follows {} goal with a {} strategy".format( player.color, player.goal, player.strategy)) self.computers = [ Player(i, computer=True, strategy="Neutral", goal=self.random.choice(self.players_goals), model=self) for i in range(self.n_players, self.n_players + self.n_computers) ] self.points_limit = points_limit # limit at which one player wins self.deck = self.create_deck() self.random.shuffle(self.deck) self.trashed_cards = [] self.precompute_tris_reinforces_by_goal() # Initialize map self.G, self.territories_dict = self.create_graph_map() self.grid = NetworkGrid(self.G) self.datacollector = DataCollector( model_reporters={ "Winner": get_winner, "Turn": get_winner_turn, "Strategy": get_player_strategy, "Goal": get_player_goal }) # Schedule self.schedule = RandomActivation(self) # Subgraphs self.ground_areas = [] self.sea_areas = [] path = os.path.abspath((os.path.join(__file__, '..', '..'))) # Probabilities that the attacker wins on a ground combact with open(path + '/matrices/atta_wins_combact.pkl', 'rb') as f: self.atta_wins_combact = pickle.load(f) # Probabilities that the attacker wins on a combact by sea with open(path + '/matrices/atta_wins_combact_by_sea.pkl', 'rb') as f: self.atta_wins_combact_by_sea = pickle.load(f) territories = list(range(45)) random.shuffle(territories) """ If there're 4 players, Italia must be owned by the only computer player """ if self.n_players == 4: territories.remove(15) # Remove Italia from the territories t = GroundArea(*itemgetter("id", "name", "type", "coords")( self.territories_dict["territories"][15]), model=self) t.armies = 3 t.owner = self.computers[0] self.grid.place_agent(t, 15) self.ground_areas.append(self.grid.get_cell_list_contents([15])[0]) """ Connect nodes to territories and assign them to players """ for i, node in enumerate(territories): t = GroundArea(*itemgetter("id", "name", "type", "coords")( self.territories_dict["territories"][node]), model=self) if i < 9 * self.n_players: t.armies = 2 t.owner = self.players[i % self.n_players] else: t.armies = 3 t.owner = self.computers[i % self.n_computers] self.grid.place_agent(t, node) self.ground_areas.append( self.grid.get_cell_list_contents([node])[0]) """ Add sea area """ for i, node in enumerate(range(45, 57)): t = SeaArea(*itemgetter("id", "name", "type", "coords")( self.territories_dict["sea_areas"][i]), model=self) t.trireme = [0 for _ in range(self.n_players)] self.grid.place_agent(t, node) self.sea_areas.append(self.grid.get_cell_list_contents([node])[0]) self.ground_areas.sort(key=lambda x: x.unique_id) self.sea_areas.sort(key=lambda x: x.unique_id) self.running = True # self.datacollector.collect(self) def get_strategy_setup(self, strategy, i): strats = ["Aggressive", "Passive", "Neutral"] if strategy == "Random": strategy = strats[i % 3] return strategy @staticmethod def get_movable_armies_by_strategy(strategy, minimum, maximum): return round((maximum - minimum) * strategies.nomads_percentage[strategy] + minimum) @staticmethod def create_graph_map(): # Read map configuration from file with open( os.path.join(os.path.dirname(__file__), "config/territories.json"), "r") as f: territories_dict = json.load(f) graph_map = nx.Graph() for territory in territories_dict["territories"]: graph_map.add_node(territory["id"]) for sea in territories_dict['sea_areas']: graph_map.add_node(sea['id']) for edges in territories_dict["edges"]: graph_map.add_edge(edges[0], edges[1]) return graph_map, territories_dict @staticmethod def create_deck(custom_numbers=None): # Read deck from configuration file deck = [] with open(os.path.join(os.path.dirname(__file__), "config/cards.json"), "r") as f: cards = json.load(f) # custom cards' numbers if custom_numbers: # do something return deck for card in cards: c = { "type": card["type"], "adds_on_tris": card["adds_on_tris"], "image": card["image"] } for _ in range(card["number_in_deck"]): deck.append(c) return deck def draw_a_card(self): # if deck is empty, refill from trashed cards if len(self.deck) == 0: if len(self.trashed_cards) == 0: # We finished cards, players must do some tris to refill deck! return None self.deck.extend(self.trashed_cards) self.trashed_cards = [] # return last card from deck return self.deck.pop() @staticmethod def reinforces_from_tris(cards): # assert len(cards) == 3, "Wrong number of cards given to 'tris' method" if len(cards) != 3: return None cards_in_tris = set([card["type"] for card in cards]) # assert len(cards_in_tris) == 3 or len(cards_in_tris) == 1, \ # Tris must be composed of three different cards or three of the same type if len(cards_in_tris) != 3 and len(cards_in_tris) != 1: return None reinforces = { "legionaries": 8 if len(cards_in_tris) == 1 else 10, "centers": 0, "triremes": 0 } for card in cards: for key, value in card["adds_on_tris"].items(): reinforces[key] += value return reinforces def precompute_tris_reinforces_by_goal(self): # precompute tris and assign points based on strategy all_possible_tris = [ list(t) for t in itertools.combinations(self.deck, 3) ] all_reinforces = [ SPQRisiko.reinforces_from_tris(tris) for tris in all_possible_tris ] # Remove None from list real_tris = [ all_possible_tris[i] for i in range(len(all_reinforces)) if all_reinforces[i] ] all_reinforces = [i for i in all_reinforces if i] named_tris = {} for i, tris in enumerate(real_tris): name = self.get_tris_name(tris) named_tris[name] = all_reinforces[i] self.reinforces_by_goal[name] = {} for goal, value in strategies.strategies.items(): self.reinforces_by_goal[name][ goal] = self.get_reinforcements_score( all_reinforces[i], value["tris"]) # order tris name by score for goal, value in strategies.strategies.items(): self.tris_by_goal[goal] = [] for tris in real_tris: name = self.get_tris_name(tris) if name not in self.tris_by_goal[goal]: self.tris_by_goal[goal].append(name) self.tris_by_goal[goal] = sorted( self.tris_by_goal[goal], key=cmp_to_key(lambda a, b: self.reinforces_by_goal[b][goal] - self.reinforces_by_goal[a][goal])) self.reinforces_by_goal["average"] = {} for goal, value in strategies.strategies.items(): points, count = 0, 0 for tris in real_tris: count += 1 points += self.reinforces_by_goal[self.get_tris_name( tris)][goal] self.reinforces_by_goal["average"][goal] = float(points) / count def count_players_sea_areas(self): sea_areas = [0] * self.n_players for sea in self.sea_areas: m = max(sea.trireme) players_max_trireme = [ player for player, n_trireme in enumerate(sea.trireme) if n_trireme == m ] if len(players_max_trireme) == 1: sea_areas[players_max_trireme[0]] += 1 return sea_areas def count_players_territories_power_places(self): territories = [0] * self.n_players power_places = [0] * self.n_players for territory in self.ground_areas: if not territory.owner.computer: territories[territory.owner.unique_id] += 1 if territory.power_place: power_places[territory.owner.unique_id] += 1 return territories, power_places def get_weakest_power_place(self, player): weakest = None for territory in self.ground_areas: if not territory.owner.computer and territory.owner.unique_id == player.unique_id: if territory.power_place: if not weakest or territory.armies < weakest.armies: weakest = territory return weakest def get_weakest_adversary_power_place(self, player): weakest = None for territory in self.ground_areas: if territory.owner.computer or territory.owner.unique_id != player.unique_id: if territory.power_place: if not weakest or territory.armies < weakest.armies: weakest = territory return weakest def find_nearest(self, territory, player): # It's a BFS visit to get the node whose distance from territory is the lesser than any other for ground_area in self.ground_areas: ground_area.found = 0 for sea_area in self.sea_areas: sea_area.found = 0 territory.found = 1 visited = [territory] distances = [0] * 57 while len(visited) > 0: t = visited.pop(0) if distances[t.unique_id] > 4: break for neighbor in self.grid.get_neighbors(t.unique_id): neighbor = self.grid.get_cell_list_contents([neighbor])[0] if neighbor.found == 0: neighbor.found = 1 distances[neighbor.unique_id] = distances[t.unique_id] + 1 visited.append(neighbor) if neighbor.type == "ground" and neighbor.owner.unique_id == player.unique_id: return neighbor return None def get_largest_empire(self, player): # It's another DFS visit in which we account for the membership of a node to a connected component def __dfs_visit__(territory, ground_areas, cc_num): territory.found = 1 ground_areas[territory.unique_id] = cc_num for neighbor in self.grid.get_neighbors(territory.unique_id): neighbor = self.grid.get_cell_list_contents([neighbor])[0] if neighbor.type == "ground" and \ neighbor.found == 0 and \ neighbor.owner.unique_id == player.unique_id: __dfs_visit__(neighbor, ground_areas, cc_num) cc_num = 0 ground_areas = [-1] * 45 for territory in self.ground_areas: territory.found = 0 for territory in self.ground_areas: if territory.type == "ground" and territory.found == 0 and territory.owner.unique_id == player.unique_id: __dfs_visit__(territory, ground_areas, cc_num) cc_num += 1 stats = list( collections.Counter([t for t in ground_areas if t != -1]).most_common()) if stats != []: return [ self.ground_areas[idx] for idx, cc in enumerate(ground_areas) if cc == stats[0][0] ] return stats def maximum_empires(self): # It's a DFS visit in which we account for # the length of every connected components def __dfs_visit__(territory, d): territory.found = 1 for neighbor in self.grid.get_neighbors(territory.unique_id): neighbor = self.grid.get_cell_list_contents([neighbor])[0] if neighbor.type == "ground" and \ neighbor.found == 0 and \ neighbor.owner.unique_id == territory.owner.unique_id: d = __dfs_visit__(neighbor, d) return d + 1 cc_lengths = [0] * self.n_players for territory in self.ground_areas: territory.found = 0 for territory in self.ground_areas: if not territory.owner.computer and territory.found == 0: distance = __dfs_visit__(territory, 0) if distance > cc_lengths[territory.owner.unique_id]: cc_lengths[territory.owner.unique_id] = distance return cc_lengths # Controlla se `player` ha vinto oppure se c'è un vincitore tra tutti def winner(self, player=None): if player is not None: if player.victory_points >= self.points_limit: return True return False max_points = -1 max_player = None for p in self.players: if p.victory_points > max_points: max_points = p.victory_points max_player = p won = True if max_points > self.points_limit else False return max_player, won def get_territories_by_player(self, player: Player, ground_type="ground"): if ground_type == "ground": return [ t for t in self.ground_areas if t.owner.unique_id == player.unique_id ] elif ground_type == "sea": return [ t for t in self.sea_areas if t.trireme[self.players.index(player)] > 0 or max(t.trireme) == 0 ] def get_sea_area_near_ground_area(self, player): sea_areas = [] for sea_area in self.sea_areas: for neighbor in self.grid.get_neighbors(sea_area.unique_id): neighbor = self.grid.get_cell_list_contents([neighbor])[0] if isinstance(neighbor, GroundArea) and \ neighbor.owner.unique_id == player.unique_id: sea_areas.append(sea_area) return sea_areas def n_power_places(self): n = 0 for area in self.ground_areas: if area.power_place: n += 1 return n def update_atta_wins_combact_matrix(self, attacker_armies, defender_armies, mat_type='combact'): print(attacker_armies, defender_armies, self.atta_wins_combact.shape, self.atta_wins_combact_by_sea.shape) if mat_type == 'combact': if attacker_armies > self.atta_wins_combact.shape[ 0] and defender_armies > self.atta_wins_combact.shape[1]: self.atta_wins_combact = get_probabilities_ground_combact( attacker_armies, defender_armies) elif attacker_armies > self.atta_wins_combact.shape[0]: self.atta_wins_combact = get_probabilities_ground_combact( attacker_armies, self.atta_wins_combact.shape[1]) elif defender_armies > self.atta_wins_combact.shape[1]: self.atta_wins_combact = get_probabilities_ground_combact( self.atta_wins_combact.shape[0], defender_armies) elif mat_type == 'combact_by_sea': if attacker_armies > self.atta_wins_combact_by_sea.shape[ 0] and defender_armies > self.atta_wins_combact_by_sea.shape[ 1]: self.atta_wins_combact_by_sea = get_probabilities_combact_by_sea( attacker_armies, defender_armies) elif attacker_armies > self.atta_wins_combact_by_sea.shape[0]: self.atta_wins_combact_by_sea = get_probabilities_combact_by_sea( attacker_armies, self.atta_wins_combact_by_sea.shape[1]) elif defender_armies > self.atta_wins_combact_by_sea.shape[1]: self.atta_wins_combact_by_sea = get_probabilities_combact_by_sea( self.atta_wins_combact_by_sea.shape[0], defender_armies) def step(self): self.current_turn += 1 for player in self.players: if not player.eliminated: can_draw = False territories, power_places = self.count_players_territories_power_places( ) player_territories = self.get_territories_by_player( player, "ground") sea_areas = self.count_players_sea_areas() empires = self.maximum_empires() # 1) Aggiornamento del punteggio player.update_victory_points(empires, territories, sea_areas, power_places) # 1.1) Controllo vittoria if self.winner(player): self.running = False print(player) print(get_winner(self)) print(get_winner_turn(self)) self.datacollector.collect(self) self.log("{} has won!".format(player.color)) return True # 2) Fase dei rinforzi print('\nREINFORCES') player.update_ground_reinforces_power_places() reinforces = Player.get_ground_reinforces(player_territories) self.log( "{} earns {} legionaries (he owns {} territories)".format( player.color, reinforces, territories[player.unique_id])) player.put_reinforces(self, reinforces) # player.sacrifice_trireme(sea_area_from, ground_area_to) # use card combination # displace ground, naval and/or power places on the ground tris = player.get_best_tris(self) if tris: reinforces = player.play_tris(self, tris) self.log("{} play tris {}".format( player.color, self.get_tris_name(tris))) player.put_reinforces(self, reinforces) # TODO: log where reinforces are put # 3) Movimento navale # player.naval_movement(sea_area_from, sea_area_to, n_trireme) # 4) Combattimento navale print('\nNAVAL COMBACT!!') # Get all sea_areas that the current player can attack attackable_sea_areas = [] for sea_area in self.get_territories_by_player( player, ground_type='sea'): # Choose the adversary that has the lower probability of winning the combact min_trireme = min(sea_area.trireme) if min_trireme > 0: adv_min_trireme = sea_area.trireme.index(min_trireme) # Check if the atta_wins_combact probabilities matrix needs to be recomputed # self.update_atta_wins_combact_matrix(sea_area.trireme[player.unique_id], sea_area.trireme[adv_min_trireme]) row = sea_area.trireme[player.unique_id] col = sea_area.trireme[adv_min_trireme] m = max(row, col) ratio = 100 / m if ratio < 1: row = min(round(ratio * row), 100) col = min(round(ratio * col), 100) if player.unique_id != adv_min_trireme and \ self.atta_wins_combact[row - 1, col - 1] >= strategies.probs_win[player.strategy]: attackable_sea_areas.append( [sea_area, adv_min_trireme]) for sea_area, adv in attackable_sea_areas: # Randomly select how many attack and defense trireme attacker_trireme = sea_area.trireme[player.unique_id] # The defender must always use the maximux number of armies to defend itself # n_defense_trireme = sea_area.trireme[adversary.unique_id] if sea_area.trireme[adversary.unique_id] <= 3 else 3 # Let's combact biatch!! print('Start battle!') print('Trireme in ' + sea_area.name + ': ', sea_area.trireme) print('Player ' + str(player.unique_id) + ' attacks Player ' + str(adv) + ' on ' + sea_area.name) player.naval_combact(sea_area, adv, attacker_trireme, strategies.probs_win[player.strategy], self.atta_wins_combact) # 5) Attacchi via mare print('\nCOMBACT BY SEA!!') for ground_area in self.ground_areas: ground_area.already_attacked_by_sea = False attacks = self.get_attackable_ground_areas_by_sea(player) # attacks.sort(key=lambda x: x["prob_win"], reverse=True) while 0 < len(attacks): attack = attacks[0] # if not attack['defender'].already_attacked_by_sea: attack['defender'].already_attacked_by_sea = True attacker_armies = attack["attacker"].armies - attack[ "armies_to_leave"] print( 'Battle: {} (player {}) with {} VS {} (player {}) with {}' .format(attack["attacker"].name, player.unique_id, attacker_armies, attack["defender"].name, attack["defender"].owner.unique_id, attack["defender"].armies)) conquered, min_moveable_armies = player.combact_by_sea( attack["attacker"], attack["defender"], attacker_armies) if conquered: # Move armies from attacker area to conquered max_moveable_armies = attack[ "attacker"].armies - attack["armies_to_leave"] nomads = SPQRisiko.get_movable_armies_by_strategy( player.strategy, min_moveable_armies, max_moveable_armies) attack["attacker"].armies -= nomads attack["defender"].armies = nomads can_draw = True # Remove from possible attacks all of those containing as defender the conquered territory # and update the probability attacks = self.update_attacks_by_sea(player, attacks) # 6) Attacchi terrestri print('\nGROUND COMBACT!!') attacks = [] attacks = self.get_attackable_ground_areas(player) # attacks.sort(key=lambda x: x["prob_win"], reverse=True) while 0 < len(attacks): attack = attacks[0] attacker_armies = attack["attacker"].armies - 1 print( 'Battle: {} (player {}) with {} VS {} (player {}) with {}' .format(attack["attacker"].name, player.unique_id, attacker_armies, attack["defender"].name, attack["defender"].owner.unique_id, attack["defender"].armies)) conquered, min_moveable_armies = player.combact( attack["attacker"], attack["defender"], attacker_armies, strategies.probs_win[player.strategy], self.atta_wins_combact) if conquered: # Move armies from attacker area to conquered max_moveable_armies = attack["attacker"].armies - 1 nomads = SPQRisiko.get_movable_armies_by_strategy( player.strategy, min_moveable_armies, max_moveable_armies) attack["attacker"].armies -= nomads attack["defender"].armies = nomads can_draw = True self.log( "{} conquered {} from {} and it moves {} armies there out of {}" .format(player.color, attack["defender"].name, attack["attacker"].name, nomads, max_moveable_armies)) # Re-sort newly attackable areas with newer probabilities attacks = self.get_attackable_ground_areas(player) # attacks.sort(key=lambda x: x["prob_win"], reverse=True) # Controllo se qualche giocatore è stato eliminato for adv in self.players: if adv.unique_id != player.unique_id and not adv.eliminated: territories = self.get_territories_by_player(adv) if len(territories) == 0: self.log("{} has been eliminated by {}".format( adv.color, player.color)) player.cards.extend(adv.cards) adv.cards = [] adv.eliminated = True for sea_area in self.get_territories_by_player( adv, ground_type="sea"): sea_area.trireme[adv.unique_id] = 0 # 7) Spostamento strategico di fine turno player.move_armies_by_goal(self) # 8) Presa della carta # Il giocatore può dimenticarsi di pescare la carta ahah sarebbe bello fare i giocatori smemorati if can_draw and random.random() <= 1: card = self.draw_a_card() if card: player.cards.append(card) self.schedule.step() return False def update_attacks_by_sea(self, player, future_attacks): attack_num = 0 last_attacker = future_attacks[0]['attacker'] del future_attacks[0] while attack_num < len(future_attacks): attack = future_attacks[attack_num] if attack['defender'].owner.unique_id == player.unique_id: print('Since the defender has been conquered, I delete it') del future_attacks[attack_num] elif attack['defender'].already_attacked_by_sea: print( 'Since the defender has already been attacked by sea, I delete it' ) del future_attacks[attack_num] elif attack['attacker'].unique_id == last_attacker.unique_id: print('The attacker may attack again') # Maybe it could change the armies to leave due to garrisons armies_to_leave = self.get_armies_to_leave(attack['attacker']) if attack['attacker'].armies - armies_to_leave >= min( 3, attack['defender'].armies): prob_win = self.atta_wins_combact_by_sea[ attack['attacker'].armies - armies_to_leave - 1, attack['defender'].armies - 1] if prob_win >= strategies.probs_win[player.strategy]: print('The attacker can attack again') attack['prob_win'] = prob_win attack_num += 1 else: print( 'Since the attacker has a lower prob to win, I delete it' ) del future_attacks[attack_num] else: print( 'Since the attacker hasn\'t the min required armies, I delete it' ) del future_attacks[attack_num] else: attack_num += 1 if player.goal == "PP": future_attacks.sort(key=lambda x: (x['defender'].power_place, x['prob_win']), reverse=True) else: future_attacks.sort(key=lambda x: x['prob_win'], reverse=True) return future_attacks def get_attackable_ground_areas_by_sea(self, player): attacks = [] for ground_area in self.get_territories_by_player(player): for neighbor in self.grid.get_neighbors(ground_area.unique_id): neighbor = self.grid.get_cell_list_contents([neighbor])[0] # A player can attack a ground area through sea, only if it posesses a number of # trireme greater than the possible adversary. if isinstance( neighbor, SeaArea) and neighbor.trireme[player.unique_id] > min( neighbor.trireme): for sea_area_neighbor in self.grid.get_neighbors( neighbor.unique_id): sea_area_neighbor = self.grid.get_cell_list_contents( [sea_area_neighbor])[0] if isinstance(sea_area_neighbor, GroundArea) and \ ground_area.unique_id != sea_area_neighbor.unique_id and \ sea_area_neighbor.owner.unique_id != player.unique_id and \ (sea_area_neighbor.owner.computer or neighbor.trireme[player.unique_id] > neighbor.trireme[sea_area_neighbor.owner.unique_id]): armies_to_leave = self.get_armies_to_leave( ground_area) if ground_area.armies - armies_to_leave >= min( 3, sea_area_neighbor.armies): # self.update_atta_wins_combact_matrix(ground_area.armies - armies_to_leave, sea_area_neighbor.armies, mat_type='combact_by_sea') row = ground_area.armies - armies_to_leave col = sea_area_neighbor.armies m = max(row, col) ratio = 100 / m if ratio < 1: row = min(round(ratio * row), 100) col = min(round(ratio * col), 100) prob_win = self.atta_wins_combact_by_sea[row - 1, col - 1] if prob_win >= strategies.probs_win[ player.strategy]: attacks.append({ "defender": sea_area_neighbor, "attacker": ground_area, "armies_to_leave": armies_to_leave, "prob_win": prob_win }) if player.goal == "PP": attacks.sort(key=lambda x: (x['defender'].power_place, x['prob_win']), reverse=True) else: attacks.sort(key=lambda x: x['prob_win'], reverse=True) return attacks def get_armies_to_leave(self, ground_area): ground_area_neighbors = self.grid.get_neighbors(ground_area.unique_id) for ground_area_neighbor in ground_area_neighbors: ground_area_neighbor = self.grid.get_cell_list_contents( [ground_area_neighbor])[0] if isinstance(ground_area_neighbor, GroundArea) and \ ground_area_neighbor.owner.unique_id != ground_area.owner.unique_id: return 2 return 1 def get_attackable_ground_areas_from(self, ground_area): attacks = [] if ground_area.armies > 1: for neighbor in self.grid.get_neighbors(ground_area.unique_id): neighbor = self.grid.get_cell_list_contents([neighbor])[0] if neighbor.type == "ground" and \ neighbor.owner.unique_id != ground_area.owner.unique_id and \ ground_area.armies - 1 >= min(3, neighbor.armies): # self.update_atta_wins_combact_matrix(ground_area.armies - 1, neighbor.armies) row = ground_area.armies - 1 col = neighbor.armies m = max(row, col) ratio = 100 / m if ratio < 1: row = min(round(ratio * row), 100) col = min(round(ratio * col), 100) prob_win = self.atta_wins_combact[row - 1, col - 1] if prob_win >= strategies.probs_win[ ground_area.owner.strategy]: attacks.append({ "defender": neighbor, "attacker": ground_area, "prob_win": prob_win }) return attacks def get_attackable_ground_areas(self, player): attacks = [] for ground_area in self.get_territories_by_player(player): attackables = self.get_attackable_ground_areas_from(ground_area) if attackables != []: for attackable in attackables: attacks.append(attackable) if player.goal == "PP": attacks.sort(key=lambda x: (x['defender'].power_place, x['prob_win']), reverse=True) else: attacks.sort(key=lambda x: x['prob_win'], reverse=True) return attacks # Get non attackable areas wiht at least 2 armies and with an ally neighbor def non_attackable_areas(self, player, territories=None): non_attackables = [] if not territories: territories = self.get_territories_by_player(player) for ground_area in territories: if ground_area.armies > 1: attackable, has_ally_neighbor = False, False for neighbor in self.grid.get_neighbors(ground_area.unique_id): neighbor = self.grid.get_cell_list_contents([neighbor])[0] if neighbor.type == "ground" and neighbor.owner.unique_id != player.unique_id: attackable = True break has_ally_neighbor = True if not attackable and has_ally_neighbor: non_attackables.append(ground_area) return non_attackables def is_not_attackable(self, area): for neighbor in self.grid.get_neighbors(area.unique_id): neighbor = self.grid.get_cell_list_contents([neighbor])[0] if isinstance( neighbor, GroundArea ) and neighbor.owner.unique_id != area.owner.unique_id: return False return True def get_strongest_ally_neighbor(self, area): strongest = None for neighbor in self.grid.get_neighbors(area.unique_id): neighbor = self.grid.get_cell_list_contents([neighbor])[0] if isinstance(neighbor, GroundArea) and ( not strongest or strongest.armies < neighbor.armies): strongest = neighbor return strongest def is_neighbor_of(self, area1, area2): for neighbor in self.grid.get_neighbors(area1.unique_id): neighbor = self.grid.get_cell_list_contents([neighbor])[0] if neighbor.owner.unique_id == area2.unique_id: return True return False def log(self, log): self.journal.append("Turn {}: ".format(self.current_turn) + log) def run_model(self, n): for _ in range(n): self.step() # Tris name is the ordered initial letters of cards type def get_tris_name(self, tris): if len(tris) != 3: raise Exception("tris name parameter error") return "-".join( [card[0] for card in sorted(set([card["type"] for card in tris]))]) def get_reinforcements_score(self, reinforces, multipliers): m, r = multipliers, reinforces return m[0] * r["legionaries"] + m[1] * r["triremes"] + m[2] * r[ "centers"] def get_n_armies_by_player(self, player=None): if player is not None: return sum( [t.armies for t in self.get_territories_by_player(player)]) else: sum_a = 0 for player in self.players: sum_a += sum( [t.armies for t in self.get_territories_by_player(player)]) return sum_a / len(self.players)
class InfoSpread(Model): """A virus model with some number of agents""" def __init__(self, num_nodes=10, avg_node_degree=3, rewire_prob=.1, initial_outbreak_size=1, threshold_fake=2, threshold_real=-2, fake_p=1, real_p=1): self.fake_p = fake_p self.real_p = real_p self.num_nodes = num_nodes self.G = nx.watts_strogatz_graph( n=self.num_nodes, k=avg_node_degree, p=rewire_prob) #G generate graph structure self.grid = NetworkGrid( self.G ) #grid is the Masa native defintion of space: a coorindate with specified topology on which agents sits and interact self.schedule = SimultaneousActivation(self) self.initial_outbreak_size = (initial_outbreak_size if initial_outbreak_size <= num_nodes else num_nodes) self.datacollector = DataCollector({ "Infected_fake": number_infected_fake, "Infected_real": number_infected_real, }) # Create agents for i, node in enumerate(self.G.nodes()): a = User( i, self, 0, #make the state a int threshold_fake, threshold_real) self.schedule.add(a) # Add the agent to the node self.grid.place_agent(a, node) # Infect some nodes, initial infection bug free infected_nodes_fake = self.random.sample(self.G.nodes(), self.initial_outbreak_size) infected_nodes_real = self.random.sample(self.G.nodes(), self.initial_outbreak_size) for a in self.grid.get_cell_list_contents(infected_nodes_fake): a.state = 1 neighbors_nodes = self.grid.get_neighbors(a.pos) for n in self.grid.get_cell_list_contents(neighbors_nodes): n.state = 1 for a in self.grid.get_cell_list_contents(infected_nodes_real): a.state = -1 neighbors_nodes = self.grid.get_neighbors(a.pos) for n in self.grid.get_cell_list_contents(neighbors_nodes): n.state = -1 """ state measures fake score!! the more negative the less likely to spread fake news also this model assumes that 1 one piece of real news can cancel out one piece of fake news This model can be modulated by changing the value of fake and real 2 the inital braeakout size of fake and real news are the same This can be chaged by introducing a different initial breaksize for real and fake news however this score is kepet the same intentionally because too uch complexity is not good for modeling """ self.running = True self.datacollector.collect(self) def proportion_infected_fake(self): try: tot_fake = 0 for i in self.grid.get_cell_list_contents(self.G): if i.state == 1: tot_fake += 1 return tot_fake / self.num_nodes except ZeroDivisionError: return math.inf def proportion_infected_real(self): try: tot_real = 0 for i in self.grid.get_cell_list_contents(self.G): if i.state == -1: tot_real += 1 return tot_real / self.num_nodes except ZeroDivisionError: return math.inf def step(self): self.schedule.step( ) #this model updates with symoutanous schedule, meaning, # collect data self.datacollector.collect(self) def run_model(self, n): ''' could experiment terminating model here too''' for i in range(n): self.step()
class InfoSpread(Model): """A virus model with some number of agents""" def __init__( self, num_nodes=10, avg_node_degree=3, rewire_prob=.1, initial_outbreak_size=1, threshold=2, ): self.num_nodes = num_nodes self.G = nx.watts_strogatz_graph( n=self.num_nodes, k=avg_node_degree, p=rewire_prob) #G generate graph structure self.grid = NetworkGrid( self.G ) #grid is the Masa native defintion of space: a coorindate with specified topology on which agents sits and interact self.schedule = SimultaneousActivation(self) self.initial_outbreak_size = (initial_outbreak_size if initial_outbreak_size <= num_nodes else num_nodes) self.datacollector = DataCollector({ "Infected": number_infected, "Susceptible": number_susceptible, }) # Create agents for i, node in enumerate(self.G.nodes()): a = User(i, self, "susceptible", threshold) self.schedule.add(a) # Add the agent to the node self.grid.place_agent(a, node) # Infect some nodes infected_nodes = self.random.sample(self.G.nodes(), self.initial_outbreak_size) for a in self.grid.get_cell_list_contents(infected_nodes): a.state = "infected" neighbors_nodes = self.grid.get_neighbors(a.pos) for n in self.grid.get_cell_list_contents(neighbors_nodes): n.state = 'infected' self.running = True self.datacollector.collect(self) def proportion_infected(self): try: return number_state(self, "infected") / self.num_nodes except ZeroDivisionError: return math.inf def step(self): self.schedule.step( ) #this model updates with symoutanous schedule, meaning, # collect data self.datacollector.collect(self) def run_model(self, n): ''' could experiment terminating model here too''' for i in range(n): self.step()
class RecoveryModel(Model): def __init__(self, locations, N1, scheduleClass, initialization=None, seed=None): if initialization == None: raise Exception("Initialization cannot be none") N = len(locations['features'] ) * N1 #N1 =density of housholds in one building #print(N) self.landuse = locations['features'][0]['landuse'] #print(self.landuse[0]) self.running = True self.num_agents = N self.schedule = scheduleClass(self) self.G = nx.complete_graph(self.num_agents) self.nw = NetworkGrid(self.G) self.grid = GeoSpace(crs='epsg:4326') agent_kwargs = dict(model=self, unique_id='id') self.grid.create_agents_from_GeoJSON(locations, agent=LocationAgent, **agent_kwargs) self.locations = list(self.grid.agents) self.initialize(initialization) self.datacollector = DataCollector( agent_reporters={ "NewShape": agent_loc, "Satisfaction": agent_satisfaction, "LandUse": agent_LU, "H**o": agent_homo, "WorkPlace": agent_workplace }) self.grid.update_bbox() self.grid.create_rtree() def initialize(self, initialization): list_of_random_nodes = random.sample(self.G.nodes(), self.num_agents) for i in range(self.num_agents): agent, self.landuse1 = initialization.next('Household ' + str(i), self) self.nw.place_agent(agent, list_of_random_nodes[i]) self.schedule.add(agent) self.grid.add_agent(agent) def calculate_homophily(self): for i in range(self.G.number_of_nodes()): HH_homphily = 0 total_homophily = 0 c = 1 for agent in self.G.node[i]['agent']: attr_self = [agent.workplace, agent.income, agent.education] agent_location = agent.shape for HH in self.locations: if HH.shape.contains(agent_location): HH_polygon = HH.shape neighbor_nodes = self.nw.get_neighbors(i, include_center=False) for node in neighbor_nodes: for nbr in self.G.node[node]['agent']: attr_neighbor = [nbr.workplace, nbr.income, nbr.education] self.G[i][node]['weight'] = jaccard_similarity( attr_self, attr_neighbor) total_homophily = total_homophily + jaccard_similarity( attr_self, attr_neighbor) neighbor_point = nbr.shape if HH_polygon.contains(neighbor_point): c = c + 1 HH_homphily = HH_homphily + jaccard_similarity( attr_self, attr_neighbor) else: pass agent.h**o = total_homophily agent.shomo = HH_homphily / c def step(self): #print (self.schedule.time) self.datacollector.collect(self) self.schedule.step() self.grid.create_rtree() self.calculate_homophily()