Card Jitsu Fire handlers

This commit is contained in:
Ben 2020-06-26 03:28:50 +01:00
parent a44d505bb8
commit 1ac22ca1d4

View File

@ -1,15 +1,651 @@
from houdini import IWaddle
import asyncio
import itertools
import math
import random
from collections import Counter
from dataclasses import dataclass, field
from typing import List, Union
from houdini import IWaddle, handlers
from houdini.data.ninja import Card
from houdini.handlers import XTPacket
from houdini.handlers.games.ninja.card import ninja_stamps_earned
from houdini.penguin import Penguin
@dataclass
class FireNinja:
penguin: Penguin
seat_id: int
deck: List[Card] = field(default_factory=list)
chosen: Union[int, None] = None
energy: int = 6
energy_won: int = 0
state: int = 0
ready: bool = False
class CardJitsuFireLogic(IWaddle):
room_id = 997
Board = ['b', 's', 'w', 'f', 'c',
's', 'f', 'w', 'b', 's',
'w', 'f', 'c', 'w', 's', 'f']
RuleSet = {'f': 's', 'w': 'f', 's': 'w'}
DefaultTiles = [0, 8, 4, 12]
AutoBattleTimeout = 22
ItemAwards = [6025, 4120, 2013, 1086, 3032]
StampAwards = {2: 256, 4: 262}
RankSpeed = 1
def __init__(self, waddle):
super().__init__(waddle)
self.ninjas = [FireNinja(
penguin=p,
seat_id=seat_id
) for seat_id, p in enumerate(waddle.penguins)]
self.battle_ninjas = []
self.podium = [0]*self.seats
self.finish_position = self.seats
self.tile_ids = CardJitsuFireLogic.DefaultTiles[:self.seats].copy()
self.current_player = None
self.ninja_circle = itertools.cycle(self.ninjas)
self.spin_amount = 0
self.move_clockwise = 0
self.move_anticlockwise = 0
self.tab_id = None
self.current_battle_state = 0
self.current_battle_element = 0
self.current_battle_type = 'bt'
self.choose_card_timeout = None
self.choose_board_timeout = None
self.next()
self.deal()
self.spin()
self.set_choose_board_timeout()
def next(self):
self.current_player = next(self.ninja_circle)
while self.current_player not in self.ninjas:
self.current_player = next(self.ninja_circle)
def deal(self):
for ninja in self.ninjas:
penguin = ninja.penguin
deck = Counter(penguin.server.cards[card.card_id]
for card in penguin.cards.values()
for _ in range(card.quantity + card.member_quantity))
dealt = Counter(ninja.deck)
can_deal = list((deck - dealt).elements())
if ninja.chosen is None:
ninja.deck = random.sample(can_deal, 5)
else:
ninja.deck[ninja.chosen] = random.choice(can_deal)
def spin(self):
self.tab_id = None
self.spin_amount = random.randrange(1, 7)
player_position = self.tile_ids[self.current_player.seat_id]
self.move_clockwise = (player_position + self.spin_amount) % 16
self.move_anticlockwise = (player_position - self.spin_amount) % 16
def set_choose_board_timeout(self):
loop = asyncio.get_running_loop()
self.choose_board_timeout = loop.call_later(
CardJitsuFireLogic.AutoBattleTimeout,
lambda: asyncio.ensure_future(self.auto_choose_board())
)
def set_choose_card_timeout(self):
loop = asyncio.get_running_loop()
self.choose_card_timeout = loop.call_later(
CardJitsuFireLogic.AutoBattleTimeout,
lambda: asyncio.ensure_future(self.auto_choose_card())
)
async def auto_choose_board(self):
self.tab_id = 1
await self.current_player.penguin.send_xt('zm', 'tb')
move = self.move_anticlockwise if random.randint(0, 1) else self.move_clockwise
await self.choose_board(move, is_auto_play=True)
async def auto_choose_card(self):
for ninja in self.battle_ninjas:
if ninja.chosen is None:
playable_cards = self.get_playable_cards(ninja)
await self.choose_card(ninja, random.choice(playable_cards))
def get_ninja_by_seat_id(self, seat_id):
return next(ninja for ninja in self.ninjas if ninja.seat_id == seat_id)
def get_ninja_by_penguin(self, penguin):
return next(ninja for ninja in self.ninjas if ninja.penguin == penguin)
def get_ninjas_by_tile_id(self, tile_id):
return [ninja for ninja in self.ninjas if self.tile_ids[ninja.seat_id] == tile_id]
def is_card_playable(self, ninja, card_id):
if self.current_battle_type == 'bt':
if ninja.deck[card_id].element == self.current_battle_element:
return True
return all(card.element != self.current_battle_element for card in ninja.deck)
return True
def get_playable_cards(self, ninja):
return [card_id for card_id in range(len(ninja.deck)) if self.is_card_playable(ninja, card_id)]
def resolve_battle(self):
if self.current_battle_type == 'be':
first_ninja, second_ninja = self.battle_ninjas[:2]
first_card = first_ninja.deck[first_ninja.chosen]
second_card = second_ninja.deck[second_ninja.chosen]
battle_result = CardJitsuFireLogic.get_battle_result(first_card, second_card)
if battle_result == 0:
first_ninja.state, second_ninja.state = (4, 1)
first_ninja.energy += 1
second_ninja.energy -= 1
first_ninja.energy_won += 1
elif battle_result == 1:
first_ninja.state, second_ninja.state = (1, 4)
first_ninja.energy -= 1
second_ninja.energy += 1
second_ninja.energy_won += 1
else:
first_ninja.state, second_ninja.state = (2, 2)
self.current_battle_element = first_card.element if battle_result == 0 else second_card.element
elif self.current_battle_type == 'bt':
battle_card_values = [n.deck[n.chosen].value
if n.deck[n.chosen].element == self.current_battle_element
else 0 for n in self.battle_ninjas]
highest_battle_card = max(battle_card_values)
is_battle_tie = battle_card_values.count(highest_battle_card) >= 2
for ninja in self.battle_ninjas:
card = ninja.deck[ninja.chosen]
if card.element != self.current_battle_element:
ninja.state = 1
ninja.energy -= 1
elif is_battle_tie and card.value == highest_battle_card:
ninja.state = 2
elif card.value == highest_battle_card:
ninja.state = 3
else:
ninja.state = 1
ninja.energy -= 1
async def click_spinner(self, tab_id):
if self.current_battle_state == 0 and self.tab_id is None and 0 <= tab_id <= 6:
self.tab_id = tab_id
await self.send_xt('zm', 'is', self.current_player.seat_id, tab_id)
async def ready_ninja(self, ninja):
if self.current_battle_state == 0:
ninja.ready = True
if all(n.ready for n in self.ninjas):
self.next()
self.spin()
self.deal()
for n in self.ninjas:
n.chosen = None
n.ready = False
deck = ','.join(str(card.id) for card in n.deck)
spin = f'{self.spin_amount},{self.move_clockwise},{self.move_anticlockwise}'
await n.penguin.send_xt('zm', 'nt', self.current_player.seat_id, spin, deck)
self.set_choose_board_timeout()
async def choose_board(self, tile_id, is_auto_play=False):
if self.current_battle_state == 0 and tile_id == self.move_clockwise or tile_id == self.move_anticlockwise:
if not is_auto_play or self.current_battle_state == 0:
self.tile_ids[self.current_player.seat_id] = tile_id
tile_ids = ','.join(map(str, self.tile_ids))
element = CardJitsuFireLogic.Board[tile_id]
await self.send_xt('zm', 'ub', self.current_player.seat_id, tile_ids, self.tab_id)
self.current_battle_type = 'bt'
self.battle_ninjas = self.ninjas
else:
tile_id = self.tile_ids[self.current_player.seat_id]
element = CardJitsuFireLogic.Board[tile_id]
ninjas_on_tile = self.get_ninjas_by_tile_id(tile_id)
if len(ninjas_on_tile) > 1:
self.current_battle_state = 2
self.current_battle_element = element
if is_auto_play:
ninjas_on_tile.remove(self.current_player)
opponent = random.choice(ninjas_on_tile)
await self.choose_opponent(opponent.seat_id)
else:
if len(ninjas_on_tile) > 2:
battle_seat_ids = ','.join(str(ninja.seat_id) for ninja in ninjas_on_tile)
await self.send_xt('zm', 'co', 0, battle_seat_ids)
else:
opponent = next(n for n in ninjas_on_tile if n != self.current_player)
await self.choose_opponent(opponent.seat_id)
elif element in CardJitsuFireLogic.Board[1:4]:
battle_seat_ids = ','.join(str(ninja.seat_id) for ninja in self.ninjas)
self.current_battle_element = element
self.current_battle_state = 3
await self.send_xt('zm', 'sb', self.current_battle_type, battle_seat_ids, element)
self.choose_board_timeout.cancel()
self.set_choose_card_timeout()
elif element == 'c':
if is_auto_play:
battle_seat_ids = ','.join(str(ninja.seat_id) for ninja in self.ninjas)
self.current_battle_element = random.choice(CardJitsuFireLogic.Board[1:4])
self.current_battle_state = 3
await self.send_xt('zm', 'sb', self.current_battle_type, battle_seat_ids,
self.current_battle_element)
self.choose_board_timeout.cancel()
self.set_choose_card_timeout()
else:
self.current_battle_state = 1
await self.send_xt('zm', 'ct')
elif element == 'b':
self.current_battle_element = element
self.current_battle_state = 2
if is_auto_play:
opponent = next(n for n in self.ninjas if n != self.current_player)
await self.choose_opponent(opponent.seat_id)
else:
if len(self.ninjas) > 2:
battle_seat_ids = ','.join(str(ninja.seat_id) for ninja in self.ninjas)
await self.send_xt('zm', 'co', 0, battle_seat_ids)
else:
opponent = next(n for n in self.ninjas if n != self.current_player)
await self.choose_opponent(opponent.seat_id)
async def choose_card(self, ninja, card_id):
if ninja.chosen is None and self.is_card_playable(ninja, card_id):
ninja.chosen = card_id
await self.send_xt('zm', 'ic', ninja.seat_id, f=lambda p: p != ninja.penguin)
if all(n.chosen is not None for n in self.battle_ninjas):
self.choose_card_timeout.cancel()
self.resolve_battle()
for n in self.ninjas:
if n.energy == 0:
self.podium[n.seat_id] = self.finish_position
self.finish_position -= 1
if self.finish_position == 1:
winner_seat_id = self.podium.index(0)
self.podium[winner_seat_id] = 1
battle_seat_ids = ','.join(str(n.seat_id) for n in self.battle_ninjas)
battle_card_ids = ','.join(str(n.deck[n.chosen].id) for n in self.battle_ninjas)
battle_energy = ','.join(str(n.energy) for n in self.battle_ninjas)
battle_states = ','.join(str(n.state) for n in self.battle_ninjas)
finish_positions = ','.join(str(position) for position in self.podium)
battle = f'{self.current_battle_type},{self.current_battle_element}'
for n in self.ninjas.copy():
deck = ','.join(str(card.id) for card in n.deck)
await n.penguin.send_xt(
'zm', 'rb',
battle_seat_ids,
battle_card_ids,
battle_energy,
battle_states,
battle,
deck,
finish_positions
)
if n.energy == 0 or self.finish_position == 1:
player_finish_position = self.podium[n.seat_id]
await end_game_stamps(n, player_finish_position)
await fire_ninja_progress(n.penguin, self.podium[n.seat_id])
finish_positions = ','.join(str(max(position, 1)) for position in self.podium)
await n.penguin.send_xt('zm', 'zo', finish_positions)
await self.remove_penguin(n.penguin, quit_early=False)
self.current_battle_state = 0
async def choose_opponent(self, seat_id):
if self.current_battle_state == 2 and seat_id != self.current_player.seat_id:
opponent = self.get_ninja_by_seat_id(seat_id)
self.battle_ninjas = [self.current_player, opponent]
self.current_battle_type = 'be'
self.current_battle_state = 3
battle_seat_ids = ','.join(str(ninja.seat_id) for ninja in self.battle_ninjas)
await self.send_xt('zm', 'sb', self.current_battle_type, battle_seat_ids, self.current_battle_element)
self.choose_board_timeout.cancel()
self.set_choose_card_timeout()
async def choose_trump(self, element):
if self.current_battle_state == 1 and element in CardJitsuFireLogic.Board[1:4]:
self.current_battle_element = element
self.current_battle_state = 3
battle_seat_ids = ','.join(str(ninja.seat_id) for ninja in self.ninjas)
await self.send_xt('zm', 'sb', self.current_battle_type, battle_seat_ids, self.current_battle_element)
self.choose_board_timeout.cancel()
self.set_choose_card_timeout()
async def remove_penguin(self, p, quit_early=True):
await super().remove_penguin(p)
ninja = self.get_ninja_by_penguin(p)
self.ninjas.remove(ninja)
if quit_early:
self.podium[ninja.seat_id] = self.finish_position
self.finish_position -= 1
await self.send_xt('zm', 'cz', ninja.seat_id)
if len(self.ninjas) == 1:
opponent = next(n for n in self.ninjas if n != ninja)
await opponent.penguin.send_xt('cz')
await self.remove_penguin(opponent.penguin)
elif len(self.ninjas) >= 2:
if ninja == self.current_player and 0 <= self.current_battle_state <= 2:
self.choose_board_timeout.cancel()
await self.auto_choose_board()
elif ninja.chosen is None and self.current_battle_state == 3:
playable_cards = self.get_playable_cards(ninja)
await self.choose_card(ninja, random.choice(playable_cards))
@classmethod
def get_battle_result(cls, first_card, second_card):
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.value > first_card.value:
return 1
return -1
class FireMatLogic(CardJitsuFireLogic):
RankSpeed = 0.5
class FireSenseiLogic(CardJitsuFireLogic):
def __init__(self, waddle):
super().__init__(waddle)
sensei = FireNinja(
penguin=waddle.penguins[0],
seat_id=1
)
self.ninjas.append(sensei)
self.tile_ids.append(CardJitsuFireLogic.DefaultTiles[1])
async def auto_choose_card(self):
ninja = self.ninjas[0]
playable_cards = self.get_playable_cards(ninja)
await self.choose_card(ninja, random.choice(playable_cards))
async def ready_ninja(self, ninja):
if self.current_battle_state == 0:
self.next()
self.spin()
self.deal()
ninja.chosen = None
deck = ','.join(str(card.id) for card in ninja.deck)
spin = f'{self.spin_amount},{self.move_clockwise},{self.move_anticlockwise}'
await ninja.penguin.send_xt('zm', 'nt', self.current_player.seat_id, spin, deck)
if self.current_player == ninja:
self.set_choose_board_timeout()
else:
await self.auto_choose_board()
async def choose_card(self, ninja, card_id):
if ninja.chosen is None and self.is_card_playable(ninja, card_id):
self.choose_card_timeout.cancel()
sensei = self.ninjas[1]
ninja.chosen = card_id
card = ninja.deck[ninja.chosen]
can_beat_sensei = ninja.penguin.fire_ninja_rank >= 4
sensei_card = random.choice(list(ninja.penguin.server.cards.values())) \
if can_beat_sensei else self.get_win_card(card)
sensei.chosen = 0
sensei.deck = [sensei_card]
await ninja.penguin.send_xt('zm', 'ic', 1)
self.resolve_battle()
if ninja.energy == 0:
self.podium = [2, 1]
if sensei.energy == 0:
self.podium = [1, 2]
battle_seat_ids = ','.join(str(n.seat_id) for n in self.battle_ninjas)
battle_card_ids = ','.join(str(n.deck[n.chosen].id) for n in self.battle_ninjas)
battle_energy = ','.join(str(n.energy) for n in self.battle_ninjas)
battle_states = ','.join(str(n.state) for n in self.battle_ninjas)
finish_positions = ','.join(str(position) for position in self.podium)
deck = ','.join(str(card.id) for card in ninja.deck)
battle = f'{self.current_battle_type},{self.current_battle_element}'
await ninja.penguin.send_xt(
'zm', 'rb',
battle_seat_ids,
battle_card_ids,
battle_energy,
battle_states,
battle,
deck,
finish_positions
)
player_finish_position = self.podium[ninja.seat_id]
if player_finish_position > 0:
await end_game_stamps(ninja, player_finish_position)
await fire_ninja_progress(ninja.penguin, player_finish_position)
await ninja.penguin.send_xt('zm', 'zo', finish_positions)
await self.remove_penguin(ninja.penguin, quit_early=False)
self.current_battle_state = 0
def get_win_card(self, card):
cards_to_pick = self.penguins[0].server.cards
cards_iter = cards_to_pick.values()
cards_end_to_end = itertools.chain(cards_iter, cards_iter)
start_position = random.randint(0, len(cards_to_pick))
cards_random_start = itertools.islice(cards_end_to_end, start_position, start_position+len(cards_to_pick))
if self.current_battle_type == 'bt':
return next(card_check for card_check in cards_random_start
if card_check.element == self.current_battle_element
and card_check.value >= card.value)
elif self.current_battle_type == 'be':
return next(card_check for card_check in cards_random_start
if FireSenseiLogic.beats_card(card_check, card))
async def remove_penguin(self, p, quit_early=True):
ninja = self.get_ninja_by_penguin(p)
await super().remove_penguin(p, False)
if ninja == self.current_player and 0 <= self.current_battle_state <= 2:
self.choose_board_timeout.cancel()
elif ninja.chosen is None and self.current_battle_state == 3:
self.choose_card_timeout.cancel()
@classmethod
def beats_card(cls, card_check, card_play):
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 fire_ninja_rank_up(p, ranks=1):
if p.fire_ninja_rank + ranks > len(CardJitsuFireLogic.ItemAwards):
return False
for rank in range(p.fire_ninja_rank, p.fire_ninja_rank+ranks):
await p.add_inventory(p.server.items[CardJitsuFireLogic.ItemAwards[rank]], notify=False)
if rank in CardJitsuFireLogic.StampAwards:
await p.add_stamp(p.server.stamps[CardJitsuFireLogic.StampAwards[rank]])
await p.update(
fire_ninja_rank=p.fire_ninja_rank + ranks,
fire_ninja_progress=p.fire_ninja_progress % 100
).apply()
return True
async def fire_ninja_progress(p, finish_position=1):
if p.fire_ninja_rank < 4:
speed = type(p.waddle).RankSpeed
points = math.floor((25 / (p.fire_ninja_rank+1) / finish_position) * speed)
await p.update(fire_ninja_progress=p.fire_ninja_progress+points).apply()
elif p.fire_ninja_rank == 4 and finish_position == 1:
await p.update(fire_ninja_progress=100).apply()
if p.fire_ninja_progress >= 100:
await fire_ninja_rank_up(p)
await p.send_xt('zm', 'nr', 0, p.fire_ninja_rank)
async def end_game_stamps(ninja, finish_position):
if finish_position == 1:
await ninja.penguin.update(fire_matches_won=ninja.penguin.fire_matches_won + 1).apply()
if ninja.penguin.fire_matches_won >= 10:
await ninja.penguin.add_stamp(ninja.penguin.server.stamps[252])
if ninja.penguin.fire_matches_won >= 50:
await ninja.penguin.add_stamp(ninja.penguin.server.stamps[268])
if ninja.energy >= 6:
await ninja.penguin.add_stamp(ninja.penguin.server.stamps[260])
if ninja.energy_won >= 1:
await ninja.penguin.add_stamp(ninja.penguin.server.stamps[254])
if ninja.energy_won >= 3:
await ninja.penguin.add_stamp(ninja.penguin.server.stamps[266])
@handlers.handler(XTPacket('gz', ext='z'))
@handlers.waddle(CardJitsuFireLogic, FireMatLogic)
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)
nicknames = ','.join(penguin.safe_name for penguin in p.waddle.penguins)
colors = ','.join(str(penguin.color) for penguin in p.waddle.penguins)
energy = ','.join(str(ninja.energy) for ninja in p.waddle.ninjas)
tile_ids = ','.join(map(str, p.waddle.tile_ids))
deck = ','.join(str(card.id) for card in p.waddle.ninjas[seat_id].deck)
spin = f'{p.waddle.spin_amount},{p.waddle.move_clockwise},{p.waddle.move_anticlockwise}'
ninja_ranks = ','.join(str(penguin.fire_ninja_rank) for penguin in p.waddle.penguins)
await p.send_xt('sz', 0, nicknames, colors, energy, tile_ids, deck, spin, ninja_ranks)
@handlers.handler(XTPacket('gz', ext='z'))
@handlers.waddle(FireSenseiLogic)
async def handle_get_sensei_game(p):
await p.send_xt('gz', p.waddle.seats, len(p.waddle.penguins))
await p.send_xt('jz', 0)
nicknames = f'{p.safe_name},Sensei'
colors = f'{p.color},-1'
energy = ','.join(str(ninja.energy) for ninja in p.waddle.ninjas)
tile_ids = ','.join(map(str, p.waddle.tile_ids))
deck = ','.join(str(card.id) for card in p.waddle.ninjas[0].deck)
spin = f'{p.waddle.spin_amount},{p.waddle.move_clockwise},{p.waddle.move_anticlockwise}'
ninja_ranks = ','.join(str(penguin.fire_ninja_rank) for penguin in p.waddle.penguins)
await p.send_xt('sz', 0, nicknames, colors, energy, tile_ids, deck, spin, ninja_ranks)
@handlers.handler(XTPacket('zm', ext='z'), match=['is'])
@handlers.waddle(CardJitsuFireLogic, FireMatLogic, FireSenseiLogic)
async def handle_info_click_spinner(p, *, tab_id: int):
seat_id = p.waddle.get_seat_id(p)
if seat_id == p.waddle.current_player.seat_id:
await p.waddle.click_spinner(tab_id)
@handlers.handler(XTPacket('zm', ext='z'), match=['cb'])
@handlers.waddle(CardJitsuFireLogic, FireMatLogic, FireSenseiLogic)
async def handle_choose_board(p, *, tile_id: int):
seat_id = p.waddle.get_seat_id(p)
if seat_id == p.waddle.current_player.seat_id:
await p.waddle.choose_board(tile_id)
@handlers.handler(XTPacket('zm', ext='z'), match=['co'])
@handlers.waddle(CardJitsuFireLogic, FireMatLogic, FireSenseiLogic)
async def handle_choose_opponent(p, *, opponent_seat_id: int):
seat_id = p.waddle.get_seat_id(p)
if seat_id == p.waddle.current_player.seat_id:
await p.waddle.choose_opponent(opponent_seat_id)
@handlers.handler(XTPacket('zm', ext='z'), match=['ct'])
@handlers.waddle(CardJitsuFireLogic, FireMatLogic, FireSenseiLogic)
async def handle_choose_trump(p, *, element: str):
seat_id = p.waddle.get_seat_id(p)
if seat_id == p.waddle.current_player.seat_id:
await p.waddle.choose_trump(element)
@handlers.handler(XTPacket('zm', ext='z'), match=['cc'])
@handlers.waddle(CardJitsuFireLogic, FireMatLogic, FireSenseiLogic)
async def handle_choose_card(p, *, card_id: int):
ninja = p.waddle.get_ninja_by_penguin(p)
await p.waddle.choose_card(ninja, card_id)
@handlers.handler(XTPacket('zm', ext='z'), match=['ir'])
@handlers.waddle(CardJitsuFireLogic, FireMatLogic, FireSenseiLogic)
async def handle_info_ready_sync(p):
ninja = p.waddle.get_ninja_by_penguin(p)
await p.waddle.ready_ninja(ninja)
@handlers.handler(XTPacket('lz', ext='z'))
@handlers.player_in_room(CardJitsuFireLogic.room_id)
async def handle_leave_game(p):
await ninja_stamps_earned(p)