示例#1
0
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]
        ]
示例#2
0
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]]
示例#3
0
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)
示例#4
0
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()
示例#5
0
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()
示例#6
0
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()