2017-05-25 13:58:35 +00:00
from BaseClasses import World , CollectionState , Item
2017-05-25 10:09:50 +00:00
from Regions import create_regions
from EntranceShuffle import link_entrances
2017-07-14 12:37:34 +00:00
from Rom import patch_rom , LocalRom , JsonRom
2017-05-15 18:28:04 +00:00
from Rules import set_rules
2017-10-15 17:52:42 +00:00
from Dungeons import create_dungeons , fill_dungeons , fill_dungeons_restrictive
2017-05-25 13:58:35 +00:00
from Items import ItemFactory
2017-10-15 19:35:45 +00:00
from Fill import distribute_items_cutoff , distribute_items_staleness , distribute_items_restrictive , fill_restrictive , flood_items
2017-07-18 10:44:13 +00:00
from collections import OrderedDict
2017-05-15 18:28:04 +00:00
import random
import time
2017-05-16 19:23:47 +00:00
import logging
2017-07-16 21:20:54 +00:00
import json
2017-05-15 18:28:04 +00:00
2017-08-05 15:52:30 +00:00
__version__ = ' 0.4.7-dev '
2017-05-20 12:07:40 +00:00
2017-08-05 18:53:21 +00:00
logic_hash = [ 118 , 17 , 154 , 187 , 209 , 19 , 0 , 97 , 63 , 62 , 164 , 160 , 155 , 28 , 136 , 220 , 251 , 76 , 55 , 109 , 174 , 36 , 82 , 140 , 87 , 226 , 26 , 150 , 200 , 115 , 6 , 238 ,
2017-08-01 17:26:06 +00:00
85 , 229 , 49 , 141 , 66 , 199 , 112 , 212 , 182 , 98 , 249 , 54 , 201 , 161 , 148 , 126 , 179 , 5 , 47 , 162 , 108 , 152 , 67 , 203 , 239 , 15 , 211 , 132 , 198 , 124 , 221 , 81 ,
217 , 191 , 177 , 37 , 145 , 216 , 84 , 56 , 65 , 190 , 163 , 138 , 186 , 157 , 9 , 23 , 189 , 8 , 188 , 69 , 204 , 29 , 22 , 114 , 79 , 175 , 59 , 202 , 107 , 231 , 96 , 91 ,
45 , 64 , 228 , 2 , 43 , 74 , 89 , 205 , 246 , 123 , 166 , 83 , 219 , 248 , 117 , 241 , 94 , 60 , 227 , 20 , 35 , 18 , 1 , 252 , 250 , 110 , 137 , 58 , 42 , 102 , 106 , 93 ,
101 , 105 , 193 , 77 , 39 , 119 , 223 , 73 , 51 , 218 , 78 , 100 , 21 , 247 , 41 , 214 , 170 , 185 , 237 , 130 , 12 , 24 , 92 , 180 , 16 , 178 , 235 , 4 , 240 , 158 , 57 , 197 ,
133 , 88 , 142 , 234 , 147 , 196 , 146 , 224 , 139 , 207 , 31 , 232 , 243 , 3 , 121 , 210 , 167 , 99 , 13 , 44 , 70 , 213 , 168 , 244 , 153 , 127 , 171 , 233 , 172 , 75 , 34 , 236 ,
113 , 25 , 149 , 134 , 53 , 222 , 122 , 80 , 195 , 254 , 27 , 169 , 255 , 242 , 143 , 159 , 225 , 135 , 230 , 151 , 48 , 33 , 72 , 10 , 95 , 103 , 253 , 184 , 52 , 125 , 206 , 144 ,
128 , 32 , 61 , 176 , 215 , 50 , 194 , 40 , 183 , 173 , 131 , 46 , 111 , 90 , 192 , 208 , 86 , 181 , 68 , 104 , 129 , 116 , 165 , 156 , 11 , 14 , 120 , 30 , 71 , 245 , 7 , 38 ]
2017-05-25 19:41:37 +00:00
2017-05-20 12:07:40 +00:00
2017-05-25 13:58:35 +00:00
def main ( args , seed = None ) :
2017-05-20 12:07:40 +00:00
start = time . clock ( )
2017-05-15 18:28:04 +00:00
# initialize the world
2017-10-28 22:34:37 +00:00
world = World ( args . shuffle , args . logic , args . mode , args . difficulty , args . goal , args . algorithm , not args . nodungeonitems , args . beatableonly , args . shuffleganon , args . quickswap , args . fastmenu , args . keysanity )
2017-05-20 12:07:40 +00:00
logger = logging . getLogger ( ' ' )
if seed is None :
random . seed ( None )
world . seed = random . randint ( 0 , 999999999 )
else :
2017-05-30 05:33:23 +00:00
world . seed = int ( seed )
2017-05-20 12:07:40 +00:00
random . seed ( world . seed )
2017-07-18 10:44:13 +00:00
logger . info ( ' ALttP Entrance Randomizer Version %s - Seed: %s \n \n ' % ( __version__ , world . seed ) )
2017-05-20 12:07:40 +00:00
2017-05-15 18:28:04 +00:00
create_regions ( world )
2017-11-04 18:23:57 +00:00
create_dungeons ( world )
2017-05-15 18:28:04 +00:00
2017-05-20 12:07:40 +00:00
logger . info ( ' Shuffling the World about. ' )
2017-07-18 10:44:13 +00:00
link_entrances ( world )
2017-05-20 12:07:40 +00:00
logger . info ( ' Calculating Access Rules. ' )
2017-07-18 10:44:13 +00:00
set_rules ( world )
2017-05-20 12:07:40 +00:00
2017-08-05 15:52:18 +00:00
logger . info ( ' Generating Item Pool. ' )
generate_itempool ( world )
2017-06-23 19:32:31 +00:00
logger . info ( ' Placing Dungeon Items. ' )
2017-05-20 12:07:40 +00:00
2017-10-15 20:34:46 +00:00
shuffled_locations = None
2017-10-15 17:52:42 +00:00
if args . algorithm == ' vt26 ' :
2017-10-15 20:34:46 +00:00
shuffled_locations = world . get_unfilled_locations ( )
random . shuffle ( shuffled_locations )
fill_dungeons_restrictive ( world , shuffled_locations )
2017-10-15 17:52:42 +00:00
else :
fill_dungeons ( world )
2017-05-20 12:07:40 +00:00
logger . info ( ' Fill the world. ' )
2017-05-25 13:58:35 +00:00
if args . algorithm == ' flood ' :
2017-05-20 12:07:40 +00:00
flood_items ( world ) # different algo, biased towards early game progress items
2017-06-03 19:28:02 +00:00
elif args . algorithm == ' vt21 ' :
distribute_items_cutoff ( world , 1 )
elif args . algorithm == ' vt22 ' :
2017-06-04 14:15:59 +00:00
distribute_items_cutoff ( world , 0.66 )
2017-06-03 19:28:02 +00:00
elif args . algorithm == ' freshness ' :
distribute_items_staleness ( world )
2017-08-01 17:07:44 +00:00
elif args . algorithm == ' vt25 ' :
2017-07-20 09:22:35 +00:00
distribute_items_restrictive ( world , 0 )
2017-10-15 17:52:42 +00:00
elif args . algorithm == ' vt26 ' :
2017-10-15 20:34:46 +00:00
distribute_items_restrictive ( world , random . randint ( 0 , 15 ) , shuffled_locations )
2017-06-03 19:28:02 +00:00
2017-05-20 12:07:40 +00:00
logger . info ( ' Calculating playthrough. ' )
2017-07-18 10:44:13 +00:00
create_playthrough ( world )
2017-05-20 12:07:40 +00:00
logger . info ( ' Patching ROM. ' )
2017-05-26 16:39:32 +00:00
if args . sprite is not None :
sprite = bytearray ( open ( args . sprite , ' rb ' ) . read ( ) )
else :
sprite = None
2017-11-04 18:23:57 +00:00
outfilebase = ' ER_ %s _ %s - %s - %s _ %s - %s %s %s %s %s _ %s ' % ( world . logic , world . difficulty , world . mode , world . goal , world . shuffle , world . algorithm , " -keysanity " if world . keysanity else " " , " -fastmenu " if world . fastmenu else " " , " -quickswap " if world . quickswap else " " , " -shuffleganon " if world . shuffle_ganon else " " , world . seed )
2017-05-20 12:07:40 +00:00
2017-06-04 11:09:47 +00:00
if not args . suppress_rom :
2017-07-14 12:37:34 +00:00
if args . jsonout :
rom = JsonRom ( )
else :
rom = LocalRom ( args . rom )
2017-08-01 17:43:46 +00:00
patch_rom ( world , rom , bytearray ( logic_hash ) , args . heartbeep , sprite )
2017-07-16 21:20:54 +00:00
if args . jsonout :
2017-07-18 10:44:13 +00:00
print ( json . dumps ( { ' patch ' : rom . patches , ' spoiler ' : world . spoiler . to_json ( ) } ) )
2017-07-16 21:20:54 +00:00
else :
rom . write_to_file ( args . jsonout or ' %s .sfc ' % outfilebase )
2017-06-04 11:09:47 +00:00
2017-07-16 21:20:54 +00:00
if args . create_spoiler and not args . jsonout :
2017-07-18 10:44:13 +00:00
world . spoiler . to_file ( ' %s _Spoiler.txt ' % outfilebase )
2017-05-20 12:07:40 +00:00
logger . info ( ' Done. Enjoy. ' )
logger . debug ( ' Total Time: %s ' % ( time . clock ( ) - start ) )
2017-05-15 18:28:04 +00:00
return world
2017-05-16 19:23:47 +00:00
def generate_itempool ( world ) :
2017-08-01 17:07:44 +00:00
if world . difficulty not in [ ' normal ' , ' timed ' , ' timed-ohko ' , ' timed-countdown ' ] or world . goal not in [ ' ganon ' , ' pedestal ' , ' dungeons ' , ' triforcehunt ' , ' crystals ' ] or world . mode not in [ ' open ' , ' standard ' , ' swordless ' ] :
2017-05-15 18:28:04 +00:00
raise NotImplementedError ( ' Not supported yet ' )
2017-05-25 13:58:35 +00:00
world . push_item ( ' Ganon ' , ItemFactory ( ' Triforce ' ) , False )
2017-11-06 03:08:36 +00:00
world . get_location ( ' Ganon ' ) . event = True
2017-06-17 12:40:37 +00:00
world . push_item ( ' Agahnim 1 ' , ItemFactory ( ' Beat Agahnim 1 ' ) , False )
world . get_location ( ' Agahnim 1 ' ) . event = True
world . push_item ( ' Agahnim 2 ' , ItemFactory ( ' Beat Agahnim 2 ' ) , False )
world . get_location ( ' Agahnim 2 ' ) . event = True
2017-05-15 18:28:04 +00:00
# set up item pool
2017-06-04 12:44:23 +00:00
if world . difficulty in [ ' timed ' , ' timed-countdown ' ] :
world . itempool = ItemFactory ( [ ' Arrow Upgrade (+5) ' ] * 2 + [ ' Bomb Upgrade (+5) ' ] * 2 + [ ' Arrow Upgrade (+10) ' ] * 3 + [ ' Bomb Upgrade (+10) ' ] * 3 +
2017-06-24 16:48:03 +00:00
[ ' Progressive Armor ' ] * 2 + [ ' Progressive Shield ' ] * 3 + [ ' Progressive Glove ' ] * 2 +
2017-06-04 12:44:23 +00:00
[ ' Bottle ' ] * 4 +
[ ' Bombos ' , ' Book of Mudora ' , ' Blue Boomerang ' , ' Bow ' , ' Bug Catching Net ' , ' Cane of Byrna ' , ' Cane of Somaria ' ,
' Ether ' , ' Fire Rod ' , ' Flippers ' , ' Ocarina ' , ' Hammer ' , ' Hookshot ' , ' Ice Rod ' , ' Lamp ' , ' Cape ' , ' Magic Powder ' ,
' Red Boomerang ' , ' Mushroom ' , ' Pegasus Boots ' , ' Quake ' , ' Shovel ' , ' Silver Arrows ' ] +
[ ' Sanctuary Heart Container ' ] + [ ' Rupees (100) ' ] * 2 + [ ' Boss Heart Container ' ] * 12 + [ ' Piece of Heart ' ] * 16 +
2017-08-01 15:25:08 +00:00
[ ' Rupees (50) ' ] * 8 + [ ' Rupees (300) ' ] * 6 + [ ' Rupees (20) ' ] * 4 +
[ ' Arrows (10) ' ] * 3 + [ ' Bombs (3) ' ] * 10 + [ ' Red Clock ' ] * 10 + [ ' Blue Clock ' ] * 10 + [ ' Green Clock ' ] * 20 )
2017-06-04 12:44:23 +00:00
world . clock_mode = ' stopwatch ' if world . difficulty == ' timed ' else ' countdown '
elif world . difficulty == ' timed-ohko ' :
world . itempool = ItemFactory ( [ ' Arrow Upgrade (+5) ' ] * 6 + [ ' Bomb Upgrade (+5) ' ] * 6 + [ ' Arrow Upgrade (+10) ' , ' Bomb Upgrade (+10) ' ] +
2017-06-24 16:48:03 +00:00
[ ' Progressive Armor ' ] * 2 + [ ' Progressive Shield ' ] * 3 + [ ' Progressive Glove ' ] * 2 +
2017-06-04 12:44:23 +00:00
[ ' Bottle ' ] * 4 +
[ ' Bombos ' , ' Book of Mudora ' , ' Blue Boomerang ' , ' Bow ' , ' Bug Catching Net ' , ' Cane of Byrna ' , ' Cane of Somaria ' ,
' Ether ' , ' Fire Rod ' , ' Flippers ' , ' Ocarina ' , ' Hammer ' , ' Hookshot ' , ' Ice Rod ' , ' Lamp ' , ' Cape ' , ' Magic Powder ' ,
' Red Boomerang ' , ' Mushroom ' , ' Pegasus Boots ' , ' Quake ' , ' Shovel ' , ' Silver Arrows ' ] +
[ ' Single Arrow ' , ' Sanctuary Heart Container ' ] + [ ' Rupees (100) ' ] * 3 + [ ' Boss Heart Container ' ] * 10 + [ ' Piece of Heart ' ] * 24 +
2017-08-01 15:25:08 +00:00
[ ' Rupees (50) ' ] * 7 + [ ' Rupees (300) ' ] * 7 + [ ' Rupees (20) ' ] * 5 +
[ ' Arrows (10) ' ] * 5 + [ ' Bombs (3) ' ] * 10 + [ ' Green Clock ' ] * 25 )
2017-06-04 12:44:23 +00:00
world . clock_mode = ' ohko '
else :
world . itempool = ItemFactory ( [ ' Arrow Upgrade (+5) ' ] * 6 + [ ' Bomb Upgrade (+5) ' ] * 6 + [ ' Arrow Upgrade (+10) ' , ' Bomb Upgrade (+10) ' ] +
2017-06-24 16:48:03 +00:00
[ ' Progressive Armor ' ] * 2 + [ ' Progressive Shield ' ] * 3 + [ ' Progressive Glove ' ] * 2 +
2017-06-04 12:44:23 +00:00
[ ' Bottle ' ] * 4 +
[ ' Bombos ' , ' Book of Mudora ' , ' Blue Boomerang ' , ' Bow ' , ' Bug Catching Net ' , ' Cane of Byrna ' , ' Cane of Somaria ' ,
' Ether ' , ' Fire Rod ' , ' Flippers ' , ' Ocarina ' , ' Hammer ' , ' Hookshot ' , ' Ice Rod ' , ' Lamp ' , ' Cape ' , ' Magic Powder ' ,
' Red Boomerang ' , ' Mushroom ' , ' Pegasus Boots ' , ' Quake ' , ' Shovel ' , ' Silver Arrows ' ] +
[ ' Single Arrow ' , ' Sanctuary Heart Container ' , ' Rupees (100) ' ] + [ ' Boss Heart Container ' ] * 10 + [ ' Piece of Heart ' ] * 24 +
2017-08-01 15:25:08 +00:00
[ ' Rupees (50) ' ] * 7 + [ ' Rupees (5) ' ] * 4 + [ ' Rupee (1) ' ] * 2 + [ ' Rupees (300) ' ] * 5 + [ ' Rupees (20) ' ] * 28 +
[ ' Arrows (10) ' ] * 5 + [ ' Bombs (3) ' ] * 10 )
2017-05-15 18:28:04 +00:00
2017-06-24 16:48:03 +00:00
if world . mode == ' swordless ' :
2017-10-14 18:45:59 +00:00
world . itempool . extend ( ItemFactory ( [ ' Rupees (20) ' ] * 4 ) )
2017-06-24 16:48:03 +00:00
elif world . mode == ' standard ' :
2017-10-29 03:42:35 +00:00
world . push_item ( ' Link \' s Uncle ' , ItemFactory ( ' Progressive Sword ' ) , False )
world . get_location ( ' Link \' s Uncle ' ) . event = True
2017-06-24 16:48:03 +00:00
world . itempool . extend ( ItemFactory ( [ ' Progressive Sword ' ] * 3 ) )
2017-05-16 19:23:47 +00:00
else :
2017-06-24 16:48:03 +00:00
world . itempool . extend ( ItemFactory ( [ ' Progressive Sword ' ] * 4 ) )
2017-05-16 19:23:47 +00:00
2017-05-22 17:52:50 +00:00
# provide mirror and pearl so you can avoid fake DW/LW and do dark world exploration as intended by algorithm, for now
if world . shuffle == ' insanity ' :
2017-10-29 03:42:35 +00:00
world . push_item ( ' Links House ' , ItemFactory ( ' Magic Mirror ' ) , False )
world . get_location ( ' Links House ' ) . event = True
world . push_item ( ' Sanctuary ' , ItemFactory ( ' Moon Pearl ' ) , False )
world . get_location ( ' Sanctuary ' ) . event = True
2017-05-22 17:52:50 +00:00
else :
2017-05-25 13:58:35 +00:00
world . itempool . extend ( ItemFactory ( [ ' Magic Mirror ' , ' Moon Pearl ' ] ) )
2017-05-22 17:52:50 +00:00
2017-05-16 19:23:47 +00:00
if world . goal == ' pedestal ' :
2017-10-29 03:42:35 +00:00
world . push_item ( ' Master Sword Pedestal ' , ItemFactory ( ' Triforce ' ) , False )
2017-11-06 03:08:36 +00:00
world . get_location ( ' Master Sword Pedestal ' ) . event = True
2017-06-04 12:44:23 +00:00
elif world . goal == ' triforcehunt ' :
2017-08-01 17:07:44 +00:00
world . treasure_hunt_count = 20
2017-06-04 12:44:23 +00:00
world . treasure_hunt_icon = ' Triforce Piece '
2017-08-01 17:07:44 +00:00
world . itempool . extend ( ItemFactory ( [ ' Triforce Piece ' ] * 30 ) )
2017-05-16 19:23:47 +00:00
2017-11-04 18:23:57 +00:00
world . itempool . append ( ItemFactory ( ' Magic Upgrade (1/2) ' ) )
2017-05-15 18:28:04 +00:00
# shuffle medallions
mm_medallion = [ ' Ether ' , ' Quake ' , ' Bombos ' ] [ random . randint ( 0 , 2 ) ]
tr_medallion = [ ' Ether ' , ' Quake ' , ' Bombos ' ] [ random . randint ( 0 , 2 ) ]
world . required_medallions = ( mm_medallion , tr_medallion )
2017-08-05 15:52:18 +00:00
# distribute crystals
crystals = ItemFactory ( [ ' Red Pendant ' , ' Blue Pendant ' , ' Green Pendant ' , ' Crystal 1 ' , ' Crystal 2 ' , ' Crystal 3 ' , ' Crystal 4 ' , ' Crystal 7 ' , ' Crystal 5 ' , ' Crystal 6 ' ] )
2017-11-09 00:23:21 +00:00
crystal_locations = [ world . get_location ( ' Turtle Rock - Prize ' ) , world . get_location ( ' Eastern Palace - Prize ' ) , world . get_location ( ' Desert Palace - Prize ' ) , world . get_location ( ' Tower of Hera - Prize ' ) , world . get_location ( ' Palace of Darkness - Prize ' ) ,
2017-10-29 03:42:35 +00:00
world . get_location ( ' Thieves Town - Prize ' ) , world . get_location ( ' Skull Woods - Prize ' ) , world . get_location ( ' Swamp Palace - Prize ' ) , world . get_location ( ' Ice Palace - Prize ' ) ,
world . get_location ( ' Misery Mire - Prize ' ) ]
2017-08-05 15:52:18 +00:00
random . shuffle ( crystal_locations )
fill_restrictive ( world , world . get_all_state ( keys = True ) , crystal_locations , crystals )
2017-05-15 18:28:04 +00:00
2017-05-16 19:23:47 +00:00
def copy_world ( world ) :
# ToDo: Not good yet
2017-10-28 22:34:37 +00:00
ret = World ( world . shuffle , world . logic , world . mode , world . difficulty , world . goal , world . algorithm , world . place_dungeon_items , world . check_beatable_only , world . shuffle_ganon , world . quickswap , world . fastmenu , world . keysanity )
2017-05-16 19:23:47 +00:00
ret . required_medallions = list ( world . required_medallions )
2017-05-26 07:52:38 +00:00
ret . swamp_patch_required = world . swamp_patch_required
2017-08-01 17:08:46 +00:00
ret . ganon_at_pyramid = world . ganon_at_pyramid
2017-06-04 11:10:22 +00:00
ret . treasure_hunt_count = world . treasure_hunt_count
ret . treasure_hunt_icon = world . treasure_hunt_icon
ret . sewer_light_cone = world . sewer_light_cone
ret . light_world_light_cone = world . light_world_light_cone
ret . dark_world_light_cone = world . dark_world_light_cone
2017-06-19 19:31:08 +00:00
ret . seed = world . seed
2017-07-17 21:13:39 +00:00
ret . can_access_trock_eyebridge = world . can_access_trock_eyebridge
2017-05-16 19:23:47 +00:00
create_regions ( ret )
2017-10-15 16:16:07 +00:00
create_dungeons ( ret )
2017-05-16 19:23:47 +00:00
# connect copied world
for region in world . regions :
for entrance in region . entrances :
ret . get_entrance ( entrance . name ) . connect ( ret . get_region ( region . name ) )
# fill locations
for location in world . get_locations ( ) :
if location . item is not None :
2017-10-15 16:16:07 +00:00
item = Item ( location . item . name , location . item . advancement , location . item . priority , location . item . type )
2017-05-16 19:23:47 +00:00
ret . get_location ( location . name ) . item = item
item . location = ret . get_location ( location . name )
2017-06-17 12:40:37 +00:00
if location . event :
ret . get_location ( location . name ) . event = True
2017-05-16 19:23:47 +00:00
# copy remaining itempool. No item in itempool should have an assigned location
for item in world . itempool :
2017-10-15 16:16:07 +00:00
ret . itempool . append ( Item ( item . name , item . advancement , item . priority , item . type ) )
2017-05-16 19:23:47 +00:00
# copy progress items in state
ret . state . prog_items = list ( world . state . prog_items )
2017-07-17 20:29:32 +00:00
set_rules ( ret )
2017-05-16 19:23:47 +00:00
return ret
def create_playthrough ( world ) :
# create a copy as we will modify it
2017-06-04 14:15:59 +00:00
old_world = world
2017-05-16 19:23:47 +00:00
world = copy_world ( world )
2017-05-26 07:53:34 +00:00
2017-06-04 12:44:23 +00:00
# in treasure hunt and pedestal goals, ganon is invincible
2017-08-01 17:07:44 +00:00
if world . goal in [ ' pedestal ' , ' triforcehunt ' ] :
2017-05-26 07:53:34 +00:00
world . get_location ( ' Ganon ' ) . item = None
2017-05-16 19:23:47 +00:00
2017-06-23 20:15:29 +00:00
# if we only check for beatable, we can do this sanity check first before writing down spheres
if world . check_beatable_only and not world . can_beat_game ( ) :
raise RuntimeError ( ' Cannot beat game. Something went terribly wrong here! ' )
2017-05-16 19:23:47 +00:00
# get locations containing progress items
2017-10-28 22:34:37 +00:00
prog_locations = [ location for location in world . get_locations ( ) if location . item is not None and ( location . item . advancement or ( location . item . key and world . keysanity ) ) ]
2017-05-16 19:23:47 +00:00
collection_spheres = [ ]
state = CollectionState ( world )
sphere_candidates = list ( prog_locations )
2017-05-26 07:55:24 +00:00
logging . getLogger ( ' ' ) . debug ( ' Building up collection spheres. ' )
2017-05-16 19:23:47 +00:00
while sphere_candidates :
2017-10-28 22:34:37 +00:00
if not world . keysanity :
state . sweep_for_events ( key_only = True )
2017-06-24 09:11:56 +00:00
2017-05-16 19:23:47 +00:00
sphere = [ ]
# build up spheres of collection radius. Everything in each sphere is independent from each other in dependencies and only depends on lower spheres
for location in sphere_candidates :
if state . can_reach ( location ) :
sphere . append ( location )
for location in sphere :
sphere_candidates . remove ( location )
2017-06-17 12:40:37 +00:00
state . collect ( location . item , True )
2017-05-16 19:23:47 +00:00
collection_spheres . append ( sphere )
2017-05-26 07:55:24 +00:00
logging . getLogger ( ' ' ) . debug ( ' Calculated sphere %i , containing %i of %i progress items. ' % ( len ( collection_spheres ) , len ( sphere ) , len ( prog_locations ) ) )
if not sphere :
2017-06-23 20:15:29 +00:00
logging . getLogger ( ' ' ) . debug ( ' The following items could not be reached: %s ' % [ ' %s at %s ' % ( location . item . name , location . name ) for location in sphere_candidates ] )
if not world . check_beatable_only :
raise RuntimeError ( ' Not all progression items reachable. Something went terribly wrong here. ' )
else :
break
2017-05-26 07:55:24 +00:00
2017-05-16 19:23:47 +00:00
# in the second phase, we cull each sphere such that the game is still beatable, reducing each range of influence to the bare minimum required inside it
for sphere in reversed ( collection_spheres ) :
to_delete = [ ]
for location in sphere :
# we remove the item at location and check if game is still beatable
2017-05-26 07:55:24 +00:00
logging . getLogger ( ' ' ) . debug ( ' Checking if %s is required to beat the game. ' % location . item . name )
2017-05-16 19:23:47 +00:00
old_item = location . item
location . item = None
state . remove ( old_item )
world . _item_cache = { } # need to invalidate
if world . can_beat_game ( ) :
to_delete . append ( location )
else :
# still required, got to keep it around
location . item = old_item
# cull entries in spheres for spoiler walkthrough at end
for location in to_delete :
sphere . remove ( location )
# we are now down to just the required progress items in collection_spheres in a minimum number of spheres. As a cleanup, we right trim empty spheres (can happen if we have multiple triforces)
collection_spheres = [ sphere for sphere in collection_spheres if sphere ]
2017-06-04 14:15:59 +00:00
# store the required locations for statistical analysis
old_world . required_locations = [ location . name for sphere in collection_spheres for location in sphere ]
2017-05-16 19:23:47 +00:00
# we can finally output our playthrough
2017-07-18 10:44:13 +00:00
old_world . spoiler . playthrough = OrderedDict ( [ ( str ( i + 1 ) , { str ( location ) : str ( location . item ) for location in sphere } ) for i , sphere in enumerate ( collection_spheres ) ] )