diff --git a/variants.py b/variants.py index 087b5bc..a200a95 100644 --- a/variants.py +++ b/variants.py @@ -50,6 +50,24 @@ class Suit: self.colors = colors + def rank_touches(self, card_rank: int, clue_rank: int) -> bool: + match self.rank_clues: + case ClueBehaviour.none: + return False + case ClueBehaviour.default: + return card_rank == clue_rank + case ClueBehaviour.all: + return True + + def color_touches(self, clue_color: int) -> bool: + match self.color_clues: + case ClueBehaviour.none: + return False + case ClueBehaviour.default: + return clue_color in self.colors + case ClueBehaviour.all: + return True + def __str__(self): return self.name @@ -83,21 +101,21 @@ class Variant: self.name = name self.clue_starved = clue_starved self.throw_it_in_a_hole = throw_it_in_a_hole - self.alternating_clues = alternating_clues - self.synesthesia = synesthesia - self.chimneys = chimneys - self.funnels = funnels - self.no_color_clues = no_color_clues - self.no_rank_clues = no_rank_clues - self.empty_color_clues = empty_color_clues - self.empty_rank_clues = empty_rank_clues - self.odds_and_evens = odds_and_evens - self.up_or_down = up_or_down + self.alternating_clues = alternating_clues # TODO: has to be somehow supported by game itself + self.synesthesia = synesthesia # + self.chimneys = chimneys # + self.funnels = funnels # + self.no_color_clues = no_color_clues # + self.no_rank_clues = no_rank_clues # + self.empty_color_clues = empty_color_clues # + self.empty_rank_clues = empty_rank_clues # + self.odds_and_evens = odds_and_evens # + self.up_or_down = up_or_down # TODO: currently not supported self.critical_fours = critical_fours self.num_suits = len(suits) - self.special_rank = special_rank - self.special_rank_ranks = special_rank_ranks - self.special_rank_colors = special_rank_colors + self.special_rank = special_rank # + self.special_rank_ranks = special_rank_ranks # + self.special_rank_colors = special_rank_colors # self.special_deceptive = special_deceptive self.suits = suits @@ -123,8 +141,76 @@ class Variant: self.num_colors = len(self.colors) + def _preprocess_rank(self, value: int) -> List[int]: + if self.empty_rank_clues: + return [] + if self.chimneys: + return [rank for rank in self.ranks if rank >= value] + if self.funnels: + return [rank for rank in self.ranks if rank <= value] + if self.odds_and_evens: + return [rank for rank in self.ranks if (rank - value) % 2 == 0] + return [value] + + def _synesthesia_ranks(self, color_value: int) -> List[int]: + return [rank for rank in self.ranks if (rank - color_value) % len(self.colors) == 0] + def rank_touches(self, card: DeckCard, value: int): - pass + assert 0 <= card.suitIndex < self.num_suits,\ + f"Unexpected card {card}, suitIndex {card.suitIndex} out of bounds for {self.num_suits} suits." + assert not self.no_rank_clues, "Cluing rank not allowed in this variant." + assert value in self.ranks, f"Cluing value {value} not allowed in this variant." + + if self.special_rank is not None and self.special_rank_ranks != ClueBehaviour.default: + suit = self.suits[card.suitIndex] + match suit.rank_clues: + case ClueBehaviour.none: + return False + case ClueBehaviour.default: + match self.special_rank_ranks: + case ClueBehaviour.none: + return False + case ClueBehaviour.default: + assert False, "Programming error" + case ClueBehaviour.all: + return True + case ClueBehaviour.all: + return True + + ranks = self._preprocess_rank(value) + return any(self.suits[card.suitIndex].rank_touches(card.rank, rank) for rank in ranks) + + def color_touches(self, card: DeckCard, value: int): + assert 0 <= card.suitIndex < self.num_suits, \ + f"Unexpected card {card}, suitIndex {card.suitIndex} out of bounds for {self.num_suits} suits." + assert not self.no_color_clues, "Cluing color not allowed in this variant." + assert 0 <= value < len(self.colors), f"Color clue with index {value} does not exist in this variant." + + if self.special_rank is not None and self.special_rank_colors != ClueBehaviour.default: + suit = self.suits[card.suitIndex] + match suit.color_clues: + case ClueBehaviour.none: + return False + case ClueBehaviour.default: + match self.special_rank_colors: + case ClueBehaviour.none: + return False + case ClueBehaviour.default: + assert False, "Programming error" + case ClueBehaviour.all: + return True + case ClueBehaviour.all: + return True + + if self.empty_color_clues: + return False + if self.synesthesia and any(self.rank_touches(card, rank_val) for rank_val in self._synesthesia_ranks(value)): + return True + + suit = self.suits[card.suitIndex] + if suit.prism and value == ((card.rank - 1) % len(self.colors)): + return True + return suit.color_touches(self.colors[value]) @staticmethod def from_db(var_id):