| 
									
										
										
										
											2025-03-08 01:44:06 +01:00
										 |  |  | from datetime import date | 
					
						
							| 
									
										
										
										
											2023-07-18 20:02:57 -07:00
										 |  |  | from math import floor | 
					
						
							| 
									
										
										
										
											2023-06-25 02:00:56 +02:00
										 |  |  | from pkgutil import get_data | 
					
						
							| 
									
										
										
										
											2024-07-02 23:59:26 +02:00
										 |  |  | from random import Random | 
					
						
							| 
									
										
										
										
											2025-03-13 23:59:09 +01:00
										 |  |  | from typing import Collection, FrozenSet, Iterable, List, Optional, Set, Tuple, TypeVar | 
					
						
							| 
									
										
										
										
											2024-07-02 23:59:26 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-13 23:59:09 +01:00
										 |  |  | from .definition_classes import AreaDefinition, ConnectionDefinition, RegionDefinition, WitnessRule | 
					
						
							| 
									
										
										
										
											2024-06-01 23:11:28 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-13 23:59:09 +01:00
										 |  |  | T = TypeVar("T") | 
					
						
							| 
									
										
										
										
											2024-02-28 04:44:22 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-10-02 00:02:17 +02:00
										 |  |  | def cast_not_none(value: Optional[T]) -> T: | 
					
						
							|  |  |  |     assert value is not None | 
					
						
							|  |  |  |     return value | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-02 23:59:26 +02:00
										 |  |  | def weighted_sample(world_random: Random, population: List[T], weights: List[float], k: int) -> List[T]: | 
					
						
							| 
									
										
										
										
											2024-02-28 04:44:22 +01:00
										 |  |  |     positions = range(len(population)) | 
					
						
							| 
									
										
										
										
											2024-07-02 23:59:26 +02:00
										 |  |  |     indices: List[int] = [] | 
					
						
							| 
									
										
										
										
											2024-02-28 04:44:22 +01:00
										 |  |  |     while True: | 
					
						
							|  |  |  |         needed = k - len(indices) | 
					
						
							|  |  |  |         if not needed: | 
					
						
							|  |  |  |             break | 
					
						
							|  |  |  |         for i in world_random.choices(positions, weights, k=needed): | 
					
						
							|  |  |  |             if weights[i]: | 
					
						
							|  |  |  |                 weights[i] = 0.0 | 
					
						
							|  |  |  |                 indices.append(i) | 
					
						
							|  |  |  |     return [population[i] for i in indices] | 
					
						
							| 
									
										
										
										
											2023-03-03 00:08:24 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-20 02:10:48 +02:00
										 |  |  | def build_weighted_int_list(inputs: Collection[float], total: int) -> List[int]: | 
					
						
							| 
									
										
										
										
											2023-07-18 20:02:57 -07:00
										 |  |  |     """
 | 
					
						
							|  |  |  |     Converts a list of floats to a list of ints of a given length, using the Largest Remainder Method. | 
					
						
							|  |  |  |     """
 | 
					
						
							| 
									
										
										
										
											2022-05-09 07:20:28 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-18 20:02:57 -07:00
										 |  |  |     # Scale the inputs to sum to the desired total. | 
					
						
							|  |  |  |     scale_factor: float = total / sum(inputs) | 
					
						
							|  |  |  |     scaled_input = [x * scale_factor for x in inputs] | 
					
						
							| 
									
										
										
										
											2022-05-09 07:20:28 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-18 20:02:57 -07:00
										 |  |  |     # Generate whole number counts, always rounding down. | 
					
						
							| 
									
										
										
										
											2023-07-20 02:10:48 +02:00
										 |  |  |     rounded_output: List[int] = [floor(x) for x in scaled_input] | 
					
						
							| 
									
										
										
										
											2023-07-18 20:02:57 -07:00
										 |  |  |     rounded_sum = sum(rounded_output) | 
					
						
							| 
									
										
										
										
											2022-05-09 07:20:28 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-18 20:02:57 -07:00
										 |  |  |     # If the output's total is insufficient, increment the value that has the largest remainder until we meet our goal. | 
					
						
							| 
									
										
										
										
											2023-07-20 02:10:48 +02:00
										 |  |  |     remainders: List[float] = [real - rounded for real, rounded in zip(scaled_input, rounded_output)] | 
					
						
							| 
									
										
										
										
											2023-07-18 20:02:57 -07:00
										 |  |  |     while rounded_sum < total: | 
					
						
							|  |  |  |         max_remainder = max(remainders) | 
					
						
							|  |  |  |         if max_remainder == 0: | 
					
						
							|  |  |  |             break | 
					
						
							| 
									
										
										
										
											2022-05-09 07:20:28 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-18 20:02:57 -07:00
										 |  |  |         # Consume the remainder and increment the total for the given target. | 
					
						
							|  |  |  |         max_remainder_index = remainders.index(max_remainder) | 
					
						
							|  |  |  |         remainders[max_remainder_index] = 0 | 
					
						
							|  |  |  |         rounded_output[max_remainder_index] += 1 | 
					
						
							|  |  |  |         rounded_sum += 1 | 
					
						
							| 
									
										
										
										
											2022-05-09 07:20:28 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-18 20:02:57 -07:00
										 |  |  |     return rounded_output | 
					
						
							| 
									
										
										
										
											2022-04-29 00:42:11 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-13 23:59:09 +01:00
										 |  |  | def define_new_region(region_string: str, area: AreaDefinition) -> Tuple[RegionDefinition, List[ConnectionDefinition]]: | 
					
						
							| 
									
										
										
										
											2022-04-29 00:42:11 +02:00
										 |  |  |     """
 | 
					
						
							|  |  |  |     Returns a region object by parsing a line in the logic file | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     region_string = region_string[:-1] | 
					
						
							|  |  |  |     line_split = region_string.split(" - ") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     region_name_full = line_split.pop(0) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     region_name_split = region_name_full.split(" (") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     region_name = region_name_split[0] | 
					
						
							|  |  |  |     region_name_simple = region_name_split[1][:-1] | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-13 23:59:09 +01:00
										 |  |  |     options = [] | 
					
						
							| 
									
										
										
										
											2022-04-29 00:42:11 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     for _ in range(len(line_split) // 2): | 
					
						
							|  |  |  |         connected_region = line_split.pop(0) | 
					
						
							| 
									
										
										
										
											2025-03-13 23:59:09 +01:00
										 |  |  |         traversal_rule_string = line_split.pop(0) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         options.append(ConnectionDefinition(connected_region, parse_witness_rule(traversal_rule_string))) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     region_obj = RegionDefinition(region_name, region_name_simple, area) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-29 00:42:11 +02:00
										 |  |  |     return region_obj, options | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-13 23:59:09 +01:00
										 |  |  | def parse_witness_rule(rule_string: str) -> WitnessRule: | 
					
						
							| 
									
										
										
										
											2022-04-29 00:42:11 +02:00
										 |  |  |     """
 | 
					
						
							| 
									
										
										
										
											2025-03-13 23:59:09 +01:00
										 |  |  |     Turns a rule string literal like this: a | b & c | 
					
						
							|  |  |  |     into a set of sets (called "WitnessRule") like this: {{a}, {b, c}} | 
					
						
							|  |  |  |     The rule string has to be in DNF. | 
					
						
							| 
									
										
										
										
											2022-04-29 00:42:11 +02:00
										 |  |  |     """
 | 
					
						
							| 
									
										
										
										
											2025-03-13 23:59:09 +01:00
										 |  |  |     if rule_string == "True": | 
					
						
							| 
									
										
										
										
											2022-04-29 00:42:11 +02:00
										 |  |  |         return frozenset([frozenset()]) | 
					
						
							| 
									
										
										
										
											2025-03-13 23:59:09 +01:00
										 |  |  |     split_ands = set(rule_string.split(" | ")) | 
					
						
							| 
									
										
										
										
											2024-07-02 23:59:26 +02:00
										 |  |  |     return frozenset({frozenset(a.split(" & ")) for a in split_ands}) | 
					
						
							| 
									
										
										
										
											2022-04-29 00:42:11 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-02 23:59:26 +02:00
										 |  |  | _adjustment_file_cache = {} | 
					
						
							| 
									
										
										
										
											2024-06-06 03:40:47 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-24 06:27:03 +01:00
										 |  |  | def get_adjustment_file(adjustment_file: str) -> List[str]: | 
					
						
							| 
									
										
										
										
											2024-06-06 03:40:47 +02:00
										 |  |  |     if adjustment_file not in _adjustment_file_cache: | 
					
						
							| 
									
										
										
										
											2024-07-02 23:59:26 +02:00
										 |  |  |         data = get_data(__name__, adjustment_file) | 
					
						
							|  |  |  |         if data is None: | 
					
						
							|  |  |  |             raise FileNotFoundError(f"Could not find {adjustment_file}") | 
					
						
							|  |  |  |         _adjustment_file_cache[adjustment_file] = [line.strip() for line in data.decode("utf-8").split("\n")] | 
					
						
							| 
									
										
										
										
											2024-06-06 03:40:47 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     return _adjustment_file_cache[adjustment_file] | 
					
						
							| 
									
										
										
										
											2022-06-16 03:04:45 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-24 06:27:03 +01:00
										 |  |  | def get_disable_unrandomized_list() -> List[str]: | 
					
						
							|  |  |  |     return get_adjustment_file("settings/Exclusions/Disable_Unrandomized.txt") | 
					
						
							| 
									
										
										
										
											2022-06-16 03:04:45 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-24 06:27:03 +01:00
										 |  |  | def get_early_caves_list() -> List[str]: | 
					
						
							|  |  |  |     return get_adjustment_file("settings/Early_Caves.txt") | 
					
						
							| 
									
										
										
										
											2022-07-17 12:56:22 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-24 06:27:03 +01:00
										 |  |  | def get_early_caves_start_list() -> List[str]: | 
					
						
							|  |  |  |     return get_adjustment_file("settings/Early_Caves_Start.txt") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def get_symbol_shuffle_list() -> List[str]: | 
					
						
							| 
									
										
										
										
											2022-07-17 12:56:22 +02:00
										 |  |  |     return get_adjustment_file("settings/Symbol_Shuffle.txt") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-24 06:27:03 +01:00
										 |  |  | def get_complex_doors() -> List[str]: | 
					
						
							|  |  |  |     return get_adjustment_file("settings/Door_Shuffle/Complex_Doors.txt") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def get_simple_doors() -> List[str]: | 
					
						
							|  |  |  |     return get_adjustment_file("settings/Door_Shuffle/Simple_Doors.txt") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-07-17 12:56:22 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-24 06:27:03 +01:00
										 |  |  | def get_complex_door_panels() -> List[str]: | 
					
						
							|  |  |  |     return get_adjustment_file("settings/Door_Shuffle/Complex_Door_Panels.txt") | 
					
						
							| 
									
										
										
										
											2022-07-17 12:56:22 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-24 06:27:03 +01:00
										 |  |  | def get_complex_additional_panels() -> List[str]: | 
					
						
							|  |  |  |     return get_adjustment_file("settings/Door_Shuffle/Complex_Additional_Panels.txt") | 
					
						
							| 
									
										
										
										
											2022-07-17 12:56:22 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-24 06:27:03 +01:00
										 |  |  | def get_simple_panels() -> List[str]: | 
					
						
							|  |  |  |     return get_adjustment_file("settings/Door_Shuffle/Simple_Panels.txt") | 
					
						
							| 
									
										
										
										
											2022-07-17 12:56:22 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-24 06:27:03 +01:00
										 |  |  | def get_simple_additional_panels() -> List[str]: | 
					
						
							|  |  |  |     return get_adjustment_file("settings/Door_Shuffle/Simple_Additional_Panels.txt") | 
					
						
							| 
									
										
										
										
											2022-07-17 12:56:22 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-24 06:27:03 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | def get_boat() -> List[str]: | 
					
						
							|  |  |  |     return get_adjustment_file("settings/Door_Shuffle/Boat.txt") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def get_laser_shuffle() -> List[str]: | 
					
						
							| 
									
										
										
										
											2022-07-17 12:56:22 +02:00
										 |  |  |     return get_adjustment_file("settings/Laser_Shuffle.txt") | 
					
						
							| 
									
										
										
										
											2022-10-09 04:13:52 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-24 06:27:03 +01:00
										 |  |  | def get_audio_logs() -> List[str]: | 
					
						
							| 
									
										
										
										
											2022-10-09 04:13:52 +02:00
										 |  |  |     return get_adjustment_file("settings/Audio_Logs.txt") | 
					
						
							| 
									
										
										
										
											2023-02-01 21:18:07 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-24 06:27:03 +01:00
										 |  |  | def get_ep_all_individual() -> List[str]: | 
					
						
							| 
									
										
										
										
											2023-02-01 21:18:07 +01:00
										 |  |  |     return get_adjustment_file("settings/EP_Shuffle/EP_All.txt") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-24 06:27:03 +01:00
										 |  |  | def get_ep_obelisks() -> List[str]: | 
					
						
							| 
									
										
										
										
											2023-02-01 21:18:07 +01:00
										 |  |  |     return get_adjustment_file("settings/EP_Shuffle/EP_Sides.txt") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-12 20:04:13 +01:00
										 |  |  | def get_obelisk_keys() -> List[str]: | 
					
						
							|  |  |  |     return get_adjustment_file("settings/Door_Shuffle/Obelisk_Keys.txt") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-24 06:27:03 +01:00
										 |  |  | def get_ep_easy() -> List[str]: | 
					
						
							| 
									
										
										
										
											2023-02-01 21:18:07 +01:00
										 |  |  |     return get_adjustment_file("settings/EP_Shuffle/EP_Easy.txt") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-24 06:27:03 +01:00
										 |  |  | def get_ep_no_eclipse() -> List[str]: | 
					
						
							| 
									
										
										
										
											2023-02-01 21:18:07 +01:00
										 |  |  |     return get_adjustment_file("settings/EP_Shuffle/EP_NoEclipse.txt") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-24 06:27:03 +01:00
										 |  |  | def get_vault_exclusion_list() -> List[str]: | 
					
						
							|  |  |  |     return get_adjustment_file("settings/Exclusions/Vaults.txt") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def get_discard_exclusion_list() -> List[str]: | 
					
						
							|  |  |  |     return get_adjustment_file("settings/Exclusions/Discards.txt") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-01 23:11:28 +02:00
										 |  |  | def get_caves_except_path_to_challenge_exclusion_list() -> List[str]: | 
					
						
							|  |  |  |     return get_adjustment_file("settings/Exclusions/Caves_Except_Path_To_Challenge.txt") | 
					
						
							| 
									
										
										
										
											2023-11-24 06:27:03 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-20 01:16:35 +02:00
										 |  |  | def get_entity_hunt() -> List[str]: | 
					
						
							|  |  |  |     return get_adjustment_file("settings/Entity_Hunt.txt") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-24 06:27:03 +01:00
										 |  |  | def get_sigma_normal_logic() -> List[str]: | 
					
						
							| 
									
										
										
										
											2023-06-25 02:00:56 +02:00
										 |  |  |     return get_adjustment_file("WitnessLogic.txt") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-24 06:27:03 +01:00
										 |  |  | def get_sigma_expert_logic() -> List[str]: | 
					
						
							| 
									
										
										
										
											2023-06-25 02:00:56 +02:00
										 |  |  |     return get_adjustment_file("WitnessLogicExpert.txt") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-05 17:10:09 +02:00
										 |  |  | def get_umbra_variety_logic() -> List[str]: | 
					
						
							|  |  |  |     return get_adjustment_file("WitnessLogicVariety.txt") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-24 06:27:03 +01:00
										 |  |  | def get_vanilla_logic() -> List[str]: | 
					
						
							| 
									
										
										
										
											2023-06-25 02:00:56 +02:00
										 |  |  |     return get_adjustment_file("WitnessLogicVanilla.txt") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-24 06:27:03 +01:00
										 |  |  | def get_items() -> List[str]: | 
					
						
							| 
									
										
										
										
											2023-06-25 02:00:56 +02:00
										 |  |  |     return get_adjustment_file("WitnessItems.txt") | 
					
						
							| 
									
										
										
										
											2023-11-24 06:27:03 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-01 23:11:28 +02:00
										 |  |  | def optimize_witness_rule(witness_rule: WitnessRule) -> WitnessRule: | 
					
						
							| 
									
										
										
										
											2023-11-24 06:27:03 +01:00
										 |  |  |     """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() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-01 23:11:28 +02:00
										 |  |  |     for option1 in witness_rule: | 
					
						
							|  |  |  |         for option2 in witness_rule: | 
					
						
							| 
									
										
										
										
											2023-11-24 06:27:03 +01:00
										 |  |  |             if option2 < option1: | 
					
						
							|  |  |  |                 to_remove.add(option1) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-01 23:11:28 +02:00
										 |  |  |     return witness_rule - to_remove | 
					
						
							| 
									
										
										
										
											2023-11-24 06:27:03 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-01 23:11:28 +02:00
										 |  |  | def logical_and_witness_rules(witness_rules: Iterable[WitnessRule]) -> WitnessRule: | 
					
						
							| 
									
										
										
										
											2023-11-24 06:27:03 +01:00
										 |  |  |     """
 | 
					
						
							|  |  |  |     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". | 
					
						
							|  |  |  |     These can be easily and-ed by just using the boolean distributive law: (a | b) & c = a & c | a & b. | 
					
						
							|  |  |  |     """
 | 
					
						
							| 
									
										
										
										
											2024-07-02 23:59:26 +02:00
										 |  |  |     current_overall_requirement: FrozenSet[FrozenSet[str]] = frozenset({frozenset()}) | 
					
						
							| 
									
										
										
										
											2023-11-24 06:27:03 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-01 23:11:28 +02:00
										 |  |  |     for next_dnf_requirement in witness_rules: | 
					
						
							| 
									
										
										
										
											2023-11-24 06:27:03 +01:00
										 |  |  |         new_requirement: Set[FrozenSet[str]] = set() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         for option1 in current_overall_requirement: | 
					
						
							|  |  |  |             for option2 in next_dnf_requirement: | 
					
						
							|  |  |  |                 new_requirement.add(option1 | option2) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         current_overall_requirement = frozenset(new_requirement) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-01 23:11:28 +02:00
										 |  |  |     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)) | 
					
						
							| 
									
										
										
										
											2025-03-08 01:44:06 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def is_easter_time() -> bool: | 
					
						
							|  |  |  |     # dateutils would have been nice here, because it has an easter() function. | 
					
						
							|  |  |  |     # But adding it as a requirement seems heavier than necessary. | 
					
						
							|  |  |  |     # Thus, we just take a range from the earliest to latest possible easter dates. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     today = date.today() | 
					
						
							| 
									
										
										
										
											2025-03-22 20:52:18 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     if today < date(2025, 3, 31): # Don't go live early if 0.6.0 RC3 happens, with a little leeway | 
					
						
							|  |  |  |         return False | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-08 01:44:06 +01:00
										 |  |  |     earliest_easter_day = date(today.year, 3, 20)  # Earliest possible is 3/22 + 2 day buffer for Good Friday | 
					
						
							|  |  |  |     last_easter_day = date(today.year, 4, 26)  # Latest possible is 4/25 + 1 day buffer for Easter Monday | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return earliest_easter_day <= today <= last_easter_day |