Exemplo n.º 1
0
def parse_card(data: Dict) -> (object, Dict):
    """
    Devuelve los datos necesarios para la inicialización de una carta de los
    datos JSON.
    """

    # En el caso de cartas simples solo se necesita el color
    simple_cards = {
        "organ": Organ,
        "medicine": Medicine,
        "virus": Virus,
    }
    cls = simple_cards.get(data["type"])
    try:
        col = Color(data["color"])
    except ValueError:
        col = None  # Fallará con los tratamientos
    if cls is not None:
        return cls, {"color": col}

    # Si no es una carta simple es un tratamiento, que no tiene color
    treatment_cards = {
        "latex_glove": LatexGlove,
        "organ_thief": OrganThief,
        "infection": Infection,
        "medical_error": MedicalError,
        "transplant": Transplant,
    }
    cls = treatment_cards.get(data["treatment_type"])
    if cls is not None:
        return cls, {}

    raise GameLogicException(f"Couldn't parse card with data {data}")
Exemplo n.º 2
0
    def apply(self, caller: "Player", game: "Game") -> GameUpdate:
        logger.info(f"{caller.name} plays a card")

        # No podrá jugar una carta si el mismo jugador está en proceso de
        # descarte.
        if game.discarding:
            raise GameLogicException("El jugador está en proceso de descarte")

        self.caller = caller

        # Obtiene la carta y la elimina de su mano. No hace falta actualizar el
        # estado al eliminar la carta porque ya se hará cuando robe al final del
        # turno.
        card = caller.get_card(self.slot)

        # NOTE: no hay ninguna carta que intercambie manos de jugadores, en ese
        # caso habría que guardar el estado completo de la mano anterior y
        # borrar la carta (para que cuando se intercambiase no hubiera
        # problemas) y, en caso de fallo, restaurarla.

        # Usa la carta
        update = card.apply(self, game)

        # Solo si hemos podido "aplicar" el comportamiento de la carta, la
        # quitaremos de la mano.
        if card.is_placeable():
            # No devolvemos la carta a la baraja (está puesta en un cuerpo).
            caller.remove_card(self.slot)
        else:
            caller.remove_card(self.slot, return_to=game.deck)
        return update
Exemplo n.º 3
0
    def get_action_data(self, action: "PlayCard", game: "Game") -> None:
        # Jugador con el que queremos intercambiar el cuerpo
        self.target_name = action.data.get("target")
        if self.target_name in (None, ""):
            raise GameLogicException("Parámetro target vacío")

        self.target = game.get_unfinished_player(self.target_name)
Exemplo n.º 4
0
    def player_finished(self, player: Player) -> GameUpdate:
        """
        Finaliza la partida para un jugador en concreto.
        """

        if player.has_finished():
            raise GameLogicException("El jugador ya ha terminado")

        self._players_finished += 1
        player.position = self._players_finished

        logger.info(f"{player.name} has finished at position {player.position}")

        # Vaciamos la mano del jugador y devolvemos las cartas a la baraja
        player.empty_hand(return_to=self.deck)
        # Vaciamos el cuerpo del jugador
        player.empty_body(return_to=self.deck)

        # Generamos un GameUpdate donde:
        # 1. Avisamos a todos los jugadores de que el jugador ha acabado.
        update = GameUpdate(self)
        update.repeat({"leaderboard": self._leaderboard()})
        # 2. Mostramos las pilas vacías
        empty_piles = GameUpdate(self)
        empty_piles.repeat({"bodies": {player.name: player.body.piles}})
        update.merge_with(empty_piles)
        # 3. Le enviamos al jugador la mano vacía
        empty_hand = GameUpdate(self)
        empty_hand.add(player.name, {"hand": player.hand})
        update.merge_with(empty_hand)

        return update
Exemplo n.º 5
0
    def set_paused(
        self, paused: bool, paused_by: str, resume_callback
    ) -> Optional[GameUpdate]:
        with self._paused_lock:
            if self.is_paused() == paused:
                return None

            # Solo el jugador que ha pausado la partida puede volver a reanudarla.
            if self._paused and self._paused_by != paused_by:
                raise GameLogicException(
                    "Solo el jugador que inicia la pausa puede reanudar"
                )

            # Si la pausa pasa del tiempo límite comentado anteriormente, la
            # partida se reanuda automáticamente
            if paused:
                # Se para mientras tanto el timer del turno
                self._turn_timer.pause()

                # Iniciamos un timer
                self._pause_timer = Timer(TIME_UNTIL_RESUME, resume_callback)
                self._pause_timer.start()

                logger.info(f"Game paused by {paused_by}")
            else:
                # Continúa el timer del turno
                self._turn_timer.resume()

                self._pause_timer.cancel()
                logger.info("Game resumed")

            self._paused = paused
            self._paused_by = paused_by
            return self.pause_update()
Exemplo n.º 6
0
    def get_action_data(self, action: "PlayCard", game: "Game") -> None:
        """ """
        # Jugador objetivo
        target = action.data.get("target")
        # Pilas del jugador objetivo
        self.organ_pile_slot = action.data.get("organ_pile")

        if None in (target, self.organ_pile_slot):
            raise GameLogicException("Parámetro vacío")

        if type(target) is not str or type(self.organ_pile_slot) is not int:
            raise GameLogicException("Tipo de parámetro incorrecto")

        self.target = game.get_unfinished_player(target)

        self.organ_pile = self.target.body.get_pile(self.organ_pile_slot)
Exemplo n.º 7
0
    def apply(self, action: "PlayCard", game: "Game") -> GameUpdate:
        self.get_action_data(action, game)

        if self.target.name == action.caller.name:
            raise GameLogicException("No puedes colocar un virus en tu cuerpo")

        logger.info(
            f"{self.color}-colored virus played over {self.target.name}")

        # Comprobamos si hay que extirpar o destruir vacuna
        if self.organ_pile.is_infected():
            # Lo añadimos para que vuelva a la baraja
            self.organ_pile.add_modifier(self)
            # Si está infectado -> se extirpa el órgano
            self.organ_pile.remove_organ(return_to=game.deck)
        elif self.organ_pile.is_protected():
            # Lo añadimos para que vuelva a la baraja
            self.organ_pile.add_modifier(self)
            # Si está protegido -> se destruye la vacuna
            self.organ_pile.pop_modifiers(return_to=game.deck)
        else:
            # Se infecta el órgano (se añade el virus a los modificadores)
            self.organ_pile.add_modifier(self)

        update = self.piles_update(game)
        update.msg = (
            f"un virus {self.color.translate()['male']} sobre {self.target.name}"
        )
        return update
Exemplo n.º 8
0
    def get_pile(self, pile: int) -> OrganPile:
        if pile < 0 or pile > 3:
            raise GameLogicException("Slot de pila inválido")

        if self.piles[pile] is None:
            self.piles[pile] = OrganPile()
        return self.piles[pile]
Exemplo n.º 9
0
    def apply(self, action: "PlayCard", game: "Game") -> GameUpdate:
        self.get_action_data(action, game)

        # Comprobamos que la pila tiene órgano
        if self.organ_pile.is_empty():
            raise GameLogicException("No puedes robar órganos inexistentes")

        # Comprobamos que ninguno de los dos órganos está inmunizado
        if self.organ_pile.is_immune():
            raise GameLogicException("No puedes robar órganos inmunes")

        # Comprobamos que el caller no tiene ya un órgano de ese color
        if not action.caller.body.organ_unique(self.organ_pile.organ):
            raise GameLogicException("Ya tienes un órgano de ese color")

        # Comprobamos que no se va a robar un órgano a sí mismo
        if action.caller == self.target:
            raise GameLogicException("No puedes robarte un órgano a ti mismo")

        # Obtenemos un espacio libre del caller
        self.empty_slot = None
        for (slot, pile) in enumerate(action.caller.body.piles):
            if pile.is_empty():
                self.empty_slot = slot
                break
        if self.empty_slot is None:
            raise GameLogicException("No tienes espacio libre")

        logger.info("organ-thief played")

        # Robamos la pila del target y la guardamos en el caller
        empty_pile = action.caller.body.piles[self.empty_slot]
        action.caller.body.piles[self.empty_slot] = self.organ_pile
        self.target.body.piles[self.organ_pile_slot] = empty_pile

        update = GameUpdate(game)
        # Añadimos el cuerpo del caller al GameUpdate
        update.repeat({
            "bodies": {
                self.target.name: self.target.body.piles,
                action.caller.name: action.caller.body.piles,
            },
        })

        update.msg = f"un Ladrón de Órganos sobre {self.target.name}"
        return update
Exemplo n.º 10
0
 def remove_card(self, slot: int, return_to: Optional[List[Card]] = None) -> None:
     try:
         card = self.hand[slot]
         if return_to is not None:
             return_to.insert(0, card)
         del self.hand[slot]
     except IndexError:
         raise GameLogicException("Slot no existente en la mano del jugador")
Exemplo n.º 11
0
    def apply(self, action: "PlayCard", game: "Game") -> GameUpdate:
        self.get_action_data(action, game)

        # Comprobamos que las dos pilas tienen órgano
        if self.organ_pile1.is_empty() or self.organ_pile2.is_empty():
            raise GameLogicException(
                "No puedes intercambiar órganos inexistentes")

        # Comprobamos que ninguno de los dos órganos está inmunizado
        if self.organ_pile1.is_immune() or self.organ_pile2.is_immune():
            raise GameLogicException("No puedes intercambiar órganos inmunes")

        # Comprobamos que no se haga un transplante a sí mismo.
        if self.player1 == self.player2:
            raise GameLogicException(
                "No puedes intercambiar óganos entre el mismo jugador")

        # Comprobamos que ninguno de los dos jugadores tienen ya un órgano del
        # mismo color del órgano a añadir. NOTE: Ignoramos las pilas sobre las
        # que se va a reemplazar, porque no crean conflicto.
        if not (self.player1.body.organ_unique(self.organ_pile2.organ,
                                               ignored_piles=[self.pile_slot1])
                and self.player2.body.organ_unique(
                    self.organ_pile1.organ, ignored_piles=[self.pile_slot2])):
            raise GameLogicException("Ya tiene un órgano de ese color")

        logger.info("transplant played")

        update = GameUpdate(game)

        # Intercambiamos las pilas de ambos jugadores
        tmp = self.player1.body.piles[self.pile_slot1]
        self.player1.body.piles[self.pile_slot1] = self.player2.body.piles[
            self.pile_slot2]
        self.player2.body.piles[self.pile_slot2] = tmp
        # Añadimos los dos cuerpos al GameUpdate
        update.repeat({
            "bodies": {
                self.player1.name: self.player1.body.piles,
                self.player2.name: self.player2.body.piles,
            },
        })

        update.msg = f"un Transplante entre {self.player1.name} y {self.player2.name}"
        return update
Exemplo n.º 12
0
 def get_unfinished_player(self, user_name: str) -> Player:
     """
     Devuelve un jugador que esté todavía jugando (que no haya ganado
     todavía).
     """
     player = self.get_player(user_name)
     if player.has_finished():
         raise GameLogicException("El jugador ya ha acabado")
     return player
Exemplo n.º 13
0
    def apply(self, action: "PlayCard", game: "Game") -> GameUpdate:
        self.get_action_data(action, game)

        if self.target.name != action.caller.name:
            raise GameLogicException(
                "No puedes colocar un órgano en otro cuerpo")

        if not self.target.body.organ_unique(self):
            raise GameLogicException("No puedes colocar un órgano repetido")

        logger.info(
            f"{self.color}-colored organ played over {self.target.name}")

        self.organ_pile.set_organ(self)

        update = self.piles_update(game)
        update.msg = f"un órgano {self.color.translate()['male']}"
        return update
Exemplo n.º 14
0
    def apply(self, caller: "Player", game: "Game") -> GameUpdate:
        if not game.discarding:
            raise GameLogicException(
                "El jugador no está en la fase de descarte")

        logger.info(f"{caller.name} stops discarding cards")

        game.discarding = False

        return GameUpdate(game, msg="un descarte")
Exemplo n.º 15
0
    def __init__(self, data) -> None:
        # Slot de la mano con la carta que queremos jugar.
        self.slot = data.get("slot")
        # Todos los datos pasados por el usuario
        self.data = data
        # El jugador que usa la carta
        self.caller: "Player" = None

        if self.slot is None:
            raise GameLogicException("Slot vacío")
Exemplo n.º 16
0
    def get_action_data(self, action: "PlayCard", game: "Game") -> None:
        """
        Extraer la información común para las cartas simples y
        realizar las comprobaciones correspondientes.
        """

        # Jugador donde queremos colocar la carta (en su cuerpo).
        target_name = action.data.get("target")
        # Pila de órgano donde se va a colocar la carta (dentro de dicho cuerpo).
        organ_pile_slot = action.data.get("organ_pile")

        if None in (target_name, organ_pile_slot):
            raise GameLogicException("Parámetro vacío")

        self.target = game.get_unfinished_player(target_name)
        self.organ_pile = self.target.body.get_pile(organ_pile_slot)

        # Comprobamos si podemos colocar
        if not self.organ_pile.can_place(self):
            raise GameLogicException("No se puede colocar la carta ahí")
Exemplo n.º 17
0
    def run_action(self, caller: str, action: Action) -> List[GameUpdate]:
        """
        Llamado ante cualquier acción de un jugador en la partida. Devolverá el
        nuevo estado de la partida por cada jugador, o en caso de que ya hubiera
        terminado anteriormente o estuviera pausada, un error.

        Se terminará el turno automáticamente en caso de que no haya quedado el
        usuario en fase de descarte.
        """

        with self._turn_lock:
            if self.is_finished():
                raise GameLogicException("El juego ya ha terminado")

            if self.is_paused():
                raise GameLogicException("El juego está pausado")

            if self.players[self._turn].name != caller:
                raise GameLogicException("No es tu turno")

            player = self.get_unfinished_player(caller)
            try:
                # Importante el orden para que se inicialice
                update = action.apply(player, game=self)
            except GameLogicException as e:
                logger.info(f"Error running action: {e}")
                raise

            if not self.discarding and not self.is_finished():
                end_update = self._end_turn()
                update.merge_with(end_update)

            # Se reestablecen los turnos AFK del usuario que ha terminado
            # correctamente la partida.
            player.afk_turns = 0

            return update
Exemplo n.º 18
0
    def _ai_turn(self) -> GameUpdate:
        """
        Ejecuta un turno de la inteligencia artificial.
        """

        logger.info("AI turn starts")
        attempts = AI.next_action(self.turn_player(), game=self)

        # Se iteran todos los intentos, cada uno con una lista de acciones a
        # probar.
        for actions in attempts:
            success, update = self._ai_attempt(actions)
            if success:
                return update

        # La IA garantiza que siempre realizará una acción.
        raise GameLogicException("Unreachable: no attempts remaining for the IA")
Exemplo n.º 19
0
    def get_action_data(self, action: "PlayCard", game: "Game") -> None:
        """ """
        # Jugadores entre los que queremos
        player1 = action.data.get("target1")
        player2 = action.data.get("target2")

        # Pilas de los jugadores a intercambiar
        self.pile_slot1 = action.data.get("organ_pile1")
        self.pile_slot2 = action.data.get("organ_pile2")

        if None in (player1, player2, self.pile_slot1, self.pile_slot2):
            raise GameLogicException("Parámetro vacío")

        self.player1 = game.get_unfinished_player(player1)
        self.player2 = game.get_unfinished_player(player2)

        self.organ_pile1 = self.player1.body.get_pile(self.pile_slot1)
        self.organ_pile2 = self.player2.body.get_pile(self.pile_slot2)
Exemplo n.º 20
0
    def apply(self, caller: "Player", game: "Game") -> GameUpdate:
        logger.info(
            f"{caller.name} discards their card at position {self.position}")

        # Activa la fase de descarte
        game.discarding = True

        if len(caller.hand) == 0:
            raise GameLogicException("El jugador no tiene cartas")

        # Elimina la carta de la mano del jugador y la añade al principio del
        # mazo, como indican las reglas del juego.
        caller.remove_card(self.position, return_to=game.deck)

        # No hay mensaje: ya se mostrará al pasar de turno de forma condensada.
        update = GameUpdate(game)
        update.add(caller.name, {"hand": caller.hand})
        return update
Exemplo n.º 21
0
def next_action(player: "Player", game: "Game") -> ActionAttempts:
    """
    Punto principal de entrada que devuelve intentos a realizar por la IA.
    """

    # Prioridad de las acciones, como se indica en el comentario del módulo:
    actions_priority = [
        _action_special,
        _action_survive,
        _action_attack,
        _action_pass,
    ]
    for func in actions_priority:
        # Itera todos los intentos de esa acción
        attempts = func(player, game)
        for actions in attempts:
            yield actions

    # Nunca deberia llegarse aquí, puesto que la acción de pasar siempre
    # funcionará.
    raise GameLogicException("Unreachable: no possible action found for the IA")
Exemplo n.º 22
0
    def apply(self, action: "PlayCard", game: "Game") -> GameUpdate:
        self.get_action_data(action, game)

        if action.caller == self.target:
            raise GameLogicException(
                "No puedes intercambiar tu cuerpo contigo mismo")

        logger.info("medical-error played")

        update = GameUpdate(game)

        # Intercambiamos los cuerpos de ambos jugadores
        action.caller.body, self.target.body = self.target.body, action.caller.body
        # Añadimos los dos cuerpos al GameUpdate
        update.repeat({
            "bodies": {
                self.target.name: self.target.body.piles,
                action.caller.name: action.caller.body.piles,
            },
        })

        update.msg = f"un Error Médico sobre {self.target.name}"
        return update
Exemplo n.º 23
0
    def apply(self, action: "PlayCard", game: "Game") -> GameUpdate:
        self.get_action_data(action, game)

        if self.target.name != action.caller.name:
            raise GameLogicException(
                "No puedes colocar una medicina en otro cuerpo")

        logger.info(
            f"{self.color}-colored medicine played over {self.target.name}")

        # Comprobamos si hay que destruir un virus
        if self.organ_pile.is_infected():
            # Lo añadimos para que vuelva a la baraja
            self.organ_pile.add_modifier(self)
            # Destruimos el virus
            self.organ_pile.pop_modifiers(return_to=game.deck)
        else:
            # Se proteje o se inmuniza el órgano (se añade la vacuna a los
            # modificadores)
            self.organ_pile.add_modifier(self)

        update = self.piles_update(game)
        update.msg = f"una medicina {self.color.translate()['female']}"
        return update
Exemplo n.º 24
0
 def get_card(self, slot: int) -> Card:
     try:
         return self.hand[slot]
     except IndexError:
         raise GameLogicException("Slot no existente en la mano del jugador")
Exemplo n.º 25
0
    def apply(self, action: "PlayCard", game: "Game") -> GameUpdate:
        logger.info("infection played")

        # Diccionario: color -> lista de pilas con virus de ese color
        virus = dict()
        for color in Color:
            virus[color] = []

        # Listamos los virus que tiene en el cuerpo accediendo en orden
        # aleatorio a las pilas.
        for pile in random.sample(action.caller.body.piles, 4):
            if pile.is_infected():
                color = pile.get_top_color()
                virus[color].append(pile)

        if all(map(lambda x: len(x) == 0, virus.values())):
            raise GameLogicException("No tienes virus disponibles")

        # Lista de pilas libres de todos los jugadores
        candidates = []

        # Accederemos a los jugadores en orden aleatorio
        unfinished = game.get_unfinished_players()
        random.shuffle(unfinished)
        for player in unfinished:
            # Eliminamos al caller de la iteración
            if player == action.caller:
                continue

            # Añadimos las pilas libres a la lista de candidatas
            candidates.extend(
                list(filter(lambda p: p.is_free(), player.body.piles)))

        if len(candidates) == 0:
            raise GameLogicException(
                "No hay nadie que pueda recibir tus virus")

        # Aplicamos un orden aleatorio también a las pilas candidatas
        for candidate_pile in random.sample(candidates, len(candidates)):
            color = candidate_pile.get_top_color()

            # Asignamos el primer virus de ese color y lo quitamos de los
            # posibles.

            if len(virus[color]) == 0:
                # Si no hay virus de ese color -> comprobamos si hay virus
                # multicolor
                if len(virus[Color.All]) > 0:
                    color = Color.All
                else:  # No tenemos opción
                    continue

            pile = virus[color].pop()
            # Eliminamos el virus del cuerpo del caller
            pile.pop_modifiers()
            # Lo colocamos en la pila candidata
            candidate_pile.add_modifier(Virus(color=color))

        # Por simplificar, devolvemos el cuerpo de todos los jugadores
        update = GameUpdate(game)
        for player in game.players:
            body_update = GameUpdate(game)
            body_update.repeat({"bodies": {player.name: player.body.piles}})
            update.merge_with(body_update)

        update.msg = "un Contagio"
        return update
Exemplo n.º 26
0
    def get_player(self, user_name: str) -> Player:
        for player in self.players:
            if player.name == user_name:
                return player

        raise GameLogicException("El jugador no está en la partida")