The Witness: Automatic Postgame & Disabled Panels Calculation (#2698)

* Refactor postgame code to be more readable

* Change all references to options to strings

* oops

* Fix some outdated code related to yaml-disabled EPs

* Small fixes to short/longbox stuff (thanks Medic)

* comment

* fix duplicate

* Removed triplicate lmfao

* Better comment

* added another 'unfun' postgame consideration

* comment

* more option strings

* oops

* Remove an unnecessary comparison

* another string missed

* New classification changes (Credit: Exempt-Medic)

* Don't need to pass world

* Comments

* Replace it with another magic system because why not at this point :DDDDDD

* oops

* Oops

* Another was missed

* Make events conditions. Disable_Non_Randomized will no longer just 'have all events'

* What the fuck? Has this just always been broken?

* Don't have boolean function with 'not' in the name

* Another useful classification

* slight code refactor

* Funny haha booleans

* This would create a really bad merge error

* I can't believe this actually kind of works

* And here's the punchline. + some bugfixes

* Comment dat code

* Comments galore

* LMAO OOPS

* so nice I did it twice

* debug x2

* Careful

* Add more comments

* That comment is a bit unnecessary now

* Fix overriding region connections

* Correct a comment

* Correct again

* Rename variable

* Idk I guess this is in this branch now

* More tweaking of postgame & comments

* This is commit just exists to fix that grammar error

* I think I can just fucking delete this now???

* Forgot to reset something here

* Delete dead codepath

* Obelisk Keys were getting yote erroneously

* More comments

* Fix duplicate connections

* Oopsington III

* performance improvements & cleanup

* More rules cleanup and performance improvements

* Oh cool I can do this huh

* Okay but this is even more swag tho

* Lazy eval

* remove some implicit checks

* Is this too magical yet

* more guard magic

* Maaaaaaaagiccccccccc

* Laaaaaaaaaaaaaaaazzzzzzyyyyyyyyyyy

* Make it docstring

* Newline bc I like that better

* this is a little spooky lol

* lol

* Wait

* spoO

* Better variable name and comment

* Improved comment again

* better API

* oops I deleted a deepcopy

* lol help

* Help???

* player_regionsns lmao

* Add some comments

* Make doors disabled properly again. I hope this works

* Don't disable lasers

* Omega oops

* Make Floor 2 Exit not exist

* Make a fix that's warps compatible

* I think this was an oversight, I tested a seed and it seems to have the same result

* This is definitely less Violet than before

* Does this feel more violet lol

* Exception if a laser gets disabled, cleanup

* Ruff

* >:(

* consistent utils import

* Make autopostgame more reviewable (hopefully)

* more reviewability

* WitnessRule

* replace another instance of it

* lint

* style

* comment

* found the bug

* Move comment

* Get rid of cache and ugly allow_victory

* comments and lint
This commit is contained in:
NewSoupVi
2024-06-01 23:11:28 +02:00
committed by GitHub
parent da33d1576a
commit e49b1f9fbb
19 changed files with 643 additions and 518 deletions

View File

@@ -2,7 +2,14 @@ from functools import lru_cache
from math import floor
from pkgutil import get_data
from random import random
from typing import Any, Collection, Dict, FrozenSet, List, Set, Tuple
from typing import Any, Collection, Dict, FrozenSet, Iterable, List, Set, Tuple
# A WitnessRule is just an or-chain of and-conditions.
# It represents the set of all options that could fulfill this requirement.
# E.g. if something requires "Dots or (Shapers and Stars)", it'd be represented as: {{"Dots"}, {"Shapers, "Stars"}}
# {} is an unusable requirement.
# {{}} is an always usable requirement.
WitnessRule = FrozenSet[FrozenSet[str]]
def weighted_sample(world_random: random, population: List, weights: List[float], k: int) -> List:
@@ -48,7 +55,7 @@ def build_weighted_int_list(inputs: Collection[float], total: int) -> List[int]:
return rounded_output
def define_new_region(region_string: str) -> Tuple[Dict[str, Any], Set[Tuple[str, FrozenSet[FrozenSet[str]]]]]:
def define_new_region(region_string: str) -> Tuple[Dict[str, Any], Set[Tuple[str, WitnessRule]]]:
"""
Returns a region object by parsing a line in the logic file
"""
@@ -76,12 +83,13 @@ def define_new_region(region_string: str) -> Tuple[Dict[str, Any], Set[Tuple[str
region_obj = {
"name": region_name,
"shortName": region_name_simple,
"panels": list()
"entities": list(),
"physical_entities": list(),
}
return region_obj, options
def parse_lambda(lambda_string) -> FrozenSet[FrozenSet[str]]:
def parse_lambda(lambda_string) -> WitnessRule:
"""
Turns a lambda String literal like this: a | b & c
into a set of sets like this: {{a}, {b, c}}
@@ -181,36 +189,8 @@ def get_discard_exclusion_list() -> List[str]:
return get_adjustment_file("settings/Exclusions/Discards.txt")
def get_caves_exclusion_list() -> List[str]:
return get_adjustment_file("settings/Postgame/Caves.txt")
def get_beyond_challenge_exclusion_list() -> List[str]:
return get_adjustment_file("settings/Postgame/Beyond_Challenge.txt")
def get_bottom_floor_discard_exclusion_list() -> List[str]:
return get_adjustment_file("settings/Postgame/Bottom_Floor_Discard.txt")
def get_bottom_floor_discard_nondoors_exclusion_list() -> List[str]:
return get_adjustment_file("settings/Postgame/Bottom_Floor_Discard_NonDoors.txt")
def get_mountain_upper_exclusion_list() -> List[str]:
return get_adjustment_file("settings/Postgame/Mountain_Upper.txt")
def get_challenge_vault_box_exclusion_list() -> List[str]:
return get_adjustment_file("settings/Postgame/Challenge_Vault_Box.txt")
def get_path_to_challenge_exclusion_list() -> List[str]:
return get_adjustment_file("settings/Postgame/Path_To_Challenge.txt")
def get_mountain_lower_exclusion_list() -> List[str]:
return get_adjustment_file("settings/Postgame/Mountain_Lower.txt")
def get_caves_except_path_to_challenge_exclusion_list() -> List[str]:
return get_adjustment_file("settings/Exclusions/Caves_Except_Path_To_Challenge.txt")
def get_elevators_come_to_you() -> List[str]:
@@ -233,21 +213,21 @@ def get_items() -> List[str]:
return get_adjustment_file("WitnessItems.txt")
def dnf_remove_redundancies(dnf_requirement: FrozenSet[FrozenSet[str]]) -> FrozenSet[FrozenSet[str]]:
def optimize_witness_rule(witness_rule: WitnessRule) -> WitnessRule:
"""Removes any redundant terms from a logical formula in disjunctive normal form.
This means removing any terms that are a superset of any other term get removed.
This is possible because of the boolean absorption law: a | (a & b) = a"""
to_remove = set()
for option1 in dnf_requirement:
for option2 in dnf_requirement:
for option1 in witness_rule:
for option2 in witness_rule:
if option2 < option1:
to_remove.add(option1)
return dnf_requirement - to_remove
return witness_rule - to_remove
def dnf_and(dnf_requirements: List[FrozenSet[FrozenSet[str]]]) -> FrozenSet[FrozenSet[str]]:
def logical_and_witness_rules(witness_rules: Iterable[WitnessRule]) -> WitnessRule:
"""
performs the "and" operator on a list of logical formula in disjunctive normal form, represented as a set of sets.
A logical formula might look like this: {{a, b}, {c, d}}, which would mean "a & b | c & d".
@@ -255,7 +235,7 @@ def dnf_and(dnf_requirements: List[FrozenSet[FrozenSet[str]]]) -> FrozenSet[Froz
"""
current_overall_requirement = frozenset({frozenset()})
for next_dnf_requirement in dnf_requirements:
for next_dnf_requirement in witness_rules:
new_requirement: Set[FrozenSet[str]] = set()
for option1 in current_overall_requirement:
@@ -264,4 +244,8 @@ def dnf_and(dnf_requirements: List[FrozenSet[FrozenSet[str]]]) -> FrozenSet[Froz
current_overall_requirement = frozenset(new_requirement)
return dnf_remove_redundancies(current_overall_requirement)
return optimize_witness_rule(current_overall_requirement)
def logical_or_witness_rules(witness_rules: Iterable[WitnessRule]) -> WitnessRule:
return optimize_witness_rule(frozenset.union(*witness_rules))