385 lines
10 KiB
Python
385 lines
10 KiB
Python
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
from typing import Iterable, Dict, List, Union, FrozenSet, Set
|
|
|
|
from BaseClasses import CollectionState, ItemClassification
|
|
from .items import item_table
|
|
|
|
MISSING_ITEM = "THIS ITEM IS MISSING"
|
|
|
|
|
|
class StardewRule:
|
|
def __call__(self, state: CollectionState) -> bool:
|
|
raise NotImplementedError
|
|
|
|
def __or__(self, other) -> StardewRule:
|
|
if type(other) is Or:
|
|
return Or(self, *other.rules)
|
|
|
|
return Or(self, other)
|
|
|
|
def __and__(self, other) -> StardewRule:
|
|
if type(other) is And:
|
|
return And(other.rules.union({self}))
|
|
|
|
return And(self, other)
|
|
|
|
def get_difficulty(self):
|
|
raise NotImplementedError
|
|
|
|
def simplify(self) -> StardewRule:
|
|
return self
|
|
|
|
|
|
class True_(StardewRule): # noqa
|
|
|
|
def __new__(cls, _cache=[]): # noqa
|
|
# Only one single instance will be ever created.
|
|
if not _cache:
|
|
_cache.append(super(True_, cls).__new__(cls))
|
|
return _cache[0]
|
|
|
|
def __call__(self, state: CollectionState) -> bool:
|
|
return True
|
|
|
|
def __or__(self, other) -> StardewRule:
|
|
return self
|
|
|
|
def __and__(self, other) -> StardewRule:
|
|
return other
|
|
|
|
def __repr__(self):
|
|
return "True"
|
|
|
|
def get_difficulty(self):
|
|
return 0
|
|
|
|
|
|
class False_(StardewRule): # noqa
|
|
|
|
def __new__(cls, _cache=[]): # noqa
|
|
# Only one single instance will be ever created.
|
|
if not _cache:
|
|
_cache.append(super(False_, cls).__new__(cls))
|
|
return _cache[0]
|
|
|
|
def __call__(self, state: CollectionState) -> bool:
|
|
return False
|
|
|
|
def __or__(self, other) -> StardewRule:
|
|
return other
|
|
|
|
def __and__(self, other) -> StardewRule:
|
|
return self
|
|
|
|
def __repr__(self):
|
|
return "False"
|
|
|
|
def get_difficulty(self):
|
|
return 999999999
|
|
|
|
|
|
false_ = False_()
|
|
true_ = True_()
|
|
assert false_ is False_()
|
|
assert true_ is True_()
|
|
|
|
|
|
class Or(StardewRule):
|
|
rules: FrozenSet[StardewRule]
|
|
_simplified: bool
|
|
|
|
def __init__(self, rule: Union[StardewRule, Iterable[StardewRule]], *rules: StardewRule):
|
|
rules_list: Set[StardewRule]
|
|
|
|
if isinstance(rule, Iterable):
|
|
rules_list = {*rule}
|
|
else:
|
|
rules_list = {rule}
|
|
|
|
if rules is not None:
|
|
rules_list.update(rules)
|
|
|
|
assert rules_list, "Can't create a Or conditions without rules"
|
|
|
|
if any(type(rule) is Or for rule in rules_list):
|
|
new_rules: Set[StardewRule] = set()
|
|
for rule in rules_list:
|
|
if type(rule) is Or:
|
|
new_rules.update(rule.rules)
|
|
else:
|
|
new_rules.add(rule)
|
|
rules_list = new_rules
|
|
|
|
self.rules = frozenset(rules_list)
|
|
self._simplified = False
|
|
|
|
def __call__(self, state: CollectionState) -> bool:
|
|
return any(rule(state) for rule in self.rules)
|
|
|
|
def __repr__(self):
|
|
return f"({' | '.join(repr(rule) for rule in self.rules)})"
|
|
|
|
def __or__(self, other):
|
|
if other is true_:
|
|
return other
|
|
if other is false_:
|
|
return self
|
|
if type(other) is Or:
|
|
return Or(self.rules.union(other.rules))
|
|
|
|
return Or(self.rules.union({other}))
|
|
|
|
def __eq__(self, other):
|
|
return isinstance(other, self.__class__) and other.rules == self.rules
|
|
|
|
def __hash__(self):
|
|
return hash(self.rules)
|
|
|
|
def get_difficulty(self):
|
|
return min(rule.get_difficulty() for rule in self.rules)
|
|
|
|
def simplify(self) -> StardewRule:
|
|
if self._simplified:
|
|
return self
|
|
if true_ in self.rules:
|
|
return true_
|
|
|
|
simplified_rules = [simplified for simplified in {rule.simplify() for rule in self.rules}
|
|
if simplified is not false_]
|
|
|
|
if not simplified_rules:
|
|
return false_
|
|
|
|
if len(simplified_rules) == 1:
|
|
return simplified_rules[0]
|
|
|
|
self.rules = frozenset(simplified_rules)
|
|
self._simplified = True
|
|
return self
|
|
|
|
|
|
class And(StardewRule):
|
|
rules: FrozenSet[StardewRule]
|
|
_simplified: bool
|
|
|
|
def __init__(self, rule: Union[StardewRule, Iterable[StardewRule]], *rules: StardewRule):
|
|
rules_list: Set[StardewRule]
|
|
|
|
if isinstance(rule, Iterable):
|
|
rules_list = {*rule}
|
|
else:
|
|
rules_list = {rule}
|
|
|
|
if rules is not None:
|
|
rules_list.update(rules)
|
|
|
|
if not rules_list:
|
|
rules_list.add(true_)
|
|
elif any(type(rule) is And for rule in rules_list):
|
|
new_rules: Set[StardewRule] = set()
|
|
for rule in rules_list:
|
|
if type(rule) is And:
|
|
new_rules.update(rule.rules)
|
|
else:
|
|
new_rules.add(rule)
|
|
rules_list = new_rules
|
|
|
|
self.rules = frozenset(rules_list)
|
|
self._simplified = False
|
|
|
|
def __call__(self, state: CollectionState) -> bool:
|
|
return all(rule(state) for rule in self.rules)
|
|
|
|
def __repr__(self):
|
|
return f"({' & '.join(repr(rule) for rule in self.rules)})"
|
|
|
|
def __and__(self, other):
|
|
if other is true_:
|
|
return self
|
|
if other is false_:
|
|
return other
|
|
if type(other) is And:
|
|
return And(self.rules.union(other.rules))
|
|
|
|
return And(self.rules.union({other}))
|
|
|
|
def __eq__(self, other):
|
|
return isinstance(other, self.__class__) and other.rules == self.rules
|
|
|
|
def __hash__(self):
|
|
return hash(self.rules)
|
|
|
|
def get_difficulty(self):
|
|
return max(rule.get_difficulty() for rule in self.rules)
|
|
|
|
def simplify(self) -> StardewRule:
|
|
if self._simplified:
|
|
return self
|
|
if false_ in self.rules:
|
|
return false_
|
|
|
|
simplified_rules = [simplified for simplified in {rule.simplify() for rule in self.rules}
|
|
if simplified is not true_]
|
|
|
|
if not simplified_rules:
|
|
return true_
|
|
|
|
if len(simplified_rules) == 1:
|
|
return simplified_rules[0]
|
|
|
|
self.rules = frozenset(simplified_rules)
|
|
self._simplified = True
|
|
return self
|
|
|
|
|
|
class Count(StardewRule):
|
|
count: int
|
|
rules: List[StardewRule]
|
|
|
|
def __init__(self, count: int, rule: Union[StardewRule, Iterable[StardewRule]], *rules: StardewRule):
|
|
rules_list: List[StardewRule]
|
|
|
|
if isinstance(rule, Iterable):
|
|
rules_list = [*rule]
|
|
else:
|
|
rules_list = [rule]
|
|
|
|
if rules is not None:
|
|
rules_list.extend(rules)
|
|
|
|
assert rules_list, "Can't create a Count conditions without rules"
|
|
assert len(rules_list) >= count, "Count need at least as many rules at the count"
|
|
|
|
self.rules = rules_list
|
|
self.count = count
|
|
|
|
def __call__(self, state: CollectionState) -> bool:
|
|
c = 0
|
|
for r in self.rules:
|
|
if r(state):
|
|
c += 1
|
|
if c >= self.count:
|
|
return True
|
|
return False
|
|
|
|
def __repr__(self):
|
|
return f"Received {self.count} {repr(self.rules)}"
|
|
|
|
def get_difficulty(self):
|
|
rules_sorted_by_difficulty = sorted(self.rules, key=lambda x: x.get_difficulty())
|
|
easiest_n_rules = rules_sorted_by_difficulty[0:self.count]
|
|
return max(rule.get_difficulty() for rule in easiest_n_rules)
|
|
|
|
def simplify(self):
|
|
return Count(self.count, [rule.simplify() for rule in self.rules])
|
|
|
|
|
|
class TotalReceived(StardewRule):
|
|
count: int
|
|
items: Iterable[str]
|
|
player: int
|
|
|
|
def __init__(self, count: int, items: Union[str, Iterable[str]], player: int):
|
|
items_list: List[str]
|
|
|
|
if isinstance(items, Iterable):
|
|
items_list = [*items]
|
|
else:
|
|
items_list = [items]
|
|
|
|
assert items_list, "Can't create a Total Received conditions without items"
|
|
for item in items_list:
|
|
assert item_table[item].classification & ItemClassification.progression, \
|
|
"Item has to be progression to be used in logic"
|
|
|
|
self.player = player
|
|
self.items = items_list
|
|
self.count = count
|
|
|
|
def __call__(self, state: CollectionState) -> bool:
|
|
c = 0
|
|
for item in self.items:
|
|
c += state.count(item, self.player)
|
|
if c >= self.count:
|
|
return True
|
|
return False
|
|
|
|
def __repr__(self):
|
|
return f"Received {self.count} {self.items}"
|
|
|
|
def get_difficulty(self):
|
|
return self.count
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class Received(StardewRule):
|
|
item: str
|
|
player: int
|
|
count: int
|
|
|
|
def __post_init__(self):
|
|
assert item_table[self.item].classification & ItemClassification.progression, \
|
|
f"Item [{item_table[self.item].name}] has to be progression to be used in logic"
|
|
|
|
def __call__(self, state: CollectionState) -> bool:
|
|
return state.has(self.item, self.player, self.count)
|
|
|
|
def __repr__(self):
|
|
if self.count == 1:
|
|
return f"Received {self.item}"
|
|
return f"Received {self.count} {self.item}"
|
|
|
|
def get_difficulty(self):
|
|
if self.item == "Spring":
|
|
return 0
|
|
if self.item == "Summer":
|
|
return 1
|
|
if self.item == "Fall":
|
|
return 2
|
|
if self.item == "Winter":
|
|
return 3
|
|
return self.count
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class Reach(StardewRule):
|
|
spot: str
|
|
resolution_hint: str
|
|
player: int
|
|
|
|
def __call__(self, state: CollectionState) -> bool:
|
|
return state.can_reach(self.spot, self.resolution_hint, self.player)
|
|
|
|
def __repr__(self):
|
|
return f"Reach {self.resolution_hint} {self.spot}"
|
|
|
|
def get_difficulty(self):
|
|
return 1
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class Has(StardewRule):
|
|
item: str
|
|
# For sure there is a better way than just passing all the rules everytime
|
|
other_rules: Dict[str, StardewRule]
|
|
|
|
def __call__(self, state: CollectionState) -> bool:
|
|
if isinstance(self.item, str):
|
|
return self.other_rules[self.item](state)
|
|
|
|
def __repr__(self):
|
|
if not self.item in self.other_rules:
|
|
return f"Has {self.item} -> {MISSING_ITEM}"
|
|
return f"Has {self.item} -> {repr(self.other_rules[self.item])}"
|
|
|
|
def get_difficulty(self):
|
|
return self.other_rules[self.item].get_difficulty() + 1
|
|
|
|
def __hash__(self):
|
|
return hash(self.item)
|
|
|
|
def simplify(self) -> StardewRule:
|
|
return self.other_rules[self.item].simplify()
|