diff --git a/houdini.sql b/houdini.sql index b945ffe..91bf600 100644 --- a/houdini.sql +++ b/houdini.sql @@ -9835,7 +9835,7 @@ INSERT INTO room (id, name, member, max_users, required_item, game, blackhole, s (994, 'igloo_card', FALSE, 80, NULL, TRUE, FALSE, FALSE, NULL), (995, 'Water', FALSE, 80, NULL, TRUE, FALSE, FALSE, 34), (997, 'Fire', FALSE, 80, NULL, TRUE, FALSE, FALSE, 32), - (998, 'Card Jitsu', FALSE, 0, NULL, TRUE, FALSE, FALSE, 38), + (998, 'Card Jitsu', FALSE, 80, NULL, TRUE, FALSE, FALSE, 38), (999, 'Sled Race', TRUE, 80, NULL, TRUE, FALSE, FALSE, NULL), (324, 'Dojo Exterior Solo', FALSE, 80, NULL, FALSE, FALSE, FALSE, NULL), (326, 'Dojo Snow', FALSE, 80, NULL, FALSE, FALSE, FALSE, NULL), diff --git a/houdini/__init__.py b/houdini/__init__.py index 32d6afd..b853c3e 100644 --- a/houdini/__init__.py +++ b/houdini/__init__.py @@ -78,8 +78,8 @@ class IWaddle(ABC): self.penguins.remove(p) p.waddle = None - async def send_xt(self, *data): - for penguin in self.penguins: + async def send_xt(self, *data, f=None): + for penguin in filter(f, self.penguins): await penguin.send_xt(*data) def get_seat_id(self, p): diff --git a/houdini/data/ninja.py b/houdini/data/ninja.py index bfc43db..7616196 100644 --- a/houdini/data/ninja.py +++ b/houdini/data/ninja.py @@ -13,6 +13,9 @@ class Card(db.Model): value = db.Column(db.SmallInteger, nullable=False, server_default=db.text("2")) description = db.Column(db.String(255), nullable=False, server_default=db.text("''::character varying")) + def get_string(self): + return f'{self.id}|{self.element}|{self.value}|{self.color}|{self.power_id}' + class CardStarterDeck(db.Model): __tablename__ = 'card_starter_deck' diff --git a/houdini/handlers/games/match.py b/houdini/handlers/games/match.py index d4b9436..86884d3 100644 --- a/houdini/handlers/games/match.py +++ b/houdini/handlers/games/match.py @@ -5,9 +5,9 @@ from dataclasses import dataclass from houdini import handlers from houdini.data.room import RoomWaddle from houdini.handlers import XTPacket -from houdini.handlers.games.ninja.card import CardJitsuLogic -from houdini.handlers.games.ninja.fire import CardJitsuFireLogic -from houdini.handlers.games.ninja.water import CardJitsuWaterLogic +from houdini.handlers.games.ninja.card import SenseiLogic, CardJitsuLogic, CardJitsuMatLogic +from houdini.handlers.games.ninja.fire import FireSenseiLogic, CardJitsuFireLogic +from houdini.handlers.games.ninja.water import WaterSenseiLogic, CardJitsuWaterLogic from houdini.penguin import Penguin @@ -89,13 +89,15 @@ async def card_color_tick(matched): await mm.penguin.send_xt('tmm', len(matched), mm.tick, *nicknames) -def card_match(waddle_room_id, waddle_game): +def card_match(waddle_game): async def match(matched): nicknames = [f'{mm.penguin.safe_name}|{mm.penguin.color}' for mm in matched] host = matched[0].penguin - waddle_room = host.server.rooms[waddle_room_id] + waddle_room = host.server.rooms[waddle_game.room_id] rw = RoomWaddle(id=host.id, room_id=waddle_room.id, seats=len(matched), game=waddle_game, temporary=True) + rw.room = waddle_room + rw.logic = waddle_game waddle_room.waddles[rw.id] = rw for mm in matched: @@ -103,9 +105,9 @@ def card_match(waddle_room_id, waddle_game): return match -card_matched = card_match(CardJitsuLogic.room_id, 'card') -card_fire_matched = card_match(CardJitsuFireLogic.room_id, 'fire') -card_water_matched = card_match(CardJitsuWaterLogic.room_id, 'water') +card_matched = card_match(CardJitsuLogic) +card_fire_matched = card_match(CardJitsuFireLogic) +card_water_matched = card_match(CardJitsuWaterLogic) @handlers.boot @@ -142,11 +144,23 @@ async def handle_join_water_match_making(p): await p.send_xt('jmm', p.safe_name) +@handlers.handler(XTPacket('gwcj', ext='z')) +async def handle_get_card_jitsu_mat(p, waddle_id: int): + waddle_id = max(200, min(300, waddle_id)) + if p.room.igloo and waddle_id not in p.room.waddles: + rw = RoomWaddle(id=waddle_id, room_id=p.room.id, seats=2, game='card', temporary=True) + rw.room = p.room + rw.logic = CardJitsuMatLogic + p.room.waddles[rw.id] = rw + + @handlers.handler(XTPacket('jsen', ext='z')) @handlers.player_in_room(MatchMaking.SenseiRoom) async def handle_join_sensei_match(p): - waddle_room = p.server.rooms[CardJitsuLogic.room_id] + waddle_room = p.server.rooms[SenseiLogic.room_id] rw = RoomWaddle(id=p.id, room_id=waddle_room.id, seats=1, game='sensei', temporary=True) + rw.room = waddle_room + rw.logic = SenseiLogic waddle_room.waddles[rw.id] = rw await p.send_xt('scard', waddle_room.id, rw.id, 1, 0, f'{p.safe_name}|{p.color}') @@ -155,8 +169,10 @@ async def handle_join_sensei_match(p): @handlers.handler(XTPacket('jsen', ext='z')) @handlers.player_in_room(MatchMaking.SenseiFireRoom) async def handle_join_fire_sensei_match(p): - waddle_room = p.server.rooms[CardJitsuFireLogic.room_id] + waddle_room = p.server.rooms[FireSenseiLogic.room_id] rw = RoomWaddle(id=p.id, room_id=waddle_room.id, seats=1, game='firesensei', temporary=True) + rw.room = waddle_room + rw.logic = FireSenseiLogic waddle_room.waddles[rw.id] = rw await p.send_xt('scard', waddle_room.id, rw.id, 1, 0, f'{p.safe_name}|{p.color}') @@ -165,8 +181,10 @@ async def handle_join_fire_sensei_match(p): @handlers.handler(XTPacket('jsen', ext='z')) @handlers.player_in_room(MatchMaking.SenseiWaterRoom) async def handle_join_water_sensei_match(p): - waddle_room = p.server.rooms[CardJitsuWaterLogic.room_id] + waddle_room = p.server.rooms[WaterSenseiLogic.room_id] rw = RoomWaddle(id=p.id, room_id=waddle_room.id, seats=1, game='watersensei', temporary=True) + rw.room = waddle_room + rw.logic = WaterSenseiLogic waddle_room.waddles[rw.id] = rw await p.send_xt('scard', waddle_room.id, rw.id, 1, 0, f'{p.safe_name}|{p.color}') diff --git a/houdini/handlers/games/ninja/card.py b/houdini/handlers/games/ninja/card.py index 4636e5d..a15f32f 100644 --- a/houdini/handlers/games/ninja/card.py +++ b/houdini/handlers/games/ninja/card.py @@ -1,9 +1,13 @@ import itertools +import math +import random +from collections import Counter from dataclasses import dataclass from typing import Dict, List, Union -from houdini import IWaddle +from houdini import IWaddle, handlers from houdini.data.ninja import Card +from houdini.handlers import XTPacket from houdini.penguin import Penguin @@ -13,34 +17,47 @@ class Played: card: Card player: int opponent: int + value: int + element: str @dataclass class Ninja: penguin: Penguin - deck: Dict[int, Card] + deck: Dict[int, Played] bank: Dict[str, List[Played]] chosen: Union[Played, None] class CardJitsuLogic(IWaddle): room_id = 998 - rule_set = {'f': 's', 'w': 'f', 's': 'w'} - discard_elements = {4: 's', 5: 'w', 6: 'f'} - discard_colors = {7: 'r', 8: 'b', 9: 'g', 10: 'y', 11: 'o', 12: 'p'} - replacements = {16: ['w', 'f'], 17: ['s', 'w'], 18: ['f', 's']} - rank_speed = 1 + + RuleSet = {'f': 's', 'w': 'f', 's': 'w'} + DiscardElements = {4: 's', 5: 'w', 6: 'f'} + DiscardColors = {7: 'r', 8: 'b', 9: 'g', 10: 'y', 11: 'o', 12: 'p'} + Replacements = {16: ['w', 'f'], 17: ['s', 'w'], 18: ['f', 's']} + PowerLimiters = {13: 's', 14: 'f', 15: 'w'} + + OnPlayed = {1, 16, 17, 18} + CurrentRound = {4, 5, 6, 7, 8, 9, 10, 11, 12, 16, 17, 18} + AffectsOwnPlayer = {2} + + ItemAwards = [4025, 4026, 4027, 4028, 4029, 4030, 4031, 4032, 4033, 104] + PostcardAwards = {0: 177, 4: 178, 8: 179} + StampAwards = {0: 230, 4: 232, 8: 234, 9: 236} + StampGroupId = 38 + + RankSpeed = 1 def __init__(self, waddle): super().__init__(waddle) - self.ninjas = [ - Ninja( - penguin=p, - deck={}, - bank={'f': [], 'w': [], 's': []}, - chosen=None - ) for p in waddle.penguins] + self.ninjas = [Ninja( + penguin=p, + deck={}, + bank={'f': [], 'w': [], 's': []}, + chosen=None + ) for p in waddle.penguins] self.card_id = 1 self.powers = {} @@ -64,29 +81,28 @@ class CardJitsuLogic(IWaddle): return False, -1 def has_cards_to_play(self, seat_id): - power_limiters = {13: 's', 14: 'f', 15: 'w'} - for power_id, element in power_limiters.items(): + for power_id, element in CardJitsuLogic.PowerLimiters.items(): if power_id in self.powers: power_card = self.powers[power_id] if power_card.opponent == seat_id: opponent_deck = self.ninjas[power_card.opponent].deck for card_id, card in opponent_deck.items(): - if card.element != element: + if card.card.element != element: return True return False return True def discard_opponent_card(self, power_id, opponent_seat_id): opponent_cards = self.ninjas[opponent_seat_id].bank - if power_id in self.discard_elements: - element_to_discard = self.discard_elements[power_id] + if power_id in self.DiscardElements: + element_to_discard = self.DiscardElements[power_id] if len(opponent_cards[element_to_discard]) > 0: card_to_discard = self.ninjas[opponent_seat_id].bank[element_to_discard][-1] self.discards.append(card_to_discard.id) del self.ninjas[opponent_seat_id].bank[element_to_discard][-1] return True - if power_id in self.discard_colors: - color_to_discard = self.discard_colors[power_id] + if power_id in self.DiscardColors: + color_to_discard = self.DiscardColors[power_id] for element, cards in opponent_cards.items(): for index, card in enumerate(cards): if card.card.color == color_to_discard: @@ -98,76 +114,81 @@ class CardJitsuLogic(IWaddle): def adjust_card_values(self, first_card, second_card): for power_id, power_card in self.powers.items(): - if power_card.card.power_id == 1 and first_card.card.element == second_card.card.element: - first_card.card.value = 1 - second_card.card.value = 1 + if power_card.card.power_id == 1 and first_card.element == second_card.element: + swap_value = first_card.value + first_card.value = second_card.value + second_card.value = swap_value if power_card.card.power_id == 2: if power_card.player == 0: - first_card.card.value += 2 + first_card.value += 2 else: - second_card.card.value += 2 + second_card.value += 2 if power_card.card.power_id == 3: if power_card.player == 0: - second_card.card.value -= 2 + second_card.value -= 2 else: - first_card.card.value -= 2 - - def get_round_winner(self): - first_card, second_card = self.ninjas[0].chosen, self.ninjas[1].chosen - winner_seat_id = self.get_winner_seat_id(first_card, second_card) - self.adjust_card_values(first_card, second_card) + first_card.value -= 2 + def on_played_effects(self, first_card, second_card): for ninja_seat_id, ninja in enumerate(self.ninjas): played_card = ninja.chosen power_id = played_card.card.power_id if not power_id: continue - - opponent_seat_id = 1 if ninja_seat_id == 0 else 0 - - on_played = power_id in {1, 16, 17, 18} - on_scored = not on_played - current_round = power_id in {4, 5, 6, 7, 8, 9, 10, 11, 12, 16, 17, 18} + on_played = power_id in CardJitsuLogic.OnPlayed + current_round = power_id in CardJitsuLogic.CurrentRound next_round = not current_round - - if on_played and next_round: - self.powers[power_id] = played_card - if on_scored and ninja_seat_id == winner_seat_id: + if on_played: if next_round: self.powers[power_id] = played_card if current_round: - self.discard_opponent_card(power_id, opponent_seat_id) - if on_played and current_round: - self.replace_opponent_card(power_id, first_card, second_card, played_card.player) - winner_seat_id = self.get_winner_seat_id(first_card, second_card) + self.replace_cards(power_id, first_card, second_card) - self.ninjas[0].chosen = None - self.ninjas[1].chosen = None + def on_scored_effects(self, first_card, second_card): + winner_seat_id = self.get_winner_seat_id(first_card, second_card) + for ninja_seat_id, ninja in enumerate(self.ninjas): + power_id = ninja.chosen.card.power_id + if power_id: + opponent_seat_id = (ninja_seat_id + 1) % 2 + on_scored = power_id not in CardJitsuLogic.OnPlayed + current_round = power_id in CardJitsuLogic.CurrentRound + next_round = not current_round + if on_scored and ninja_seat_id == winner_seat_id: + if next_round: + self.powers[power_id] = ninja.chosen + if current_round: + self.discard_opponent_card(power_id, opponent_seat_id) + + def get_round_winner(self): + first_card, second_card = self.ninjas[0].chosen, self.ninjas[1].chosen + self.adjust_card_values(first_card, second_card) + self.powers = {} + self.on_played_effects(first_card, second_card) + self.on_scored_effects(first_card, second_card) + winner_seat_id = self.get_winner_seat_id(first_card, second_card) return winner_seat_id @classmethod - def replace_opponent_card(cls, power_id, first_card, second_card, seat_id): - for replace_power_id, replacement in cls.replacements.items(): - if power_id == replace_power_id: - original, replace = replacement - if seat_id == 1 and first_card.card.element == original: - first_card.card.element = replace - if seat_id == 0 and second_card.card.element == original: - second_card.card.element = replace + def replace_cards(cls, power_id, first_card, second_card): + original, replace = cls.Replacements[power_id] + if first_card.element == original: + first_card.element = replace + if second_card.element == original: + second_card.element = replace @classmethod def get_winner_seat_id(cls, first_card, second_card): - if first_card.card.element != second_card.card.element: - return 0 if cls.rule_set[first_card.card.element] == second_card.card.element else 1 - elif first_card.card.value > second_card.card.value: + if first_card.element != second_card.element: + return 0 if cls.RuleSet[first_card.element] == second_card.element else 1 + elif first_card.value > second_card.value: return 0 - elif second_card.card.value > first_card.card.value: + elif second_card.value > first_card.value: return 1 return -1 class CardJitsuMatLogic(CardJitsuLogic): - rank_speed = 0.5 + RankSpeed = 0.5 class SenseiLogic(CardJitsuLogic): @@ -175,27 +196,341 @@ class SenseiLogic(CardJitsuLogic): def __init__(self, waddle): super().__init__(waddle) - self.ninja = Ninja( - penguin=waddle.penguins[0], - deck={}, - bank={'f': [], 'w': [], 's': []}, - chosen=None - ) + self.ninjas.insert(0, Ninja( + penguin=waddle.penguins[0], + deck={}, + bank={'f': [], 'w': [], 's': []}, + chosen=None + )) self.sensei_move = {} self.colors = [] def get_win_card(self, card): self.colors = [] if len(self.colors) >= 6 else self.colors - for card_check in self.ninja.penguin.server.cards.values(): - if self.beats_card(card_check, card) and card_check.color not in self.colors: + for card_check in self.penguins[0].server.cards.values(): + if card_check.color not in self.colors and self.beats_card(card_check, card): self.colors.append(card_check.color) return card_check @classmethod def beats_card(cls, card_check, card_play): - if card_check.card.element != card_play.card.element: - return True if cls.rule_set[card_check.card.element] == card_play.card.element else False - elif card_check.card.value > card_play.card.value: + if card_check.element != card_play.element: + return True if cls.RuleSet[card_check.element] == card_play.element else False + elif card_check.value > card_play.value: return True return False + + +async def ninja_rank_up(p, ranks=1): + if p.ninja_rank + ranks > len(CardJitsuLogic.ItemAwards): + return False + for rank in range(p.ninja_rank, p.ninja_rank+ranks): + await p.add_inventory(p.server.items[CardJitsuLogic.ItemAwards[rank]], notify=False) + if rank in CardJitsuLogic.PostcardAwards: + await p.add_inbox(p.server.postcards[CardJitsuLogic.PostcardAwards[rank]]) + if rank in CardJitsuLogic.StampAwards: + await p.add_stamp(p.server.stamps[CardJitsuLogic.StampAwards[rank]]) + await p.update(ninja_rank=p.ninja_rank + ranks, ninja_progress=0).apply() + return True + + +async def ninja_progress(p, won=False): + if p.ninja_rank == 0: + await p.update(ninja_progress=100).apply() + elif p.ninja_rank < 9: + speed = type(p.waddle).RankSpeed + if not won: + speed *= 0.5 + points = math.floor((100 / p.ninja_rank) * speed) + await p.update(ninja_progress=p.ninja_progress+points).apply() + if p.ninja_progress >= 100: + await ninja_rank_up(p) + await p.send_xt('cza', p.ninja_rank) + + +async def ninja_stamps_earned(p): + game_stamps = [stamp for stamp in p.server.stamps.values() if stamp.group_id == p.room.stamp_group] + collected_stamps = [stamp for stamp in game_stamps if stamp.id in p.stamps] + total_collected_stamps = len(collected_stamps) + total_game_stamps = len(game_stamps) + collected_stamps_string = '|'.join(str(stamp.id) for stamp in collected_stamps) + await p.send_xt('cjsi', collected_stamps_string, total_collected_stamps, total_game_stamps) + + +async def ninja_win(winner, loser): + await ninja_progress(winner.penguin, won=True) + await ninja_progress(loser.penguin, won=False) + await ninja_stamps_earned(winner.penguin) + await ninja_stamps_earned(loser.penguin) + await winner.penguin.waddle.remove_penguin(winner.penguin) + await loser.penguin.waddle.remove_penguin(loser.penguin) + + +@handlers.handler(XTPacket('gz', ext='z')) +@handlers.waddle(CardJitsuLogic, CardJitsuMatLogic) +async def handle_get_game(p): + seat_id = p.waddle.get_seat_id(p) + await p.send_xt('gz', p.waddle.seats, len(p.waddle.penguins)) + await p.send_xt('jz', seat_id, p.safe_name, p.color, p.ninja_rank) + + +@handlers.handler(XTPacket('uz', ext='z')) +@handlers.waddle(CardJitsuLogic, CardJitsuMatLogic) +async def handle_update_game(p): + players = [f'{seat_id}|{player.safe_name}|{player.color}|{player.ninja_rank}' + for seat_id, player in enumerate(p.waddle.penguins)] + await p.send_xt('uz', *players) + await p.send_xt('sz') + + +@handlers.handler(XTPacket('lz', ext='z')) +@handlers.waddle(CardJitsuLogic, CardJitsuMatLogic, SenseiLogic) +async def handle_leave_game(p): + seat_id = p.waddle.get_seat_id(p) + await p.waddle.send_xt('cz', p.safe_name, f=lambda penguin: penguin is not p) + await p.waddle.send_xt('lz', seat_id, f=lambda penguin: penguin is not p) + await p.waddle.remove_penguin(p) + + +@handlers.handler(XTPacket('zm', ext='z'), match=['deal']) +@handlers.waddle(CardJitsuLogic, CardJitsuMatLogic) +async def handle_send_deal(p, action: str): + seat_id = p.waddle.get_seat_id(p) + opponent_seat_id = (seat_id + 1) % 2 + me = p.waddle.ninjas[seat_id] + + deck = Counter((card.card_id for card in p.cards.values() for _ in range(card.quantity + card.member_quantity))) + dealt = Counter((played.card.id for played in me.deck.values())) + undealt = random.sample(list((deck - dealt).elements()), 5 - len(me.deck)) + + strings = [] + for card_id in undealt: + card = p.server.cards[card_id] + me.deck[p.waddle.card_id] = Played( + id=p.waddle.card_id, + card=card, + player=seat_id, + opponent=opponent_seat_id, + value=card.value, + element=card.element + ) + strings.append(f'{p.waddle.card_id}|{card.get_string()}') + p.waddle.card_id += 1 + + await p.waddle.send_xt('zm', action, seat_id, *strings) + + +@handlers.handler(XTPacket('zm', ext='z'), match=['pick']) +@handlers.waddle(CardJitsuLogic, CardJitsuMatLogic) +async def handle_send_pick(p, action: str, card_id: int): + seat_id = p.waddle.get_seat_id(p) + opponent_seat_id = (seat_id + 1) % 2 + me = p.waddle.ninjas[seat_id] + opponent = p.waddle.ninjas[opponent_seat_id] + + if card_id not in me.deck or me.chosen is not None: + return + + me.chosen = me.deck[card_id] + del me.deck[card_id] + await p.waddle.send_xt('zm', action, seat_id, card_id) + + if me.chosen and opponent.chosen: + winner_seat_id = p.waddle.get_round_winner() + + if me.chosen.card.id == 256 or opponent.chosen.card.id == 256: + stamp = p.server.stamps[246] + await me.penguin.add_stamp(stamp, notify=True) + await opponent.penguin.add_stamp(stamp, notify=True) + + if me.chosen.card.power_id and me.chosen.card.power_id in CardJitsuLogic.OnPlayed: + await p.waddle.send_xt('zm', 'power', seat_id, opponent_seat_id, me.chosen.card.power_id) + if opponent.chosen.card.power_id and opponent.chosen.card.power_id in CardJitsuLogic.OnPlayed: + await p.waddle.send_xt('zm', 'power', opponent_seat_id, seat_id, opponent.chosen.card.power_id) + + if winner_seat_id != -1: + loser_seat_id = (winner_seat_id + 1) % 2 + winner = p.waddle.ninjas[winner_seat_id] + loser = p.waddle.ninjas[loser_seat_id] + winning_card = winner.chosen + winner.bank[winning_card.card.element].append(winning_card) + + if winning_card.card.power_id and winning_card.card.power_id not in CardJitsuLogic.OnPlayed: + affects_own_player = winning_card.card.power_id in CardJitsuLogic.AffectsOwnPlayer + sender, recipient = (winner_seat_id, winner_seat_id) if affects_own_player else (winner_seat_id, loser_seat_id) + await p.waddle.send_xt('zm', 'power', sender, recipient, winning_card.card.power_id, *p.waddle.discards) + p.waddle.discards = [] + + winning_cards, win_method = p.waddle.get_winning_cards(winner_seat_id) + if winning_cards: + await p.waddle.send_xt('czo', 0, winner_seat_id, *(card.id for card in winning_cards)) + + stamp = p.server.stamps[[244, 242][win_method]] + await winner.penguin.add_stamp(stamp, notify=True) + if all(not cards for cards in loser.bank.values()): + stamp = p.server.stamps[238] + await winner.penguin.add_stamp(stamp, notify=True) + if sum(1 for cards in winner.bank.values() for _ in cards) >= 9: + stamp = p.server.stamps[248] + await winner.penguin.add_stamp(stamp, notify=True) + + await winner.penguin.update(ninja_matches_won=winner.penguin.ninja_matches_won+1).apply() + if winner.penguin.ninja_matches_won == 25: + stamp = p.server.stamps[240] + await winner.penguin.add_stamp(stamp, notify=True) + + await ninja_win(winner, loser) + else: + for seat_id, ninja in enumerate(p.waddle.ninjas): + if not p.waddle.has_cards_to_play(seat_id): + winner_seat_id = (seat_id + 1) % 2 + winner = p.waddle.ninjas[winner_seat_id] + loser = p.waddle.ninjas[seat_id] + + await p.waddle.send_xt('czo', 0, winner_seat_id) + await ninja_win(winner, loser) + await me.penguin.send_xt('zm', 'judge', winner_seat_id) + await opponent.penguin.send_xt('zm', 'judge', winner_seat_id) + me.chosen = None + opponent.chosen = None + + +@handlers.handler(XTPacket('gz', ext='z')) +@handlers.waddle(SenseiLogic) +async def handle_get_sensei_game(p): + await p.send_xt('gz', 2, 2) + await p.send_xt('jz', 1, p.safe_name, p.color, p.ninja_rank) + + +@handlers.handler(XTPacket('uz', ext='z')) +@handlers.waddle(SenseiLogic) +async def handle_update_sensei_game(p): + await p.send_xt('uz', '0|Sensei|14|10', f'1|{p.safe_name}|{p.color}|{p.ninja_rank}') + await p.send_xt('sz') + + +@handlers.handler(XTPacket('zm', ext='z'), match=['deal']) +@handlers.waddle(SenseiLogic) +async def handle_send_sensei_deal(p, action: str): + can_beat_sensei = p.ninja_rank > len(CardJitsuLogic.ItemAwards) - 1 + sensei, me = p.waddle.ninjas + deck = Counter((card.card_id for card in p.cards.values() if + can_beat_sensei or p.server.cards[card.card_id].power_id == 0 + for _ in range(card.quantity + card.member_quantity))) + dealt = Counter((played.card.id for played in me.deck.values())) + undealt = random.sample(list((deck - dealt).elements()), 5 - len(me.deck)) + + strings = [] + sensei_strings = [] + for card_id in undealt: + card = p.server.cards[card_id] + me.deck[p.waddle.card_id] = Played( + id=p.waddle.card_id, + card=card, + player=1, + opponent=0, + value=card.value, + element=card.element + ) + strings.append(f'{p.waddle.card_id}|{card.get_string()}') + p.waddle.card_id += 1 + + sensei_card = random.choice(list(p.server.cards.values())) if can_beat_sensei else p.waddle.get_win_card(card) + + sensei.deck[p.waddle.card_id] = Played( + id=p.waddle.card_id, + card=sensei_card, + player=0, + opponent=1, + value=sensei_card.value, + element=sensei_card.element + ) + + sensei_strings.append(f'{p.waddle.card_id}|{sensei_card.get_string()}') + p.waddle.sensei_move[p.waddle.card_id - 1] = p.waddle.card_id + p.waddle.card_id += 1 + + await p.waddle.send_xt('zm', action, 0, *sensei_strings) + await p.waddle.send_xt('zm', action, 1, *strings) + + +@handlers.handler(XTPacket('zm', ext='z'), match=['pick']) +@handlers.waddle(SenseiLogic) +async def handle_send_sensei_pick(p, action: str, card_id: int): + sensei, me = p.waddle.ninjas + + if card_id not in me.deck or me.chosen is not None: + return + + me.chosen = me.deck[card_id] + sensei.chosen = sensei.deck[p.waddle.sensei_move[card_id]] + + del me.deck[card_id] + del sensei.deck[p.waddle.sensei_move[card_id]] + del p.waddle.sensei_move[card_id] + + await p.send_xt('zm', action, 0, sensei.chosen.id) + await p.send_xt('zm', action, 1, me.chosen.id) + + winner_seat_id = p.waddle.get_round_winner() + + if me.chosen.card.id == 256 or sensei.chosen.card.id == 256: + stamp = p.server.stamps[246] + await p.add_stamp(stamp, notify=True) + + if me.chosen.card.power_id and me.chosen.card.power_id in CardJitsuLogic.OnPlayed: + await p.send_xt('zm', 'power', 1, 0, me.chosen.card.power_id) + if sensei.chosen.card.power_id and sensei.chosen.card.power_id in CardJitsuLogic.OnPlayed: + await p.send_xt('zm', 'power', 0, 1, sensei.chosen.card.power_id) + + if winner_seat_id != -1: + loser_seat_id = (winner_seat_id + 1) % 2 + winner = sensei if winner_seat_id == 0 else me + winning_card = sensei.chosen if winner_seat_id == 0 else me.chosen + winner.bank[winning_card.card.element].append(winning_card) + + if winning_card.card.power_id and winning_card.card.power_id not in CardJitsuLogic.OnPlayed: + affects_own_player = winning_card.card.power_id in CardJitsuLogic.AffectsOwnPlayer + sender, recipient = (winner_seat_id, winner_seat_id) if affects_own_player else (winner_seat_id, loser_seat_id) + await p.send_xt('zm', 'power', sender, recipient, winning_card.card.power_id, *p.waddle.discards) + p.waddle.discards = [] + + winning_cards, win_method = p.waddle.get_winning_cards(winner_seat_id) + if winning_cards: + await p.waddle.send_xt('czo', 0, winner_seat_id, *(card.id for card in winning_cards)) + + if winner == me: + stamp = p.server.stamps[[244, 242][win_method]] + await p.add_stamp(stamp, notify=True) + if all(not cards for cards in sensei.bank.values()): + stamp = p.server.stamps[238] + await p.add_stamp(stamp, notify=True) + if sum(1 for cards in me.bank.values() for _ in cards) >= 9: + stamp = p.server.stamps[248] + await p.add_stamp(stamp, notify=True) + + await p.update(ninja_matches_won=p.ninja_matches_won + 1).apply() + if p.ninja_matches_won == 25: + stamp = p.server.stamps[240] + await p.add_stamp(stamp, notify=True) + + await ninja_stamps_earned(p) + can_rank_up = await ninja_rank_up(p) + if can_rank_up: + await p.send_xt('cza', p.ninja_rank) + else: + for seat_id, ninja in enumerate(p.waddle.ninjas): + if not p.waddle.has_cards_to_play(seat_id): + winner_seat_id = (seat_id + 1) % 2 + winner = p.waddle.ninjas[winner_seat_id] + if winner == me: + can_rank_up = await ninja_rank_up(p) + if can_rank_up: + await p.send_xt('cza', p.ninja_rank) + await p.waddle.send_xt('czo', 0, winner_seat_id) + await ninja_stamps_earned(p) + + await p.send_xt('zm', 'judge', winner_seat_id) + me.chosen = None + sensei.chosen = None diff --git a/houdini/handlers/play/navigation.py b/houdini/handlers/play/navigation.py index 9ee7f02..fc046c1 100644 --- a/houdini/handlers/play/navigation.py +++ b/houdini/handlers/play/navigation.py @@ -13,7 +13,7 @@ from houdini.data.room import PenguinBackyardRoom, PenguinIglooRoom, Room, RoomC from houdini.handlers import XTPacket from houdini.handlers.games.four import ConnectFourLogic from houdini.handlers.games.mancala import MancalaLogic -from houdini.handlers.games.ninja.card import CardJitsuLogic, SenseiLogic +from houdini.handlers.games.ninja.card import CardJitsuLogic, CardJitsuMatLogic, SenseiLogic from houdini.handlers.games.ninja.fire import CardJitsuFireLogic, FireSenseiLogic from houdini.handlers.games.ninja.water import CardJitsuWaterLogic, WaterSenseiLogic from houdini.handlers.games.sled import SledRacingLogic @@ -29,7 +29,8 @@ TableLogicMapping = { WaddleLogicMapping = { 'sled': SledRacingLogic, - 'card': CardJitsuLogic, + 'card': CardJitsuMatLogic, + 'match': CardJitsuLogic, 'sensei': SenseiLogic, 'water': CardJitsuWaterLogic,