示例#1
0
def disconnect():
    # La sesión del usuario se limpia al reconectarse, aunque existen casos que
    # necesitan limpieza.
    logger.info(f"Ending session with user {session['user'].name}")

    # Puede estar buscando una partida pública
    if session["user"] in MM.users_waiting:
        MM.stop_waiting(session["user"])

    # NOTE: si el usuario está en una partida privada se cuenta como una
    # desconexión temporal y se podrá volver a unir.
    remove_from_public()
示例#2
0
def play_discard(card):
    """
    Descarta la carta indicada de la mano del usuario. Esta acción se puede
    repetir varias veces hasta que se pase el turno de forma automática o con
    ``play_pass``.

    La carta a descartar se puede indicar con el índice de ésta en su mano.

    Requiere que el usuario esté en una partida y que esté empezada o se
    devolverá un :ref:`error <errores>`.

    :param card: La carta del usuario a descartar
    :type card: int

    :return: Un mensaje :ref:`msg_game_update` para cada jugador.

        Si el usuario no está en una partida se devolverá un :ref:`error
        <errores>`. También se devolverá uno en caso de que el jugador no tenga
        cartas restantes para descartar o si la carta indicada no existe en la
        mano del jugador.
    """

    if type(card) is not int:
        return {"error": "Tipo incorrecto para la carta"}

    match = MM.get_match(session["game"])
    name = session["user"].name

    try:
        match.run_action(name, Discard(card))
    except GameLogicException as e:
        return {"error": str(e)}
示例#3
0
def stop_searching():
    """
    Parar de buscar una partida pública organizada por el servidor.

    :return: Devuelve un mensaje de tipo :ref:`msg_stop_searching` si se ha
        podido cancelar la búsqueda.

        Si se produce cualquier error (por ejemplo, que el usuario no esté
        buscando partida) se devolverá un :ref:`error <errores>`.
    """

    if session["user"] in MM.users_waiting:
        MM.stop_waiting(session["user"])
        emit("stop_searching")
    else:
        return {"error": "No estás buscando partida"}
示例#4
0
def start_game():
    """
    Puesta en marcha de una partida privada.

    :return: Un broadcast de :ref:`msg_start_game`.

        Requiere que el usuario esté en una partida y que sea el líder o se
        devolverá un :ref:`error <errores>`. También deben haber al menos 2
        jugadores en total esperando la partida para empezarla.
    """

    game = session["game"]
    match = MM.get_match(game)

    # Comprobamos si el que empieza la partida es el creador
    try:
        if match.owner != session["user"]:
            return {"error": "Debes ser el lider para empezar partida"}
    except (AttributeError, TypeError):
        # Si la partida devuelta por el MM es una pública, no tiene sentido
        # empezar la partida (ya se encarga el manager)
        return {"error": "La partida no es privada"}

    if len(match.users) < 2:
        return {"error": "Se necesitan al menos dos jugadores"}

    match.start()
    def check_card_interactions(self, card_order, expected_pile_states):
        """
        Se prueba la secuencia de cartas card_order en la pila 0 del jugador 0
        y se comprueba que la interacción sea la deseada (expected_pile_states).
        """
        clients, code = self.create_game()

        # Primero se tendrá el game_update inicial
        received = clients[0].get_received()
        _, args = self.get_msg_in_received(received, "game_update", json=True)
        self.assertNotIn("error", args)

        game = MM.get_match(code)._game

        # Usaremos al cliente 0 como target
        target = game.players[0].name

        for (i, card) in enumerate(card_order):
            # Cambiamos el turno según la carta para cumplir las restricciones
            # de no colocar medicina en el cuerpo de otros, etc.
            if isinstance(card, Organ) or isinstance(card, Medicine):
                game._turn = 0
            elif isinstance(card, Virus):
                game._turn = 1

            # Obtenemos el cliente al que le toca
            turn_player = game.players[game._turn]
            turn_client = clients[self.player_names.index(turn_player.name)]

            turn_player.hand[0] = card

            # Ignoramos los mensajes anteriores en un cliente cualquiera
            _ = clients[5].get_received()

            # Colocamos la carta en el jugador target. Las cartas se colocarán
            # en el orden de testing_hand y se espera que resulten en la pila 0
            # == expected_pile_states[i]
            callback_args = turn_client.emit(
                "play_card",
                {
                    "slot": 0,
                    "target": target,
                    "organ_pile": 0,
                },
                callback=True,
            )
            self.assertNotIn("error", callback_args)

            # Recibimos en un cliente cualquiera
            received = clients[5].get_received()
            _, args = self.get_msg_in_received(received,
                                               "game_update",
                                               json=True)
            self.assertNotIn("error", args)

            self.assertIn("bodies", args)
            self.assertEqual(
                args["bodies"][target][0],  # Miramos la pila 0 del target
                expected_pile_states[i],
            )
示例#6
0
def pause_game(paused):
    """
    Pausa o reanuda una partida privada.

    :param paused: Pausar la partida
    :type paused: ``bool``

    Requiere que el usuario esté en una partida privada y que esté empezada o se
    devolverá un :ref:`error <errores>`.

    :return: Un mensaje :ref:`msg_game_update` para cada jugador.

        Si no se cumplen los requisitos comentados anteriormente, se devolverá
        un :ref:`error <errores>`.
    """

    if paused is None or type(paused) != bool:
        return {"error": "Parámetro incorrecto"}

    # TODO: Si la pausa pasa del tiempo límite comentado anteriormente, la
    # partida se reanuda automáticamente, y el jugador que no esté se le
    # irán pasando los turnos.

    match = MM.get_match(session["game"])
    name = session["user"].name

    if not isinstance(match, PrivateMatch):
        return {"error": "No estás en una partida privada"}

    try:
        match.set_paused(paused, paused_by=name)
    except GameLogicException as e:
        return {"error": str(e)}
示例#7
0
def search_game():
    """
    Unión a una partida pública organizada por el servidor.

    :return: El cliente no recibirá respuesta hasta que el servidor haya
        encontrado oponentes contra los que jugar.

        Una vez encontrada una partida, hará un broadcast de
        :ref:`msg_found_game`.

        Si ya está buscando partida, se devolverá un :ref:`error <errores>`.
    """

    if session.get("game") is not None:
        return {"error": "El usuario ya está en una partida privada"}

    try:
        MM.wait_for_game(session["user"])
    except GameLogicException as e:
        return {"error": str(e)}
示例#8
0
def remove_from_public():
    code = session.get("game")
    if code is None:
        return

    match = MM.get_match(code)
    if match is None:
        return

    if not isinstance(match, PublicMatch):
        return

    leave()
示例#9
0
    def test_auto_pass_discard(self):
        """
        Comprueba que si un usuario se olvida de pasar el turno se le descarta
        una carta y roba. Si el jugador está descartando esto no se debería
        hacer.

        Como no se puede asumir que las cartas robadas sean diferentes a las
        descartadas, no se puede saber si se ha descartado una adicional al
        final del turno, y por tanto es imposible hacer un test unitario de
        esto.
        """

        self.set_turn_timeout(0.5)
        clients, code = self.create_game()

        # Se tiene que acceder a la partida directamente para tener la mano del
        # jugador actual.

        match = MM.get_match(code)
        game = match._game
        current_player = game.turn_player()
        start_hand = [id(card) for card in current_player.hand]

        # Caso base (cambia la mano al final):
        client = self.get_current_turn_client(clients)
        client.get_received()  # Limpia recibidos

        # Espera el tiempo de partida y comprueba que la mano no sea la misma,
        # comparando las instancias y no los datos de las cartas.
        self.wait_turn_timeout()
        end_hand = [id(card) for card in current_player.hand]
        self.assertNotEqual(start_hand, end_hand)

        # Caso de descarte (no cambia la mano al final):
        client = self.get_current_turn_client(clients)
        client.get_received()  # Limpia recibidos

        # Descarte de 2 cartas
        self.discard_ok(client)
        self.discard_ok(client)

        current_player = game.turn_player()
        start_hand = [id(card) for card in current_player.hand]
        # Espera a fin de turno y se asegura que la última carta que quedaba (en
        # la posición 0) no ha sido modificada, es decir, que únicamente se han
        # descartado las dos cartas indicadas en el proceso de descarte.
        # esperado.
        self.wait_turn_timeout()
        end_hand = [id(card) for card in current_player.hand]
        self.assertEqual(start_hand[0], end_hand[0])
示例#10
0
        def wrapper(*args, **kwargs):
            game = session.get("game")
            if not game:
                return {"error": "No estás en una partida"}

            match = MM.get_match(game)
            if not match:
                # FIXME: creo que esto no podría llegar a pasar, porque para que
                # la partida no exista, el usuario ha tenido que salir. Sino, se
                # retiene la partida.
                return {"error": "La partida no existe"}

            if started and not match.is_started():
                return {"error": "La partida no ha comenzado"}

            return f(*args, **kwargs)
示例#11
0
    def place_card(self, target_body, card, place_in_self=False):
        """
        Se prueba a colocar la carta `card` en el cuerpo de otro jugador
        distinto (si `place_in_self` es True, el otro jugador es el mismo que
        usa la carta). El cuerpo inicial del jugador donde se va a colocar la
        carta será `target_body`.
        """
        clients, code = self.create_game()

        # Primero se tendrá el game_update inicial
        received = clients[0].get_received()
        _, args = self.get_msg_in_received(received, "game_update", json=True)
        self.assertNotIn("error", args)

        from gatovid.api.game.match import MM

        game = MM.get_match(code)._game

        turn_name = args["current_turn"]
        turn_client = clients[self.player_names.index(args["current_turn"])]
        turn_player = next(filter(lambda p: p.name == turn_name, game.players))
        if place_in_self:
            other_player = turn_player
        else:
            other_player = next(
                filter(lambda p: p.name != turn_name, game.players))

        turn_player.hand[0] = card
        other_player.body = target_body

        # Ignoramos los eventos anteriores
        _ = turn_client.get_received()

        # Intentamos colocar la carta en el jugador
        callback_args = turn_client.emit(
            "play_card",
            {
                "slot": 0,
                "target": other_player.name,
                "organ_pile": 0,
            },
            callback=True,
        )

        received = turn_client.get_received()

        return callback_args, received, turn_player
示例#12
0
def play_card(data):
    """
    .. warning:: Este endpoint está en construcción aún.

    Juega una carta de su mano.

    Requiere que el usuario esté en una partida y que esté empezada o se
    devolverá un :ref:`error <errores>`.

    :param data: Diccionario con datos sobre la carta a jugar. Todas las cartas
        deberán tener un campo ``slot`` (``int``) con el slot de la carta jugada
        en la mano del jugador. Adicionalmente, algunas cartas en concreto
        tendrán parámetros específicos:

        * Órgano, Virus y Medicina:
            * ``target`` (``str``): nombre del jugador destino
            * ``organ_pile`` (``int``): número de pila del jugador destino
        * Tratamientos:
            * Transplante:
                * ``target1`` (``str``): nombre de uno de los dos
                  jugadores entre los que se intercambiarán los
                  órganos.
                * ``target2`` (``str``): nombre del otro jugador.
                * ``organ_pile1`` (``str``): pila del jugador 1.
                * ``organ_pile2`` (``str``): pila del jugador 2.
            * Ladrón de Órganos:
                * ``target`` (``str``): nombre del jugador destino.
                * ``organ_pile`` (``int``): pila del jugador destino.
            * Error médico:
                * ``target`` (``str``): nombre del jugador destino

    :type data: Dict

    :return: Un mensaje :ref:`msg_game_update` para cada jugador.

        Si el usuario no está en una partida se devolverá un :ref:`error
        <errores>`.
    """

    match = MM.get_match(session["game"])
    name = session["user"].name

    try:
        match.run_action(name, PlayCard(data))
    except GameLogicException as e:
        return {"error": str(e)}
示例#13
0
def create_game():
    """
    Creación y unión automática a una partida privada.

    :return: Un mensaje de tipo :ref:`msg_create_game`.

        Si está buscando una partida pública, se devolverá un :ref:`error
        <errores>`.
    """

    if session.get("game") is not None:
        return {"error": "El usuario ya está en una partida privada"}

    try:
        game_code = MM.create_private_game(owner=session["user"])
    except GameLogicException as e:
        return {"error": str(e)}
    join(game_code)
    emit("create_game", {"code": game_code})
示例#14
0
    def test_fuzzy_ai(self):
        """
        Fuzzy Test que prueba varios segundos en los que se usa Inteligencia
        Artificial en la partida para asegurar su correcto funcionamiento por lo
        general.

        Esto se puede conseguir de forma más eficiente si se consigue modificar
        el juego de forma que se pueda componer únicamente de IA.
        """

        self.set_turn_timeout(0.5)
        clients, code = self.create_game()

        match = MM.get_match(code)
        game = match._game
        for player in game.players:
            player.is_ai = True

        # Ejecución de varios turnos
        time.sleep(5)
示例#15
0
def play_pass():
    """
    .. warning:: Este endpoint está en construcción aún.

    Pasa el turno del usuario.

    Requiere que el usuario esté en una partida y que esté empezada o se
    devolverá un :ref:`error <errores>`.

    :return: Un mensaje :ref:`msg_game_update` para cada jugador.

        Si el usuario no está en una partida se devolverá un :ref:`error
        <errores>`.
    """

    match = MM.get_match(session["game"])
    name = session["user"].name

    try:
        match.run_action(name, Pass())
    except GameLogicException as e:
        return {"error": str(e)}
示例#16
0
    def test_return_to_deck(self):
        """
        Se comprueba que las cartas se devuelven a la baraja.

        En este test también se comprueba que las cartas usadas no dan problemas
        por tener parámetros incorrectos.
        """
        TOTAL_CARDS = 30

        # Generamos una baraja custom antes de que empiece la partida y se
        # repartan las cartas.
        custom_deck = [
            Virus(color=Color.Red),
            Virus(color=Color.Red),
            Medicine(color=Color.Red),
            LatexGlove(),
        ]
        # Rellenamos las restantes con órganos. NOTE: no hacen falta 68 cartas
        # solo para 2 jugadores.
        for i in range(TOTAL_CARDS - 4):
            custom_deck.append(Organ(color=Color.Red))

        self.set_custom_deck(custom_deck)

        def try_invalid(client, slot, pile_slot, target) -> None:
            payload = {
                "slot": slot,
                "organ_pile": pile_slot,
                "target": target,
            }
            items = payload.items()

            # Faltan de 1 a 3 campos
            for length in range(3):
                for payload in permutations(items, length):
                    callback_args = client.emit("play_card",
                                                dict(payload),
                                                callback=True)
                    self.assertIn("error", callback_args)

            # Tipos inválidos
            callback_args = client.emit("play_card", {"target": True},
                                        callback=True)
            self.assertIn("error", callback_args)

        def try_use(slot, pile_cond, search_in, target) -> bool:
            pile_slot = None

            for (p_slot, pile) in enumerate(player.body.piles):
                if pile_cond(pile):
                    pile_slot = p_slot
                    break

            if pile_slot is not None:
                # Primero intenta las opciones inválidas
                try_invalid(client, slot, pile_slot, target)

                callback_args = client.emit(
                    "play_card",
                    {
                        "slot": slot,
                        "organ_pile": pile_slot,
                        "target": target,
                    },
                    callback=True,
                )
                self.assertNotIn("error", callback_args)

            return pile_slot is not None

        clients, code = self.create_game(players=2)
        game = MM.get_match(code)._game
        game._turn = 0

        def total_cards() -> int:
            count = len(game.deck)
            for player in game.players:
                count += len(player.hand)
                for pile in player.body.piles:
                    if pile.is_empty():
                        continue
                    count += 1 + len(pile.modifiers)
            return count

        # Ignoramos todos los mensajes anteriores
        for client in clients:
            _ = client.get_received()

        clients_order = list(
            map(lambda p: self.player_names.index(p.name), game.players))

        for i in range(100):
            # Evitamos problemas con los saltos de turno
            p = game._turn

            self.assertEqual(total_cards(), TOTAL_CARDS)

            which_client = clients_order[p]

            client = clients[which_client]
            player = game.players[p]
            other_player = game.players[(p + 1) % 2]

            card_types = list(map(lambda c: c.card_type, player.hand))

            if "treatment" in card_types:
                slot = card_types.index("treatment")
                callback_args = client.emit("play_card", {"slot": slot},
                                            callback=True)
                self.assertNotIn("error", callback_args)
                continue

            if "organ" in card_types:
                slot = card_types.index("organ")
                if player.body.organ_unique(player.hand[slot]):
                    if try_use(
                            slot=slot,
                            search_in=player.body.piles,
                            pile_cond=lambda p: p.is_empty(),
                            target=player.name,
                    ):
                        continue

            if "virus" in card_types:
                slot = card_types.index("virus")
                if try_use(
                        slot=slot,
                        search_in=other_player.body.piles,
                        pile_cond=lambda p: not p.is_empty(),
                        target=other_player.name,
                ):
                    continue

            if "medicine" in card_types:
                slot = card_types.index("medicine")
                if try_use(
                        slot=slot,
                        search_in=player.body.piles,
                        pile_cond=lambda p: not p.is_empty(),
                        target=player.name,
                ):
                    continue

            # Descartamos todas las cartas
            for i in reversed(range(3)):
                callback_args = client.emit("play_discard", i, callback=True)
                self.assertNotIn("error", callback_args)

            callback_args = client.emit("play_pass", callback=True)
            self.assertNotIn("error", callback_args)
示例#17
0
    def test_treatment_infection(self):
        """
        Se prueba a usar el tratamiento Contagio.
        """
        clients, code = self.create_game(players=3)

        # Primero se tendrá el game_update inicial
        received = clients[0].get_received()
        _, args = self.get_msg_in_received(received, "game_update", json=True)
        self.assertNotIn("error", args)

        game = MM.get_match(code)._game
        # Forzamos el turno al client 0
        game._turn = 0

        def organ(color: Color) -> OrganPile:
            return OrganPile.from_data(organ=Organ(color=color))

        def infected_organ(color: Color,
                           virus_color: Color = None) -> OrganPile:
            if virus_color is None:
                virus_color = color

            return OrganPile.from_data(
                organ=Organ(color=color),
                modifiers=[
                    Virus(color=virus_color),
                ],
            )

        bodies = [
            {
                "have": [
                    infected_organ(Color.Yellow),
                    infected_organ(Color.Red, virus_color=Color.All),
                    infected_organ(Color.Blue),
                    # El virus de este no se debería colocar en ningún sitio
                    infected_organ(Color.Green),
                ],
                "expected": [
                    organ(Color.Yellow),
                    organ(Color.Red),
                    organ(Color.Blue),
                    infected_organ(Color.Green),
                ],
            },
            {
                "have": [
                    # No se debería colocar en esta
                    infected_organ(Color.Green, virus_color=Color.All),
                    OrganPile(),
                    organ(Color.Blue),
                    # Se debería colocar el multicolor
                    organ(Color.Red),
                ],
                "expected": [
                    infected_organ(Color.Green, virus_color=Color.All),
                    OrganPile(),
                    infected_organ(Color.Blue),
                    infected_organ(Color.Red, virus_color=Color.All),
                ],
            },
            {
                "have": [
                    OrganPile(),
                    # No se debería colocar en este
                    OrganPile.from_data(
                        organ=Organ(color=Color.Green),
                        modifiers=[Medicine(color=Color.Green)],
                    ),
                    OrganPile(),
                    organ(Color.Yellow),
                ],
                "expected": [
                    OrganPile(),
                    OrganPile.from_data(
                        organ=Organ(color=Color.Green),
                        modifiers=[Medicine(color=Color.Green)],
                    ),
                    OrganPile(),
                    infected_organ(Color.Yellow),
                ],
            },
        ]

        clients_order = list(
            map(lambda p: self.player_names.index(p.name), game.players))

        # Para todos los clientes, inicializamos su cuerpo al cuerpo de pruebas
        # y le damos la carta de contagio al cliente 0.
        for (i, which_client) in enumerate(clients_order):
            client = clients[which_client]
            player = game.players[i]

            if which_client == 0:
                player.hand[0] = Infection()
            player.body = Body.from_data(piles=bodies[i]["have"])

        # Ignoramos los eventos anteriores con los clientes
        for client in clients:
            _ = client.get_received()

        # Usamos la carta desde el cliente 0
        callback_args = clients[0].emit("play_card", {"slot": 0},
                                        callback=True)
        self.assertNotIn("error", callback_args)

        # Comprobamos que todos los clientes reciben los cuerpos esperados.
        for (i, which_client) in enumerate(clients_order):
            client = clients[which_client]
            player = game.players[i]

            received = client.get_received()
            _, args = self.get_msg_in_received(received,
                                               "game_update",
                                               json=True)
            self.assertNotIn("error", args)

            self.assertIn("bodies", args)
            self.assertIn(player.name, args["bodies"])
            expected = list(
                map(
                    lambda b: asdict(b, dict_factory=asdict_factory_enums),
                    bodies[i]["expected"],
                ))
            self.assertEqual(args["bodies"][player.name], expected)
示例#18
0
    def test_treatment_organ_thief(self):
        """
        Se prueba a usar el tratamiento Ladrón de órganos.
        """
        clients, code = self.create_game()

        caller_name = GENERIC_USERS_NAME.format(0)
        target_name = GENERIC_USERS_NAME.format(1)

        game = MM.get_match(code)._game
        # Forzamos el turno al client 0
        game._turn = 0

        caller_player = game.players[game._turn]
        target_player = game.players[(game._turn + 1) % 2]

        caller_player.hand[0] = OrganThief()
        caller_player.body = Body.from_data(piles=[
            OrganPile(),
            OrganPile.from_data(organ=Organ(color=Color.Red)),
            OrganPile.from_data(
                organ=Organ(color=Color.Blue),
                modifiers=[
                    Virus(color=Color.Blue),
                ],
            ),
            OrganPile(),
        ])

        target_player.body = Body.from_data(piles=[
            OrganPile.from_data(organ=Organ(color=Color.Green)),
            OrganPile(),
            OrganPile.from_data(
                organ=Organ(color=Color.Yellow),
                modifiers=[
                    Virus(color=Color.Yellow),
                ],
            ),
            OrganPile(),
        ])
        # Guardamos en el cliente el cuerpo anterior
        clients[1].last_pile = asdict(target_player.body)["piles"][2]

        # Ignoramos los eventos anteriores con todos los clientes
        for client in clients:
            _ = client.get_received()

        # Usamos la carta desde el cliente 0
        callback_args = clients[0].emit(
            "play_card",
            {
                "slot": 0,
                "target": target_name,
                "organ_pile": 2,
            },
            callback=True,
        )
        self.assertNotIn("error", callback_args)

        # Comprobamos que todos los clientes reciben los cuerpos intercambiados.
        for client in clients:
            received = client.get_received()
            _, args = self.get_msg_in_received(received,
                                               "game_update",
                                               json=True)
            self.assertNotIn("error", args)

            self.assertIn("bodies", args)
            self.assertIn(caller_name, args["bodies"])
            self.assertIn(target_name, args["bodies"])
            self.assertEqual(args["bodies"][caller_name][0],
                             clients[1].last_pile)
            self.assertEqual(args["bodies"][target_name][2],
                             asdict(OrganPile()))
示例#19
0
    def test_treatment_medical_error(self):
        """
        Se prueba a usar el tratamiento Error Médico.
        """
        clients, code = self.create_game()

        caller_name = GENERIC_USERS_NAME.format(0)
        target_name = GENERIC_USERS_NAME.format(1)

        # Primero se tendrá el game_update inicial
        received = clients[0].get_received()
        _, args = self.get_msg_in_received(received, "game_update", json=True)
        self.assertNotIn("error", args)

        game = MM.get_match(code)._game
        # Forzamos el turno al client 0
        game._turn = 0

        caller_player = game.players[game._turn]
        caller_player.hand[0] = MedicalError()
        caller_player.body = Body.from_data(piles=[
            OrganPile(),
            OrganPile.from_data(organ=Organ(color=Color.Red)),
            OrganPile.from_data(
                organ=Organ(color=Color.Blue),
                modifiers=[
                    Virus(color=Color.Blue),
                ],
            ),
            OrganPile(),
        ])
        clients[0].last_body = asdict(caller_player.body)["piles"]

        # Ignoramos los eventos anteriores con el target
        received = clients[1].get_received()
        _, args = self.get_msg_in_received(received, "game_update", json=True)
        # Guardamos en el cliente el cuerpo anterior
        clients[1].last_body = asdict(Body())["piles"]

        # Ignoramos los eventos anteriores con el resto de los clientes
        for client in clients[2:]:
            _ = client.get_received()

        # Usamos la carta desde el cliente 0
        callback_args = clients[0].emit(
            "play_card",
            {
                "slot": 0,
                "target": target_name,
            },
            callback=True,
        )
        self.assertNotIn("error", callback_args)

        # Comprobamos que todos los clientes reciben los cuerpos intercambiados.
        for client in clients:
            received = client.get_received()
            _, args = self.get_msg_in_received(received,
                                               "game_update",
                                               json=True)
            self.assertNotIn("error", args)

            self.assertIn("bodies", args)
            self.assertIn(caller_name, args["bodies"])
            self.assertIn(target_name, args["bodies"])
            self.assertEqual(args["bodies"][caller_name], clients[1].last_body)
            self.assertEqual(args["bodies"][target_name], clients[0].last_body)
示例#20
0
    def test_treatment_latex_glove(self):
        """
        Se prueba a usar el tratamiento Guante de látex y se comprueba que el
        resto de jugadores han descartado las cartas, mientras que el jugador
        que lanza el tratamiento conserva su mano (excepto la carta de
        tratamiento).
        """
        # HACK: Establecemos siempre la misma semilla para evitar el caso en el
        # que el random genere una mano igual a la que se tenia anteriormente.
        random.seed(10)
        clients, code = self.create_game()

        # Primero se tendrá el game_update inicial
        received = clients[0].get_received()
        _, args = self.get_msg_in_received(received, "game_update", json=True)
        self.assertNotIn("error", args)

        game = MM.get_match(code)._game
        # Forzamos el turno al client 0
        game._turn = 0

        turn_player = game.players[game._turn]
        turn_player.hand[0] = LatexGlove()
        last_hand = list(map(asdict, turn_player.hand.copy()))

        # Ignoramos los eventos anteriores en el resto de jugadores y guardamos
        # la mano anterior de estos.
        for client in clients[1:]:
            received = client.get_received()
            _, args = self.get_msg_in_received(received,
                                               "game_update",
                                               json=True)
            # Guardamos en el cliente la mano anterior (para iterar facilmente)
            client.last_hand = args["hand"].copy()

        # Usamos la carta desde el cliente 0
        callback_args = clients[0].emit(
            "play_card",
            {
                "slot": 0,
            },
            callback=True,
        )
        self.assertNotIn("error", callback_args)

        for client in clients[1:]:
            # Comprobamos que al resto de jugadores les ha borrado la mano (se
            # habrán robado nuevas cartas al saltarles el turno).
            received = client.get_received()
            _, args = self.get_msg_in_received(received,
                                               "game_update",
                                               json=True)
            self.assertNotIn("error", args)

            self.assertIn("hand", args)
            self.assertNotEqual(args["hand"], client.last_hand)

        # Comprobamos que el cliente que la lanza conserva su mano
        received = clients[0].get_received()
        _, args = self.get_msg_in_received(received, "game_update", json=True)
        self.assertNotIn("error", args)

        self.assertIn("hand", args)
        self.assertEqual(args["hand"][0], last_hand[1])
        self.assertEqual(args["hand"][1], last_hand[2])
示例#21
0
    def test_player_leaderboard(self):
        """
        Se comprueba la recepción del leaderboard cuando todos los jugadores han
        acabado.
        """
        TOTAL_CARDS = 68

        # Generamos una baraja custom antes de que empiece la partida y se
        # repartan las cartas. Todas las cartas serán órganos para solo permitir
        # ganar la partida.
        custom_deck = []
        for i in range(int(TOTAL_CARDS / 4)):
            custom_deck.append(Organ(color=Color.Red))
            custom_deck.append(Organ(color=Color.Green))
            custom_deck.append(Organ(color=Color.Blue))
            custom_deck.append(Organ(color=Color.Yellow))

        self.set_custom_deck(custom_deck)

        def try_use(slot, pile_cond, search_in, target) -> bool:
            pile_slot = None

            for (p_slot, pile) in enumerate(player.body.piles):
                if pile_cond(pile):
                    pile_slot = p_slot
                    break

            if pile_slot is not None:
                callback_args = client.emit(
                    "play_card",
                    {
                        "slot": slot,
                        "organ_pile": pile_slot,
                        "target": target,
                    },
                    callback=True,
                )
                self.assertNotIn("error", callback_args)

            return pile_slot is not None

        clients, code = self.create_game()
        game = MM.get_match(code)._game
        game._turn = 0

        # Ignoramos todos los mensajes anteriores
        for client in clients:
            _ = client.get_received()

        clients_order = list(
            map(lambda p: self.player_names.index(p.name), game.players)
        )

        players_finished = []
        leaderboards_received = 0

        for i in range(100):
            # Evitamos problemas con los saltos de turno
            p = game._turn
            which_client = clients_order[p]
            client = clients[which_client]
            player = game.players[p]

            # Tratamos de colocar alguno de los órganos
            could_place = False
            for (slot, organ) in enumerate(player.hand):
                if player.body.organ_unique(player.hand[slot]):
                    if try_use(
                        slot=slot,
                        search_in=player.body.piles,
                        pile_cond=lambda p: p.is_empty(),
                        target=player.name,
                    ):
                        could_place = True
                        break

            # Si no hemos podido colocar un órgano, descartamos toda la mano
            if not could_place:
                # Descartamos todas las cartas
                for i in reversed(range(3)):
                    callback_args = client.emit("play_discard", i, callback=True)
                    self.assertNotIn("error", callback_args)

                callback_args = client.emit("play_pass", callback=True)
                self.assertNotIn("error", callback_args)

            # Obtenemos el game_update
            received = clients[0].get_received()
            _, args = self.get_msg_in_received(received, "game_update", json=True)

            if args.get("leaderboard") is not None:
                # Si hemos recibido el leaderboard, es porque el jugador actual
                # ha acabado.
                players_finished.append(player.name)
                leaderboards_received += 1
                # Debería estar su nombre en la clasificación.
                self.assertIn(player.name, args["leaderboard"])

                # Si hemos contado 5 jugadores finalizados, deberíamos haber
                # recibido también el finished y el playtime_mins.
                if len(players_finished) == 5:
                    self.assertIn("finished", args)
                    self.assertIn("playtime_mins", args)

                    self.assertEqual(args["finished"], True)
                    expected_leaderboard = dict()
                    for player in game.players:
                        if player.name not in players_finished:
                            players_finished.append(player.name)
                    for (pos, player) in enumerate(players_finished):
                        pos = pos + 1
                        expected_leaderboard[player] = {
                            "position": pos,
                            "coins": 10 * (6 - pos),
                        }

                    self.assertEqual(args["leaderboard"], expected_leaderboard)
                    break

        # Comprobamos que realmente ha acabado la partida
        self.assertEqual(len(players_finished), 6)
        # Y que se han recibido todos los leaderboards esperados
        self.assertEqual(leaderboards_received, 5)
示例#22
0
def join(game_code):
    """
    Unión a una partida proporcionando un código de partida.

    Si se proporciona un código con minúsculas se intentará con el equivalente
    en mayúsculas.

    :param game_code: Código de partida
    :type game_code: ``str``

    :return: Si la partida es privada, un broadcast de :ref:`msg_users_waiting`.

        En cualquier caso, un broadcast de :ref:`msg_chat` indicando que el
        jugador se ha unido a la partida.

        Un jugador no se puede unir a una partida si ya está en otra o si ya
        está llena. En caso contrario se devolverá un :ref:`error <errores>`.
    """

    if session.get("game"):
        return {"error": "Ya estás en una partida"}

    if type(game_code) is not str:
        return {"error": "Tipo incorrecto para el código de partida"}

    # No importa si es minúsculas
    game_code = game_code.upper()

    # Restricciones para unirse a la sala
    match = MM.get_match(game_code)
    if match is None or len(match.users) > MAX_MATCH_USERS:
        return {"error": "La partida no existe o está llena"}

    # Guardamos la partida actual en la sesión
    session["game"] = game_code
    # Actualizamos los datos del usuario. NOTE: estos ya serán los
    # definitivos, no los puede modificar a mitad de partida.
    session["user"] = db.session.query(User).get(session["user"].email)
    db.session.commit()
    session["user"].sid = request.sid

    # Unimos al usuario a la sesión de socketio
    join_room(game_code)

    # Comprobar si es una reconexión y en ese caso indicarle que empiece
    # directamente.
    can_rejoin, initial_update = match.check_rejoin(session["user"])
    if can_rejoin:
        logger.info(f"User {session['user']} reconnecting to game")
        # Actualizamos el SID del usuario y el nombre si lo ha cambiado
        try:
            match.update_user(session["user"])
        except GameLogicException as e:
            # NOTE: No debería darse este error por la condición de
            # can_rejoin, pero por curarnos en salud.
            return {"error": str(e)}
        emit("start_game", room=session["user"].sid)
        emit("game_update", initial_update, room=session["user"].sid)
        return

    # Guardamos al jugador en la partida
    try:
        match.add_user(session["user"])
    except GameLogicException as e:
        return {"error": str(e)}

    if isinstance(match, PrivateMatch):
        # Si es una partida privada, informamos a todos los de la sala del nuevo
        # número de jugadores. El lider decidirá cuando iniciarla.
        emit("users_waiting", len(match.users), room=game_code)
    else:
        # Si es una partida pública, iniciamos la partida si ya están todos.
        # Si hay algún jugador que no se une a la partida, la partida acabará
        # empezando (si hay suficientes jugadores) debido al start_timer en
        # PublicMatch.
        if len(match.users) == match.num_users:
            match.start()

    emit(
        "chat",
        {
            "msg": session["user"].name + " se ha unido a la partida",
            "owner": "[GATOVID]",
        },
        room=game_code,
    )

    logger.info(f"User {session['user'].name} has joined the game {game_code}")
示例#23
0
def leave():
    """
    Salir de la partida actual.

    :return:
        * Si la partida no se borra porque quedan jugadores:

          - Un mensaje de tipo :ref:`msg_users_waiting`.
          - Un broadcast de :ref:`msg_chat` indicando que el jugador ha
            abandonado la partida.
          - Si se ha delegado el cargo de líder, el nuevo líder recibirá un
            mensaje de tipo :ref:`msg_game_owner`.
        * Si la partida se borra porque no quedan jugadores:

          - Si ya había terminado, un :ref:`error <errores>`.
          - Si no había terminado y se ha cancelado, un mensaje de tipo
            :ref:`msg_game_cancelled`.

        Requiere que el usuario esté en una partida o se devolverá un
        :ref:`error <errores>`.
    """

    # No hay partida de la que salir ni limpieza que hacer
    if session.get("game") is None:
        return {"error": "No hay ninguna partida de la que salir"}

    game_code = session["game"]
    leave_room(game_code)
    emit(
        "chat",
        {
            "msg": session["user"].name + " ha abandonado la partida",
            "owner": "[GATOVID]",
        },
        room=game_code,
    )
    del session["game"]

    match = MM.get_match(game_code)
    if match is None:
        return  # Limpieza de partidas ya canceladas, no hace falta seguir
    match.remove_user(session["user"])
    logger.info(f"User {session['user'].name} has left the game {game_code}")
    if len(match.users) == 0:
        match.end()
        MM.remove_match(game_code)
        return  # La partida ha acabado, no seguir

    emit("users_waiting", len(match.users), room=game_code)

    # Comprobar si hay que delegar el cargo de lider
    if isinstance(match, PrivateMatch):
        if match.owner == session["user"]:
            # Si él es el lider, delegamos el cargo de lider a otro jugador
            match.owner = match.users[0]

            emit(
                "chat",
                {
                    "msg": match.owner.name + " es el nuevo líder",
                    "owner": "[GATOVID]",
                },
                room=game_code,
            )

            # Mensaje solo al nuevo dueño de la sala
            emit("game_owner", room=match.owner.sid)