| 
									
										
											  
											
												Ocarina of Time (#64)
* first commit (not including OoT data files yet)
* added some basic options
* rule parser works now at least
* make sure to commit everything this time
* temporary change to BaseClasses for oot
* overworld location graph builds mostly correctly
* adding oot data files
* commenting out world options until later since they only existed to make the RuleParser work
* conversion functions between AP ids and OOT ids
* world graph outputs
* set scrub prices
* itempool generates, entrances connected, way too many options added
* fixed set_rules and set_shop_rules
* temp baseclasses changes
* Reaches the fill step now, old event-based system retained in case the new way breaks
* Song placements and misc fixes everywhere
* temporary changes to make oot work
* changed root exits for AP fill framework
* prevent infinite recursion due to OoT sharing usage of the address field
* age reachability works hopefully, songs are broken again
* working spoiler log generation on beatable-only
* Logic tricks implemented
* need this for logic tricks
* fixed map/compass being placed on Serenade location
* kill unreachable events before filling the world
* add a bunch of utility functions to prepare for rom patching
* move OptionList into generic options
* fixed some silly bugs with OptionList
* properly seed all random behavior (so far)
* ROM generation working
* fix hints trying to get alttp dungeon hint texts
* continue fixing hints
* add oot to network data package
* change item and location IDs to 66000 and 67000 range respectively
* push removed items to precollected items
* fixed various issues with cross-contamination with multiple world generation
* reenable glitched logic (hopefully)
* glitched world files age-check fix
* cleaned up some get_locations calls
* added token shuffle and scrub shuffle, modified some options slightly to make the parsing work
* reenable MQ dungeons
* fix forest mq exception
* made targeting style an option for now, will be cosmetic later
* reminder to move targeting to cosmetics
* some oot option maintenance
* enabled starting time of day
* fixed issue breaking shop slots in multiworld generation
* added "off" option for text shuffle and hints
* shopsanity functionality restored
* change patch file extension
* remove unnecessary utility functions + imports
* update MIT license
* change option to "patch_uncompressed_rom" instead of "compress_rom"
* compliance with new AutoWorld systems
* Kill only internal events, remove non-internal big poe event in code
* re-add the big poe event and handle it correctly
* remove extra method in Range option
* fix typo
* Starting items, starting with consumables option
* do not remove nonexistent item
* move set_shop_rules to after shop items are placed
* some cleanup
* add retries for song placement
* flagged Skull Mask and Mask of Truth as advancement items
* update OoT to use LogicMixin
* Fixed trying to assign starting items from the wrong players
* fixed song retry step
* improved option handling, comments, and starting item replacements
* DefaultOnToggle writes Yes or No to spoiler
* enable compression of output if Compress executable is present
* clean up compression
* check whether (de)compressor exists before running the process
* allow specification of rom path in host.yaml
* check if decompressed file already exists before decompressing again
* fix triforce hunt generation
* rename all the oot state functions with prefix
* OoT: mark triforce pieces as completion goal for triforce hunt
* added overworld and any-dungeon shuffle for dungeon items
* Hide most unshuffled locations and events from the list of locations in spoiler
* build oot option ranges with a generic function instead of defining each separately
* move oot output-type control to host.yaml instead of individual yamls
* implement dungeon song shuffle
* minor improvements to overworld dungeon item shuffle
* remove random ice trap names in shops, mostly to avoid maintaining a massive censor list
* always output patch file to folder, remove option to generate ROM in preparation for removal
* re-add the fix for infinite recursion due to not being light or dark world
* change AP-sendable to Ocarina of Time model, since the triforce piece has some extra code apparently
* oot: remove item_names and location_names
* oot: minor fixes
* oot: comment out ROM patching
* oot: only add CollectionState objects on creation if actually needed
* main entrance shuffle method and entrances-based rules
* fix entrances based rules
* disable master quest and big poe count options for client compatibility
* use get_player_name instead of get_player_names
* fix OptionList
* fix oot options for new option system
* new coop section in oot rom: expand player names to 16 bytes, write AP_PLAYER_NAME at end of PLAYER_NAMES
* fill AP player name in oot rom with 0 instead of 0xDF
* encode player name with ASCII for fixed-width
* revert oot player name array to 8 bytes per name
* remove Pierre location if fast scarecrow is on
* check player name length
* "free_scarecrow" not "fast_scarecrow"
* OoT locations now properly store the AP ID instead of the oot internal ID
* oot __version__ updates in lockstep with AP version
* pull in unmodified oot cosmetic files
* also grab JSONDump since it's needed apparently
* gather extra needed methods, modify imports
* delete cosmetics log, replace all instances of SettingsList with OOTWorld
* cosmetic options working, except for sound effects (due to ear-safe issues)
* SFX, Music, and Fanfare randomization reenabled
* move OoT data files into the worlds folder
* move Compress and Decompress into oot data folder
* Replace get_all_state with custom method to avoid the cache
* OoT ROM: increment item counter before setting incoming item/player values to 0, preventing desync issues
* set data_version to 0
* make Kokiri Sword shuffle off by default
* reenable "Random Choice" for various cosmetic options
* kill Ruto's Letter turnin if open fountain
also fix for shopsanity
* place Buy Goron/Zora Tunic first in shop shuffle
* make ice traps appear as other items instead of breaking generation
* managed to break ice traps on non-major-only
* only handle ice traps if they are on
* fix shopsanity for non-oot games, and write player name instead of player number
* light arrows hint uses player name instead of player number
* Reenable "skip child zelda" option
* fix entrances_based_rules
* fix ganondorf hint if starting with light arrows
* fix dungeonitem shuffle and shopsanity interaction
* remove has_all_of, has_any_of, count_of in BaseClasses, replace usage with has_all, has_any, has_group
* force local giveable item on ZL if skip_child_zelda and shuffle_song_items is any
* keep bosses and bombchu bowling chus out of data package
* revert workaround for infinite recursion and fix it properly
* fix shared shop id caches during patching process
* fix shop text box overflows, as much as possible
* add default oot host.yaml option
* add .apz5, .n64, .z64 to gitignore
* Properly document and name all (functioning) OOT options
* clean up some imports
* remove unnecessary files from oot's data
* fix typo in gitignore
* readd the Compress and Decompress utilities, since they are needed for generation
* cleanup of imports and some minor optimizations
* increase shop offset for item IDs to 0xCB
* remove shop item AP ids entirely
* prevent triforce pieces for other players from being received by yourself
* add "excluded" property to Location
* Hint system adapted and reenabled; hints still unseeded
* make hints deterministic with lists instead of sets
* do not allow hints to point to Light Arrows on non-vanilla bridge
* foreign locations hint as their full name in OoT rather than their region
* checkedLocations now stores hint names by player ID, so that the same location in different worlds can have hints associated
* consolidate versioning in Utils
* ice traps appear as major items rather than any progression item
* set prescription and claim check as defaults for adult trade item settings
* add oot options to playerSettings
* allow case-insensitive logic tricks in yaml
* fix oot shopsanity option formatting
* Write OoT override info even if local item, enabling local checks to show up immediately in the client
* implement CollectionState.can_live_dmg for oot glitched logic
* filter item names for invalid characters when patching shops
* make ice traps appear according to the settings of the world they are shuffled into, rather than the original world
* set hidden-spoiler items and locations with Shop items to events
* make GF carpenters, Gerudo Card, Malon, ZL, and Impa events if the relevant settings are enabled, preventing them from appearing in the client on game start
* Fix oot Glitched and No Logic generation
* fix indenting
* Greatly reduce displayed cosmetic options
* Change oot data version to 1
* add apz5 distribution to webhost
* print player name if an ALttP dungeon contains a good item for OoT world
* delete unneeded commented code
* remove OcarinaSongs import to satisfy lint
											
										 
											2021-09-02 08:35:05 -04:00
										 |  |  | import worlds.oot.Messages as Messages | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | # Least common multiple of all possible character widths. A line wrap must occur when the combined widths of all of the | 
					
						
							|  |  |  | # characters on a line reach this value. | 
					
						
							|  |  |  | NORMAL_LINE_WIDTH = 1801800 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | # Attempting to display more lines in a single text box will cause additional lines to bleed past the bottom of the box. | 
					
						
							|  |  |  | LINES_PER_BOX = 4 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | # Attempting to display more characters in a single text box will cause buffer overflows. First, visual artifacts will | 
					
						
							|  |  |  | # appear in lower areas of the text box. Eventually, the text box will become uncloseable. | 
					
						
							|  |  |  | MAX_CHARACTERS_PER_BOX = 200 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | CONTROL_CHARS = { | 
					
						
							|  |  |  |     'LINE_BREAK':   ['&', '\x01'], | 
					
						
							|  |  |  |     'BOX_BREAK':    ['^', '\x04'], | 
					
						
							|  |  |  |     'NAME':         ['@', '\x0F'], | 
					
						
							|  |  |  |     'COLOR':        ['#', '\x05\x00'], | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | TEXT_END   = '\x02' | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def line_wrap(text, strip_existing_lines=False, strip_existing_boxes=False, replace_control_chars=True): | 
					
						
							|  |  |  |     # Replace stand-in characters with their actual control code. | 
					
						
							|  |  |  |     if replace_control_chars: | 
					
						
							|  |  |  |         for char in CONTROL_CHARS.values(): | 
					
						
							|  |  |  |             text = text.replace(char[0], char[1]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Parse the text into a list of control codes. | 
					
						
							|  |  |  |     text_codes = Messages.parse_control_codes(text) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Existing line/box break codes to strip. | 
					
						
							|  |  |  |     strip_codes = [] | 
					
						
							|  |  |  |     if strip_existing_boxes: | 
					
						
							|  |  |  |         strip_codes.append(0x04) | 
					
						
							|  |  |  |     if strip_existing_lines: | 
					
						
							|  |  |  |         strip_codes.append(0x01) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Replace stripped codes with a space. | 
					
						
							|  |  |  |     if strip_codes: | 
					
						
							|  |  |  |         index = 0 | 
					
						
							|  |  |  |         while index < len(text_codes): | 
					
						
							|  |  |  |             text_code = text_codes[index] | 
					
						
							|  |  |  |             if text_code.code in strip_codes: | 
					
						
							|  |  |  |                 # Check for existing whitespace near this control code. | 
					
						
							|  |  |  |                 # If one is found, simply remove this text code. | 
					
						
							|  |  |  |                 if index > 0 and text_codes[index-1].code == 0x20: | 
					
						
							|  |  |  |                     text_codes.pop(index) | 
					
						
							|  |  |  |                     continue | 
					
						
							|  |  |  |                 if index + 1 < len(text_codes) and text_codes[index+1].code == 0x20: | 
					
						
							|  |  |  |                     text_codes.pop(index) | 
					
						
							|  |  |  |                     continue | 
					
						
							|  |  |  |                 # Replace this text code with a space. | 
					
						
							|  |  |  |                 text_codes[index] = Messages.Text_Code(0x20, 0) | 
					
						
							|  |  |  |             index += 1 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Split the text codes by current box breaks. | 
					
						
							|  |  |  |     boxes = [] | 
					
						
							|  |  |  |     start_index = 0 | 
					
						
							|  |  |  |     end_index = 0 | 
					
						
							|  |  |  |     for text_code in text_codes: | 
					
						
							|  |  |  |         end_index += 1 | 
					
						
							|  |  |  |         if text_code.code == 0x04: | 
					
						
							|  |  |  |             boxes.append(text_codes[start_index:end_index]) | 
					
						
							|  |  |  |             start_index = end_index | 
					
						
							|  |  |  |     boxes.append(text_codes[start_index:end_index]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Split the boxes into lines and words. | 
					
						
							|  |  |  |     processed_boxes = [] | 
					
						
							|  |  |  |     for box_codes in boxes: | 
					
						
							|  |  |  |         line_width = NORMAL_LINE_WIDTH | 
					
						
							|  |  |  |         icon_code = None | 
					
						
							|  |  |  |         words = [] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Group the text codes into words. | 
					
						
							|  |  |  |         index = 0 | 
					
						
							|  |  |  |         while index < len(box_codes): | 
					
						
							|  |  |  |             text_code = box_codes[index] | 
					
						
							|  |  |  |             index += 1 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             # Check for an icon code and lower the width of this box if one is found. | 
					
						
							|  |  |  |             if text_code.code == 0x13: | 
					
						
							|  |  |  |                 line_width = 1441440 | 
					
						
							|  |  |  |                 icon_code = text_code | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             # Find us a whole word. | 
					
						
							|  |  |  |             if text_code.code in [0x01, 0x04, 0x20]: | 
					
						
							|  |  |  |                 if index > 1: | 
					
						
							|  |  |  |                     words.append(box_codes[0:index-1]) | 
					
						
							|  |  |  |                 if text_code.code in [0x01, 0x04]: | 
					
						
							|  |  |  |                     # If we have ran into a line or box break, add it as a "word" as well. | 
					
						
							|  |  |  |                     words.append([box_codes[index-1]]) | 
					
						
							|  |  |  |                 box_codes = box_codes[index:] | 
					
						
							|  |  |  |                 index = 0 | 
					
						
							|  |  |  |             if index > 0 and index == len(box_codes): | 
					
						
							|  |  |  |                 words.append(box_codes) | 
					
						
							|  |  |  |                 box_codes = [] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Arrange our words into lines. | 
					
						
							|  |  |  |         lines = [] | 
					
						
							|  |  |  |         start_index = 0 | 
					
						
							|  |  |  |         end_index = 0 | 
					
						
							|  |  |  |         box_count = 1 | 
					
						
							|  |  |  |         while end_index < len(words): | 
					
						
							|  |  |  |             # Our current confirmed line. | 
					
						
							|  |  |  |             end_index += 1 | 
					
						
							|  |  |  |             line = words[start_index:end_index] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             # If this word is a line/box break, trim our line back a word and deal with it later. | 
					
						
							|  |  |  |             break_char = False | 
					
						
							|  |  |  |             if words[end_index-1][0].code in [0x01, 0x04]: | 
					
						
							|  |  |  |                 line = words[start_index:end_index-1] | 
					
						
							|  |  |  |                 break_char = True | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             # Check the width of the line after adding one more word. | 
					
						
							|  |  |  |             if end_index == len(words) or break_char or calculate_width(words[start_index:end_index+1]) > line_width: | 
					
						
							|  |  |  |                 if line or lines: | 
					
						
							|  |  |  |                     lines.append(line) | 
					
						
							|  |  |  |                 start_index = end_index | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             # If we've reached the end of the box, finalize it. | 
					
						
							|  |  |  |             if end_index == len(words) or words[end_index-1][0].code == 0x04 or len(lines) == LINES_PER_BOX: | 
					
						
							|  |  |  |                 # Append the same icon to any wrapped boxes. | 
					
						
							|  |  |  |                 if icon_code and box_count > 1: | 
					
						
							|  |  |  |                     lines[0][0] = [icon_code] + lines[0][0] | 
					
						
							|  |  |  |                 processed_boxes.append(lines) | 
					
						
							|  |  |  |                 lines = [] | 
					
						
							|  |  |  |                 box_count += 1 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Construct our final string. | 
					
						
							|  |  |  |     # This is a hideous level of list comprehension. Sorry. | 
					
						
							|  |  |  |     return '\x04'.join('\x01'.join(' '.join(''.join(code.get_string() for code in word) for word in line) for line in box) for box in processed_boxes) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def calculate_width(words): | 
					
						
							|  |  |  |     words_width = 0 | 
					
						
							|  |  |  |     for word in words: | 
					
						
							|  |  |  |         index = 0 | 
					
						
							|  |  |  |         while index < len(word): | 
					
						
							|  |  |  |             character = word[index] | 
					
						
							|  |  |  |             index += 1 | 
					
						
							|  |  |  |             if character.code in Messages.CONTROL_CODES: | 
					
						
							|  |  |  |                 if character.code == 0x06: | 
					
						
							|  |  |  |                     words_width += character.data | 
					
						
							|  |  |  |             words_width += get_character_width(chr(character.code)) | 
					
						
							|  |  |  |     spaces_width = get_character_width(' ') * (len(words) - 1) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return words_width + spaces_width | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def get_character_width(character): | 
					
						
							|  |  |  |     try: | 
					
						
							|  |  |  |         return character_table[character] | 
					
						
							|  |  |  |     except KeyError: | 
					
						
							|  |  |  |         if ord(character) < 0x20: | 
					
						
							|  |  |  |             if character in control_code_width: | 
					
						
							|  |  |  |                 return sum([character_table[c] for c in control_code_width[character]]) | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 return 0 | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             # A sane default with the most common character width | 
					
						
							|  |  |  |             return character_table[' '] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | control_code_width = { | 
					
						
							|  |  |  |     '\x0F': '00000000', | 
					
						
							|  |  |  |     '\x16': '00\'00"', | 
					
						
							|  |  |  |     '\x17': '00\'00"', | 
					
						
							|  |  |  |     '\x18': '00000', | 
					
						
							|  |  |  |     '\x19': '100', | 
					
						
							|  |  |  |     '\x1D': '00', | 
					
						
							|  |  |  |     '\x1E': '00000', | 
					
						
							|  |  |  |     '\x1F': '00\'00"', | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | # Tediously measured by filling a full line of a gossip stone's text box with one character until it is reasonably full | 
					
						
							|  |  |  | # (with a right margin) and counting how many characters fit. OoT does not appear to use any kerning, but, if it does, | 
					
						
							|  |  |  | # it will only make the characters more space-efficient, so this is an underestimate of the number of letters per line, | 
					
						
							|  |  |  | # at worst. This ensures that we will never bleed text out of the text box while line wrapping. | 
					
						
							|  |  |  | # Larger numbers in the denominator mean more of that character fits on a line; conversely, larger values in this table | 
					
						
							|  |  |  | # mean the character is wider and can't fit as many on one line. | 
					
						
							|  |  |  | character_table = { | 
					
						
							|  |  |  |     '\x0F': 655200, | 
					
						
							|  |  |  |     '\x16': 292215, | 
					
						
							|  |  |  |     '\x17': 292215, | 
					
						
							|  |  |  |     '\x18': 300300, | 
					
						
							|  |  |  |     '\x19': 145860, | 
					
						
							|  |  |  |     '\x1D': 85800, | 
					
						
							|  |  |  |     '\x1E': 300300, | 
					
						
							|  |  |  |     '\x1F': 265980, | 
					
						
							|  |  |  |     'a':  51480, # LINE_WIDTH /  35 | 
					
						
							|  |  |  |     'b':  51480, # LINE_WIDTH /  35 | 
					
						
							|  |  |  |     'c':  51480, # LINE_WIDTH /  35 | 
					
						
							|  |  |  |     'd':  51480, # LINE_WIDTH /  35 | 
					
						
							|  |  |  |     'e':  51480, # LINE_WIDTH /  35 | 
					
						
							|  |  |  |     'f':  34650, # LINE_WIDTH /  52 | 
					
						
							|  |  |  |     'g':  51480, # LINE_WIDTH /  35 | 
					
						
							|  |  |  |     'h':  51480, # LINE_WIDTH /  35 | 
					
						
							|  |  |  |     'i':  25740, # LINE_WIDTH /  70 | 
					
						
							|  |  |  |     'j':  34650, # LINE_WIDTH /  52 | 
					
						
							|  |  |  |     'k':  51480, # LINE_WIDTH /  35 | 
					
						
							|  |  |  |     'l':  25740, # LINE_WIDTH /  70 | 
					
						
							|  |  |  |     'm':  81900, # LINE_WIDTH /  22 | 
					
						
							|  |  |  |     'n':  51480, # LINE_WIDTH /  35 | 
					
						
							|  |  |  |     'o':  51480, # LINE_WIDTH /  35 | 
					
						
							|  |  |  |     'p':  51480, # LINE_WIDTH /  35 | 
					
						
							|  |  |  |     'q':  51480, # LINE_WIDTH /  35 | 
					
						
							|  |  |  |     'r':  42900, # LINE_WIDTH /  42 | 
					
						
							|  |  |  |     's':  51480, # LINE_WIDTH /  35 | 
					
						
							|  |  |  |     't':  42900, # LINE_WIDTH /  42 | 
					
						
							|  |  |  |     'u':  51480, # LINE_WIDTH /  35 | 
					
						
							|  |  |  |     'v':  51480, # LINE_WIDTH /  35 | 
					
						
							|  |  |  |     'w':  81900, # LINE_WIDTH /  22 | 
					
						
							|  |  |  |     'x':  51480, # LINE_WIDTH /  35 | 
					
						
							|  |  |  |     'y':  51480, # LINE_WIDTH /  35 | 
					
						
							|  |  |  |     'z':  51480, # LINE_WIDTH /  35 | 
					
						
							|  |  |  |     'A':  81900, # LINE_WIDTH /  22 | 
					
						
							|  |  |  |     'B':  51480, # LINE_WIDTH /  35 | 
					
						
							|  |  |  |     'C':  72072, # LINE_WIDTH /  25 | 
					
						
							|  |  |  |     'D':  72072, # LINE_WIDTH /  25 | 
					
						
							|  |  |  |     'E':  51480, # LINE_WIDTH /  35 | 
					
						
							|  |  |  |     'F':  51480, # LINE_WIDTH /  35 | 
					
						
							|  |  |  |     'G':  81900, # LINE_WIDTH /  22 | 
					
						
							|  |  |  |     'H':  60060, # LINE_WIDTH /  30 | 
					
						
							|  |  |  |     'I':  25740, # LINE_WIDTH /  70 | 
					
						
							|  |  |  |     'J':  51480, # LINE_WIDTH /  35 | 
					
						
							|  |  |  |     'K':  60060, # LINE_WIDTH /  30 | 
					
						
							|  |  |  |     'L':  51480, # LINE_WIDTH /  35 | 
					
						
							|  |  |  |     'M':  81900, # LINE_WIDTH /  22 | 
					
						
							|  |  |  |     'N':  72072, # LINE_WIDTH /  25 | 
					
						
							|  |  |  |     'O':  81900, # LINE_WIDTH /  22 | 
					
						
							|  |  |  |     'P':  51480, # LINE_WIDTH /  35 | 
					
						
							|  |  |  |     'Q':  81900, # LINE_WIDTH /  22 | 
					
						
							|  |  |  |     'R':  60060, # LINE_WIDTH /  30 | 
					
						
							|  |  |  |     'S':  60060, # LINE_WIDTH /  30 | 
					
						
							|  |  |  |     'T':  51480, # LINE_WIDTH /  35 | 
					
						
							|  |  |  |     'U':  60060, # LINE_WIDTH /  30 | 
					
						
							|  |  |  |     'V':  72072, # LINE_WIDTH /  25 | 
					
						
							|  |  |  |     'W': 100100, # LINE_WIDTH /  18 | 
					
						
							|  |  |  |     'X':  72072, # LINE_WIDTH /  25 | 
					
						
							|  |  |  |     'Y':  60060, # LINE_WIDTH /  30 | 
					
						
							|  |  |  |     'Z':  60060, # LINE_WIDTH /  30 | 
					
						
							|  |  |  |     ' ':  51480, # LINE_WIDTH /  35 | 
					
						
							|  |  |  |     '1':  25740, # LINE_WIDTH /  70 | 
					
						
							|  |  |  |     '2':  51480, # LINE_WIDTH /  35 | 
					
						
							|  |  |  |     '3':  51480, # LINE_WIDTH /  35 | 
					
						
							|  |  |  |     '4':  60060, # LINE_WIDTH /  30 | 
					
						
							|  |  |  |     '5':  51480, # LINE_WIDTH /  35 | 
					
						
							|  |  |  |     '6':  51480, # LINE_WIDTH /  35 | 
					
						
							|  |  |  |     '7':  51480, # LINE_WIDTH /  35 | 
					
						
							|  |  |  |     '8':  51480, # LINE_WIDTH /  35 | 
					
						
							|  |  |  |     '9':  51480, # LINE_WIDTH /  35 | 
					
						
							|  |  |  |     '0':  60060, # LINE_WIDTH /  30 | 
					
						
							|  |  |  |     '!':  51480, # LINE_WIDTH /  35 | 
					
						
							|  |  |  |     '?':  72072, # LINE_WIDTH /  25 | 
					
						
							|  |  |  |     '\'': 17325, # LINE_WIDTH / 104 | 
					
						
							|  |  |  |     '"':  34650, # LINE_WIDTH /  52 | 
					
						
							|  |  |  |     '.':  25740, # LINE_WIDTH /  70 | 
					
						
							|  |  |  |     ',':  25740, # LINE_WIDTH /  70 | 
					
						
							|  |  |  |     '/':  51480, # LINE_WIDTH /  35 | 
					
						
							|  |  |  |     '-':  34650, # LINE_WIDTH /  52 | 
					
						
							|  |  |  |     '_':  51480, # LINE_WIDTH /  35 | 
					
						
							|  |  |  |     '(':  42900, # LINE_WIDTH /  42 | 
					
						
							|  |  |  |     ')':  42900, # LINE_WIDTH /  42 | 
					
						
							|  |  |  |     '$':  51480  # LINE_WIDTH /  35 | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | # To run tests, enter the following into a python3 REPL: | 
					
						
							|  |  |  | # >>> import Messages | 
					
						
							|  |  |  | # >>> from TextBox import line_wrap_tests | 
					
						
							|  |  |  | # >>> line_wrap_tests() | 
					
						
							|  |  |  | def line_wrap_tests(): | 
					
						
							|  |  |  |     test_wrap_simple_line() | 
					
						
							|  |  |  |     test_honor_forced_line_wraps() | 
					
						
							|  |  |  |     test_honor_box_breaks() | 
					
						
							|  |  |  |     test_honor_control_characters() | 
					
						
							|  |  |  |     test_honor_player_name() | 
					
						
							|  |  |  |     test_maintain_multiple_forced_breaks() | 
					
						
							|  |  |  |     test_trim_whitespace() | 
					
						
							|  |  |  |     test_support_long_words() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def test_wrap_simple_line(): | 
					
						
							|  |  |  |     words = 'Hello World! Hello World! Hello World!' | 
					
						
							|  |  |  |     expected = 'Hello World! Hello World! Hello\x01World!' | 
					
						
							|  |  |  |     result = line_wrap(words) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if result != expected: | 
					
						
							|  |  |  |         print('"Wrap Simple Line" test failed: Got ' + result + ', wanted ' + expected) | 
					
						
							|  |  |  |     else: | 
					
						
							|  |  |  |         print('"Wrap Simple Line" test passed!') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def test_honor_forced_line_wraps(): | 
					
						
							|  |  |  |     words = 'Hello World! Hello World!&Hello World! Hello World! Hello World!' | 
					
						
							|  |  |  |     expected = 'Hello World! Hello World!\x01Hello World! Hello World! Hello\x01World!' | 
					
						
							|  |  |  |     result = line_wrap(words) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if result != expected: | 
					
						
							|  |  |  |         print('"Honor Forced Line Wraps" test failed: Got ' + result + ', wanted ' + expected) | 
					
						
							|  |  |  |     else: | 
					
						
							|  |  |  |         print('"Honor Forced Line Wraps" test passed!') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def test_honor_box_breaks(): | 
					
						
							|  |  |  |     words = 'Hello World! Hello World!^Hello World! Hello World! Hello World!' | 
					
						
							|  |  |  |     expected = 'Hello World! Hello World!\x04Hello World! Hello World! Hello\x01World!' | 
					
						
							|  |  |  |     result = line_wrap(words) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if result != expected: | 
					
						
							|  |  |  |         print('"Honor Box Breaks" test failed: Got ' + result + ', wanted ' + expected) | 
					
						
							|  |  |  |     else: | 
					
						
							|  |  |  |         print('"Honor Box Breaks" test passed!') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def test_honor_control_characters(): | 
					
						
							|  |  |  |     words = 'Hello World! #Hello# World! Hello World!' | 
					
						
							|  |  |  |     expected = 'Hello World! \x05\x00Hello\x05\x00 World! Hello\x01World!' | 
					
						
							|  |  |  |     result = line_wrap(words) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if result != expected: | 
					
						
							|  |  |  |         print('"Honor Control Characters" test failed: Got ' + result + ', wanted ' + expected) | 
					
						
							|  |  |  |     else: | 
					
						
							|  |  |  |         print('"Honor Control Characters" test passed!') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def test_honor_player_name(): | 
					
						
							|  |  |  |     words = 'Hello @! Hello World! Hello World!' | 
					
						
							|  |  |  |     expected = 'Hello \x0F! Hello World!\x01Hello World!' | 
					
						
							|  |  |  |     result = line_wrap(words) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if result != expected: | 
					
						
							|  |  |  |         print('"Honor Player Name" test failed: Got ' + result + ', wanted ' + expected) | 
					
						
							|  |  |  |     else: | 
					
						
							|  |  |  |         print('"Honor Player Name" test passed!') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def test_maintain_multiple_forced_breaks(): | 
					
						
							|  |  |  |     words = 'Hello World!&&&Hello World!' | 
					
						
							|  |  |  |     expected = 'Hello World!\x01\x01\x01Hello World!' | 
					
						
							|  |  |  |     result = line_wrap(words) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if result != expected: | 
					
						
							|  |  |  |         print('"Maintain Multiple Forced Breaks" test failed: Got ' + result + ', wanted ' + expected) | 
					
						
							|  |  |  |     else: | 
					
						
							|  |  |  |         print('"Maintain Multiple Forced Breaks" test passed!') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def test_trim_whitespace(): | 
					
						
							|  |  |  |     words = 'Hello World! & Hello World!' | 
					
						
							|  |  |  |     expected = 'Hello World!\x01Hello World!' | 
					
						
							|  |  |  |     result = line_wrap(words) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if result != expected: | 
					
						
							|  |  |  |         print('"Trim Whitespace" test failed: Got ' + result + ', wanted ' + expected) | 
					
						
							|  |  |  |     else: | 
					
						
							|  |  |  |         print('"Trim Whitespace" test passed!') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def test_support_long_words(): | 
					
						
							|  |  |  |     words = 'Hello World! WWWWWWWWWWWWWWWWWWWW Hello World!' | 
					
						
							|  |  |  |     expected = 'Hello World!\x01WWWWWWWWWWWWWWWWWWWW\x01Hello World!' | 
					
						
							|  |  |  |     result = line_wrap(words) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if result != expected: | 
					
						
							|  |  |  |         print('"Support Long Words" test failed: Got ' + result + ', wanted ' + expected) | 
					
						
							|  |  |  |     else: | 
					
						
							|  |  |  |         print('"Support Long Words" test passed!') | 
					
						
							| 
									
										
										
										
											2023-04-15 17:45:31 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | # AP additions | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | rom_safe_lambda = lambda c: c if c in character_table else '?' | 
					
						
							|  |  |  | def rom_safe_text(text): | 
					
						
							|  |  |  |     return ''.join(map(rom_safe_lambda, text)) |