2017-05-25 13:58:35 +00:00
from BaseClasses import World , CollectionState , Item
2017-12-13 14:51:53 +00:00
from Regions import create_regions , mark_light_world_regions
2017-05-25 10:09:50 +00:00
from EntranceShuffle import link_entrances
2017-11-13 05:29:42 +00:00
from Rom import patch_rom , Sprite , 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-11-10 10:17:28 +00:00
from ItemList import generate_itempool
2017-11-28 14:36:32 +00:00
from Utils import output_path
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-12-02 22:15:25 +00:00
__version__ = ' 0.5.1-dev '
logic_hash = [ 117 , 227 , 77 , 12 , 94 , 219 , 67 , 70 , 58 , 42 , 7 , 75 , 132 , 55 , 130 , 97 , 235 , 46 , 206 , 185 , 243 , 64 , 109 , 161 , 107 , 91 , 224 , 142 , 25 , 84 , 4 , 78 ,
160 , 245 , 143 , 18 , 251 , 114 , 165 , 157 , 13 , 26 , 119 , 92 , 188 , 216 , 27 , 39 , 76 , 238 , 152 , 113 , 231 , 193 , 191 , 103 , 118 , 182 , 213 , 134 , 41 , 90 , 246 , 82 ,
57 , 225 , 150 , 139 , 99 , 151 , 184 , 11 , 85 , 209 , 144 , 147 , 47 , 56 , 129 , 247 , 121 , 177 , 79 , 1 , 215 , 207 , 126 , 136 , 105 , 100 , 180 , 5 , 2 , 14 , 153 , 6 ,
163 , 192 , 198 , 88 , 98 , 174 , 149 , 201 , 249 , 200 , 158 , 116 , 196 , 80 , 220 , 31 , 111 , 214 , 194 , 248 , 221 , 167 , 250 , 115 , 38 , 10 , 32 , 218 , 133 , 19 , 253 , 122 ,
239 , 16 , 52 , 48 , 156 , 205 , 127 , 3 , 138 , 237 , 234 , 190 , 37 , 112 , 189 , 86 , 223 , 236 , 195 , 54 , 71 , 181 , 43 , 49 , 226 , 255 , 0 , 135 , 186 , 203 , 175 , 87 ,
21 , 229 , 120 , 124 , 145 , 171 , 252 , 155 , 22 , 62 , 199 , 51 , 35 , 179 , 159 , 44 , 69 , 30 , 172 , 242 , 140 , 74 , 9 , 83 , 183 , 93 , 202 , 137 , 108 , 241 , 173 , 23 ,
164 , 45 , 222 , 232 , 166 , 176 , 230 , 63 , 154 , 96 , 170 , 34 , 66 , 50 , 17 , 211 , 95 , 53 , 208 , 244 , 36 , 123 , 81 , 187 , 106 , 131 , 169 , 29 , 104 , 72 , 101 , 141 ,
68 , 24 , 168 , 125 , 217 , 240 , 15 , 162 , 148 , 8 , 40 , 102 , 33 , 89 , 128 , 61 , 210 , 204 , 73 , 228 , 59 , 146 , 28 , 110 , 233 , 178 , 254 , 65 , 197 , 20 , 212 , 60 ]
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-11-28 10:54:14 +00:00
world = World ( args . shuffle , args . logic , args . mode , args . difficulty , args . timer , args . progressive , args . goal , args . algorithm , not args . nodungeonitems , args . beatableonly , args . shuffleganon , args . quickswap , args . fastmenu , args . disablemusic , 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-12-13 14:51:53 +00:00
mark_light_world_regions ( 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-11-09 10:45:13 +00:00
if args . algorithm == ' vt26 ' or args . keysanity :
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-11-12 00:05:06 +00:00
elif args . algorithm == ' balanced ' :
distribute_items_restrictive ( world , random . randint ( 0 , 15 ) )
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 :
2017-12-09 00:28:22 +00:00
if isinstance ( args . sprite , Sprite ) :
sprite = args . sprite
else :
sprite = Sprite ( args . sprite )
2017-05-26 16:39:32 +00:00
else :
sprite = None
2017-11-12 00:05:06 +00:00
outfilebase = ' ER_ %s _ %s - %s - %s %s _ %s - %s %s %s %s %s %s _ %s ' % ( world . logic , world . difficulty , world . mode , world . goal , " " if world . timer in [ ' none ' , ' display ' ] else " - " + world . timer , world . shuffle , world . algorithm , " -keysanity " if world . keysanity else " " , " -progressives_ " + world . progressive if world . progressive in [ ' off ' , ' random ' ] 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 :
2017-11-28 14:36:32 +00:00
rom . write_to_file ( args . jsonout or output_path ( ' %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-11-28 14:36:32 +00:00
world . spoiler . to_file ( output_path ( ' %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 copy_world ( world ) :
# ToDo: Not good yet
2017-11-28 10:54:14 +00:00
ret = World ( world . shuffle , world . logic , world . mode , world . difficulty , world . timer , world . progressive , world . goal , world . algorithm , world . place_dungeon_items , world . check_beatable_only , world . shuffle_ganon , world . quickswap , world . fastmenu , world . disable_music , 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 :
2017-12-13 14:51:53 +00:00
copied_region = ret . get_region ( region . name )
copied_region . is_light_world = region . is_light_world
2017-05-16 19:23:47 +00:00
for entrance in region . entrances :
2017-12-13 14:51:53 +00:00
ret . get_entrance ( entrance . name ) . connect ( copied_region )
2017-05-16 19:23:47 +00:00
# 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-11-19 01:43:37 +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 ) ] )