Esempio n. 1
0
class World:
    DEBUGGING_MODE = False
    LOG_FILE_POINTER = None
    _shortest_path = dict()

    def __init__(self, world=None, queue=None):
        self._start_time = 0
        self._init_message = None
        self._game_constants = None

        self._turn_updates = None

        self._map = None
        self._base_units = None
        self._current_turn = 0

        self._players = []
        self._player = None
        self._player_friend = None
        self._player_first_enemy = None
        self._player_second_enemy = None
        self._spells = []
        self._cast_spells = []

        if world is not None:
            self._init_message = world._init_message
            self._game_constants = world._game_constants

            self._turn_updates = TurnUpdates(turn_updates=world._turn_updates)

            self._map = world._map
            self._base_units = world._base_units
            self._spells = world._spells
            self._current_turn = world._current_turn

            self._players = world._players
            self._player = world._player
            self._player_friend = world._player_friend
            self._player_first_enemy = world._player_first_enemy
            self._player_second_enemy = world._player_second_enemy
            self._cast_spells = world._cast_spells

            self._queue = world._queue
        else:
            self._queue = queue

        if len(World._shortest_path) == 0:
            self._pre_process_shortest_path()

    def _pre_process_shortest_path(self):
        def path_count(paths_from_player, paths_from_friend, path_to_friend):
            shortest_path = [[None for i in range(self._map.col_num)]
                             for j in range(self._map.row_num)]
            shortest_dist = [[0 for i in range(self._map.col_num)]
                             for j in range(self._map.row_num)]
            for p in paths_from_player:
                num = 0
                for c in p.cells:
                    row = c.row
                    col = c.col
                    if shortest_path[row][col] is None:
                        shortest_path[row][col] = p
                        shortest_dist[row][col] = num
                    elif shortest_dist[row][col] > num:
                        shortest_dist[row][col] = num
                        shortest_path[row][col] = p
                    num += 1

            l = len(path_to_friend.cells)
            for p in paths_from_friend:
                num = l - 1
                for c in p.cells:
                    row = c.row
                    col = c.col
                    if shortest_path[row][col] is None:
                        shortest_path[row][col] = p
                        shortest_dist[row][col] = num
                    elif shortest_dist[row][col] > num:
                        shortest_dist[row][col] = num
                        shortest_path[row][col] = p
                    num += 1
            return shortest_path

        for player in self._players:
            World._shortest_path.update({
                player.player_id:
                path_count(
                    player.paths_from_player,
                    self._get_friend_by_id(player.player_id).paths_from_player,
                    player.path_to_friend)
            })

    def _get_current_time_millis(self):
        return int(round(time.time() * 1000))

    def _get_time_past(self):
        return self._get_current_time_millis() - self._start_time

    def _game_constant_init(self, game_constants_msg):
        self._game_constants = GameConstants(
            max_ap=game_constants_msg["maxAP"],
            max_turns=game_constants_msg["maxTurns"],
            turn_timeout=game_constants_msg["turnTimeout"],
            pick_timeout=game_constants_msg["pickTimeout"],
            turns_to_upgrade=game_constants_msg["turnsToUpgrade"],
            turns_to_spell=game_constants_msg["turnsToSpell"],
            damage_upgrade_addition=game_constants_msg[
                "damageUpgradeAddition"],
            range_upgrade_addition=game_constants_msg["rangeUpgradeAddition"],
            hand_size=game_constants_msg["handSize"],
            deck_size=game_constants_msg["deckSize"],
            ap_addition=game_constants_msg["apAddition"])

    def _find_path_starting_and_ending_with(self, first, last, paths):
        for path in paths:
            c_path = Path(path=path)
            if c_path.cells[0] == first and c_path.cells[-1] == last:
                return c_path
            c_path.cells.reverse()
            if c_path.cells[0] == first and c_path.cells[-1] == last:
                return c_path
        return None

    def _map_init(self, map_msg):
        row_num = map_msg["rows"]
        col_num = map_msg["cols"]

        input_cells = [[Cell(row=row, col=col) for col in range(col_num)]
                       for row in range(row_num)]

        paths = [
            Path(id=path["id"],
                 cells=[
                     input_cells[cell["row"]][cell["col"]]
                     for cell in path["cells"]
                 ]) for path in map_msg["paths"]
        ]
        kings = [
            King(player_id=king["playerId"],
                 center=input_cells[king["center"]["row"]][king["center"]
                                                           ["col"]],
                 hp=king["hp"],
                 attack=king["attack"],
                 range=king["range"],
                 target=None,
                 target_cell=None,
                 is_alive=True) for king in map_msg["kings"]
        ]

        self._players = [
            Player(player_id=map_msg["kings"][i]["playerId"],
                   king=kings[i],
                   deck=[],
                   hand=[],
                   ap=self._game_constants.max_ap,
                   paths_from_player=self._get_paths_starting_with(
                       kings[i].center, paths),
                   path_to_friend=self._find_path_starting_and_ending_with(
                       kings[i].center, kings[i ^ 1].center, paths),
                   units=[],
                   cast_area_spell=None,
                   cast_unit_spell=None,
                   duplicate_units=[],
                   hasted_units=[],
                   played_units=[],
                   died_units=[],
                   range_upgraded_unit=None,
                   damage_upgraded_unit=None,
                   spells=[]) for i in range(4)
        ]

        for player in self._players:
            player.paths_from_player.remove(player.path_to_friend)

        self._player = self._players[0]
        self._player_friend = self._players[1]
        self._player_first_enemy = self._players[2]
        self._player_second_enemy = self._players[3]

        self._map = Map(row_num=row_num,
                        col_num=col_num,
                        paths=paths,
                        kings=kings,
                        cells=input_cells,
                        units=[])

    def _base_unit_init(self, msg):
        self._base_units = [
            BaseUnit(type_id=b_unit["typeId"],
                     max_hp=b_unit["maxHP"],
                     base_attack=b_unit["baseAttack"],
                     base_range=b_unit["baseRange"],
                     target_type=UnitTarget.get_value(b_unit["target"]),
                     is_flying=b_unit["isFlying"],
                     is_multiple=b_unit["isMultiple"],
                     ap=b_unit["ap"]) for b_unit in msg
        ]

    def _get_base_unit_by_id(self, type_id):
        for base_unit in self._base_units:
            if base_unit.type_id == type_id:
                return base_unit
        return None

    def _spells_init(self, msg):
        self._spells = [
            Spell(type=SpellType.get_value(spell["type"]),
                  type_id=spell["typeId"],
                  duration=spell["duration"],
                  priority=spell["priority"],
                  range=spell["range"],
                  power=spell["power"],
                  target=SpellTarget.get_value(spell["target"]),
                  is_damaging=spell["power"] < 0) for spell in msg
        ]

    def _handle_init_message(self, msg):
        self._init_message = msg
        self._start_time = self._get_current_time_millis()
        self._game_constant_init(msg['gameConstants'])
        self._map_init(msg["map"])
        self._base_unit_init(msg["baseUnits"])
        self._spells_init(msg["spells"])
        self._current_turn = 0

    def _handle_turn_kings(self, msg):
        for king_msg in msg:
            self.get_player_by_id(
                king_msg["playerId"]).king.is_alive = king_msg["isAlive"]
            self.get_player_by_id(
                king_msg["playerId"]).king.hp = king_msg["hp"]
            if king_msg["target"] != -1:
                self.get_player_by_id(
                    king_msg["playerId"]).king.target = self.get_unit_by_id(
                        king_msg["target"])
                self.get_player_by_id(king_msg["playerId"]
                                      ).king.target_cell = self.get_unit_by_id(
                                          king_msg["target"]).cell
            else:
                self.get_player_by_id(king_msg["playerId"]).king.target = None
                self.get_player_by_id(
                    king_msg["playerId"]).king.target_cell = None

    def _handle_turn_units(self, msg, is_dead_unit=False):
        if not is_dead_unit:
            self._map._clear_units()
            for player in self._players:
                player.units.clear()
                player.played_units.clear()
                player.hasted_units.clear()
                player.duplicate_units.clear()
                player.range_upgraded_unit = None
                player.damage_upgraded_unit = None
        else:
            for player in self._players:
                player.died_units.clear()

        unit_input_list = []

        for unit_msg in msg:
            unit_id = unit_msg["unitId"]
            player = self.get_player_by_id(player_id=unit_msg["playerId"])
            base_unit = self._base_units[unit_msg["typeId"]]

            if not unit_msg['target'] == -1:
                target_cell = Cell(row=unit_msg["targetCell"]["row"],
                                   col=unit_msg["targetCell"]["col"])
            else:
                target_cell = None
            unit = Unit(
                unit_id=unit_id,
                base_unit=base_unit,
                cell=self._map.get_cell(unit_msg["cell"]["row"],
                                        unit_msg["cell"]["col"]),
                path=self._map.get_path_by_id(unit_msg["pathId"]),
                hp=unit_msg["hp"],
                damage_level=unit_msg["damageLevel"],
                range_level=unit_msg["rangeLevel"],
                is_duplicate=unit_msg["isDuplicate"],
                is_hasted=unit_msg["isHasted"],
                range=unit_msg["range"],
                attack=unit_msg["attack"],
                target=None,  # will be set later when all units are in set
                target_cell=target_cell,
                affected_spells=[
                    self.get_cast_spell_by_id(cast_spell_id)
                    for cast_spell_id in unit_msg["affectedSpells"]
                ],
                target_if_king=None
                if self.get_player_by_id(unit_msg["target"]) is None else
                self.get_player_by_id(unit_msg["target"]).king,
                player_id=unit_msg["playerId"])
            unit_input_list.append(unit)

            if unit.path is not None:
                if self.get_player_by_id(
                        unit.player_id
                ).king.center in unit.path.cells and unit.path.cells[
                        0] != self.get_player_by_id(
                            unit.player_id).king.center:
                    unit.path = Path(path=unit.path)
                    unit.path.cells.reverse()
                if self._get_friend_by_id(
                        unit.player_id
                ).king.center in unit.path.cells and unit.path.cells[
                        0] != self._get_friend_by_id(
                            unit.player_id).king.center:
                    unit.path = Path(path=unit.path)
                    unit.path.cells.reverse()

            if not is_dead_unit:
                self._map._add_unit_in_cell(unit.cell.row, unit.cell.col, unit)
                player.units.append(unit)
                if unit_msg["wasDamageUpgraded"]:
                    player.damage_upgraded_unit = unit
                if unit_msg["wasRangeUpgraded"]:
                    player.range_upgraded_unit = unit
                if unit_msg["wasPlayedThisTurn"]:
                    player.played_units.append(unit)
                if unit.is_hasted:
                    player.hasted_units.append(unit)
                if unit.is_duplicate:
                    player.duplicate_units.append(unit)
            else:
                player.died_units.append(unit)

        for i in range(len(unit_input_list)):
            unit = unit_input_list[i]
            if unit.target_if_king is not None:
                unit.target = None
            else:
                unit.target = self.get_unit_by_id(msg[i]["target"])

    def _handle_turn_cast_spells(self, msg):
        self._cast_spells = []
        for cast_spell_msg in msg:
            spell = self.get_spell_by_id(cast_spell_msg["typeId"])
            cell = self._map.get_cell(cast_spell_msg["cell"]["row"],
                                      cast_spell_msg["cell"]["col"])
            affected_units = [
                self.get_unit_by_id(affected_unit_id)
                for affected_unit_id in cast_spell_msg["affectedUnits"]
            ]
            if spell.is_area_spell():
                cast_area_spell = CastAreaSpell(
                    spell=spell,
                    id=cast_spell_msg["id"],
                    caster_id=cast_spell_msg["casterId"],
                    cell=cell,
                    remaining_turns=cast_spell_msg["remainingTurns"],
                    affected_units=affected_units)
                self._cast_spells.append(cast_area_spell)
                if cast_spell_msg["wasCastThisTurn"]:
                    self.get_player_by_id(cast_spell_msg["casterId"]
                                          ).cast_area_spell = cast_area_spell

            elif spell.is_unit_spell():
                cast_unit_spell = CastUnitSpell(
                    spell=spell,
                    id=cast_spell_msg["id"],
                    caster_id=cast_spell_msg["casterId"],
                    cell=cell,
                    unit=self.get_unit_by_id(cast_spell_msg["unitId"]),
                    path=self._map.get_path_by_id(cast_spell_msg["pathId"]),
                    affected_units=affected_units)
                self._cast_spells.append(cast_unit_spell)
                if cast_spell_msg["wasCastThisTurn"]:
                    self.get_player_by_id(cast_spell_msg["casterId"]
                                          ).cast_unit_spell = cast_unit_spell

    def get_cast_spell_by_id(self, id: int) -> Optional["CastSpell"]:
        for cast_spell in self._cast_spells:
            if cast_spell.id == id:
                return cast_spell
        return None

    def _handle_turn_message(self, msg):
        self._start_time = self._get_current_time_millis()
        self._handle_init_message(self._init_message)
        self._current_turn = msg['currTurn']
        self._player.deck = [
            self._get_base_unit_by_id(deck_type_id)
            for deck_type_id in msg["deck"]
        ]
        self._player.hand = [
            self._get_base_unit_by_id(hand_type_id)
            for hand_type_id in msg["hand"]
        ]
        self._handle_turn_units(msg=msg["diedUnits"], is_dead_unit=True)
        self._handle_turn_cast_spells(msg["castSpells"])
        self._handle_turn_units(msg["units"])
        self._handle_turn_kings(msg["kings"])
        self._turn_updates = TurnUpdates(
            received_spell=msg["receivedSpell"],
            friend_received_spell=msg["friendReceivedSpell"],
            got_range_upgrade=msg["gotRangeUpgrade"],
            got_damage_upgrade=msg["gotDamageUpgrade"],
            available_range_upgrades=msg["availableRangeUpgrades"],
            available_damage_upgrades=msg["availableDamageUpgrades"])

        self._player.set_spells(
            [self.get_spell_by_id(spell_id) for spell_id in msg["mySpells"]])
        self._player_friend.set_spells([
            self.get_spell_by_id(spell_id) for spell_id in msg["friendSpells"]
        ])
        self._player.ap = msg["remainingAP"]

    def choose_hand_by_id(self, type_ids: List[int]) -> None:
        message = Message(type="pick", turn=self.get_current_turn(), info=None)
        if type_ids is not None:
            for type_id in type_ids:
                if type(type_id) is not int:
                    Logs.show_log("type_ids are not int")
                    return

            message.info = {"units": type_ids}
            self._queue.put(message)
        else:
            Logs.show_log(
                "choose_hand_by_id function called with None type_eds")

    # in the first turn 'deck picking' give unit_ids or list of unit names to pick in that turn
    def choose_hand(self, base_units: List[BaseUnit]) -> None:
        message = Message(type="pick", turn=self.get_current_turn(), info=None)
        if base_units is not None:
            for base_unit in base_units:
                if type(base_unit) is not BaseUnit:
                    Logs.show_log("base_units is not an array of BaseUnits")
                    return
            message.info = {"units": [unit.type_id for unit in base_units]}
            self._queue.put(message)
        else:
            Logs.show_log("choose_hand function called with None base_units")

    def get_me(self) -> Player:
        return self._player

    def get_friend(self) -> Player:
        return self._player_friend

    def _get_friend_by_id(self, player_id):
        if self._player.player_id == player_id:
            return self._player_friend
        elif self._player_friend.player_id == player_id:
            return self._player
        elif self._player_first_enemy.player_id == player_id:
            return self._player_second_enemy
        elif self._player_second_enemy.player_id == player_id:
            return self._player_first_enemy
        else:
            Logs.show_log(
                "get_friend_by_id function no player with given player_id")
            return None

    def get_first_enemy(self) -> Player:
        return self._player_first_enemy

    def get_second_enemy(self) -> Player:
        return self._player_second_enemy

    def get_map(self) -> Map:
        return self._map

    # return a list of paths crossing one cell
    def get_paths_crossing_cell(self,
                                cell: Cell = None,
                                row: int = None,
                                col: int = None) -> List[Path]:
        if cell is None:
            if row is None or col is None:
                Logs.show_log(
                    "get_paths_crossing cell function called with no valid argument"
                )
                return []
            cell = self._map.get_cell(row, col)

        if not isinstance(cell, Cell):
            Logs.show_log("Given cell is invalid!")
            return []

        paths = []
        for p in self._map.paths:
            if cell in p.cells:
                paths.append(p)
        return paths

    # return a list of units in a cell
    def get_cell_units(self,
                       cell: Cell = None,
                       row: int = None,
                       col: int = None) -> List[Unit]:
        if cell is None:
            if row is None and col is None:
                Logs.show_log(
                    "get_paths_crossing cell function called with no valid argument"
                )
                return []
            cell = self._map.get_cell(row, col)
        if not isinstance(cell, Cell):
            Logs.show_log("Given cell is invalid!")
            return []
        return cell.units

    # return the shortest path from player_id fortress to cell
    # this path is in the available path list
    # path may cross from friend
    def get_shortest_path_to_cell(self,
                                  from_player_id: int = None,
                                  from_player: Player = None,
                                  cell: Cell = None,
                                  row: int = None,
                                  col: int = None) -> Optional["Path"]:
        if from_player is not None:
            from_player_id = from_player.player_id
        elif from_player_id is None:
            return None

        if self.get_player_by_id(from_player_id) is None:
            return None

        if cell is None:
            if row is None or col is None:
                return None
            cell = self._map.get_cell(row, col)
        shortest_path_from_player = World._shortest_path.get(
            from_player_id, None)
        if shortest_path_from_player is None:
            return None
        return shortest_path_from_player[cell.row][cell.col]

    # place unit with type_id in path_id
    def put_unit(self,
                 type_id: int = None,
                 path_id: int = None,
                 base_unit: BaseUnit = None,
                 path: Path = None) -> None:
        fail = False
        if type_id is not None and type(type_id) is not int:
            Logs.show_log(
                "put_unit function called with invalid type_id argument!")
            fail = True
        if path_id is not None and type(path_id) is not int:
            Logs.show_log(
                "put_unit function called with invalid path_id argument!")
            fail = True
        if base_unit is not None and type(base_unit) is not BaseUnit:
            Logs.show_log(
                "put_unit function called with invalid base_unit argument")
            fail = True
        if path is not None and type(path) is not Path:
            Logs.show_log(
                "put_unit function called with invalid path argument")
            fail = True
        if fail is True:
            return

        if base_unit is not None:
            type_id = base_unit.type_id
        if path is not None:
            path_id = path.id
        if path_id is None or type_id is None:
            return None
        if type_id is None:
            Logs.show_log("type_id is None in cast_area spell function call!")
            return

        message = Message(turn=self.get_current_turn(),
                          type="putUnit",
                          info={
                              "typeId": type_id,
                              "pathId": path_id
                          })
        self._queue.put(message)

    # return the number of turns passed
    def get_current_turn(self) -> int:
        return self._current_turn

    def get_remaining_time(self) -> int:
        if self.get_current_turn() > 0:
            return self._game_constants.turn_timeout - self._get_time_past()
        else:
            return self._game_constants.pick_timeout - self._get_time_past()

    # put unit_id in path_id in position 'index' all spells of one kind have the same id
    def cast_unit_spell(self,
                        unit: Unit = None,
                        unit_id: int = None,
                        path: Path = None,
                        path_id: int = None,
                        cell: Cell = None,
                        row: int = None,
                        col: int = None,
                        spell: Spell = None,
                        spell_id: int = None) -> None:
        if spell is None and spell_id is None:
            Logs.show_log(
                "cast_unit_spell function called with no spell input!")
            return None
        if spell is None:
            if type(spell_id) is not int:
                Logs.show_log(
                    "spell_id is not an integer in cast_unit_spell function call!"
                )
                return
            spell = self.get_spell_by_id(spell_id)

        if row is not None and col is not None:
            if type(row) is not int or type(col) is not int:
                Logs.show_log(
                    "row and column arguments are invalid in cast_unit_spell function call"
                )
                return
            cell = Cell(row, col)

        if unit is not None:
            if type(unit) is not Unit:
                Logs.show_log(
                    "unit argument is invalid in cast_unit_spell function call"
                )
                return
            unit_id = unit.unit_id
        if path is not None:
            if type(path) is not Path:
                Logs.show_log(
                    "path argument is invalid in cast_unit_spell function call"
                )
                return
            path_id = path.id

        if type(unit_id) is not int:
            Logs.show_log(
                "unit_id argument is invalid in cast_unit_spell function call")
            return

        if type(path_id) is not int:
            Logs.show_log(
                "path_id argument is invalid in cast_unit_spell function call")
            return

        message = Message(type="castSpell",
                          turn=self.get_current_turn(),
                          info={
                              "typeId": spell.type_id,
                              "cell": {
                                  "row": cell.row,
                                  "col": cell.col
                              },
                              "unitId": unit_id,
                              "pathId": path_id
                          })
        self._queue.put(message)

    # cast spell in the cell 'center'
    def cast_area_spell(self,
                        center: Cell = None,
                        row: int = None,
                        col: int = None,
                        spell: Spell = None,
                        spell_id: int = None) -> None:
        if spell is None:
            if spell_id is None or type(spell_id) is not int:
                Logs.show_log("no valid spell selected in cast_area_spell!")
                return
            spell = self.get_spell_by_id(spell_id)
        if type(spell) is not Spell:
            Logs.show_log("no valid spell selected in cast_area_spell!")
            return

        if row is not None and col is not None:
            center = self._map.get_cell(row, col)

        if center is not None:
            message = Message(type="castSpell",
                              turn=self.get_current_turn(),
                              info={
                                  "typeId": spell.type_id,
                                  "cell": {
                                      "row": center.row,
                                      "col": center.col
                                  },
                                  "unitId": -1,
                                  "pathId": -1
                              })
            self._queue.put(message)
        else:
            Logs.show_log("invalid cell selected in cast_area_spell")

    # returns a list of units the spell casts effects on
    def get_area_spell_targets(self,
                               center: Cell = None,
                               row: int = None,
                               col: int = None,
                               spell: Spell = None,
                               type_id: int = None) -> List["Unit"]:
        if spell is None:
            if type_id is not None:
                spell = self.get_cast_spell_by_id(type_id)
            else:
                return []
        if type(spell) is not Spell:
            Logs.show_log("invalid spell chosen in get_area_spell_targets")
            return []
        if not spell.is_area_spell():
            return []
        if center is None:
            center = Cell(row, col)
        ls = []
        for i in range(max(0, center.row - spell.range),
                       min(center.row + spell.range + 1, self._map.row_num)):
            for j in range(
                    max(0, center.col - spell.range),
                    min(center.col + spell.range + 1, self._map.col_num)):
                cell = self._map.get_cell(i, j)
                for u in cell.units:
                    if self._is_unit_targeted(u, spell.target):
                        ls.append(u)
        return ls

    def _is_unit_targeted(self, unit, spell_target):
        if spell_target == SpellTarget.SELF:
            if unit in self._player.units:
                return True
        elif spell_target == SpellTarget.ALLIED:
            if unit in self._player_friend.units or unit in self._player.units:
                return True
        elif spell_target == SpellTarget.ENEMY:
            if unit in self._player_first_enemy.units or unit in self._player_second_enemy.units:
                return True
        return False

    # every once in a while you can upgrade, this returns the remaining time for upgrade
    def get_remaining_turns_to_upgrade(self) -> int:
        rem_turn = (self._game_constants.turns_to_upgrade -
                    self._current_turn) % self._game_constants.turns_to_upgrade
        if rem_turn == 0:
            return self._game_constants.turns_to_upgrade
        return rem_turn

    # every once in a while a spell is given this remains the remaining time to get new spell
    def get_remaining_turns_to_get_spell(self) -> int:
        rem_turn = (self._game_constants.turns_to_spell -
                    self._current_turn) % self._game_constants.turns_to_spell
        if rem_turn == 0:
            return self._game_constants.turns_to_spell
        return rem_turn

    # returns a list of spells casted on a cell
    def get_range_upgrade_number(self) -> int:
        return self._turn_updates.available_range_upgrade

    def get_damage_upgrade_number(self) -> int:
        return self._turn_updates.available_damage_upgrade

    # returns the spell given in that turn
    def get_received_spell(self) -> Optional["Spell"]:
        spell_id = self._turn_updates.received_spell
        spell = self.get_spell_by_id(spell_id)
        return spell

    # returns the spell given in that turn to friend
    def get_friend_received_spell(self) -> Optional["Spell"]:
        spell_id = self._turn_updates.friend_received_spell
        spell = self.get_spell_by_id(spell_id)
        return spell

    def upgrade_unit_range(self,
                           unit: Unit = None,
                           unit_id: int = None) -> None:
        if unit is not None:
            unit_id = unit.unit_id

        if unit_id is not None and type(unit_id) is int:
            self._queue.put(
                Message(type="rangeUpgrade",
                        turn=self.get_current_turn(),
                        info={"unitId": unit_id}))
        else:
            Logs.show_log("invalid unit or unit_id in upgrade_unit_range")

    def upgrade_unit_damage(self,
                            unit: Unit = None,
                            unit_id: int = None) -> None:
        if unit is not None:
            unit_id = unit.unit_id

        if unit_id is not None and type(unit_id) is int:
            self._queue.put(
                Message(type="damageUpgrade",
                        turn=self.get_current_turn(),
                        info={"unitId": unit_id}))
        else:
            Logs.show_log("invalid unit or unit_id in upgrade_unit_damage")

    def get_all_base_units(self) -> List[BaseUnit]:
        return copy.deepcopy(self._base_units)

    def get_all_spells(self) -> List[Spell]:
        return copy.deepcopy(self._spells)

    def get_king_by_id(self, player_id: int) -> Optional["King"]:
        for p in self._players:
            if p.player_id == player_id:
                return p.king
        return None

    def get_base_unit_by_id(self, type_id: int) -> Optional["BaseUnit"]:
        for bu in self._base_units:
            if bu.type_id == type_id:
                return bu
        return None

    # returns unit in map with a unit_id
    def get_unit_by_id(self, unit_id: int) -> Optional["Unit"]:
        for unit in self._map.units:
            if unit.unit_id == unit_id:
                return unit
        return None

    def get_player_by_id(self, player_id: int) -> Optional["Player"]:
        for player in self._players:
            if player.player_id == player_id:
                return player
        return None

    def get_spell_by_id(self, type_id: int) -> Optional["Spell"]:
        for spell in self._spells:
            if spell.type_id == type_id:
                return spell
        return None

    def get_game_constants(self) -> GameConstants:
        return self._game_constants

    def _get_paths_starting_with(self, first, paths):
        ret = []
        for path in paths:
            c_path = Path(path=path)
            if c_path.cells[-1] == first:
                c_path.cells.reverse()
            if c_path.cells[0] == first:
                ret.append(c_path)
        return ret

    def _handle_end_message(self, scores_list_msg):
        return dict([(score["playerId"], score["score"])
                     for score in scores_list_msg])
Esempio n. 2
0
class World(ABC):
    DEBUGGING_MODE = False
    LOG_FILE_POINTER = None

    def __init__(self, world=None, queue=None):
        self.game_constants = None

        self.turn_updates = None

        self.map = None
        self.base_units = None
        self.current_turn = 0

        self.players = []
        self.player = None
        self.player_friend = None
        self.player_first_enemy = None
        self.player_second_enemy = None
        self.spells = []
        self.cast_spells = []

        if world is not None:
            self.game_constants = world.game_constants

            self.turn_updates = TurnUpdates(turn_updates=world.turn_updates)

            self.map = world.map
            self.base_units = world.base_units
            self.spells = world.spells
            self.current_turn = world.current_turn

            self.players = world.players
            self.player = world.player
            self.player_friend = world.player_friend
            self.player_first_enemy = world.player_first_enemy
            self.player_second_enemy = world.player_second_enemy
            self.cast_spells = world.cast_spells

            self.queue = world.queue
        else:
            self.queue = queue

    def get_current_time_millis(self):
        return int(round(time.time() * 1000))

    def get_time_past(self):
        return self.get_current_time_millis() - self.start_time

    def get_player_by_id(self, player_id):
        for player in self.players:
            if player.player_id == player_id:
                return player
        return None

    def get_spell_by_id(self, type_id):
        for spell in self.spells:
            if spell.type_id == type_id:
                return spell
        return None

    def _game_constant_init(self, game_constants_msg):
        self.game_constants = GameConstants(
            max_ap=game_constants_msg["maxAP"],
            max_turns=game_constants_msg["maxTurns"],
            turn_timeout=game_constants_msg["turnTimeout"],
            pick_timeout=game_constants_msg["pickTimeout"],
            turns_to_upgrade=game_constants_msg["turnsToUpgrade"],
            turns_to_spell=game_constants_msg["turnsToSpell"],
            damage_upgrade_addition=game_constants_msg[
                "damageUpgradeAddition"],
            range_upgrade_addition=game_constants_msg["rangeUpgradeAddition"],
            hand_size=game_constants_msg["handSize"],
            deck_size=game_constants_msg["deckSize"])

    def _find_path_starting_and_ending_with(self, first, last, paths):
        for path in paths:
            c_path = Path(path=path)
            if c_path.cells[0] == first and c_path.cells[-1] == last:
                return c_path
            c_path.cells.reverse()
            if c_path.cells[0] == first and c_path.cells[-1] == last:
                return c_path
        return None

    def _map_init(self, map_msg):
        row_num = map_msg["rows"]
        col_num = map_msg["cols"]

        input_cells = [[Cell(row=row, col=col) for col in range(col_num)]
                       for row in range(row_num)]

        paths = [
            Path(id=path["id"],
                 cells=[
                     input_cells[cell["row"]][cell["col"]]
                     for cell in path["cells"]
                 ]) for path in map_msg["paths"]
        ]
        kings = [
            King(player_id=king["playerId"],
                 center=input_cells[king["center"]["row"]][king["center"]
                                                           ["col"]],
                 hp=king["hp"],
                 attack=king["attack"],
                 range=king["range"],
                 target=None,
                 target_cell=None,
                 is_alive=True) for king in map_msg["kings"]
        ]

        self.players = [
            Player(player_id=map_msg["kings"][i]["playerId"],
                   king=kings[i],
                   deck=[],
                   hand=[],
                   ap=self.game_constants.max_ap,
                   paths_from_player=self._get_paths_starting_with(
                       kings[i].center, paths),
                   path_to_friend=self._find_path_starting_and_ending_with(
                       kings[i].center, kings[i ^ 1].center, paths),
                   units=[],
                   cast_area_spell=None,
                   cast_unit_spell=None,
                   duplicate_units=[],
                   hasted_units=[],
                   played_units=[],
                   died_units=[],
                   range_upgraded_unit=None,
                   damage_upgraded_unit=None,
                   spells=[]) for i in range(4)
        ]

        for player in self.players:
            player.paths_from_player.remove(player.path_to_friend)

        self.player = self.players[0]
        self.player_friend = self.players[1]
        self.player_first_enemy = self.players[2]
        self.player_second_enemy = self.players[3]

        self.map = Map(row_num=row_num,
                       column_num=col_num,
                       paths=paths,
                       kings=kings,
                       cells=input_cells,
                       units=[])

    # returns unit in map with a unit_id
    def get_unit_by_id(self, unit_id):
        for unit in self.map.units:
            if unit.unit_id == unit_id:
                return unit
        return None

    def _base_unit_init(self, msg):
        self.base_units = [
            BaseUnit(type_id=b_unit["typeId"],
                     max_hp=b_unit["maxHP"],
                     base_attack=b_unit["baseAttack"],
                     base_range=b_unit["baseRange"],
                     target_type=UnitTarget.get_value(b_unit["target"]),
                     is_flying=b_unit["isFlying"],
                     is_multiple=b_unit["isMultiple"],
                     ap=b_unit["ap"]) for b_unit in msg
        ]

    def _get_base_unit_by_id(self, type_id):
        for base_unit in self.base_units:
            if base_unit.type_id == type_id:
                return base_unit
        return None

    def _spells_init(self, msg):
        self.spells = [
            Spell(type=SpellType.get_value(spell["type"]),
                  type_id=spell["typeId"],
                  duration=spell["duration"],
                  priority=spell["priority"],
                  range=spell["range"],
                  power=spell["power"],
                  target=SpellTarget.get_value(spell["target"]),
                  is_damaging=False) for spell in msg
        ]

    def _handle_init_message(self, msg):
        self._game_constant_init(msg['gameConstants'])
        self._map_init(msg["map"])
        self._base_unit_init(msg["baseUnits"])
        self._spells_init(msg["spells"])

    def _handle_turn_kings(self, msg):
        for king_msg in msg:
            hp = king_msg["hp"] if (king_msg["hp"] > 0
                                    and king_msg["isAlive"]) else -1
            self.get_player_by_id(king_msg["playerId"]).king.hp = hp
            self.get_player_by_id(king_msg["playerId"]).king.target = king_msg[
                "target"] if king_msg["target"] != -1 else None

    def _handle_turn_units(self, msg, is_dead_unit=False):
        if not is_dead_unit:
            self.map.clear_units()
            for player in self.players:
                player.units.clear()
                player.played_units.clear()
                player.hasted_units.clear()
                player.duplicate_units.clear()
                player.range_upgraded_unit = None
                player.damage_upgraded_unit = None
        else:
            for player in self.players:
                player.died_units.clear()

        for unit_msg in msg:
            unit_id = unit_msg["unitId"]
            player = self.get_player_by_id(player_id=unit_msg["playerId"])
            base_unit = self.base_units[unit_msg["typeId"]]

            if not unit_msg['target'] == -1:
                target_cell = Cell(row=unit_msg["targetCell"]["row"],
                                   col=unit_msg["targetCell"]["col"])
            else:
                target_cell = None
            unit = Unit(unit_id=unit_id,
                        base_unit=base_unit,
                        cell=self.map.get_cell(unit_msg["cell"]["row"],
                                               unit_msg["cell"]["col"]),
                        path=self.map.get_path_by_id(unit_msg["pathId"]),
                        hp=unit_msg["hp"],
                        damage_level=unit_msg["damageLevel"],
                        range_level=unit_msg["rangeLevel"],
                        is_duplicate=unit_msg["isDuplicate"],
                        is_hasted=unit_msg["isHasted"],
                        range=unit_msg["range"],
                        attack=unit_msg["attack"],
                        target=unit_msg["target"],
                        target_cell=target_cell,
                        affected_spells=[
                            self.get_cast_spell_by_id(cast_spell_id)
                            for cast_spell_id in unit_msg["affectedSpells"]
                        ],
                        target_if_king=None
                        if self.get_player_by_id(unit_msg["target"]) == None
                        else self.get_player_by_id(unit_msg["target"]).king,
                        player_id=unit_msg["playerId"])
            if not is_dead_unit:
                self.map.add_unit_in_cell(unit.cell.row, unit.cell.col, unit)
                player.units.append(unit)
                if unit_msg["wasDamageUpgraded"]:
                    player.damage_upgraded_unit = unit
                if unit_msg["wasRangeUpgraded"]:
                    player.range_upgraded_unit = unit
                if unit_msg["wasPlayedThisTurn"]:
                    player.played_units.append(unit)
                if unit.is_hasted:
                    player.hasted_units.append(unit)
                if unit.is_duplicate:
                    player.duplicate_units.append(unit)
            else:
                player.died_units.append(unit)
        for unit in self.map.units:
            if unit.target == -1 or unit.target_if_king != None:
                unit.target = None
            else:
                unit.target = self.map.get_unit_by_id(unit.target)

    def _handle_turn_cast_spells(self, msg):
        self.cast_spells = []
        for cast_spell_msg in msg:
            spell = self.get_spell_by_id(cast_spell_msg["typeId"])
            cell = self.map.get_cell(cast_spell_msg["cell"]["row"],
                                     cast_spell_msg["cell"]["col"])
            affected_units = [
                self.get_unit_by_id(affected_unit_id)
                for affected_unit_id in cast_spell_msg["affectedUnits"]
            ]
            if spell.is_area_spell():
                self.cast_spells.append(
                    CastAreaSpell(
                        spell=spell,
                        id=cast_spell_msg["id"],
                        caster_id=cast_spell_msg["casterId"],
                        cell=cell,
                        remaining_turns=cast_spell_msg["remainingTurns"],
                        affected_units=affected_units))
            elif spell.is_unit_spell():
                self.cast_spells.append(
                    CastUnitSpell(
                        spell=spell,
                        id=cast_spell_msg["id"],
                        caster_id=cast_spell_msg["casterId"],
                        cell=cell,
                        unit=self.get_unit_by_id(cast_spell_msg["unitId"]),
                        path=self.map.get_path_by_id(cast_spell_msg["pathId"]),
                        affected_units=affected_units))

    def get_cast_spell_by_id(self, id):
        for cast_spell in self.cast_spells:
            if cast_spell.id == id:
                return cast_spell
        return None

    def _handle_turn_message(self, msg):
        self.current_turn = msg['currTurn']
        self.player.deck = [
            self._get_base_unit_by_id(deck_type_id)
            for deck_type_id in msg["deck"]
        ]
        self.player.hand = [
            self._get_base_unit_by_id(hand_type_id)
            for hand_type_id in msg["hand"]
        ]
        self._handle_turn_kings(msg["kings"])
        self._handle_turn_units(msg["units"])
        self._handle_turn_units(msg=msg["diedUnits"], is_dead_unit=True)
        self._handle_turn_cast_spells(msg["castSpells"])

        self.turn_updates = TurnUpdates(
            received_spell=msg["receivedSpell"],
            friend_received_spell=msg["friendReceivedSpell"],
            got_range_upgrade=msg["gotRangeUpgrade"],
            got_damage_upgrade=msg["gotDamageUpgrade"],
            available_range_upgrades=msg["availableRangeUpgrades"],
            available_damage_upgrades=msg["availableDamageUpgrades"])

        self.player.set_spells(
            [self.get_spell_by_id(spell_id) for spell_id in msg["mySpells"]])
        self.player_friend.set_spells([
            self.get_spell_by_id(spell_id) for spell_id in msg["friendSpells"]
        ])
        self.player.ap = msg["remainingAP"]

        self.start_time = self.get_current_time_millis()

    # in the first turn 'deck picking' give unit_ids or list of unit names to pick in that turn
    def choose_deck(self, type_ids=None, base_units=None):
        message = Message(type="pick", turn=self.get_current_turn(), info=None)
        if type_ids is not None:
            message.info = {"units": type_ids}
        elif base_units is not None:
            message.info = {"units": [unit.type_id for unit in base_units]}
        self.queue.put(message)

    def get_me(self):
        return self.player

    def get_friend(self):
        return self.player_friend

    def get_friend_by_id(self, player_id):
        if self.player.player_id == player_id:
            return self.player_friend
        elif self.player_friend.player_id == player_id:
            return self.player
        elif self.player_first_enemy.player_id == player_id:
            return self.player_second_enemy
        elif self.player_second_enemy.player_id == player_id:
            return self.player_first_enemy
        else:
            return None

    def get_first_enemy(self):
        return self.player_first_enemy

    def get_second_enemy(self):
        return self.player_second_enemy

    def get_map(self):
        return self.map

    # return a list of paths crossing one cell
    def get_paths_crossing_cell(self, cell=None, row=None, col=None):
        if cell is None:
            if row is None or col is None:
                return
            cell = self.map.get_cell(row, col)

        paths = []
        for p in self.map.paths:
            if cell in p.cells:
                paths.append(p)
        return paths

    # return a list of units in a cell
    def get_cell_units(self, cell=None, row=None, col=None):
        if cell is None:
            if row is None and col is None:
                return None
            cell = self.map.get_cell(row, col)
        return cell.units

    # return the shortest path from player_id fortress to cell
    # this path is in the available path list
    # path may cross from friend
    def get_shortest_path_to_cell(self,
                                  from_player_id=None,
                                  from_player=None,
                                  cell=None,
                                  row=None,
                                  col=None):
        def path_count(paths):
            shortest_path = None
            count = 0
            for p in paths:
                count_num = 0
                for c in p.cells:
                    if c == cell:
                        if shortest_path is None:
                            count = count_num
                            shortest_path = p

                        elif count_num < count:
                            count = count_num
                            shortest_path = p
                    count_num += 1
            return shortest_path

        if from_player is not None:
            from_player_id = from_player.player_id
        if cell is None:
            if row is None or col is None:
                return
            cell = self.map.get_cell(row, col)
        p = path_count(self.get_player_by_id(from_player_id).paths_from_player)
        if p is None:
            ls = [self.get_player_by_id(from_player_id).path_to_friend]
            ptf = path_count(ls)
            if ptf is None:
                pff = path_count(
                    self.get_friend_by_id(from_player_id).paths_from_player)
                if pff is None:
                    return None
                return pff
            return ptf
        return p

    # place unit with type_id in path_id
    def put_unit(self, type_id=None, path_id=None, base_unit=None, path=None):
        if base_unit is not None:
            type_id = base_unit.type_id
        if path is not None:
            path_id = path.id
        if path_id is None or type_id is None:
            return None
        message = Message(turn=self.get_current_turn(),
                          type="putUnit",
                          info={
                              "typeId": type_id,
                              "pathId": path_id
                          })
        self.queue.put(message)

    # return the number of turns passed
    def get_current_turn(self):
        return self.current_turn

    # put unit_id in path_id in position 'index' all spells of one kind have the same id
    def cast_unit_spell(self,
                        unit=None,
                        unit_id=None,
                        path=None,
                        path_id=None,
                        cell=None,
                        row=None,
                        col=None,
                        spell=None,
                        spell_id=None):
        if spell is None and spell_id is None:
            return None
        if spell is None:
            spell = self.get_spell_by_id(spell_id)
        if row is not None and col is not None:
            cell = Cell(row, col)
        if unit is not None:
            unit_id = unit.unit_id
        if path is not None:
            path_id = path.id
        message = Message(type="castSpell",
                          turn=self.get_current_turn(),
                          info={
                              "typeId": spell.type_id,
                              "cell": {
                                  "row": cell.row,
                                  "col": cell.col
                              },
                              "unitId": unit_id,
                              "pathId": path_id
                          })
        self.queue.put(message)

    # cast spell in the cell 'center'
    def cast_area_spell(self,
                        center=None,
                        row=None,
                        col=None,
                        spell=None,
                        spell_id=None):
        if spell is None:
            spell = self.get_spell_by_id(spell_id)
        if row is not None and col is not None:
            center = self.map.get_cell(row, col)

        if center is not None:
            message = Message(type="castSpell",
                              turn=self.get_current_turn(),
                              info={
                                  "typeId": spell.type_id,
                                  "cell": {
                                      "row": center.row,
                                      "col": center.col
                                  },
                                  "unitId": -1,
                                  "pathId": -1
                              })
            self.queue.put(message)

    # returns a list of units the spell casts effects on
    # NOT CORRECT!!! #
    def get_area_spell_targets(self,
                               center,
                               row=None,
                               col=None,
                               spell=None,
                               type_id=None):
        if spell is None:
            if type_id is not None:
                spell = self.get_cast_spell_by_id(type_id)
            else:
                return []
        if not spell.is_area_spell():
            return []
        if center is None:
            center = Cell(row, col)
        ls = []
        for i in range(max(0, center.row - spell.range),
                       min(center.row + spell.range, self.map.row_num)):
            for j in range(max(0, center.col - spell.range),
                           min(center.col + spell.range, self.map.column_num)):
                cell = self.map.get_cell(i, j)
                for u in cell.units:
                    if self._is_unit_targeted(u, spell.target):
                        ls.append(u)

    def _is_unit_targeted(self, unit, spell_target):
        if spell_target == 1:
            if unit in self.player.units:
                return True
        elif spell_target == 2:
            if unit in self.player_friend or unit in self.player.units:
                return True
        elif spell_target == 3:
            if unit in self.player_first_enemy or unit in self.player_second_enemy:
                return True
        return False

    # every once in a while you can upgrade, this returns the remaining time for upgrade
    def get_remaining_turns_to_upgrade(self):
        rem_turn = (self.game_constants.turns_to_upgrade -
                    self.current_turn) % self.game_constants.turns_to_upgrade
        if rem_turn == 0:
            return self.game_constants.turns_to_upgrade
        return rem_turn

    # every once in a while a spell is given this remains the remaining time to get new spell
    def get_remaining_turns_to_get_spell(self):
        rem_turn = (self.game_constants.turns_to_spell -
                    self.current_turn) % self.game_constants.turns_to_spell
        if rem_turn == 0:
            return self.game_constants.turns_to_spell
        return rem_turn

    # returns a list of spells casted on a cell
    def get_range_upgrade_number(self):
        return self.turn_updates.available_range_upgrade

    def get_damage_upgrade_number(self):
        return self.turn_updates.available_damage_upgrade

    # returns the spell given in that turn
    def get_received_spell(self):
        spell_id = self.turn_updates.received_spell
        spell = self.get_spell_by_id(spell_id)
        return spell

    # returns the spell given in that turn to friend
    def get_friend_received_spell(self):
        spell_id = self.turn_updates.friend_received_spell
        spell = self.get_spell_by_id(spell_id)
        return spell

    def upgrade_unit_range(self, unit=None, unit_id=None):
        if unit is not None:
            unit_id = unit.unit_id

        if unit_id is not None:
            self.queue.put(
                Message(type="rangeUpgrade",
                        turn=self.get_current_turn(),
                        info={"unitId": unit_id}))

    def upgrade_unit_damage(self, unit=None, unit_id=None):
        if unit is not None:
            unit_id = unit.unit_id

        if unit_id is not None:
            self.queue.put(
                Message(type="damageUpgrade",
                        turn=self.get_current_turn(),
                        info={"unitId": unit_id}))

    def get_player_hasted_units(self, player_id):
        return [
            unit for unit in self.get_player_by_id(player_id=player_id).units
            if unit.is_hasted > 0
        ]

    def get_player_played_units(self, player_id):
        return [
            unit for unit in self.get_player_by_id(player_id=player_id).units
            if unit.was_played_this_turn
        ]

    def get_all_base_unit(self):
        return copy.deepcopy(self.base_units)

    def get_all_spells(self):
        return copy.deepcopy(self.spells)

    def get_king_by_id(self, player_id):
        for p in self.players:
            if p.player_id == player_id:
                return p.king

        return None

    def get_base_unit_by_id(self, type_id):
        for bu in self.base_units:
            if bu.type_id == type_id:
                return bu
        return None

    def get_game_constants(self):
        return self.game_constants

    def _get_paths_starting_with(self, first, paths):
        ret = []
        for path in paths:
            c_path = Path(path=path)
            if c_path.cells[-1] == first:
                c_path.cells.reverse()
            if c_path.cells[0] == first:
                ret.append(c_path)
        return ret

    def _handle_end_message(self, scores_list_msg):
        return dict([(score["playerId"], score["score"])
                     for score in scores_list_msg])