Compare commits
20 Commits
6a58773dad
...
b1a71ef9b1
| Author | SHA1 | Date | |
|---|---|---|---|
| b1a71ef9b1 | |||
| 699d395b3d | |||
| 6859f5cd24 | |||
| 8a7cc02282 | |||
|
|
ac17d0ead8 | ||
|
|
ad8dff3b2d | ||
|
|
f1aef298ef | ||
| 950df6b1d0 | |||
|
|
2a24048e87 | ||
|
|
afe888a87a | ||
| dab247807f | |||
|
|
09254850d3 | ||
|
|
31f634396a | ||
|
|
daa54e8aad | ||
|
|
b16777ede7 | ||
| fb96d04e56 | |||
| 54981b5c51 | |||
| f43fb18bad | |||
| ff4f546ec9 | |||
|
|
83254f4c78 |
@@ -1,5 +1,13 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
# 0.7.0.2
|
||||||
|
- Fixed money error for bombbag/take-anys
|
||||||
|
- Fixed palette issue for map check/flute menu
|
||||||
|
- Fixed issue with dungeon icons showing in opposite world in Inverted
|
||||||
|
- Fixed Links position in map checks when in Special OW areas
|
||||||
|
- Fixed buffered sword issue in OW Shuffle water transitions
|
||||||
|
- Added PlasmaKappa tribute TF room text
|
||||||
|
|
||||||
# 0.7.0.1
|
# 0.7.0.1
|
||||||
- Fixed buggy sprites in post-Aga Zora's Domain
|
- Fixed buggy sprites in post-Aga Zora's Domain
|
||||||
- Fixed L/R map switch when in special OW screens
|
- Fixed L/R map switch when in special OW screens
|
||||||
|
|||||||
2
Fill.py
2
Fill.py
@@ -1112,7 +1112,7 @@ def balance_money_progression(world):
|
|||||||
slot = shop_to_location_table[location.parent_region.name].index(location.name)
|
slot = shop_to_location_table[location.parent_region.name].index(location.name)
|
||||||
shop = location.parent_region.shop
|
shop = location.parent_region.shop
|
||||||
shop_item = shop.inventory[slot]
|
shop_item = shop.inventory[slot]
|
||||||
if location.item and interesting_item(location, location.item, world, location.item.player):
|
if shop_item and location.item and interesting_item(location, location.item, world, location.item.player):
|
||||||
if location.item.name.startswith('Rupee') and loc_player == location.item.player:
|
if location.item.name.startswith('Rupee') and loc_player == location.item.player:
|
||||||
if shop_item['price'] < rupee_chart[location.item.name]:
|
if shop_item['price'] < rupee_chart[location.item.name]:
|
||||||
wallet[loc_player] -= shop_item['price'] # will get picked up in the location_free block
|
wallet[loc_player] -= shop_item['price'] # will get picked up in the location_free block
|
||||||
|
|||||||
19
ItemList.py
19
ItemList.py
@@ -43,8 +43,8 @@ alwaysitems = ['Bombos', 'Book of Mudora', 'Cane of Somaria', 'Ether', 'Fire Rod
|
|||||||
progressivegloves = ['Progressive Glove'] * 2
|
progressivegloves = ['Progressive Glove'] * 2
|
||||||
basicgloves = ['Power Glove', 'Titans Mitts']
|
basicgloves = ['Power Glove', 'Titans Mitts']
|
||||||
|
|
||||||
normalbottles = ['Bottle', 'Bottle (Red Potion)', 'Bottle (Green Potion)', 'Bottle (Blue Potion)', 'Bottle (Fairy)', 'Bottle (Bee)', 'Bottle (Good Bee)']
|
normalbottles = ['Bottle (Red Potion)', 'Bottle (Green Potion)', 'Bottle (Blue Potion)', 'Bottle (Fairy)', 'Bottle (Bee)', 'Bottle (Good Bee)']
|
||||||
hardbottles = ['Bottle', 'Bottle (Red Potion)', 'Bottle (Green Potion)', 'Bottle (Blue Potion)', 'Bottle (Bee)', 'Bottle (Good Bee)']
|
hardbottles = ['Bottle (Red Potion)', 'Bottle (Green Potion)', 'Bottle (Blue Potion)', 'Bottle (Bee)', 'Bottle (Good Bee)']
|
||||||
|
|
||||||
normalbaseitems = (['Magic Upgrade (1/2)', 'Single Arrow', 'Sanctuary Heart Container', 'Arrows (10)', 'Bombs (10)'] +
|
normalbaseitems = (['Magic Upgrade (1/2)', 'Single Arrow', 'Sanctuary Heart Container', 'Arrows (10)', 'Bombs (10)'] +
|
||||||
['Rupees (300)'] * 4 + ['Boss Heart Container'] * 10 + ['Piece of Heart'] * 24)
|
['Rupees (300)'] * 4 + ['Boss Heart Container'] * 10 + ['Piece of Heart'] * 24)
|
||||||
@@ -364,9 +364,10 @@ def generate_itempool(world, player):
|
|||||||
world.escape_assist[player].append('bombs')
|
world.escape_assist[player].append('bombs')
|
||||||
|
|
||||||
for (location, item) in placed_items.items():
|
for (location, item) in placed_items.items():
|
||||||
world.push_item(world.get_location(location, player), ItemFactory(item, player), False)
|
loc = world.get_location(location, player)
|
||||||
world.get_location(location, player).event = True
|
world.push_item(loc, ItemFactory(item, player), False)
|
||||||
world.get_location(location, player).locked = True
|
loc.event = True
|
||||||
|
loc.locked = True
|
||||||
|
|
||||||
if world.shopsanity[player] and not skip_pool_adjustments:
|
if world.shopsanity[player] and not skip_pool_adjustments:
|
||||||
for shop in world.shops[player]:
|
for shop in world.shops[player]:
|
||||||
@@ -463,11 +464,11 @@ def generate_itempool(world, player):
|
|||||||
|
|
||||||
# shuffle bottle refills
|
# shuffle bottle refills
|
||||||
if world.difficulty[player] in ['hard', 'expert']:
|
if world.difficulty[player] in ['hard', 'expert']:
|
||||||
waterfall_bottle = hardbottles[random.randint(0, 5)]
|
waterfall_bottle = hardbottles[random.randint(0, 4)]
|
||||||
pyramid_bottle = hardbottles[random.randint(0, 5)]
|
pyramid_bottle = hardbottles[random.randint(0, 4)]
|
||||||
else:
|
else:
|
||||||
waterfall_bottle = normalbottles[random.randint(0, 6)]
|
waterfall_bottle = normalbottles[random.randint(0, 5)]
|
||||||
pyramid_bottle = normalbottles[random.randint(0, 6)]
|
pyramid_bottle = normalbottles[random.randint(0, 5)]
|
||||||
world.bottle_refills[player] = (waterfall_bottle, pyramid_bottle)
|
world.bottle_refills[player] = (waterfall_bottle, pyramid_bottle)
|
||||||
|
|
||||||
set_up_shops(world, player)
|
set_up_shops(world, player)
|
||||||
|
|||||||
2
Main.py
2
Main.py
@@ -50,7 +50,6 @@ from Items import ItemFactory
|
|||||||
from KeyDoorShuffle import validate_key_placement
|
from KeyDoorShuffle import validate_key_placement
|
||||||
from OverworldGlitchRules import create_owg_connections
|
from OverworldGlitchRules import create_owg_connections
|
||||||
from OverworldShuffle import (
|
from OverworldShuffle import (
|
||||||
create_dynamic_flute_exits,
|
|
||||||
create_dynamic_mirror_exits,
|
create_dynamic_mirror_exits,
|
||||||
link_overworld,
|
link_overworld,
|
||||||
update_world_regions,
|
update_world_regions,
|
||||||
@@ -86,6 +85,7 @@ from source.item.FillUtil import (
|
|||||||
verify_item_pool_config,
|
verify_item_pool_config,
|
||||||
)
|
)
|
||||||
from source.overworld.EntranceShuffle2 import link_entrances_new
|
from source.overworld.EntranceShuffle2 import link_entrances_new
|
||||||
|
from source.overworld.FluteShuffle import create_dynamic_flute_exits
|
||||||
from source.rom.DataTables import init_data_tables
|
from source.rom.DataTables import init_data_tables
|
||||||
from source.tools.BPS import create_bps_from_data
|
from source.tools.BPS import create_bps_from_data
|
||||||
from UnderworldGlitchRules import (
|
from UnderworldGlitchRules import (
|
||||||
|
|||||||
@@ -25,6 +25,11 @@ from OWEdges import (
|
|||||||
)
|
)
|
||||||
from Regions import mark_light_dark_world_regions
|
from Regions import mark_light_dark_world_regions
|
||||||
from source.overworld.EntranceShuffle2 import connect_simple
|
from source.overworld.EntranceShuffle2 import connect_simple
|
||||||
|
from source.overworld.FluteShuffle import (
|
||||||
|
default_flute_connections,
|
||||||
|
flute_data,
|
||||||
|
shuffle_flute_spots,
|
||||||
|
)
|
||||||
from Utils import bidict
|
from Utils import bidict
|
||||||
|
|
||||||
parallel_links_new = None # needs to be globally available, reset every new generation/player
|
parallel_links_new = None # needs to be globally available, reset every new generation/player
|
||||||
@@ -349,6 +354,7 @@ def link_overworld(world, player):
|
|||||||
world.owcrossededges[player].extend(edge_set)
|
world.owcrossededges[player].extend(edge_set)
|
||||||
assert len(world.owcrossededges[player]) == len(set(world.owcrossededges[player])), "Same edge candidate added to crossed edges"
|
assert len(world.owcrossededges[player]) == len(set(world.owcrossededges[player])), "Same edge candidate added to crossed edges"
|
||||||
|
|
||||||
|
# TODO: Someday don't force parallels also? Keeping for now to ensure full reachability
|
||||||
for edge in copy.deepcopy(world.owcrossededges[player]):
|
for edge in copy.deepcopy(world.owcrossededges[player]):
|
||||||
if edge in parallel_links_new:
|
if edge in parallel_links_new:
|
||||||
if parallel_links_new[edge] not in world.owcrossededges[player]:
|
if parallel_links_new[edge] not in world.owcrossededges[player]:
|
||||||
@@ -587,169 +593,8 @@ def link_overworld(world, player):
|
|||||||
|
|
||||||
# flute shuffle
|
# flute shuffle
|
||||||
logging.getLogger('').debug('Shuffling flute spots')
|
logging.getLogger('').debug('Shuffling flute spots')
|
||||||
def connect_flutes(flute_destinations):
|
shuffle_flute_spots(world, player)
|
||||||
for o in range(0, len(flute_destinations)):
|
|
||||||
owid = flute_destinations[o]
|
|
||||||
regions = flute_data[owid][0]
|
|
||||||
if not world.is_tile_swapped(owid, player):
|
|
||||||
connect_simple(world, 'Flute Spot ' + str(o + 1), regions[0], player)
|
|
||||||
else:
|
|
||||||
connect_simple(world, 'Flute Spot ' + str(o + 1), regions[1], player)
|
|
||||||
|
|
||||||
if world.owFluteShuffle[player] == 'vanilla':
|
|
||||||
flute_spots = default_flute_connections.copy()
|
|
||||||
sort_flute_spots(world, player, flute_spots)
|
|
||||||
world.owflutespots[player] = flute_spots
|
|
||||||
connect_flutes(flute_spots)
|
|
||||||
else:
|
|
||||||
flute_spots = 8
|
|
||||||
flute_pool = list(flute_data.keys())
|
|
||||||
new_spots = list()
|
|
||||||
ignored_regions = set()
|
|
||||||
used_flute_regions = []
|
|
||||||
forbidden_spots = []
|
|
||||||
forbidden_regions = []
|
|
||||||
|
|
||||||
def addSpot(owid, ignore_proximity, forced):
|
|
||||||
if world.owFluteShuffle[player] == 'balanced':
|
|
||||||
def getIgnored(regionname, base_owid, owid):
|
|
||||||
region = world.get_region(regionname, player)
|
|
||||||
for exit in region.exits:
|
|
||||||
if exit.connected_region is not None and exit.connected_region.type in [RegionType.LightWorld, RegionType.DarkWorld] and exit.connected_region.name not in new_ignored:
|
|
||||||
if exit.connected_region.name in OWTileRegions and (OWTileRegions[exit.connected_region.name] in [base_owid, owid] or OWTileRegions[regionname] == base_owid):
|
|
||||||
new_ignored.add(exit.connected_region.name)
|
|
||||||
getIgnored(exit.connected_region.name, base_owid, OWTileRegions[exit.connected_region.name])
|
|
||||||
if regionname in one_way_ledges:
|
|
||||||
for ledge_region in one_way_ledges[regionname]:
|
|
||||||
if ledge_region not in new_ignored:
|
|
||||||
new_ignored.add(ledge_region)
|
|
||||||
getIgnored(ledge_region, base_owid, OWTileRegions[ledge_region])
|
|
||||||
|
|
||||||
if not world.is_tile_swapped(owid, player):
|
|
||||||
new_region = flute_data[owid][0][0]
|
|
||||||
else:
|
|
||||||
new_region = flute_data[owid][0][1]
|
|
||||||
|
|
||||||
if new_region in ignored_regions and not forced:
|
|
||||||
return False
|
|
||||||
|
|
||||||
new_ignored = {new_region}
|
|
||||||
getIgnored(new_region, OWTileRegions[new_region], OWTileRegions[new_region])
|
|
||||||
if not ignore_proximity and not forced and random.randint(0, 31) != 0 and new_ignored.intersection(ignored_regions):
|
|
||||||
return False
|
|
||||||
ignored_regions.update(new_ignored)
|
|
||||||
if owid in flute_pool:
|
|
||||||
flute_pool.remove(owid)
|
|
||||||
if ignore_proximity and not forced:
|
|
||||||
logging.getLogger('').warning(f'Warning: Adding flute spot within proximity: {hex(owid)}')
|
|
||||||
logging.getLogger('').debug(f'Placing flute at: {hex(owid)}')
|
|
||||||
new_spots.append(owid)
|
|
||||||
else:
|
|
||||||
# TODO: Inspect later, seems to happen only with 'random' flute shuffle
|
|
||||||
logging.getLogger('').warning(f'Warning: Attempted to place flute spot not in pool: {hex(owid)}')
|
|
||||||
return True
|
|
||||||
|
|
||||||
if world.customizer:
|
|
||||||
custom_spots = world.customizer.get_owflutespots()
|
|
||||||
if custom_spots and player in custom_spots:
|
|
||||||
if 'force' in custom_spots[player]:
|
|
||||||
for id in custom_spots[player]['force']:
|
|
||||||
owid = id & 0xBF
|
|
||||||
addSpot(owid, True, True)
|
|
||||||
flute_spots -= 1
|
|
||||||
if not world.is_tile_swapped(owid, player):
|
|
||||||
used_flute_regions.append(flute_data[owid][0][0])
|
|
||||||
else:
|
|
||||||
used_flute_regions.append(flute_data[owid][0][1])
|
|
||||||
if 'forbid' in custom_spots[player]:
|
|
||||||
for id in custom_spots[player]['forbid']:
|
|
||||||
owid = id & 0xBF
|
|
||||||
if owid not in new_spots:
|
|
||||||
forbidden_spots.append(owid)
|
|
||||||
if not world.is_tile_swapped(owid, player):
|
|
||||||
forbidden_regions.append(flute_data[owid][0][0])
|
|
||||||
else:
|
|
||||||
forbidden_regions.append(flute_data[owid][0][1])
|
|
||||||
|
|
||||||
# determine sectors (isolated groups of regions) to place flute spots
|
|
||||||
flute_regions = {(f[0][0] if (o not in world.owswaps[player][0]) != (world.mode[player] == 'inverted') else f[0][1]) : o for o, f in flute_data.items() if o not in new_spots and o not in forbidden_spots}
|
|
||||||
flute_sectors = [(len([r for l in s for r in l]), [r for l in s for r in l if r in flute_regions]) for s in world.owsectors[player]]
|
|
||||||
flute_sectors = [s for s in flute_sectors if len(s[1]) > 0]
|
|
||||||
region_total = sum([c for c,_ in flute_sectors])
|
|
||||||
sector_total = len(flute_sectors)
|
|
||||||
empty_sector_total = 0
|
|
||||||
sector_has_spot = []
|
|
||||||
|
|
||||||
# determine which sectors still need a flute spot
|
|
||||||
for sector in flute_sectors:
|
|
||||||
already_has_spot = any(region in sector for region in used_flute_regions)
|
|
||||||
sector_has_spot.append(already_has_spot)
|
|
||||||
if not already_has_spot:
|
|
||||||
empty_sector_total += 1
|
|
||||||
if flute_spots < empty_sector_total:
|
|
||||||
logging.getLogger('').warning(f'Warning: Not every sector can have a flute spot, generation might fail')
|
|
||||||
# pretend like some of the empty sectors already have a flute spot, don't know if they will be reachable
|
|
||||||
for i in range(len(flute_sectors)):
|
|
||||||
if not sector_has_spot[i]:
|
|
||||||
sector_has_spot[i] = True
|
|
||||||
empty_sector_total -= 1
|
|
||||||
if flute_spots == empty_sector_total:
|
|
||||||
break
|
|
||||||
|
|
||||||
# distribute flute spots for each sector
|
|
||||||
for i in range(len(flute_sectors)):
|
|
||||||
sector = flute_sectors[i]
|
|
||||||
sector_total -= 1
|
|
||||||
if not sector_has_spot[i]:
|
|
||||||
empty_sector_total -= 1
|
|
||||||
spots_to_place = min(flute_spots - empty_sector_total, max(0 if sector_has_spot[i] else 1, round((sector[0] * (flute_spots - sector_total) / region_total) + 0.5)))
|
|
||||||
target_spots = len(new_spots) + spots_to_place
|
|
||||||
logging.getLogger('').debug(f'Sector of {sector[0]} regions gets {spots_to_place} spot(s)')
|
|
||||||
|
|
||||||
if 0x30 in flute_pool and 0x30 not in forbidden_spots and len(new_spots) < target_spots and ('Desert Teleporter Ledge' in sector[1] or 'Mire Teleporter Ledge' in sector[1]):
|
|
||||||
addSpot(0x30, True, True) # guarantee desert/mire access
|
|
||||||
|
|
||||||
random.shuffle(sector[1])
|
|
||||||
f = 0
|
|
||||||
t = 0
|
|
||||||
while len(new_spots) < target_spots:
|
|
||||||
if f >= len(sector[1]):
|
|
||||||
f = 0
|
|
||||||
t += 1
|
|
||||||
if t > 5:
|
|
||||||
raise GenerationException('Infinite loop detected in flute shuffle')
|
|
||||||
owid = flute_regions[sector[1][f]]
|
|
||||||
if owid not in new_spots and owid not in forbidden_spots:
|
|
||||||
addSpot(owid, t > 0, False)
|
|
||||||
f += 1
|
|
||||||
|
|
||||||
region_total -= sector[0]
|
|
||||||
flute_spots -= spots_to_place
|
|
||||||
|
|
||||||
# connect new flute spots
|
|
||||||
sort_flute_spots(world, player, new_spots)
|
|
||||||
world.owflutespots[player] = new_spots
|
|
||||||
connect_flutes(new_spots)
|
|
||||||
|
|
||||||
# update spoiler
|
|
||||||
s = list(map(lambda x: ' ' if x not in new_spots else 'F', [i for i in range(0x40)]))
|
|
||||||
text_output = flute_spoiler_table.replace('s', '%s') % ( s[0x02], s[0x07],
|
|
||||||
s[0x00], s[0x03], s[0x05],
|
|
||||||
s[0x00], s[0x02],s[0x03], s[0x05], s[0x07], s[0x0a], s[0x0f],
|
|
||||||
s[0x0a], s[0x0f],
|
|
||||||
s[0x10],s[0x11],s[0x12],s[0x13],s[0x14],s[0x15],s[0x16],s[0x17], s[0x10],s[0x11],s[0x12],s[0x13],s[0x14],s[0x15],s[0x16],s[0x17],
|
|
||||||
s[0x18], s[0x1a],s[0x1b], s[0x1d],s[0x1e],
|
|
||||||
s[0x22], s[0x25], s[0x1a], s[0x1d],
|
|
||||||
s[0x28],s[0x29],s[0x2a],s[0x2b],s[0x2c],s[0x2d],s[0x2e],s[0x2f], s[0x18], s[0x1b], s[0x1e],
|
|
||||||
s[0x30], s[0x32],s[0x33],s[0x34],s[0x35], s[0x37], s[0x22], s[0x25],
|
|
||||||
s[0x3a],s[0x3b],s[0x3c], s[0x3f],
|
|
||||||
s[0x28],s[0x29],s[0x2a],s[0x2b],s[0x2c],s[0x2d],s[0x2e],s[0x2f],
|
|
||||||
s[0x32],s[0x33],s[0x34], s[0x37],
|
|
||||||
s[0x30], s[0x35],
|
|
||||||
s[0x3a],s[0x3b],s[0x3c], s[0x3f])
|
|
||||||
world.spoiler.set_map('flute', text_output, new_spots, player)
|
|
||||||
|
|
||||||
create_dynamic_flute_exits(world, player)
|
|
||||||
|
|
||||||
def connect_custom(world, connected_edges, groups, forced, player):
|
def connect_custom(world, connected_edges, groups, forced, player):
|
||||||
forced_crossed, forced_noncrossed = forced
|
forced_crossed, forced_noncrossed = forced
|
||||||
@@ -1325,28 +1170,6 @@ def adjust_edge_groups(world, trimmed_groups, edges_to_swap, player):
|
|||||||
groups[(mode, wrld, dir, terrain, parallel, count, group_name)][i].extend(matches)
|
groups[(mode, wrld, dir, terrain, parallel, count, group_name)][i].extend(matches)
|
||||||
return groups
|
return groups
|
||||||
|
|
||||||
def sort_flute_spots(world, player, flute_spots):
|
|
||||||
if world.owLayout[player] != 'grid':
|
|
||||||
flute_spots.sort(key=lambda id: flute_data[id][1] if id != 0x03 or not world.is_tile_swapped(0x03, player) else 0x04)
|
|
||||||
else:
|
|
||||||
world_layout = world.owgrid[player][0] if world.mode[player] != 'inverted' else world.owgrid[player][1]
|
|
||||||
layout_list = sum(world_layout, [])
|
|
||||||
layout_map = {id & 0xBF: i for i, id in enumerate(layout_list)}
|
|
||||||
flute_spots.sort(key=lambda id: layout_map[flute_data[id][1] if id != 0x03 or not world.is_tile_swapped(0x03, player) else 0x04])
|
|
||||||
|
|
||||||
def create_dynamic_flute_exits(world, player):
|
|
||||||
flute_in_pool = True if player not in world.customitemarray else any(i for i, n in world.customitemarray[player].items() if i == 'flute' and n > 0)
|
|
||||||
if not flute_in_pool:
|
|
||||||
return
|
|
||||||
for region in (r for r in world.regions if r.player == player and r.terrain == Terrain.Land and r.name not in ['Zoras Domain', 'Master Sword Meadow', 'Hobo Bridge']):
|
|
||||||
if region.type == (RegionType.LightWorld if world.mode[player] != 'inverted' else RegionType.DarkWorld):
|
|
||||||
exitname = 'Flute From ' + region.name
|
|
||||||
exit = Entrance(region.player, exitname, region)
|
|
||||||
exit.spot_type = 'Flute'
|
|
||||||
exit.connect(world.get_region('Flute Sky', player))
|
|
||||||
region.exits.append(exit)
|
|
||||||
world.initialize_regions()
|
|
||||||
|
|
||||||
def get_mirror_exit_name(from_region, to_region):
|
def get_mirror_exit_name(from_region, to_region):
|
||||||
if from_region in mirror_connections and to_region in mirror_connections[from_region]:
|
if from_region in mirror_connections and to_region in mirror_connections[from_region]:
|
||||||
if len(mirror_connections[from_region]) == 1:
|
if len(mirror_connections[from_region]) == 1:
|
||||||
@@ -1918,10 +1741,6 @@ default_whirlpool_connections = [
|
|||||||
((0x35, 'Lake Hylia Whirlpool', 'Lake Hylia Water'), (0x0f, 'Zora Whirlpool', 'Zora Waterfall Water')),
|
((0x35, 'Lake Hylia Whirlpool', 'Lake Hylia Water'), (0x0f, 'Zora Whirlpool', 'Zora Waterfall Water')),
|
||||||
((0x12, 'Kakariko Pond Whirlpool', 'Kakariko Pond Area'), (0x3f, 'Octoballoon Whirlpool', 'Octoballoon Water')),
|
((0x12, 'Kakariko Pond Whirlpool', 'Kakariko Pond Area'), (0x3f, 'Octoballoon Whirlpool', 'Octoballoon Water')),
|
||||||
((0x55, 'Qirn Jump Whirlpool', 'Qirn Jump Water'), (0x7f, 'Bomber Corner Whirlpool', 'Bomber Corner Water'))
|
((0x55, 'Qirn Jump Whirlpool', 'Qirn Jump Water'), (0x7f, 'Bomber Corner Whirlpool', 'Bomber Corner Water'))
|
||||||
]
|
|
||||||
|
|
||||||
default_flute_connections = [
|
|
||||||
0x03, 0x16, 0x18, 0x2c, 0x2f, 0x30, 0x3b, 0x3f
|
|
||||||
]
|
]
|
||||||
|
|
||||||
ow_connections = {
|
ow_connections = {
|
||||||
@@ -2589,51 +2408,6 @@ isolated_regions = [
|
|||||||
'Pyramid Water'
|
'Pyramid Water'
|
||||||
]
|
]
|
||||||
|
|
||||||
flute_data = {
|
|
||||||
#OWID LW Region DW Region Slot VRAM BG Y BG X Link Y Link X Cam Y Cam X Unk1 Unk2 IconY IconX AltY AltX AltVRAM AltBGY AltBGX AltCamY AltCamX AltUnk1 AltUnk2 AltIconY AltIconX
|
|
||||||
0x00: (['Lost Woods East Area', 'Skull Woods Forest'], 0x09, 0x1042, 0x022e, 0x0202, 0x0290, 0x0288, 0x029b, 0x028f, 0xfff2, 0x000e, 0x0290, 0x0288, 0x0290, 0x0290),
|
|
||||||
0x02: (['Lumberjack Area', 'Dark Lumberjack Area'], 0x02, 0x059c, 0x00d6, 0x04e6, 0x0138, 0x0558, 0x0143, 0x0563, 0xfffa, 0xfffa, 0x01d8, 0x0518),
|
|
||||||
0x03: (['West Death Mountain (Bottom)', 'West Dark Death Mountain (Top)'], 0x0b, 0x1600, 0x02ca, 0x060e, 0x0328, 0x0678, 0x0337, 0x0683, 0xfff6, 0xfff2, 0x03bb, 0x0680, 0x0118, 0x0860, 0x05c0, 0x00b8, 0x07ec, 0x0127, 0x086b, 0xfff8, 0x0004, 0x0148, 0x0850),
|
|
||||||
0x05: (['East Death Mountain (Bottom)', 'East Dark Death Mountain (Bottom)'], 0x0e, 0x1860, 0x031e, 0x0d00, 0x0388, 0x0da8, 0x038d, 0x0d7d, 0x0000, 0x0000, 0x03c8, 0x0d98),
|
|
||||||
0x07: (['Death Mountain TR Pegs Area', 'Turtle Rock Area'], 0x07, 0x0804, 0x0102, 0x0e1a, 0x0160, 0x0e90, 0x016f, 0x0e97, 0xfffe, 0x0006, 0x0150, 0x0ea0),
|
|
||||||
0x0a: (['Mountain Pass Area', 'Bumper Cave Area'], 0x0a, 0x0180, 0x0220, 0x0406, 0x0280, 0x0488, 0x028f, 0x0493, 0x0000, 0xfffa, 0x0390, 0x04d8),
|
|
||||||
0x0f: (['Zora Waterfall Area', 'Catfish Area'], 0x0f, 0x0316, 0x025c, 0x0eb2, 0x02c0, 0x0f28, 0x02cb, 0x0f2f, 0x0002, 0xfffe, 0x0360, 0x0f58),
|
|
||||||
0x10: (['Lost Woods Pass West Area', 'Skull Woods Pass West Area'], 0x10, 0x0080, 0x0400, 0x0000, 0x0448, 0x0058, 0x046f, 0x0085, 0x0000, 0x0000, 0x04f8, 0x0088),
|
|
||||||
0x11: (['Kakariko Fortune Area', 'Dark Fortune Area'], 0x11, 0x0912, 0x051e, 0x0292, 0x0588, 0x0318, 0x058d, 0x031f, 0x0000, 0xfffe, 0x05f8, 0x0318),
|
|
||||||
0x12: (['Kakariko Pond Area', 'Outcast Pond Area'], 0x12, 0x0890, 0x051a, 0x0476, 0x0578, 0x04f8, 0x0587, 0x0503, 0xfff6, 0x000a, 0x05b8, 0x04f8),
|
|
||||||
0x13: (['Sanctuary Area', 'Dark Chapel Area'], 0x13, 0x051c, 0x04aa, 0x06de, 0x0508, 0x0758, 0x0517, 0x0763, 0xfff6, 0x0002, 0x05b8, 0x0738),
|
|
||||||
0x14: (['Graveyard Area', 'Dark Graveyard Area'], 0x14, 0x089c, 0x051e, 0x08e6, 0x0580, 0x0958, 0x058b, 0x0963, 0x0000, 0xfffa, 0x05f0, 0x0918, 0x0580, 0x0948),
|
|
||||||
0x15: (['River Bend East Bank', 'Qirn Jump East Bank'], 0x15, 0x041a, 0x0486, 0x0ad2, 0x04e8, 0x0b48, 0x04f3, 0x0b4f, 0x0008, 0xfffe, 0x0548, 0x0b78),
|
|
||||||
0x16: (['Potion Shop Area', 'Dark Witch Area'], 0x16, 0x0888, 0x0516, 0x0c4e, 0x0578, 0x0cc8, 0x0583, 0x0cd3, 0xfffa, 0xfff2, 0x05e8, 0x0c9f),
|
|
||||||
0x17: (['Zora Approach Ledge', 'Catfish Approach Ledge'], 0x17, 0x039e, 0x047e, 0x0ef2, 0x04e0, 0x0f68, 0x04eb, 0x0f6f, 0x0000, 0xfffe, 0x0580, 0x0f48),
|
|
||||||
0x18: (['Kakariko Village', 'Village of Outcasts'], 0x18, 0x0b30, 0x0759, 0x017e, 0x07b7, 0x0200, 0x07c6, 0x020b, 0x0007, 0x0002, 0x0830, 0x0240, 0x07c8, 0x01f8),
|
|
||||||
0x1a: (['Forgotten Forest Area', 'Shield Shop Fence'], 0x1a, 0x081a, 0x070f, 0x04d2, 0x0770, 0x0548, 0x077c, 0x054f, 0xffff, 0xfffe, 0x0770, 0x0518),
|
|
||||||
0x1b: (['Hyrule Castle Courtyard', 'Pyramid Area'], 0x1b, 0x0c30, 0x077a, 0x0786, 0x07d8, 0x07f8, 0x07e7, 0x0803, 0x0006, 0xfffa, 0x07f8, 0x07f8),
|
|
||||||
0x1d: (['Wooden Bridge Area', 'Broken Bridge Northeast'], 0x1d, 0x0602, 0x06c2, 0x0a0e, 0x0720, 0x0a80, 0x072f, 0x0a8b, 0xfffe, 0x0002, 0x0750, 0x0a70),
|
|
||||||
0x1e: (['Eastern Palace Area', 'Palace of Darkness Area'], 0x26, 0x1802, 0x091e, 0x0c0e, 0x09c0, 0x0c80, 0x098b, 0x0c8b, 0x0000, 0x0002, 0x09a0, 0x0cb0),
|
|
||||||
0x22: (['Blacksmith Area', 'Hammer Pegs Area'], 0x22, 0x058c, 0x08aa, 0x0462, 0x0908, 0x04d8, 0x0917, 0x04df, 0x0006, 0xfffe, 0x0978, 0x04e8),
|
|
||||||
0x25: (['Sand Dunes Area', 'Dark Dunes Area'], 0x25, 0x030e, 0x085a, 0x0a76, 0x08b8, 0x0ae8, 0x08c7, 0x0af3, 0x0006, 0xfffa, 0x0918, 0x0b18),
|
|
||||||
0x28: (['Maze Race Area', 'Dig Game Area'], 0x28, 0x0908, 0x0b1e, 0x003a, 0x0b88, 0x00b8, 0x0b8d, 0x00bf, 0x0000, 0x0006, 0x0ba8, 0x00b8),
|
|
||||||
0x29: (['Kakariko Suburb Area', 'Frog Area'], 0x29, 0x0408, 0x0a7c, 0x0242, 0x0ae0, 0x02c0, 0x0aeb, 0x02c7, 0x0002, 0xfffe, 0x0b30, 0x02e0),
|
|
||||||
0x2a: (['Flute Boy Area', 'Stumpy Area'], 0x2a, 0x058e, 0x0aac, 0x046e, 0x0b10, 0x04e8, 0x0b1b, 0x04f3, 0x0002, 0x0002, 0x0b60, 0x04f8),
|
|
||||||
0x2b: (['Central Bonk Rocks Area', 'Dark Bonk Rocks Area'], 0x2b, 0x0620, 0x0acc, 0x0700, 0x0b30, 0x0790, 0x0b3b, 0x0785, 0xfff2, 0x0000, 0x0b80, 0x0760),
|
|
||||||
0x2c: (['Links House Area', 'Big Bomb Shop Area'], 0x2c, 0x0588, 0x0ab9, 0x0840, 0x0b17, 0x08b8, 0x0b26, 0x08bf, 0xfff7, 0x0000, 0x0bb0, 0x08a8),
|
|
||||||
0x2d: (['Stone Bridge South Area', 'Hammer Bridge South Area'], 0x2d, 0x0886, 0x0b1e, 0x0a2a, 0x0ba0, 0x0aa8, 0x0b8b, 0x0aaf, 0x0000, 0x0006, 0x0bf0, 0x0ab8),
|
|
||||||
0x2e: (['Tree Line Area', 'Dark Tree Line Area'], 0x2e, 0x0100, 0x0a1a, 0x0c00, 0x0a78, 0x0c30, 0x0a87, 0x0c7d, 0x0006, 0x0000, 0x0ac8, 0x0c70),
|
|
||||||
0x2f: (['Eastern Nook Area', 'Darkness Nook Area'], 0x2f, 0x0798, 0x0afa, 0x0eb2, 0x0b58, 0x0f30, 0x0b67, 0x0f37, 0xfff6, 0x000e, 0x0bc0, 0x0f00),
|
|
||||||
0x30: (['Desert Teleporter Ledge', 'Mire Teleporter Ledge'], 0x38, 0x1880, 0x0f1e, 0x0000, 0x0fa8, 0x0078, 0x0f8d, 0x008d, 0x0000, 0x0000, 0x0ff0, 0x0070),
|
|
||||||
0x32: (['Flute Boy Approach Area', 'Stumpy Approach Area'], 0x32, 0x03a0, 0x0c6c, 0x0500, 0x0cd0, 0x05a8, 0x0cdb, 0x0585, 0x0002, 0x0000, 0x0d00, 0x0528),
|
|
||||||
0x33: (['C Whirlpool Outer Area', 'Dark C Whirlpool Outer Area'], 0x33, 0x0180, 0x0c20, 0x0600, 0x0c80, 0x0628, 0x0c8f, 0x067d, 0x0000, 0x0000, 0x0ce0, 0x0688),
|
|
||||||
0x34: (['Statues Area', 'Hype Cave Area'], 0x34, 0x088e, 0x0d00, 0x0866, 0x0d60, 0x08d8, 0x0d6f, 0x08e3, 0x0000, 0x000a, 0x0dd0, 0x08e8),
|
|
||||||
#0x35: (['Lake Hylia Northwest Bank', 'Ice Lake Northwest Bank'], 0x35, 0x0d00, 0x0da6, 0x0a06, 0x0e08, 0x0a80, 0x0e13, 0x0a8b, 0xfffa, 0xfffa, 0x0dc8, 0x0a90),
|
|
||||||
0x35: (['Lake Hylia South Shore', 'Ice Lake Southeast Ledge'], 0x3e, 0x1860, 0x0f1e, 0x0d00, 0x0f98, 0x0da8, 0x0f8b, 0x0d85, 0x0000, 0x0000, 0x0fd8, 0x0da8),
|
|
||||||
0x37: (['Ice Cave Area', 'Shopping Mall Area'], 0x37, 0x0786, 0x0cf6, 0x0e2e, 0x0d58, 0x0ea0, 0x0d63, 0x0eab, 0x000a, 0x0002, 0x0d98, 0x0ed0),
|
|
||||||
0x3a: (['Desert Pass Area', 'Swamp Nook Area'], 0x3a, 0x001a, 0x0e08, 0x04c6, 0x0e70, 0x0540, 0x0e7d, 0x054b, 0x0006, 0x000a, 0x0ee0, 0x0570),
|
|
||||||
0x3b: (['Dam Area', 'Swamp Area'], 0x3b, 0x069e, 0x0edf, 0x06f2, 0x0f3d, 0x0778, 0x0f4c, 0x077f, 0xfff1, 0xfffe, 0x0fd0, 0x0770),
|
|
||||||
0x3c: (['South Pass Area', 'Dark South Pass Area'], 0x3c, 0x0584, 0x0ed0, 0x081e, 0x0f38, 0x0898, 0x0f45, 0x08a3, 0xfffe, 0x0002, 0x0fa8, 0x0898),
|
|
||||||
0x3f: (['Octoballoon Area', 'Bomber Corner Area'], 0x3f, 0x0810, 0x0f05, 0x0e75, 0x0f67, 0x0ef3, 0x0f72, 0x0efa, 0xfffb, 0x000b, 0x0fd0, 0x0ef0)
|
|
||||||
}
|
|
||||||
|
|
||||||
ow_loc_prize_table = {
|
ow_loc_prize_table = {
|
||||||
'Master Sword Pedestal': (0x06d, 0x070),
|
'Master Sword Pedestal': (0x06d, 0x070),
|
||||||
'Hobo': (0xb80, 0xb90),
|
'Hobo': (0xb80, 0xb90),
|
||||||
@@ -2714,23 +2488,3 @@ H(38)| sss s| +-+-+-+-+-+-+-+-+
|
|||||||
+-+ | s +-+-+-+ s +-+
|
+-+ | s +-+-+-+ s +-+
|
||||||
Zora: |s| H(38)| |s|s|s| |s|
|
Zora: |s| H(38)| |s|s|s| |s|
|
||||||
+-+ +---+-+-+-+---+-+"""
|
+-+ +---+-+-+-+---+-+"""
|
||||||
|
|
||||||
flute_spoiler_table = \
|
|
||||||
""" 0 1 2 3 4 5 6 7
|
|
||||||
+---+-+---+---+-+
|
|
||||||
01234567 A(00)| |s| | |s|
|
|
||||||
+--------+ | s +-+ s | s +-+
|
|
||||||
A(00)|s ss s s| B(08)| |s| | |s|
|
|
||||||
B(08)| s s| +-+-+-+-+-+-+-+-+
|
|
||||||
C(10)|ssssssss| C(10)|s|s|s|s|s|s|s|s|
|
|
||||||
D(18)|s ss ss | +-+-+-+-+-+-+-+-+
|
|
||||||
E(20)| s s | D(18)| |s| |s| |
|
|
||||||
F(28)|ssssssss| | s +-+ s +-+ s |
|
|
||||||
G(30)|s ssss s| E(20)| |s| |s| |
|
|
||||||
H(38)| sss s| +-+-+-+-+-+-+-+-+
|
|
||||||
+--------+ F(28)|s|s|s|s|s|s|s|s|
|
|
||||||
+-+-+-+-+-+-+-+-+
|
|
||||||
G(30)| |s|s|s| |s|
|
|
||||||
| s +-+-+-+ s +-+
|
|
||||||
H(38)| |s|s|s| |s|
|
|
||||||
+---+-+-+-+---+-+"""
|
|
||||||
|
|||||||
12
Rom.py
12
Rom.py
@@ -47,6 +47,7 @@ from source.enemizer.Enemizer import write_enemy_shuffle_settings
|
|||||||
from source.item.FillUtil import valid_pot_items
|
from source.item.FillUtil import valid_pot_items
|
||||||
from source.overworld.EntranceData import door_addresses, ow_prize_table
|
from source.overworld.EntranceData import door_addresses, ow_prize_table
|
||||||
from source.overworld.EntranceShuffle2 import exit_ids
|
from source.overworld.EntranceShuffle2 import exit_ids
|
||||||
|
from source.overworld.FluteShuffle import default_flute_connections, flute_data
|
||||||
from Text import (
|
from Text import (
|
||||||
Blacksmiths_texts,
|
Blacksmiths_texts,
|
||||||
Blind_texts,
|
Blind_texts,
|
||||||
@@ -84,7 +85,7 @@ from Utils import int16_as_bytes, int32_as_bytes, local_path, snes_to_pc
|
|||||||
from Versions import DRVersion, GKVersion, ORVersion
|
from Versions import DRVersion, GKVersion, ORVersion
|
||||||
|
|
||||||
JAP10HASH = '03a63945398191337e896e5771f77173'
|
JAP10HASH = '03a63945398191337e896e5771f77173'
|
||||||
RANDOMIZERBASEHASH = '33cd5e308266cf2273c80de1b1df3dac'
|
RANDOMIZERBASEHASH = 'a746c6916c3ca8e89df7d7ac79d354dd'
|
||||||
|
|
||||||
|
|
||||||
class JsonRom(object):
|
class JsonRom(object):
|
||||||
@@ -536,7 +537,8 @@ def patch_rom(world, rom, player, team, is_mystery=False, rom_header=None):
|
|||||||
else:
|
else:
|
||||||
flute_spots = world.owflutespots[player]
|
flute_spots = world.owflutespots[player]
|
||||||
owFlags |= 0x0100
|
owFlags |= 0x0100
|
||||||
write_int16(rom, snes_to_pc(0x0AB7F7), 0xEAEA)
|
if world.owFluteShuffle[player] != 'vanilla':
|
||||||
|
write_int16(rom, snes_to_pc(0x0AB80B), 0xEAEA)
|
||||||
|
|
||||||
flute_writes = [(f, flute_data[f][1]) for f in flute_spots]
|
flute_writes = [(f, flute_data[f][1]) for f in flute_spots]
|
||||||
for o in range(0, len(flute_writes)):
|
for o in range(0, len(flute_writes)):
|
||||||
@@ -1573,14 +1575,14 @@ def patch_rom(world, rom, player, team, is_mystery=False, rom_header=None):
|
|||||||
idx = int((map_index-2)/2)
|
idx = int((map_index-2)/2)
|
||||||
elif isinstance(ent, int):
|
elif isinstance(ent, int):
|
||||||
# vanilla icon positions
|
# vanilla icon positions
|
||||||
x_map_position = [0x0F30, 0x0170, 0xFF00, 0x0790, 0x0F30, 0x0160, 0x00F0, 0x0CB0, 0x0900, 0x0240, 0x0F30]
|
x_map_position = [0x0F40, 0x0140, 0xFF00, 0x0778, 0x0F40, 0x0148, 0x00B0, 0x0CA0, 0x0900, 0x01F8, 0x0F20]
|
||||||
y_map_position = [0x06E0, 0x0E50, 0xFF00, 0x0FD0, 0x06E0, 0x0D80, 0x0160, 0x0E80, 0x0130, 0x0840, 0x01B0]
|
y_map_position = [0x0660, 0x0D00, 0xFF00, 0x0F50, 0x0660, 0x0D00, 0x00C0, 0x0E00, 0x0100, 0x0800, 0x0100]
|
||||||
idx = ent
|
idx = ent
|
||||||
owid = owid_map[idx]
|
owid = owid_map[idx]
|
||||||
map_x = x_map_position[idx]
|
map_x = x_map_position[idx]
|
||||||
map_y = y_map_position[idx]
|
map_y = y_map_position[idx]
|
||||||
if owid != 0xFF:
|
if owid != 0xFF:
|
||||||
if (owid < 0x40) == (world.is_tile_swapped(owid, player)):
|
if (owid < 0x40) == (owid in world.owswaps[player][0]):
|
||||||
coord_flags |= 0x8000 # world indicator flag
|
coord_flags |= 0x8000 # world indicator flag
|
||||||
if coord_flags & 0x4000 == 0:
|
if coord_flags & 0x4000 == 0:
|
||||||
map_x, map_y = adjust_ow_coordinates_to_layout(world, player, map_x, map_y, coord_flags & 0x8000 != 0)
|
map_x, map_y = adjust_ow_coordinates_to_layout(world, player, map_x, map_y, coord_flags & 0x8000 != 0)
|
||||||
|
|||||||
2
Text.py
2
Text.py
@@ -135,6 +135,7 @@ Triforce_texts = [
|
|||||||
" I promise the\n next seed will\n be better.",
|
" I promise the\n next seed will\n be better.",
|
||||||
"\n Honk.",
|
"\n Honk.",
|
||||||
" Breakfast\n is served!",
|
" Breakfast\n is served!",
|
||||||
|
"\n send help",
|
||||||
]
|
]
|
||||||
BombShop2_texts = ['Bombs!\nBombs!\nBiggest!\nBestest!\nGreatest!\nBoomest!']
|
BombShop2_texts = ['Bombs!\nBombs!\nBiggest!\nBestest!\nGreatest!\nBoomest!']
|
||||||
Sahasrahla2_texts = ['You already got my item, idiot.', 'Why are you still talking to me?', 'This text won\'t change.', 'Have you met my brother, Hasarahshla?']
|
Sahasrahla2_texts = ['You already got my item, idiot.', 'Why are you still talking to me?', 'This text won\'t change.', 'Have you met my brother, Hasarahshla?']
|
||||||
@@ -388,7 +389,6 @@ DeathMountain_texts = [
|
|||||||
]
|
]
|
||||||
LostWoods_texts = [
|
LostWoods_texts = [
|
||||||
'thieves\' stump',
|
'thieves\' stump',
|
||||||
'He\'s got wood',
|
|
||||||
] * 2 + [
|
] * 2 + [
|
||||||
"the forest thief",
|
"the forest thief",
|
||||||
"dancing pickles",
|
"dancing pickles",
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
GKVersion = '1.0.0'
|
GKVersion = '1.0.0'
|
||||||
ORVersion = '0.7.0.1'
|
ORVersion = '0.7.0.2'
|
||||||
DRVersion = '1.5.2-u'
|
DRVersion = '1.5.2-u'
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
Mike Lenzen <m.lenzen@gmail.com> https://github.com/mlenzen
|
|
||||||
Caleb Levy <caleb.levy@berkeley.edu> https://github.com/caleblevy
|
|
||||||
Marein Könings <mail@marein.org> https://github.com/MareinK
|
|
||||||
Jad Kik <jadkik94@gmail.com> https://github.com/jadkik
|
|
||||||
Kuba Marek <blue.cube@seznam.cz> https://github.com/bluecube
|
|
||||||
@@ -1,191 +0,0 @@
|
|||||||
Apache License
|
|
||||||
Version 2.0, January 2004
|
|
||||||
http://www.apache.org/licenses/
|
|
||||||
|
|
||||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
||||||
|
|
||||||
1. Definitions.
|
|
||||||
|
|
||||||
"License" shall mean the terms and conditions for use, reproduction, and
|
|
||||||
distribution as defined by Sections 1 through 9 of this document.
|
|
||||||
|
|
||||||
"Licensor" shall mean the copyright owner or entity authorized by the copyright
|
|
||||||
owner that is granting the License.
|
|
||||||
|
|
||||||
"Legal Entity" shall mean the union of the acting entity and all other entities
|
|
||||||
that control, are controlled by, or are under common control with that entity.
|
|
||||||
For the purposes of this definition, "control" means (i) the power, direct or
|
|
||||||
indirect, to cause the direction or management of such entity, whether by
|
|
||||||
contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
||||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
||||||
|
|
||||||
"You" (or "Your") shall mean an individual or Legal Entity exercising
|
|
||||||
permissions granted by this License.
|
|
||||||
|
|
||||||
"Source" form shall mean the preferred form for making modifications, including
|
|
||||||
but not limited to software source code, documentation source, and configuration
|
|
||||||
files.
|
|
||||||
|
|
||||||
"Object" form shall mean any form resulting from mechanical transformation or
|
|
||||||
translation of a Source form, including but not limited to compiled object code,
|
|
||||||
generated documentation, and conversions to other media types.
|
|
||||||
|
|
||||||
"Work" shall mean the work of authorship, whether in Source or Object form, made
|
|
||||||
available under the License, as indicated by a copyright notice that is included
|
|
||||||
in or attached to the work (an example is provided in the Appendix below).
|
|
||||||
|
|
||||||
"Derivative Works" shall mean any work, whether in Source or Object form, that
|
|
||||||
is based on (or derived from) the Work and for which the editorial revisions,
|
|
||||||
annotations, elaborations, or other modifications represent, as a whole, an
|
|
||||||
original work of authorship. For the purposes of this License, Derivative Works
|
|
||||||
shall not include works that remain separable from, or merely link (or bind by
|
|
||||||
name) to the interfaces of, the Work and Derivative Works thereof.
|
|
||||||
|
|
||||||
"Contribution" shall mean any work of authorship, including the original version
|
|
||||||
of the Work and any modifications or additions to that Work or Derivative Works
|
|
||||||
thereof, that is intentionally submitted to Licensor for inclusion in the Work
|
|
||||||
by the copyright owner or by an individual or Legal Entity authorized to submit
|
|
||||||
on behalf of the copyright owner. For the purposes of this definition,
|
|
||||||
"submitted" means any form of electronic, verbal, or written communication sent
|
|
||||||
to the Licensor or its representatives, including but not limited to
|
|
||||||
communication on electronic mailing lists, source code control systems, and
|
|
||||||
issue tracking systems that are managed by, or on behalf of, the Licensor for
|
|
||||||
the purpose of discussing and improving the Work, but excluding communication
|
|
||||||
that is conspicuously marked or otherwise designated in writing by the copyright
|
|
||||||
owner as "Not a Contribution."
|
|
||||||
|
|
||||||
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
|
|
||||||
of whom a Contribution has been received by Licensor and subsequently
|
|
||||||
incorporated within the Work.
|
|
||||||
|
|
||||||
2. Grant of Copyright License.
|
|
||||||
|
|
||||||
Subject to the terms and conditions of this License, each Contributor hereby
|
|
||||||
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
|
|
||||||
irrevocable copyright license to reproduce, prepare Derivative Works of,
|
|
||||||
publicly display, publicly perform, sublicense, and distribute the Work and such
|
|
||||||
Derivative Works in Source or Object form.
|
|
||||||
|
|
||||||
3. Grant of Patent License.
|
|
||||||
|
|
||||||
Subject to the terms and conditions of this License, each Contributor hereby
|
|
||||||
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
|
|
||||||
irrevocable (except as stated in this section) patent license to make, have
|
|
||||||
made, use, offer to sell, sell, import, and otherwise transfer the Work, where
|
|
||||||
such license applies only to those patent claims licensable by such Contributor
|
|
||||||
that are necessarily infringed by their Contribution(s) alone or by combination
|
|
||||||
of their Contribution(s) with the Work to which such Contribution(s) was
|
|
||||||
submitted. If You institute patent litigation against any entity (including a
|
|
||||||
cross-claim or counterclaim in a lawsuit) alleging that the Work or a
|
|
||||||
Contribution incorporated within the Work constitutes direct or contributory
|
|
||||||
patent infringement, then any patent licenses granted to You under this License
|
|
||||||
for that Work shall terminate as of the date such litigation is filed.
|
|
||||||
|
|
||||||
4. Redistribution.
|
|
||||||
|
|
||||||
You may reproduce and distribute copies of the Work or Derivative Works thereof
|
|
||||||
in any medium, with or without modifications, and in Source or Object form,
|
|
||||||
provided that You meet the following conditions:
|
|
||||||
|
|
||||||
You must give any other recipients of the Work or Derivative Works a copy of
|
|
||||||
this License; and
|
|
||||||
You must cause any modified files to carry prominent notices stating that You
|
|
||||||
changed the files; and
|
|
||||||
You must retain, in the Source form of any Derivative Works that You distribute,
|
|
||||||
all copyright, patent, trademark, and attribution notices from the Source form
|
|
||||||
of the Work, excluding those notices that do not pertain to any part of the
|
|
||||||
Derivative Works; and
|
|
||||||
If the Work includes a "NOTICE" text file as part of its distribution, then any
|
|
||||||
Derivative Works that You distribute must include a readable copy of the
|
|
||||||
attribution notices contained within such NOTICE file, excluding those notices
|
|
||||||
that do not pertain to any part of the Derivative Works, in at least one of the
|
|
||||||
following places: within a NOTICE text file distributed as part of the
|
|
||||||
Derivative Works; within the Source form or documentation, if provided along
|
|
||||||
with the Derivative Works; or, within a display generated by the Derivative
|
|
||||||
Works, if and wherever such third-party notices normally appear. The contents of
|
|
||||||
the NOTICE file are for informational purposes only and do not modify the
|
|
||||||
License. You may add Your own attribution notices within Derivative Works that
|
|
||||||
You distribute, alongside or as an addendum to the NOTICE text from the Work,
|
|
||||||
provided that such additional attribution notices cannot be construed as
|
|
||||||
modifying the License.
|
|
||||||
You may add Your own copyright statement to Your modifications and may provide
|
|
||||||
additional or different license terms and conditions for use, reproduction, or
|
|
||||||
distribution of Your modifications, or for any such Derivative Works as a whole,
|
|
||||||
provided Your use, reproduction, and distribution of the Work otherwise complies
|
|
||||||
with the conditions stated in this License.
|
|
||||||
|
|
||||||
5. Submission of Contributions.
|
|
||||||
|
|
||||||
Unless You explicitly state otherwise, any Contribution intentionally submitted
|
|
||||||
for inclusion in the Work by You to the Licensor shall be under the terms and
|
|
||||||
conditions of this License, without any additional terms or conditions.
|
|
||||||
Notwithstanding the above, nothing herein shall supersede or modify the terms of
|
|
||||||
any separate license agreement you may have executed with Licensor regarding
|
|
||||||
such Contributions.
|
|
||||||
|
|
||||||
6. Trademarks.
|
|
||||||
|
|
||||||
This License does not grant permission to use the trade names, trademarks,
|
|
||||||
service marks, or product names of the Licensor, except as required for
|
|
||||||
reasonable and customary use in describing the origin of the Work and
|
|
||||||
reproducing the content of the NOTICE file.
|
|
||||||
|
|
||||||
7. Disclaimer of Warranty.
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, Licensor provides the
|
|
||||||
Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
|
|
||||||
including, without limitation, any warranties or conditions of TITLE,
|
|
||||||
NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
|
|
||||||
solely responsible for determining the appropriateness of using or
|
|
||||||
redistributing the Work and assume any risks associated with Your exercise of
|
|
||||||
permissions under this License.
|
|
||||||
|
|
||||||
8. Limitation of Liability.
|
|
||||||
|
|
||||||
In no event and under no legal theory, whether in tort (including negligence),
|
|
||||||
contract, or otherwise, unless required by applicable law (such as deliberate
|
|
||||||
and grossly negligent acts) or agreed to in writing, shall any Contributor be
|
|
||||||
liable to You for damages, including any direct, indirect, special, incidental,
|
|
||||||
or consequential damages of any character arising as a result of this License or
|
|
||||||
out of the use or inability to use the Work (including but not limited to
|
|
||||||
damages for loss of goodwill, work stoppage, computer failure or malfunction, or
|
|
||||||
any and all other commercial damages or losses), even if such Contributor has
|
|
||||||
been advised of the possibility of such damages.
|
|
||||||
|
|
||||||
9. Accepting Warranty or Additional Liability.
|
|
||||||
|
|
||||||
While redistributing the Work or Derivative Works thereof, You may choose to
|
|
||||||
offer, and charge a fee for, acceptance of support, warranty, indemnity, or
|
|
||||||
other liability obligations and/or rights consistent with this License. However,
|
|
||||||
in accepting such obligations, You may act only on Your own behalf and on Your
|
|
||||||
sole responsibility, not on behalf of any other Contributor, and only if You
|
|
||||||
agree to indemnify, defend, and hold each Contributor harmless for any liability
|
|
||||||
incurred by, or claims asserted against, such Contributor by reason of your
|
|
||||||
accepting any such warranty or additional liability.
|
|
||||||
|
|
||||||
END OF TERMS AND CONDITIONS
|
|
||||||
|
|
||||||
APPENDIX: How to apply the Apache License to your work
|
|
||||||
|
|
||||||
To apply the Apache License to your work, attach the following boilerplate
|
|
||||||
notice, with the fields enclosed by brackets "[]" replaced with your own
|
|
||||||
identifying information. (Don't include the brackets!) The text should be
|
|
||||||
enclosed in the appropriate comment syntax for the file format. We also
|
|
||||||
recommend that a file or class name and description of purpose be included on
|
|
||||||
the same "printed page" as the copyright notice for easier identification within
|
|
||||||
third-party archives.
|
|
||||||
|
|
||||||
Copyright [yyyy] [name of copyright owner]
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
"""collections_extended contains a few extra basic data structures."""
|
|
||||||
from ._compat import Collection
|
|
||||||
from .bags import bag, frozenbag
|
|
||||||
from .bijection import bijection
|
|
||||||
from .range_map import MappedRange, RangeMap
|
|
||||||
from .setlists import frozensetlist, setlist
|
|
||||||
|
|
||||||
__version__ = '1.0.2'
|
|
||||||
|
|
||||||
__all__ = (
|
|
||||||
'collection',
|
|
||||||
'setlist',
|
|
||||||
'frozensetlist',
|
|
||||||
'bag',
|
|
||||||
'frozenbag',
|
|
||||||
'bijection',
|
|
||||||
'RangeMap',
|
|
||||||
'MappedRange',
|
|
||||||
'Collection',
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def collection(iterable=None, mutable=True, ordered=False, unique=False):
|
|
||||||
"""Return a Collection with the specified properties.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
iterable (Iterable): collection to instantiate new collection from.
|
|
||||||
mutable (bool): Whether or not the new collection is mutable.
|
|
||||||
ordered (bool): Whether or not the new collection is ordered.
|
|
||||||
unique (bool): Whether or not the new collection contains only unique values.
|
|
||||||
"""
|
|
||||||
if iterable is None:
|
|
||||||
iterable = tuple()
|
|
||||||
if unique:
|
|
||||||
if ordered:
|
|
||||||
if mutable:
|
|
||||||
return setlist(iterable)
|
|
||||||
else:
|
|
||||||
return frozensetlist(iterable)
|
|
||||||
else:
|
|
||||||
if mutable:
|
|
||||||
return set(iterable)
|
|
||||||
else:
|
|
||||||
return frozenset(iterable)
|
|
||||||
else:
|
|
||||||
if ordered:
|
|
||||||
if mutable:
|
|
||||||
return list(iterable)
|
|
||||||
else:
|
|
||||||
return tuple(iterable)
|
|
||||||
else:
|
|
||||||
if mutable:
|
|
||||||
return bag(iterable)
|
|
||||||
else:
|
|
||||||
return frozenbag(iterable)
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
"""Python 2/3 compatibility helpers."""
|
|
||||||
import sys
|
|
||||||
|
|
||||||
is_py2 = sys.version_info[0] == 2
|
|
||||||
|
|
||||||
if is_py2:
|
|
||||||
def keys_set(d):
|
|
||||||
"""Return a set of passed dictionary's keys."""
|
|
||||||
return set(d.keys())
|
|
||||||
else:
|
|
||||||
keys_set = dict.keys
|
|
||||||
|
|
||||||
|
|
||||||
if sys.version_info < (3, 6):
|
|
||||||
from collections import Container, Iterable, Sized
|
|
||||||
|
|
||||||
def _check_methods(C, *methods):
|
|
||||||
mro = C.__mro__
|
|
||||||
for method in methods:
|
|
||||||
for B in mro:
|
|
||||||
if method in B.__dict__:
|
|
||||||
if B.__dict__[method] is None:
|
|
||||||
return NotImplemented
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
return NotImplemented
|
|
||||||
return True
|
|
||||||
|
|
||||||
class Collection(Sized, Iterable, Container):
|
|
||||||
"""Backport from Python3.6."""
|
|
||||||
|
|
||||||
__slots__ = tuple()
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def __subclasshook__(cls, C):
|
|
||||||
if cls is Collection:
|
|
||||||
return _check_methods(C, "__len__", "__iter__", "__contains__")
|
|
||||||
return NotImplemented
|
|
||||||
|
|
||||||
else:
|
|
||||||
from collections.abc import Collection
|
|
||||||
|
|
||||||
|
|
||||||
def handle_rich_comp_not_implemented():
|
|
||||||
"""Correctly handle unimplemented rich comparisons.
|
|
||||||
|
|
||||||
In Python 3, return NotImplemented.
|
|
||||||
In Python 2, raise a TypeError.
|
|
||||||
"""
|
|
||||||
if is_py2:
|
|
||||||
raise TypeError()
|
|
||||||
else:
|
|
||||||
return NotImplemented
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
"""util functions for collections_extended."""
|
|
||||||
|
|
||||||
|
|
||||||
def hash_iterable(it):
|
|
||||||
"""Perform a O(1) memory hash of an iterable of arbitrary length.
|
|
||||||
|
|
||||||
hash(tuple(it)) creates a temporary tuple containing all values from it
|
|
||||||
which could be a problem if it is large.
|
|
||||||
|
|
||||||
See discussion at:
|
|
||||||
https://groups.google.com/forum/#!msg/python-ideas/XcuC01a8SYs/e-doB9TbDwAJ
|
|
||||||
"""
|
|
||||||
hash_value = hash(type(it))
|
|
||||||
for value in it:
|
|
||||||
hash_value = hash((hash_value, value))
|
|
||||||
return hash_value
|
|
||||||
@@ -1,527 +0,0 @@
|
|||||||
"""Bag class definitions."""
|
|
||||||
import heapq
|
|
||||||
from collections import Hashable, MutableSet, Set
|
|
||||||
from operator import itemgetter
|
|
||||||
|
|
||||||
from . import _compat
|
|
||||||
|
|
||||||
|
|
||||||
class _basebag(Set):
|
|
||||||
"""Base class for bag classes.
|
|
||||||
|
|
||||||
Base class for bag and frozenbag. Is not mutable and not hashable, so there's
|
|
||||||
no reason to use this instead of either bag or frozenbag.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Basic object methods
|
|
||||||
|
|
||||||
def __init__(self, iterable=None):
|
|
||||||
"""Create a new basebag.
|
|
||||||
|
|
||||||
If iterable isn't given, is None or is empty then the bag starts empty.
|
|
||||||
Otherwise each element from iterable will be added to the bag
|
|
||||||
however many times it appears.
|
|
||||||
|
|
||||||
This runs in O(len(iterable))
|
|
||||||
"""
|
|
||||||
self._dict = dict()
|
|
||||||
self._size = 0
|
|
||||||
if iterable:
|
|
||||||
if isinstance(iterable, _basebag):
|
|
||||||
for elem, count in iterable._dict.items():
|
|
||||||
self._dict[elem] = count
|
|
||||||
self._size += count
|
|
||||||
else:
|
|
||||||
for value in iterable:
|
|
||||||
self._dict[value] = self._dict.get(value, 0) + 1
|
|
||||||
self._size += 1
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
if self._size == 0:
|
|
||||||
return '{0}()'.format(self.__class__.__name__)
|
|
||||||
else:
|
|
||||||
repr_format = '{class_name}({values!r})'
|
|
||||||
return repr_format.format(
|
|
||||||
class_name=self.__class__.__name__,
|
|
||||||
values=tuple(self),
|
|
||||||
)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
if self._size == 0:
|
|
||||||
return '{class_name}()'.format(class_name=self.__class__.__name__)
|
|
||||||
else:
|
|
||||||
format_single = '{elem!r}'
|
|
||||||
format_mult = '{elem!r}^{mult}'
|
|
||||||
strings = []
|
|
||||||
for elem, mult in self._dict.items():
|
|
||||||
if mult > 1:
|
|
||||||
strings.append(format_mult.format(elem=elem, mult=mult))
|
|
||||||
else:
|
|
||||||
strings.append(format_single.format(elem=elem))
|
|
||||||
return '{%s}' % ', '.join(strings)
|
|
||||||
|
|
||||||
# New public methods (not overriding/implementing anything)
|
|
||||||
|
|
||||||
def num_unique_elements(self):
|
|
||||||
"""Return the number of unique elements.
|
|
||||||
|
|
||||||
This runs in O(1) time
|
|
||||||
"""
|
|
||||||
return len(self._dict)
|
|
||||||
|
|
||||||
def unique_elements(self):
|
|
||||||
"""Return a view of unique elements in this bag.
|
|
||||||
|
|
||||||
In Python 3:
|
|
||||||
This runs in O(1) time and returns a view of the unique elements
|
|
||||||
In Python 2:
|
|
||||||
This runs in O(n) and returns set of the current elements.
|
|
||||||
"""
|
|
||||||
return _compat.keys_set(self._dict)
|
|
||||||
|
|
||||||
def count(self, value):
|
|
||||||
"""Return the number of value present in this bag.
|
|
||||||
|
|
||||||
If value is not in the bag no Error is raised, instead 0 is returned.
|
|
||||||
|
|
||||||
This runs in O(1) time
|
|
||||||
|
|
||||||
Args:
|
|
||||||
value: The element of self to get the count of
|
|
||||||
Returns:
|
|
||||||
int: The count of value in self
|
|
||||||
"""
|
|
||||||
return self._dict.get(value, 0)
|
|
||||||
|
|
||||||
def nlargest(self, n=None):
|
|
||||||
"""List the n most common elements and their counts.
|
|
||||||
|
|
||||||
List is from the most
|
|
||||||
common to the least. If n is None, the list all element counts.
|
|
||||||
|
|
||||||
Run time should be O(m log m) where m is len(self)
|
|
||||||
Args:
|
|
||||||
n (int): The number of elements to return
|
|
||||||
"""
|
|
||||||
if n is None:
|
|
||||||
return sorted(self._dict.items(), key=itemgetter(1), reverse=True)
|
|
||||||
else:
|
|
||||||
return heapq.nlargest(n, self._dict.items(), key=itemgetter(1))
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _from_iterable(cls, it):
|
|
||||||
return cls(it)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_mapping(cls, mapping):
|
|
||||||
"""Create a bag from a dict of elem->count.
|
|
||||||
|
|
||||||
Each key in the dict is added if the value is > 0.
|
|
||||||
"""
|
|
||||||
out = cls()
|
|
||||||
for elem, count in mapping.items():
|
|
||||||
if count > 0:
|
|
||||||
out._dict[elem] = count
|
|
||||||
out._size += count
|
|
||||||
return out
|
|
||||||
|
|
||||||
def copy(self):
|
|
||||||
"""Create a shallow copy of self.
|
|
||||||
|
|
||||||
This runs in O(len(self.num_unique_elements()))
|
|
||||||
"""
|
|
||||||
return self.from_mapping(self._dict)
|
|
||||||
|
|
||||||
# implementing Sized methods
|
|
||||||
|
|
||||||
def __len__(self):
|
|
||||||
"""Return the cardinality of the bag.
|
|
||||||
|
|
||||||
This runs in O(1)
|
|
||||||
"""
|
|
||||||
return self._size
|
|
||||||
|
|
||||||
# implementing Container methods
|
|
||||||
|
|
||||||
def __contains__(self, value):
|
|
||||||
"""Return the multiplicity of the element.
|
|
||||||
|
|
||||||
This runs in O(1)
|
|
||||||
"""
|
|
||||||
return self._dict.get(value, 0)
|
|
||||||
|
|
||||||
# implementing Iterable methods
|
|
||||||
|
|
||||||
def __iter__(self):
|
|
||||||
"""Iterate through all elements.
|
|
||||||
|
|
||||||
Multiple copies will be returned if they exist.
|
|
||||||
"""
|
|
||||||
for value, count in self._dict.items():
|
|
||||||
for i in range(count):
|
|
||||||
yield(value)
|
|
||||||
|
|
||||||
# Comparison methods
|
|
||||||
|
|
||||||
def _is_subset(self, other):
|
|
||||||
"""Check that every element in self has a count <= in other.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
other (Set)
|
|
||||||
"""
|
|
||||||
if isinstance(other, _basebag):
|
|
||||||
for elem, count in self._dict.items():
|
|
||||||
if not count <= other._dict.get(elem, 0):
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
for elem in self:
|
|
||||||
if self._dict.get(elem, 0) > 1 or elem not in other:
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
def _is_superset(self, other):
|
|
||||||
"""Check that every element in self has a count >= in other.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
other (Set)
|
|
||||||
"""
|
|
||||||
if isinstance(other, _basebag):
|
|
||||||
for elem, count in other._dict.items():
|
|
||||||
if not self._dict.get(elem, 0) >= count:
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
for elem in other:
|
|
||||||
if elem not in self:
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
def __le__(self, other):
|
|
||||||
if not isinstance(other, Set):
|
|
||||||
return _compat.handle_rich_comp_not_implemented()
|
|
||||||
return len(self) <= len(other) and self._is_subset(other)
|
|
||||||
|
|
||||||
def __lt__(self, other):
|
|
||||||
if not isinstance(other, Set):
|
|
||||||
return _compat.handle_rich_comp_not_implemented()
|
|
||||||
return len(self) < len(other) and self._is_subset(other)
|
|
||||||
|
|
||||||
def __gt__(self, other):
|
|
||||||
if not isinstance(other, Set):
|
|
||||||
return _compat.handle_rich_comp_not_implemented()
|
|
||||||
return len(self) > len(other) and self._is_superset(other)
|
|
||||||
|
|
||||||
def __ge__(self, other):
|
|
||||||
if not isinstance(other, Set):
|
|
||||||
return _compat.handle_rich_comp_not_implemented()
|
|
||||||
return len(self) >= len(other) and self._is_superset(other)
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
if not isinstance(other, Set):
|
|
||||||
return False
|
|
||||||
if isinstance(other, _basebag):
|
|
||||||
return self._dict == other._dict
|
|
||||||
if not len(self) == len(other):
|
|
||||||
return False
|
|
||||||
for elem in other:
|
|
||||||
if self._dict.get(elem, 0) != 1:
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
def __ne__(self, other):
|
|
||||||
return not (self == other)
|
|
||||||
|
|
||||||
# Operations - &, |, +, -, ^, * and isdisjoint
|
|
||||||
|
|
||||||
def __and__(self, other):
|
|
||||||
"""Intersection is the minimum of corresponding counts.
|
|
||||||
|
|
||||||
This runs in O(l + n) where:
|
|
||||||
n is self.num_unique_elements()
|
|
||||||
if other is a bag:
|
|
||||||
l = 1
|
|
||||||
else:
|
|
||||||
l = len(other)
|
|
||||||
"""
|
|
||||||
if not isinstance(other, _basebag):
|
|
||||||
other = self._from_iterable(other)
|
|
||||||
values = dict()
|
|
||||||
for elem in self._dict:
|
|
||||||
values[elem] = min(other._dict.get(elem, 0), self._dict.get(elem, 0))
|
|
||||||
return self.from_mapping(values)
|
|
||||||
|
|
||||||
def isdisjoint(self, other):
|
|
||||||
"""Return if this bag is disjoint with the passed collection.
|
|
||||||
|
|
||||||
This runs in O(len(other))
|
|
||||||
|
|
||||||
TODO move isdisjoint somewhere more appropriate
|
|
||||||
"""
|
|
||||||
for value in other:
|
|
||||||
if value in self:
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
def __or__(self, other):
|
|
||||||
"""Union is the maximum of all elements.
|
|
||||||
|
|
||||||
This runs in O(m + n) where:
|
|
||||||
n is self.num_unique_elements()
|
|
||||||
if other is a bag:
|
|
||||||
m = other.num_unique_elements()
|
|
||||||
else:
|
|
||||||
m = len(other)
|
|
||||||
"""
|
|
||||||
if not isinstance(other, _basebag):
|
|
||||||
other = self._from_iterable(other)
|
|
||||||
values = dict()
|
|
||||||
for elem in self.unique_elements() | other.unique_elements():
|
|
||||||
values[elem] = max(self._dict.get(elem, 0), other._dict.get(elem, 0))
|
|
||||||
return self.from_mapping(values)
|
|
||||||
|
|
||||||
def __add__(self, other):
|
|
||||||
"""Return a new bag also containing all the elements of other.
|
|
||||||
|
|
||||||
self + other = self & other + self | other
|
|
||||||
|
|
||||||
This runs in O(m + n) where:
|
|
||||||
n is self.num_unique_elements()
|
|
||||||
m is len(other)
|
|
||||||
Args:
|
|
||||||
other (Iterable): elements to add to self
|
|
||||||
"""
|
|
||||||
out = self.copy()
|
|
||||||
for value in other:
|
|
||||||
out._dict[value] = out._dict.get(value, 0) + 1
|
|
||||||
out._size += 1
|
|
||||||
return out
|
|
||||||
|
|
||||||
def __sub__(self, other):
|
|
||||||
"""Difference between the sets.
|
|
||||||
|
|
||||||
For normal sets this is all x s.t. x in self and x not in other.
|
|
||||||
For bags this is count(x) = max(0, self.count(x)-other.count(x))
|
|
||||||
|
|
||||||
This runs in O(m + n) where:
|
|
||||||
n is self.num_unique_elements()
|
|
||||||
m is len(other)
|
|
||||||
Args:
|
|
||||||
other (Iterable): elements to remove
|
|
||||||
"""
|
|
||||||
out = self.copy()
|
|
||||||
for value in other:
|
|
||||||
old_count = out._dict.get(value, 0)
|
|
||||||
if old_count == 1:
|
|
||||||
del out._dict[value]
|
|
||||||
out._size -= 1
|
|
||||||
elif old_count > 1:
|
|
||||||
out._dict[value] = old_count - 1
|
|
||||||
out._size -= 1
|
|
||||||
return out
|
|
||||||
|
|
||||||
def __mul__(self, other):
|
|
||||||
"""Cartesian product of the two sets.
|
|
||||||
|
|
||||||
other can be any iterable.
|
|
||||||
Both self and other must contain elements that can be added together.
|
|
||||||
|
|
||||||
This should run in O(m*n+l) where:
|
|
||||||
m is the number of unique elements in self
|
|
||||||
n is the number of unique elements in other
|
|
||||||
if other is a bag:
|
|
||||||
l is 0
|
|
||||||
else:
|
|
||||||
l is the len(other)
|
|
||||||
The +l will only really matter when other is an iterable with MANY
|
|
||||||
repeated elements.
|
|
||||||
For example: {'a'^2} * 'bbbbbbbbbbbbbbbbbbbbbbbbbb'
|
|
||||||
The algorithm will be dominated by counting the 'b's
|
|
||||||
"""
|
|
||||||
if not isinstance(other, _basebag):
|
|
||||||
other = self._from_iterable(other)
|
|
||||||
values = dict()
|
|
||||||
for elem, count in self._dict.items():
|
|
||||||
for other_elem, other_count in other._dict.items():
|
|
||||||
new_elem = elem + other_elem
|
|
||||||
new_count = count * other_count
|
|
||||||
values[new_elem] = new_count
|
|
||||||
return self.from_mapping(values)
|
|
||||||
|
|
||||||
def __xor__(self, other):
|
|
||||||
"""Symmetric difference between the sets.
|
|
||||||
|
|
||||||
other can be any iterable.
|
|
||||||
|
|
||||||
This runs in O(m + n) where:
|
|
||||||
m = len(self)
|
|
||||||
n = len(other)
|
|
||||||
"""
|
|
||||||
return (self - other) | (other - self)
|
|
||||||
|
|
||||||
|
|
||||||
class bag(_basebag, MutableSet):
|
|
||||||
"""bag is a mutable unhashable bag."""
|
|
||||||
|
|
||||||
def pop(self):
|
|
||||||
"""Remove and return an element of self."""
|
|
||||||
# TODO can this be done more efficiently (no need to create an iterator)?
|
|
||||||
it = iter(self)
|
|
||||||
try:
|
|
||||||
value = next(it)
|
|
||||||
except StopIteration:
|
|
||||||
raise KeyError
|
|
||||||
self.discard(value)
|
|
||||||
return value
|
|
||||||
|
|
||||||
def add(self, elem):
|
|
||||||
"""Add elem to self."""
|
|
||||||
self._dict[elem] = self._dict.get(elem, 0) + 1
|
|
||||||
self._size += 1
|
|
||||||
|
|
||||||
def discard(self, elem):
|
|
||||||
"""Remove elem from this bag, silent if it isn't present."""
|
|
||||||
try:
|
|
||||||
self.remove(elem)
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def remove(self, elem):
|
|
||||||
"""Remove elem from this bag, raising a ValueError if it isn't present.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
elem: object to remove from self
|
|
||||||
Raises:
|
|
||||||
ValueError: if the elem isn't present
|
|
||||||
"""
|
|
||||||
old_count = self._dict.get(elem, 0)
|
|
||||||
if old_count == 0:
|
|
||||||
raise ValueError
|
|
||||||
elif old_count == 1:
|
|
||||||
del self._dict[elem]
|
|
||||||
else:
|
|
||||||
self._dict[elem] -= 1
|
|
||||||
self._size -= 1
|
|
||||||
|
|
||||||
def discard_all(self, other):
|
|
||||||
"""Discard all of the elems from other."""
|
|
||||||
if not isinstance(other, _basebag):
|
|
||||||
other = self._from_iterable(other)
|
|
||||||
for elem, other_count in other._dict.items():
|
|
||||||
old_count = self._dict.get(elem, 0)
|
|
||||||
new_count = old_count - other_count
|
|
||||||
if new_count >= 0:
|
|
||||||
if new_count == 0:
|
|
||||||
if elem in self:
|
|
||||||
del self._dict[elem]
|
|
||||||
else:
|
|
||||||
self._dict[elem] = new_count
|
|
||||||
self._size += new_count - old_count
|
|
||||||
|
|
||||||
def remove_all(self, other):
|
|
||||||
"""Remove all of the elems from other.
|
|
||||||
|
|
||||||
Raises a ValueError if the multiplicity of any elem in other is greater
|
|
||||||
than in self.
|
|
||||||
"""
|
|
||||||
if not self._is_superset(other):
|
|
||||||
raise ValueError
|
|
||||||
self.discard_all(other)
|
|
||||||
|
|
||||||
def clear(self):
|
|
||||||
"""Remove all elements from this bag."""
|
|
||||||
self._dict = dict()
|
|
||||||
self._size = 0
|
|
||||||
|
|
||||||
# In-place operations
|
|
||||||
|
|
||||||
def __ior__(self, other):
|
|
||||||
"""Set multiplicity of each element to the maximum of the two collections.
|
|
||||||
|
|
||||||
if isinstance(other, _basebag):
|
|
||||||
This runs in O(other.num_unique_elements())
|
|
||||||
else:
|
|
||||||
This runs in O(len(other))
|
|
||||||
"""
|
|
||||||
if not isinstance(other, _basebag):
|
|
||||||
other = self._from_iterable(other)
|
|
||||||
for elem, other_count in other._dict.items():
|
|
||||||
old_count = self._dict.get(elem, 0)
|
|
||||||
new_count = max(other_count, old_count)
|
|
||||||
self._dict[elem] = new_count
|
|
||||||
self._size += new_count - old_count
|
|
||||||
return self
|
|
||||||
|
|
||||||
def __iand__(self, other):
|
|
||||||
"""Set multiplicity of each element to the minimum of the two collections.
|
|
||||||
|
|
||||||
if isinstance(other, _basebag):
|
|
||||||
This runs in O(other.num_unique_elements())
|
|
||||||
else:
|
|
||||||
This runs in O(len(other))
|
|
||||||
"""
|
|
||||||
if not isinstance(other, _basebag):
|
|
||||||
other = self._from_iterable(other)
|
|
||||||
for elem, old_count in set(self._dict.items()):
|
|
||||||
other_count = other._dict.get(elem, 0)
|
|
||||||
new_count = min(other_count, old_count)
|
|
||||||
if new_count == 0:
|
|
||||||
del self._dict[elem]
|
|
||||||
else:
|
|
||||||
self._dict[elem] = new_count
|
|
||||||
self._size += new_count - old_count
|
|
||||||
return self
|
|
||||||
|
|
||||||
def __ixor__(self, other):
|
|
||||||
"""Set self to the symmetric difference between the sets.
|
|
||||||
|
|
||||||
if isinstance(other, _basebag):
|
|
||||||
This runs in O(other.num_unique_elements())
|
|
||||||
else:
|
|
||||||
This runs in O(len(other))
|
|
||||||
"""
|
|
||||||
if not isinstance(other, _basebag):
|
|
||||||
other = self._from_iterable(other)
|
|
||||||
other_minus_self = other - self
|
|
||||||
self -= other
|
|
||||||
self |= other_minus_self
|
|
||||||
return self
|
|
||||||
|
|
||||||
def __isub__(self, other):
|
|
||||||
"""Discard the elements of other from self.
|
|
||||||
|
|
||||||
if isinstance(it, _basebag):
|
|
||||||
This runs in O(it.num_unique_elements())
|
|
||||||
else:
|
|
||||||
This runs in O(len(it))
|
|
||||||
"""
|
|
||||||
self.discard_all(other)
|
|
||||||
return self
|
|
||||||
|
|
||||||
def __iadd__(self, other):
|
|
||||||
"""Add all of the elements of other to self.
|
|
||||||
|
|
||||||
if isinstance(it, _basebag):
|
|
||||||
This runs in O(it.num_unique_elements())
|
|
||||||
else:
|
|
||||||
This runs in O(len(it))
|
|
||||||
"""
|
|
||||||
if not isinstance(other, _basebag):
|
|
||||||
other = self._from_iterable(other)
|
|
||||||
for elem, other_count in other._dict.items():
|
|
||||||
self._dict[elem] = self._dict.get(elem, 0) + other_count
|
|
||||||
self._size += other_count
|
|
||||||
return self
|
|
||||||
|
|
||||||
|
|
||||||
class frozenbag(_basebag, Hashable):
|
|
||||||
"""frozenbag is an immutable, hashable bab."""
|
|
||||||
|
|
||||||
def __hash__(self):
|
|
||||||
"""Compute the hash value of a frozenbag.
|
|
||||||
|
|
||||||
This was copied directly from _collections_abc.Set._hash in Python3 which
|
|
||||||
is identical to _abcoll.Set._hash
|
|
||||||
We can't call it directly because Python2 raises a TypeError.
|
|
||||||
"""
|
|
||||||
if not hasattr(self, '_hash_value'):
|
|
||||||
self._hash_value = self._hash()
|
|
||||||
return self._hash_value
|
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
"""Class definition for bijection."""
|
|
||||||
|
|
||||||
from collections import Mapping, MutableMapping
|
|
||||||
|
|
||||||
|
|
||||||
class bijection(MutableMapping):
|
|
||||||
"""A one-to-one onto mapping, a dict with unique values."""
|
|
||||||
|
|
||||||
def __init__(self, iterable=None, **kwarg):
|
|
||||||
"""Create a bijection from an iterable.
|
|
||||||
|
|
||||||
Matches dict.__init__.
|
|
||||||
"""
|
|
||||||
self._data = {}
|
|
||||||
self.__inverse = self.__new__(bijection)
|
|
||||||
self.__inverse._data = {}
|
|
||||||
self.__inverse.__inverse = self
|
|
||||||
if iterable is not None:
|
|
||||||
if isinstance(iterable, Mapping):
|
|
||||||
for key, value in iterable.items():
|
|
||||||
self[key] = value
|
|
||||||
else:
|
|
||||||
for pair in iterable:
|
|
||||||
self[pair[0]] = pair[1]
|
|
||||||
for key, value in kwarg.items():
|
|
||||||
self[key] = value
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
if len(self._data) == 0:
|
|
||||||
return '{0}()'.format(self.__class__.__name__)
|
|
||||||
else:
|
|
||||||
repr_format = '{class_name}({values!r})'
|
|
||||||
return repr_format.format(
|
|
||||||
class_name=self.__class__.__name__,
|
|
||||||
values=self._data,
|
|
||||||
)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def inverse(self):
|
|
||||||
"""Return the inverse of this bijection."""
|
|
||||||
return self.__inverse
|
|
||||||
|
|
||||||
# Required for MutableMapping
|
|
||||||
def __len__(self):
|
|
||||||
return len(self._data)
|
|
||||||
|
|
||||||
# Required for MutableMapping
|
|
||||||
def __getitem__(self, key):
|
|
||||||
return self._data[key]
|
|
||||||
|
|
||||||
# Required for MutableMapping
|
|
||||||
def __setitem__(self, key, value):
|
|
||||||
if key in self:
|
|
||||||
del self.inverse._data[self[key]]
|
|
||||||
if value in self.inverse:
|
|
||||||
del self._data[self.inverse[value]]
|
|
||||||
self._data[key] = value
|
|
||||||
self.inverse._data[value] = key
|
|
||||||
|
|
||||||
# Required for MutableMapping
|
|
||||||
def __delitem__(self, key):
|
|
||||||
value = self._data.pop(key)
|
|
||||||
del self.inverse._data[value]
|
|
||||||
|
|
||||||
# Required for MutableMapping
|
|
||||||
def __iter__(self):
|
|
||||||
return iter(self._data)
|
|
||||||
|
|
||||||
def __contains__(self, key):
|
|
||||||
return key in self._data
|
|
||||||
|
|
||||||
def clear(self):
|
|
||||||
"""Remove everything from this bijection."""
|
|
||||||
self._data.clear()
|
|
||||||
self.inverse._data.clear()
|
|
||||||
|
|
||||||
def copy(self):
|
|
||||||
"""Return a copy of this bijection."""
|
|
||||||
return bijection(self)
|
|
||||||
|
|
||||||
def items(self):
|
|
||||||
"""See Mapping.items."""
|
|
||||||
return self._data.items()
|
|
||||||
|
|
||||||
def keys(self):
|
|
||||||
"""See Mapping.keys."""
|
|
||||||
return self._data.keys()
|
|
||||||
|
|
||||||
def values(self):
|
|
||||||
"""See Mapping.values."""
|
|
||||||
return self.inverse.keys()
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
return isinstance(other, bijection) and self._data == other._data
|
|
||||||
@@ -1,383 +0,0 @@
|
|||||||
"""RangeMap class definition."""
|
|
||||||
from bisect import bisect_left, bisect_right
|
|
||||||
from collections import Mapping, MappingView, Set, namedtuple
|
|
||||||
|
|
||||||
# Used to mark unmapped ranges
|
|
||||||
_empty = object()
|
|
||||||
|
|
||||||
MappedRange = namedtuple('MappedRange', ('start', 'stop', 'value'))
|
|
||||||
|
|
||||||
|
|
||||||
class KeysView(MappingView, Set):
|
|
||||||
"""A view of the keys that mark the starts of subranges.
|
|
||||||
|
|
||||||
Since iterating over all the keys is impossible, the KeysView only
|
|
||||||
contains the keys that start each subrange.
|
|
||||||
"""
|
|
||||||
|
|
||||||
__slots__ = ()
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _from_iterable(self, it):
|
|
||||||
return set(it)
|
|
||||||
|
|
||||||
def __contains__(self, key):
|
|
||||||
loc = self._mapping._bisect_left(key)
|
|
||||||
return self._mapping._keys[loc] == key and \
|
|
||||||
self._mapping._values[loc] is not _empty
|
|
||||||
|
|
||||||
def __iter__(self):
|
|
||||||
for item in self._mapping.ranges():
|
|
||||||
yield item.start
|
|
||||||
|
|
||||||
|
|
||||||
class ItemsView(MappingView, Set):
|
|
||||||
"""A view of the items that mark the starts of subranges.
|
|
||||||
|
|
||||||
Since iterating over all the keys is impossible, the ItemsView only
|
|
||||||
contains the items that start each subrange.
|
|
||||||
"""
|
|
||||||
|
|
||||||
__slots__ = ()
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _from_iterable(self, it):
|
|
||||||
return set(it)
|
|
||||||
|
|
||||||
def __contains__(self, item):
|
|
||||||
key, value = item
|
|
||||||
loc = self._mapping._bisect_left(key)
|
|
||||||
return self._mapping._keys[loc] == key and \
|
|
||||||
self._mapping._values[loc] == value
|
|
||||||
|
|
||||||
def __iter__(self):
|
|
||||||
for mapped_range in self._mapping.ranges():
|
|
||||||
yield (mapped_range.start, mapped_range.value)
|
|
||||||
|
|
||||||
|
|
||||||
class ValuesView(MappingView):
|
|
||||||
"""A view on the values of a Mapping."""
|
|
||||||
|
|
||||||
__slots__ = ()
|
|
||||||
|
|
||||||
def __contains__(self, value):
|
|
||||||
return value in self._mapping._values
|
|
||||||
|
|
||||||
def __iter__(self):
|
|
||||||
for value in self._mapping._values:
|
|
||||||
if value is not _empty:
|
|
||||||
yield value
|
|
||||||
|
|
||||||
|
|
||||||
def _check_start_stop(start, stop):
|
|
||||||
"""Check that start and stop are valid - orderable and in the right order.
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
ValueError: if stop <= start
|
|
||||||
TypeError: if unorderable
|
|
||||||
"""
|
|
||||||
if start is not None and stop is not None and stop <= start:
|
|
||||||
raise ValueError('stop must be > start')
|
|
||||||
|
|
||||||
|
|
||||||
def _check_key_slice(key):
|
|
||||||
if not isinstance(key, slice):
|
|
||||||
raise TypeError('Can only set and delete slices')
|
|
||||||
if key.step is not None:
|
|
||||||
raise ValueError('Cannot set or delete slices with steps')
|
|
||||||
|
|
||||||
|
|
||||||
class RangeMap(Mapping):
|
|
||||||
"""Map ranges of orderable elements to values."""
|
|
||||||
|
|
||||||
def __init__(self, iterable=None, **kwargs):
|
|
||||||
"""Create a RangeMap.
|
|
||||||
|
|
||||||
A mapping or other iterable can be passed to initialize the RangeMap.
|
|
||||||
If mapping is passed, it is interpreted as a mapping from range start
|
|
||||||
indices to values.
|
|
||||||
If an iterable is passed, each element will define a range in the
|
|
||||||
RangeMap and should be formatted (start, stop, value).
|
|
||||||
|
|
||||||
default_value is a an optional keyword argument that will initialize the
|
|
||||||
entire RangeMap to that value. Any missing ranges will be mapped to that
|
|
||||||
value. However, if ranges are subsequently deleted they will be removed
|
|
||||||
and *not* mapped to the default_value.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
iterable: A Mapping or an Iterable to initialize from.
|
|
||||||
default_value: If passed, the return value for all keys less than the
|
|
||||||
least key in mapping or missing ranges in iterable. If no mapping
|
|
||||||
or iterable, the return value for all keys.
|
|
||||||
"""
|
|
||||||
default_value = kwargs.pop('default_value', _empty)
|
|
||||||
if kwargs:
|
|
||||||
raise TypeError('Unknown keyword arguments: %s' % ', '.join(kwargs.keys()))
|
|
||||||
self._keys = [None]
|
|
||||||
self._values = [default_value]
|
|
||||||
if iterable:
|
|
||||||
if isinstance(iterable, Mapping):
|
|
||||||
self._init_from_mapping(iterable)
|
|
||||||
else:
|
|
||||||
self._init_from_iterable(iterable)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_mapping(cls, mapping):
|
|
||||||
"""Create a RangeMap from a mapping of interval starts to values."""
|
|
||||||
obj = cls()
|
|
||||||
obj._init_from_mapping(mapping)
|
|
||||||
return obj
|
|
||||||
|
|
||||||
def _init_from_mapping(self, mapping):
|
|
||||||
for key, value in sorted(mapping.items()):
|
|
||||||
self.set(value, key)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_iterable(cls, iterable):
|
|
||||||
"""Create a RangeMap from an iterable of tuples defining each range.
|
|
||||||
|
|
||||||
Each element of the iterable is a tuple (start, stop, value).
|
|
||||||
"""
|
|
||||||
obj = cls()
|
|
||||||
obj._init_from_iterable(iterable)
|
|
||||||
return obj
|
|
||||||
|
|
||||||
def _init_from_iterable(self, iterable):
|
|
||||||
for start, stop, value in iterable:
|
|
||||||
self.set(value, start=start, stop=stop)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
range_format = '({range.start}, {range.stop}): {range.value}'
|
|
||||||
values = ', '.join([range_format.format(range=r) for r in self.ranges()])
|
|
||||||
return 'RangeMap(%s)' % values
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
range_format = '({range.start!r}, {range.stop!r}, {range.value!r})'
|
|
||||||
values = ', '.join([range_format.format(range=r) for r in self.ranges()])
|
|
||||||
return 'RangeMap([%s])' % values
|
|
||||||
|
|
||||||
def _bisect_left(self, key):
|
|
||||||
"""Return the index of the key or the last key < key."""
|
|
||||||
if key is None:
|
|
||||||
return 0
|
|
||||||
else:
|
|
||||||
return bisect_left(self._keys, key, lo=1)
|
|
||||||
|
|
||||||
def _bisect_right(self, key):
|
|
||||||
"""Return the index of the first key > key."""
|
|
||||||
if key is None:
|
|
||||||
return 1
|
|
||||||
else:
|
|
||||||
return bisect_right(self._keys, key, lo=1)
|
|
||||||
|
|
||||||
def ranges(self, start=None, stop=None):
|
|
||||||
"""Generate MappedRanges for all mapped ranges.
|
|
||||||
|
|
||||||
Yields:
|
|
||||||
MappedRange
|
|
||||||
"""
|
|
||||||
_check_start_stop(start, stop)
|
|
||||||
start_loc = self._bisect_right(start)
|
|
||||||
if stop is None:
|
|
||||||
stop_loc = len(self._keys)
|
|
||||||
else:
|
|
||||||
stop_loc = self._bisect_left(stop)
|
|
||||||
start_val = self._values[start_loc - 1]
|
|
||||||
candidate_keys = [start] + self._keys[start_loc:stop_loc] + [stop]
|
|
||||||
candidate_values = [start_val] + self._values[start_loc:stop_loc]
|
|
||||||
for i, value in enumerate(candidate_values):
|
|
||||||
if value is not _empty:
|
|
||||||
start_key = candidate_keys[i]
|
|
||||||
stop_key = candidate_keys[i + 1]
|
|
||||||
yield MappedRange(start_key, stop_key, value)
|
|
||||||
|
|
||||||
def __contains__(self, value):
|
|
||||||
try:
|
|
||||||
self.__getitem(value) is not _empty
|
|
||||||
except KeyError:
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
return True
|
|
||||||
|
|
||||||
def __iter__(self):
|
|
||||||
for key, value in zip(self._keys, self._values):
|
|
||||||
if value is not _empty:
|
|
||||||
yield key
|
|
||||||
|
|
||||||
def __bool__(self):
|
|
||||||
if len(self._keys) > 1:
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
return self._values[0] != _empty
|
|
||||||
|
|
||||||
__nonzero__ = __bool__
|
|
||||||
|
|
||||||
def __getitem(self, key):
|
|
||||||
"""Get the value for a key (not a slice)."""
|
|
||||||
loc = self._bisect_right(key) - 1
|
|
||||||
value = self._values[loc]
|
|
||||||
if value is _empty:
|
|
||||||
raise KeyError(key)
|
|
||||||
else:
|
|
||||||
return value
|
|
||||||
|
|
||||||
def get(self, key, restval=None):
|
|
||||||
"""Get the value of the range containing key, otherwise return restval."""
|
|
||||||
try:
|
|
||||||
return self.__getitem(key)
|
|
||||||
except KeyError:
|
|
||||||
return restval
|
|
||||||
|
|
||||||
def get_range(self, start=None, stop=None):
|
|
||||||
"""Return a RangeMap for the range start to stop.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
A RangeMap
|
|
||||||
"""
|
|
||||||
return self.from_iterable(self.ranges(start, stop))
|
|
||||||
|
|
||||||
def set(self, value, start=None, stop=None):
|
|
||||||
"""Set the range from start to stop to value."""
|
|
||||||
_check_start_stop(start, stop)
|
|
||||||
# start_index, stop_index will denote the sections we are replacing
|
|
||||||
start_index = self._bisect_left(start)
|
|
||||||
if start is not None: # start_index == 0
|
|
||||||
prev_value = self._values[start_index - 1]
|
|
||||||
if prev_value == value:
|
|
||||||
# We're setting a range where the left range has the same
|
|
||||||
# value, so create one big range
|
|
||||||
start_index -= 1
|
|
||||||
start = self._keys[start_index]
|
|
||||||
if stop is None:
|
|
||||||
new_keys = [start]
|
|
||||||
new_values = [value]
|
|
||||||
stop_index = len(self._keys)
|
|
||||||
else:
|
|
||||||
stop_index = self._bisect_right(stop)
|
|
||||||
stop_value = self._values[stop_index - 1]
|
|
||||||
stop_key = self._keys[stop_index - 1]
|
|
||||||
if stop_key == stop and stop_value == value:
|
|
||||||
new_keys = [start]
|
|
||||||
new_values = [value]
|
|
||||||
else:
|
|
||||||
new_keys = [start, stop]
|
|
||||||
new_values = [value, stop_value]
|
|
||||||
self._keys[start_index:stop_index] = new_keys
|
|
||||||
self._values[start_index:stop_index] = new_values
|
|
||||||
|
|
||||||
def delete(self, start=None, stop=None):
|
|
||||||
"""Delete the range from start to stop from self.
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
KeyError: If part of the passed range isn't mapped.
|
|
||||||
"""
|
|
||||||
_check_start_stop(start, stop)
|
|
||||||
start_loc = self._bisect_right(start) - 1
|
|
||||||
if stop is None:
|
|
||||||
stop_loc = len(self._keys)
|
|
||||||
else:
|
|
||||||
stop_loc = self._bisect_left(stop)
|
|
||||||
for value in self._values[start_loc:stop_loc]:
|
|
||||||
if value is _empty:
|
|
||||||
raise KeyError((start, stop))
|
|
||||||
# this is inefficient, we've already found the sub ranges
|
|
||||||
self.set(_empty, start=start, stop=stop)
|
|
||||||
|
|
||||||
def empty(self, start=None, stop=None):
|
|
||||||
"""Empty the range from start to stop.
|
|
||||||
|
|
||||||
Like delete, but no Error is raised if the entire range isn't mapped.
|
|
||||||
"""
|
|
||||||
self.set(_empty, start=start, stop=stop)
|
|
||||||
|
|
||||||
def clear(self):
|
|
||||||
"""Remove all elements."""
|
|
||||||
self._keys = [None]
|
|
||||||
self._values = [_empty]
|
|
||||||
|
|
||||||
@property
|
|
||||||
def start(self):
|
|
||||||
"""Get the start key of the first range.
|
|
||||||
|
|
||||||
None if RangeMap is empty or unbounded to the left.
|
|
||||||
"""
|
|
||||||
if self._values[0] is _empty:
|
|
||||||
try:
|
|
||||||
return self._keys[1]
|
|
||||||
except IndexError:
|
|
||||||
# This is empty or everything is mapped to a single value
|
|
||||||
return None
|
|
||||||
else:
|
|
||||||
# This is unbounded to the left
|
|
||||||
return self._keys[0]
|
|
||||||
|
|
||||||
@property
|
|
||||||
def end(self):
|
|
||||||
"""Get the stop key of the last range.
|
|
||||||
|
|
||||||
None if RangeMap is empty or unbounded to the right.
|
|
||||||
"""
|
|
||||||
if self._values[-1] is _empty:
|
|
||||||
return self._keys[-1]
|
|
||||||
else:
|
|
||||||
# This is unbounded to the right
|
|
||||||
return None
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
if isinstance(other, RangeMap):
|
|
||||||
return (
|
|
||||||
self._keys == other._keys and
|
|
||||||
self._values == other._values
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def __getitem__(self, key):
|
|
||||||
try:
|
|
||||||
_check_key_slice(key)
|
|
||||||
except TypeError:
|
|
||||||
return self.__getitem(key)
|
|
||||||
else:
|
|
||||||
return self.get_range(key.start, key.stop)
|
|
||||||
|
|
||||||
def __setitem__(self, key, value):
|
|
||||||
_check_key_slice(key)
|
|
||||||
self.set(value, key.start, key.stop)
|
|
||||||
|
|
||||||
def __delitem__(self, key):
|
|
||||||
_check_key_slice(key)
|
|
||||||
self.delete(key.start, key.stop)
|
|
||||||
|
|
||||||
def __len__(self):
|
|
||||||
count = 0
|
|
||||||
for v in self._values:
|
|
||||||
if v is not _empty:
|
|
||||||
count += 1
|
|
||||||
return count
|
|
||||||
|
|
||||||
def keys(self):
|
|
||||||
"""Return a view of the keys."""
|
|
||||||
return KeysView(self)
|
|
||||||
|
|
||||||
def values(self):
|
|
||||||
"""Return a view of the values."""
|
|
||||||
return ValuesView(self)
|
|
||||||
|
|
||||||
def items(self):
|
|
||||||
"""Return a view of the item pairs."""
|
|
||||||
return ItemsView(self)
|
|
||||||
|
|
||||||
# Python2 - override slice methods
|
|
||||||
def __setslice__(self, i, j, value):
|
|
||||||
"""Implement __setslice__ to override behavior in Python 2.
|
|
||||||
|
|
||||||
This is required because empty slices pass integers in python2 as opposed
|
|
||||||
to None in python 3.
|
|
||||||
"""
|
|
||||||
raise SyntaxError('Assigning slices doesn\t work in Python 2, use set')
|
|
||||||
|
|
||||||
def __delslice__(self, i, j):
|
|
||||||
raise SyntaxError('Deleting slices doesn\t work in Python 2, use delete')
|
|
||||||
|
|
||||||
def __getslice__(self, i, j):
|
|
||||||
raise SyntaxError('Getting slices doesn\t work in Python 2, use get_range.')
|
|
||||||
@@ -1,551 +0,0 @@
|
|||||||
"""Setlist class definitions."""
|
|
||||||
import random as random_
|
|
||||||
from collections import (
|
|
||||||
Hashable,
|
|
||||||
MutableSequence,
|
|
||||||
MutableSet,
|
|
||||||
Sequence,
|
|
||||||
Set,
|
|
||||||
)
|
|
||||||
|
|
||||||
from . import _util
|
|
||||||
|
|
||||||
|
|
||||||
class _basesetlist(Sequence, Set):
|
|
||||||
"""A setlist is an ordered Collection of unique elements.
|
|
||||||
|
|
||||||
_basesetlist is the superclass of setlist and frozensetlist. It is immutable
|
|
||||||
and unhashable.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, iterable=None, raise_on_duplicate=False):
|
|
||||||
"""Create a setlist.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
iterable (Iterable): Values to initialize the setlist with.
|
|
||||||
"""
|
|
||||||
self._list = list()
|
|
||||||
self._dict = dict()
|
|
||||||
if iterable:
|
|
||||||
if raise_on_duplicate:
|
|
||||||
self._extend(iterable)
|
|
||||||
else:
|
|
||||||
self._update(iterable)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
if len(self) == 0:
|
|
||||||
return '{0}()'.format(self.__class__.__name__)
|
|
||||||
else:
|
|
||||||
repr_format = '{class_name}({values!r})'
|
|
||||||
return repr_format.format(
|
|
||||||
class_name=self.__class__.__name__,
|
|
||||||
values=tuple(self),
|
|
||||||
)
|
|
||||||
|
|
||||||
# Convenience methods
|
|
||||||
def _fix_neg_index(self, index):
|
|
||||||
if index < 0:
|
|
||||||
index += len(self)
|
|
||||||
if index < 0:
|
|
||||||
raise IndexError('index is out of range')
|
|
||||||
return index
|
|
||||||
|
|
||||||
def _fix_end_index(self, index):
|
|
||||||
if index is None:
|
|
||||||
return len(self)
|
|
||||||
else:
|
|
||||||
return self._fix_neg_index(index)
|
|
||||||
|
|
||||||
def _append(self, value):
|
|
||||||
# Checking value in self will check that value is Hashable
|
|
||||||
if value in self:
|
|
||||||
raise ValueError('Value "%s" already present' % str(value))
|
|
||||||
else:
|
|
||||||
self._dict[value] = len(self)
|
|
||||||
self._list.append(value)
|
|
||||||
|
|
||||||
def _extend(self, values):
|
|
||||||
new_values = set()
|
|
||||||
for value in values:
|
|
||||||
if value in new_values:
|
|
||||||
raise ValueError('New values contain duplicates')
|
|
||||||
elif value in self:
|
|
||||||
raise ValueError('New values contain elements already present in self')
|
|
||||||
else:
|
|
||||||
new_values.add(value)
|
|
||||||
for value in values:
|
|
||||||
self._dict[value] = len(self)
|
|
||||||
self._list.append(value)
|
|
||||||
|
|
||||||
def _add(self, item):
|
|
||||||
if item not in self:
|
|
||||||
self._dict[item] = len(self)
|
|
||||||
self._list.append(item)
|
|
||||||
|
|
||||||
def _update(self, values):
|
|
||||||
for value in values:
|
|
||||||
if value not in self:
|
|
||||||
self._dict[value] = len(self)
|
|
||||||
self._list.append(value)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _from_iterable(cls, it, **kwargs):
|
|
||||||
return cls(it, **kwargs)
|
|
||||||
|
|
||||||
# Implement Container
|
|
||||||
def __contains__(self, value):
|
|
||||||
return value in self._dict
|
|
||||||
|
|
||||||
# Iterable we get by inheriting from Sequence
|
|
||||||
|
|
||||||
# Implement Sized
|
|
||||||
def __len__(self):
|
|
||||||
return len(self._list)
|
|
||||||
|
|
||||||
# Implement Sequence
|
|
||||||
def __getitem__(self, index):
|
|
||||||
if isinstance(index, slice):
|
|
||||||
return self._from_iterable(self._list[index])
|
|
||||||
return self._list[index]
|
|
||||||
|
|
||||||
def count(self, value):
|
|
||||||
"""Return the number of occurences of value in self.
|
|
||||||
|
|
||||||
This runs in O(1)
|
|
||||||
|
|
||||||
Args:
|
|
||||||
value: The value to count
|
|
||||||
Returns:
|
|
||||||
int: 1 if the value is in the setlist, otherwise 0
|
|
||||||
"""
|
|
||||||
if value in self:
|
|
||||||
return 1
|
|
||||||
else:
|
|
||||||
return 0
|
|
||||||
|
|
||||||
def index(self, value, start=0, end=None):
|
|
||||||
"""Return the index of value between start and end.
|
|
||||||
|
|
||||||
By default, the entire setlist is searched.
|
|
||||||
|
|
||||||
This runs in O(1)
|
|
||||||
|
|
||||||
Args:
|
|
||||||
value: The value to find the index of
|
|
||||||
start (int): The index to start searching at (defaults to 0)
|
|
||||||
end (int): The index to stop searching at (defaults to the end of the list)
|
|
||||||
Returns:
|
|
||||||
int: The index of the value
|
|
||||||
Raises:
|
|
||||||
ValueError: If the value is not in the list or outside of start - end
|
|
||||||
IndexError: If start or end are out of range
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
index = self._dict[value]
|
|
||||||
except KeyError:
|
|
||||||
raise ValueError
|
|
||||||
else:
|
|
||||||
start = self._fix_neg_index(start)
|
|
||||||
end = self._fix_end_index(end)
|
|
||||||
if start <= index and index < end:
|
|
||||||
return index
|
|
||||||
else:
|
|
||||||
raise ValueError
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _check_type(cls, other, operand_name):
|
|
||||||
if not isinstance(other, _basesetlist):
|
|
||||||
message = (
|
|
||||||
"unsupported operand type(s) for {operand_name}: "
|
|
||||||
"'{self_type}' and '{other_type}'").format(
|
|
||||||
operand_name=operand_name,
|
|
||||||
self_type=cls,
|
|
||||||
other_type=type(other),
|
|
||||||
)
|
|
||||||
raise TypeError(message)
|
|
||||||
|
|
||||||
def __add__(self, other):
|
|
||||||
self._check_type(other, '+')
|
|
||||||
out = self.copy()
|
|
||||||
out._extend(other)
|
|
||||||
return out
|
|
||||||
|
|
||||||
# Implement Set
|
|
||||||
|
|
||||||
def issubset(self, other):
|
|
||||||
return self <= other
|
|
||||||
|
|
||||||
def issuperset(self, other):
|
|
||||||
return self >= other
|
|
||||||
|
|
||||||
def union(self, other):
|
|
||||||
out = self.copy()
|
|
||||||
out.update(other)
|
|
||||||
return out
|
|
||||||
|
|
||||||
def intersection(self, other):
|
|
||||||
other = set(other)
|
|
||||||
return self._from_iterable(item for item in self if item in other)
|
|
||||||
|
|
||||||
def difference(self, other):
|
|
||||||
other = set(other)
|
|
||||||
return self._from_iterable(item for item in self if item not in other)
|
|
||||||
|
|
||||||
def symmetric_difference(self, other):
|
|
||||||
return self.union(other) - self.intersection(other)
|
|
||||||
|
|
||||||
def __sub__(self, other):
|
|
||||||
self._check_type(other, '-')
|
|
||||||
return self.difference(other)
|
|
||||||
|
|
||||||
def __and__(self, other):
|
|
||||||
self._check_type(other, '&')
|
|
||||||
return self.intersection(other)
|
|
||||||
|
|
||||||
def __or__(self, other):
|
|
||||||
self._check_type(other, '|')
|
|
||||||
return self.union(other)
|
|
||||||
|
|
||||||
def __xor__(self, other):
|
|
||||||
self._check_type(other, '^')
|
|
||||||
return self.symmetric_difference(other)
|
|
||||||
|
|
||||||
# Comparison
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
if not isinstance(other, _basesetlist):
|
|
||||||
return False
|
|
||||||
if not len(self) == len(other):
|
|
||||||
return False
|
|
||||||
for self_elem, other_elem in zip(self, other):
|
|
||||||
if self_elem != other_elem:
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
def __ne__(self, other):
|
|
||||||
return not (self == other)
|
|
||||||
|
|
||||||
# New methods
|
|
||||||
|
|
||||||
def sub_index(self, sub, start=0, end=None):
|
|
||||||
"""Return the index of a subsequence.
|
|
||||||
|
|
||||||
This runs in O(len(sub))
|
|
||||||
|
|
||||||
Args:
|
|
||||||
sub (Sequence): An Iterable to search for
|
|
||||||
Returns:
|
|
||||||
int: The index of the first element of sub
|
|
||||||
Raises:
|
|
||||||
ValueError: If sub isn't a subsequence
|
|
||||||
TypeError: If sub isn't iterable
|
|
||||||
IndexError: If start or end are out of range
|
|
||||||
"""
|
|
||||||
start_index = self.index(sub[0], start, end)
|
|
||||||
end = self._fix_end_index(end)
|
|
||||||
if start_index + len(sub) > end:
|
|
||||||
raise ValueError
|
|
||||||
for i in range(1, len(sub)):
|
|
||||||
if sub[i] != self[start_index + i]:
|
|
||||||
raise ValueError
|
|
||||||
return start_index
|
|
||||||
|
|
||||||
def copy(self):
|
|
||||||
return self.__class__(self)
|
|
||||||
|
|
||||||
|
|
||||||
class setlist(_basesetlist, MutableSequence, MutableSet):
|
|
||||||
"""A mutable (unhashable) setlist."""
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return '{[%s}]' % ', '.join(repr(v) for v in self)
|
|
||||||
|
|
||||||
# Helper methods
|
|
||||||
def _delete_all(self, elems_to_delete, raise_errors):
|
|
||||||
indices_to_delete = set()
|
|
||||||
for elem in elems_to_delete:
|
|
||||||
try:
|
|
||||||
elem_index = self._dict[elem]
|
|
||||||
except KeyError:
|
|
||||||
if raise_errors:
|
|
||||||
raise ValueError('Passed values contain elements not in self')
|
|
||||||
else:
|
|
||||||
if elem_index in indices_to_delete:
|
|
||||||
if raise_errors:
|
|
||||||
raise ValueError('Passed vales contain duplicates')
|
|
||||||
indices_to_delete.add(elem_index)
|
|
||||||
self._delete_values_by_index(indices_to_delete)
|
|
||||||
|
|
||||||
def _delete_values_by_index(self, indices_to_delete):
|
|
||||||
deleted_count = 0
|
|
||||||
for i, elem in enumerate(self._list):
|
|
||||||
if i in indices_to_delete:
|
|
||||||
deleted_count += 1
|
|
||||||
del self._dict[elem]
|
|
||||||
else:
|
|
||||||
new_index = i - deleted_count
|
|
||||||
self._list[new_index] = elem
|
|
||||||
self._dict[elem] = new_index
|
|
||||||
# Now remove deleted_count items from the end of the list
|
|
||||||
if deleted_count:
|
|
||||||
self._list = self._list[:-deleted_count]
|
|
||||||
|
|
||||||
# Set/Sequence agnostic
|
|
||||||
def pop(self, index=-1):
|
|
||||||
"""Remove and return the item at index."""
|
|
||||||
value = self._list.pop(index)
|
|
||||||
del self._dict[value]
|
|
||||||
return value
|
|
||||||
|
|
||||||
def clear(self):
|
|
||||||
"""Remove all elements from self."""
|
|
||||||
self._dict = dict()
|
|
||||||
self._list = list()
|
|
||||||
|
|
||||||
# Implement MutableSequence
|
|
||||||
def __setitem__(self, index, value):
|
|
||||||
if isinstance(index, slice):
|
|
||||||
old_values = self[index]
|
|
||||||
for v in value:
|
|
||||||
if v in self and v not in old_values:
|
|
||||||
raise ValueError
|
|
||||||
self._list[index] = value
|
|
||||||
self._dict = {}
|
|
||||||
for i, v in enumerate(self._list):
|
|
||||||
self._dict[v] = i
|
|
||||||
else:
|
|
||||||
index = self._fix_neg_index(index)
|
|
||||||
old_value = self._list[index]
|
|
||||||
if value in self:
|
|
||||||
if value == old_value:
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
raise ValueError
|
|
||||||
del self._dict[old_value]
|
|
||||||
self._list[index] = value
|
|
||||||
self._dict[value] = index
|
|
||||||
|
|
||||||
def __delitem__(self, index):
|
|
||||||
if isinstance(index, slice):
|
|
||||||
indices_to_delete = set(self.index(e) for e in self._list[index])
|
|
||||||
self._delete_values_by_index(indices_to_delete)
|
|
||||||
else:
|
|
||||||
index = self._fix_neg_index(index)
|
|
||||||
value = self._list[index]
|
|
||||||
del self._dict[value]
|
|
||||||
for elem in self._list[index + 1:]:
|
|
||||||
self._dict[elem] -= 1
|
|
||||||
del self._list[index]
|
|
||||||
|
|
||||||
def insert(self, index, value):
|
|
||||||
"""Insert value at index.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
index (int): Index to insert value at
|
|
||||||
value: Value to insert
|
|
||||||
Raises:
|
|
||||||
ValueError: If value already in self
|
|
||||||
IndexError: If start or end are out of range
|
|
||||||
"""
|
|
||||||
if value in self:
|
|
||||||
raise ValueError
|
|
||||||
index = self._fix_neg_index(index)
|
|
||||||
self._dict[value] = index
|
|
||||||
for elem in self._list[index:]:
|
|
||||||
self._dict[elem] += 1
|
|
||||||
self._list.insert(index, value)
|
|
||||||
|
|
||||||
def append(self, value):
|
|
||||||
"""Append value to the end.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
value: Value to append
|
|
||||||
Raises:
|
|
||||||
ValueError: If value alread in self
|
|
||||||
TypeError: If value isn't hashable
|
|
||||||
"""
|
|
||||||
self._append(value)
|
|
||||||
|
|
||||||
def extend(self, values):
|
|
||||||
"""Append all values to the end.
|
|
||||||
|
|
||||||
If any of the values are present, ValueError will
|
|
||||||
be raised and none of the values will be appended.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
values (Iterable): Values to append
|
|
||||||
Raises:
|
|
||||||
ValueError: If any values are already present or there are duplicates
|
|
||||||
in the passed values.
|
|
||||||
TypeError: If any of the values aren't hashable.
|
|
||||||
"""
|
|
||||||
self._extend(values)
|
|
||||||
|
|
||||||
def __iadd__(self, values):
|
|
||||||
"""Add all values to the end of self.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
values (Iterable): Values to append
|
|
||||||
Raises:
|
|
||||||
ValueError: If any values are already present
|
|
||||||
"""
|
|
||||||
self._check_type(values, '+=')
|
|
||||||
self.extend(values)
|
|
||||||
return self
|
|
||||||
|
|
||||||
def remove(self, value):
|
|
||||||
"""Remove value from self.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
value: Element to remove from self
|
|
||||||
Raises:
|
|
||||||
ValueError: if element is already present
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
index = self._dict[value]
|
|
||||||
except KeyError:
|
|
||||||
raise ValueError('Value "%s" is not present.')
|
|
||||||
else:
|
|
||||||
del self[index]
|
|
||||||
|
|
||||||
def remove_all(self, elems_to_delete):
|
|
||||||
"""Remove all elements from elems_to_delete, raises ValueErrors.
|
|
||||||
|
|
||||||
See Also:
|
|
||||||
discard_all
|
|
||||||
Args:
|
|
||||||
elems_to_delete (Iterable): Elements to remove.
|
|
||||||
Raises:
|
|
||||||
ValueError: If the count of any element is greater in
|
|
||||||
elems_to_delete than self.
|
|
||||||
TypeError: If any of the values aren't hashable.
|
|
||||||
"""
|
|
||||||
self._delete_all(elems_to_delete, raise_errors=True)
|
|
||||||
|
|
||||||
# Implement MutableSet
|
|
||||||
|
|
||||||
def add(self, item):
|
|
||||||
"""Add an item.
|
|
||||||
|
|
||||||
Note:
|
|
||||||
This does not raise a ValueError for an already present value like
|
|
||||||
append does. This is to match the behavior of set.add
|
|
||||||
Args:
|
|
||||||
item: Item to add
|
|
||||||
Raises:
|
|
||||||
TypeError: If item isn't hashable.
|
|
||||||
"""
|
|
||||||
self._add(item)
|
|
||||||
|
|
||||||
def update(self, values):
|
|
||||||
"""Add all values to the end.
|
|
||||||
|
|
||||||
If any of the values are present, silently ignore
|
|
||||||
them (as opposed to extend which raises an Error).
|
|
||||||
|
|
||||||
See also:
|
|
||||||
extend
|
|
||||||
Args:
|
|
||||||
values (Iterable): Values to add
|
|
||||||
Raises:
|
|
||||||
TypeError: If any of the values are unhashable.
|
|
||||||
"""
|
|
||||||
self._update(values)
|
|
||||||
|
|
||||||
def discard_all(self, elems_to_delete):
|
|
||||||
"""Discard all the elements from elems_to_delete.
|
|
||||||
|
|
||||||
This is much faster than removing them one by one.
|
|
||||||
This runs in O(len(self) + len(elems_to_delete))
|
|
||||||
|
|
||||||
Args:
|
|
||||||
elems_to_delete (Iterable): Elements to discard.
|
|
||||||
Raises:
|
|
||||||
TypeError: If any of the values aren't hashable.
|
|
||||||
"""
|
|
||||||
self._delete_all(elems_to_delete, raise_errors=False)
|
|
||||||
|
|
||||||
def discard(self, value):
|
|
||||||
"""Discard an item.
|
|
||||||
|
|
||||||
Note:
|
|
||||||
This does not raise a ValueError for a missing value like remove does.
|
|
||||||
This is to match the behavior of set.discard
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
self.remove(value)
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def difference_update(self, other):
|
|
||||||
"""Update self to include only the differene with other."""
|
|
||||||
other = set(other)
|
|
||||||
indices_to_delete = set()
|
|
||||||
for i, elem in enumerate(self):
|
|
||||||
if elem in other:
|
|
||||||
indices_to_delete.add(i)
|
|
||||||
if indices_to_delete:
|
|
||||||
self._delete_values_by_index(indices_to_delete)
|
|
||||||
|
|
||||||
def intersection_update(self, other):
|
|
||||||
"""Update self to include only the intersection with other."""
|
|
||||||
other = set(other)
|
|
||||||
indices_to_delete = set()
|
|
||||||
for i, elem in enumerate(self):
|
|
||||||
if elem not in other:
|
|
||||||
indices_to_delete.add(i)
|
|
||||||
if indices_to_delete:
|
|
||||||
self._delete_values_by_index(indices_to_delete)
|
|
||||||
|
|
||||||
def symmetric_difference_update(self, other):
|
|
||||||
"""Update self to include only the symmetric difference with other."""
|
|
||||||
other = setlist(other)
|
|
||||||
indices_to_delete = set()
|
|
||||||
for i, item in enumerate(self):
|
|
||||||
if item in other:
|
|
||||||
indices_to_delete.add(i)
|
|
||||||
for item in other:
|
|
||||||
self.add(item)
|
|
||||||
self._delete_values_by_index(indices_to_delete)
|
|
||||||
|
|
||||||
def __isub__(self, other):
|
|
||||||
self._check_type(other, '-=')
|
|
||||||
self.difference_update(other)
|
|
||||||
return self
|
|
||||||
|
|
||||||
def __iand__(self, other):
|
|
||||||
self._check_type(other, '&=')
|
|
||||||
self.intersection_update(other)
|
|
||||||
return self
|
|
||||||
|
|
||||||
def __ior__(self, other):
|
|
||||||
self._check_type(other, '|=')
|
|
||||||
self.update(other)
|
|
||||||
return self
|
|
||||||
|
|
||||||
def __ixor__(self, other):
|
|
||||||
self._check_type(other, '^=')
|
|
||||||
self.symmetric_difference_update(other)
|
|
||||||
return self
|
|
||||||
|
|
||||||
# New methods
|
|
||||||
def shuffle(self, random=None):
|
|
||||||
"""Shuffle all of the elements in self randomly."""
|
|
||||||
random_.shuffle(self._list, random=random)
|
|
||||||
for i, elem in enumerate(self._list):
|
|
||||||
self._dict[elem] = i
|
|
||||||
|
|
||||||
def sort(self, *args, **kwargs):
|
|
||||||
"""Sort this setlist in place."""
|
|
||||||
self._list.sort(*args, **kwargs)
|
|
||||||
for index, value in enumerate(self._list):
|
|
||||||
self._dict[value] = index
|
|
||||||
|
|
||||||
|
|
||||||
class frozensetlist(_basesetlist, Hashable):
|
|
||||||
"""An immutable (hashable) setlist."""
|
|
||||||
|
|
||||||
def __hash__(self):
|
|
||||||
if not hasattr(self, '_hash_value'):
|
|
||||||
self._hash_value = _util.hash_iterable(self)
|
|
||||||
return self._hash_value
|
|
||||||
28
appveyor.yml
28
appveyor.yml
@@ -1,28 +0,0 @@
|
|||||||
version: '{build}'
|
|
||||||
pull_requests:
|
|
||||||
do_not_increment_build_number: true
|
|
||||||
environment:
|
|
||||||
ProjectVersion: build$(APPVEYOR_BUILD_VERSION)
|
|
||||||
matrix:
|
|
||||||
- PYTHON: C:\PYTHON36
|
|
||||||
install:
|
|
||||||
- ps: 'if(Test-Path env:APPVEYOR_REPO_TAG_NAME) {$env:ProjectVersion=$env:APPVEYOR_REPO_TAG_NAME}'
|
|
||||||
- '%PYTHON%\python.exe --version'
|
|
||||||
- '%PYTHON%\Scripts\pip install pyinstaller'
|
|
||||||
- '%PYTHON%\Scripts\pip install markdown'
|
|
||||||
- '%PYTHON%\python.exe -m markdown README.md > README.html'
|
|
||||||
- '%PYTHON%\Scripts\pyinstaller bundle\EntranceRandomizer.spec'
|
|
||||||
- 'mkdir dist\EntranceRandomizer\ext'
|
|
||||||
- 'move dist\EntranceRandomizer\*.pyd dist\EntranceRandomizer\ext'
|
|
||||||
- 'move dist\EntranceRandomizer\tcl*.dll dist\EntranceRandomizer\ext'
|
|
||||||
- 'move dist\EntranceRandomizer\tk*.dll dist\EntranceRandomizer\ext'
|
|
||||||
- ps: '$env:ER_Version= &"$env:PYTHON\python.exe" -c "import Main; import re; print(re.match(''[0-9]+\\.[0-9]+\\.[0-9]+'',Main.__version__).group(0))"'
|
|
||||||
- '"%WIX%\bin\heat.exe" dir "dist\EntranceRandomizer" -sfrag -srd -suid -dr INSTALLDIR -cg ERFiles -ag -template fragment -t bundle\components.xslt -out build\components.wxs'
|
|
||||||
- '"%WIX%\bin\candle.exe" -out build\ bundle\*.wxs build\*.wxs'
|
|
||||||
- '"%WIX%\bin\light.exe" -ext WixUIExtension build\*.wixobj -o dist\EntranceRandomizer-Installer-%ProjectVersion%-win32.msi -b dist\EntranceRandomizer'
|
|
||||||
build: off
|
|
||||||
artifacts:
|
|
||||||
- path: dist/EntranceRandomizer*.msi
|
|
||||||
name: EntranceRandomizer-Installer-$(ProjectVersion)-win32.msi
|
|
||||||
- path: dist/EntranceRandomizer/
|
|
||||||
name: EntranceRandomizer-Raw-$(ProjectVersion)-win32.zip
|
|
||||||
Binary file not shown.
21
notes.txt
21
notes.txt
@@ -1,21 +0,0 @@
|
|||||||
Notes:
|
|
||||||
|
|
||||||
Ice Spike Stairs seems to be off and maybe Lonely Freezor
|
|
||||||
Pod bk ledge stairs
|
|
||||||
|
|
||||||
AgaTower usually doesn't have enough Key Door candidates
|
|
||||||
(Testing) Camera spots in Aga Tower: Push Statue, Lone Statue maybe?
|
|
||||||
(Testing Waterfall Now) Spiral stairs without keys look funny - change to Incognito instead of Normal
|
|
||||||
|
|
||||||
(Can't Fix yet) Key door on Layer 1 not working (Aga Tower)
|
|
||||||
(Can't Fix yet) Key door in HC Lobby E door not passable - look into it?
|
|
||||||
|
|
||||||
Pairing Notes
|
|
||||||
30-34 4f-37 #$18,-24
|
|
||||||
30-34 37-1f #$18,-24
|
|
||||||
30-34 1f-1f???
|
|
||||||
30-35 1f-1f
|
|
||||||
30-34 36-1e #$18,-24
|
|
||||||
30-34 1e-1f
|
|
||||||
|
|
||||||
inconsistencies are caused by $0492 being set wrong
|
|
||||||
@@ -7,6 +7,7 @@ requires-python = ">=3.7"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"aenum>=3.1.16",
|
"aenum>=3.1.16",
|
||||||
"aioconsole>=0.6.2",
|
"aioconsole>=0.6.2",
|
||||||
|
"collections-extended>=2.0.2",
|
||||||
"colorama>=0.4.6",
|
"colorama>=0.4.6",
|
||||||
"distro>=1.9.0",
|
"distro>=1.9.0",
|
||||||
"fast-enum>=1.3.0",
|
"fast-enum>=1.3.0",
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ from yaml.representer import Representer
|
|||||||
import RaceRandom as random
|
import RaceRandom as random
|
||||||
from BaseClasses import DoorType, LocationType
|
from BaseClasses import DoorType, LocationType
|
||||||
from OverworldShuffle import default_flute_connections, flute_data
|
from OverworldShuffle import default_flute_connections, flute_data
|
||||||
|
from source.overworld.FluteShuffle import default_flute_connections, flute_data
|
||||||
from source.tools.MysteryUtils import get_weights, roll_settings
|
from source.tools.MysteryUtils import get_weights, roll_settings
|
||||||
from Utils import HexInt, hex_representer
|
from Utils import HexInt, hex_representer
|
||||||
|
|
||||||
|
|||||||
270
source/overworld/FluteShuffle.py
Normal file
270
source/overworld/FluteShuffle.py
Normal file
@@ -0,0 +1,270 @@
|
|||||||
|
import copy
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import RaceRandom as random
|
||||||
|
from BaseClasses import Entrance, RegionType, Terrain
|
||||||
|
from DungeonGenerator import GenerationException
|
||||||
|
from OWEdges import OWTileRegions
|
||||||
|
from source.overworld.EntranceShuffle2 import connect_simple
|
||||||
|
|
||||||
|
|
||||||
|
def shuffle_flute_spots(world, player):
|
||||||
|
def connect_flutes(flute_destinations):
|
||||||
|
for o in range(0, len(flute_destinations)):
|
||||||
|
owid = flute_destinations[o]
|
||||||
|
regions = flute_data[owid][0]
|
||||||
|
if not world.is_tile_swapped(owid, player):
|
||||||
|
connect_simple(world, 'Flute Spot ' + str(o + 1), regions[0], player)
|
||||||
|
else:
|
||||||
|
connect_simple(world, 'Flute Spot ' + str(o + 1), regions[1], player)
|
||||||
|
|
||||||
|
if world.owFluteShuffle[player] == 'vanilla':
|
||||||
|
flute_spots = default_flute_connections.copy()
|
||||||
|
sort_flute_spots(world, player, flute_spots)
|
||||||
|
world.owflutespots[player] = flute_spots
|
||||||
|
connect_flutes(flute_spots)
|
||||||
|
else:
|
||||||
|
from OverworldShuffle import one_way_ledges
|
||||||
|
|
||||||
|
flute_spots = 8
|
||||||
|
flute_pool = list(flute_data.keys())
|
||||||
|
new_spots = list()
|
||||||
|
ignored_regions = set()
|
||||||
|
used_flute_regions = []
|
||||||
|
forbidden_spots = []
|
||||||
|
forbidden_regions = []
|
||||||
|
|
||||||
|
def addSpot(owid, ignore_proximity, forced):
|
||||||
|
if world.owFluteShuffle[player] == 'balanced':
|
||||||
|
def getIgnored(regionname, base_owid, owid):
|
||||||
|
region = world.get_region(regionname, player)
|
||||||
|
for exit in region.exits:
|
||||||
|
if exit.connected_region is not None and exit.connected_region.type in [RegionType.LightWorld, RegionType.DarkWorld] and exit.connected_region.name not in new_ignored:
|
||||||
|
if exit.connected_region.name in OWTileRegions and (OWTileRegions[exit.connected_region.name] in [base_owid, owid] or OWTileRegions[regionname] == base_owid):
|
||||||
|
new_ignored.add(exit.connected_region.name)
|
||||||
|
getIgnored(exit.connected_region.name, base_owid, OWTileRegions[exit.connected_region.name])
|
||||||
|
if regionname in one_way_ledges:
|
||||||
|
for ledge_region in one_way_ledges[regionname]:
|
||||||
|
if ledge_region not in new_ignored:
|
||||||
|
new_ignored.add(ledge_region)
|
||||||
|
getIgnored(ledge_region, base_owid, OWTileRegions[ledge_region])
|
||||||
|
|
||||||
|
if not world.is_tile_swapped(owid, player):
|
||||||
|
new_region = flute_data[owid][0][0]
|
||||||
|
else:
|
||||||
|
new_region = flute_data[owid][0][1]
|
||||||
|
|
||||||
|
if new_region in ignored_regions and not forced:
|
||||||
|
return False
|
||||||
|
|
||||||
|
new_ignored = {new_region}
|
||||||
|
getIgnored(new_region, OWTileRegions[new_region], OWTileRegions[new_region])
|
||||||
|
if not ignore_proximity and not forced and random.randint(0, 31) != 0 and new_ignored.intersection(ignored_regions):
|
||||||
|
return False
|
||||||
|
ignored_regions.update(new_ignored)
|
||||||
|
if owid in flute_pool:
|
||||||
|
flute_pool.remove(owid)
|
||||||
|
if ignore_proximity and not forced:
|
||||||
|
logging.getLogger('').warning(f'Warning: Adding flute spot within proximity: {hex(owid)}')
|
||||||
|
logging.getLogger('').debug(f'Placing flute at: {hex(owid)}')
|
||||||
|
new_spots.append(owid)
|
||||||
|
else:
|
||||||
|
# TODO: Inspect later, seems to happen only with 'random' flute shuffle
|
||||||
|
logging.getLogger('').warning(f'Warning: Attempted to place flute spot not in pool: {hex(owid)}')
|
||||||
|
return True
|
||||||
|
|
||||||
|
if world.customizer:
|
||||||
|
custom_spots = world.customizer.get_owflutespots()
|
||||||
|
if custom_spots and player in custom_spots:
|
||||||
|
if 'force' in custom_spots[player]:
|
||||||
|
for id in custom_spots[player]['force']:
|
||||||
|
owid = id & 0xBF
|
||||||
|
addSpot(owid, True, True)
|
||||||
|
flute_spots -= 1
|
||||||
|
if not world.is_tile_swapped(owid, player):
|
||||||
|
used_flute_regions.append(flute_data[owid][0][0])
|
||||||
|
else:
|
||||||
|
used_flute_regions.append(flute_data[owid][0][1])
|
||||||
|
if 'forbid' in custom_spots[player]:
|
||||||
|
for id in custom_spots[player]['forbid']:
|
||||||
|
owid = id & 0xBF
|
||||||
|
if owid not in new_spots:
|
||||||
|
forbidden_spots.append(owid)
|
||||||
|
if not world.is_tile_swapped(owid, player):
|
||||||
|
forbidden_regions.append(flute_data[owid][0][0])
|
||||||
|
else:
|
||||||
|
forbidden_regions.append(flute_data[owid][0][1])
|
||||||
|
|
||||||
|
# determine sectors (isolated groups of regions) to place flute spots
|
||||||
|
flute_regions = {(f[0][0] if (o not in world.owswaps[player][0]) != (world.mode[player] == 'inverted') else f[0][1]) : o for o, f in flute_data.items() if o not in new_spots and o not in forbidden_spots}
|
||||||
|
flute_sectors = [(len([r for l in s for r in l]), [r for l in s for r in l if r in flute_regions]) for s in world.owsectors[player]]
|
||||||
|
flute_sectors = [s for s in flute_sectors if len(s[1]) > 0]
|
||||||
|
region_total = sum([c for c,_ in flute_sectors])
|
||||||
|
sector_total = len(flute_sectors)
|
||||||
|
empty_sector_total = 0
|
||||||
|
sector_has_spot = []
|
||||||
|
|
||||||
|
# determine which sectors still need a flute spot
|
||||||
|
for sector in flute_sectors:
|
||||||
|
already_has_spot = any(region in sector for region in used_flute_regions)
|
||||||
|
sector_has_spot.append(already_has_spot)
|
||||||
|
if not already_has_spot:
|
||||||
|
empty_sector_total += 1
|
||||||
|
if flute_spots < empty_sector_total:
|
||||||
|
logging.getLogger('').warning(f'Warning: Not every sector can have a flute spot, generation might fail')
|
||||||
|
# pretend like some of the empty sectors already have a flute spot, don't know if they will be reachable
|
||||||
|
for i in range(len(flute_sectors)):
|
||||||
|
if not sector_has_spot[i]:
|
||||||
|
sector_has_spot[i] = True
|
||||||
|
empty_sector_total -= 1
|
||||||
|
if flute_spots == empty_sector_total:
|
||||||
|
break
|
||||||
|
|
||||||
|
# distribute flute spots for each sector
|
||||||
|
for i in range(len(flute_sectors)):
|
||||||
|
sector = flute_sectors[i]
|
||||||
|
sector_total -= 1
|
||||||
|
if not sector_has_spot[i]:
|
||||||
|
empty_sector_total -= 1
|
||||||
|
spots_to_place = min(flute_spots - empty_sector_total, max(0 if sector_has_spot[i] else 1, round((sector[0] * (flute_spots - sector_total) / region_total) + 0.5)))
|
||||||
|
target_spots = len(new_spots) + spots_to_place
|
||||||
|
logging.getLogger('').debug(f'Sector of {sector[0]} regions gets {spots_to_place} spot(s)')
|
||||||
|
|
||||||
|
if 0x30 in flute_pool and 0x30 not in forbidden_spots and len(new_spots) < target_spots and ('Desert Teleporter Ledge' in sector[1] or 'Mire Teleporter Ledge' in sector[1]):
|
||||||
|
addSpot(0x30, True, True) # guarantee desert/mire access
|
||||||
|
|
||||||
|
random.shuffle(sector[1])
|
||||||
|
f = 0
|
||||||
|
t = 0
|
||||||
|
while len(new_spots) < target_spots:
|
||||||
|
if f >= len(sector[1]):
|
||||||
|
f = 0
|
||||||
|
t += 1
|
||||||
|
if t > 5:
|
||||||
|
raise GenerationException('Infinite loop detected in flute shuffle')
|
||||||
|
owid = flute_regions[sector[1][f]]
|
||||||
|
if owid not in new_spots and owid not in forbidden_spots:
|
||||||
|
addSpot(owid, t > 0, False)
|
||||||
|
f += 1
|
||||||
|
|
||||||
|
region_total -= sector[0]
|
||||||
|
flute_spots -= spots_to_place
|
||||||
|
|
||||||
|
# connect new flute spots
|
||||||
|
sort_flute_spots(world, player, new_spots)
|
||||||
|
world.owflutespots[player] = new_spots
|
||||||
|
connect_flutes(new_spots)
|
||||||
|
|
||||||
|
# update spoiler
|
||||||
|
#new_spots = list(map(lambda o: flute_data[o][1], new_spots))
|
||||||
|
s = list(map(lambda x: ' ' if x not in new_spots else 'F', [i for i in range(0x40)]))
|
||||||
|
text_output = flute_spoiler_table.replace('s', '%s') % ( s[0x02], s[0x07],
|
||||||
|
s[0x00], s[0x03], s[0x05],
|
||||||
|
s[0x00], s[0x02],s[0x03], s[0x05], s[0x07], s[0x0a], s[0x0f],
|
||||||
|
s[0x0a], s[0x0f],
|
||||||
|
s[0x10],s[0x11],s[0x12],s[0x13],s[0x14],s[0x15],s[0x16],s[0x17], s[0x10],s[0x11],s[0x12],s[0x13],s[0x14],s[0x15],s[0x16],s[0x17],
|
||||||
|
s[0x18], s[0x1a],s[0x1b], s[0x1d],s[0x1e],
|
||||||
|
s[0x22], s[0x25], s[0x1a], s[0x1d],
|
||||||
|
s[0x28],s[0x29],s[0x2a],s[0x2b],s[0x2c],s[0x2d],s[0x2e],s[0x2f], s[0x18], s[0x1b], s[0x1e],
|
||||||
|
s[0x30], s[0x32],s[0x33],s[0x34],s[0x35], s[0x37], s[0x22], s[0x25],
|
||||||
|
s[0x3a],s[0x3b],s[0x3c], s[0x3f],
|
||||||
|
s[0x28],s[0x29],s[0x2a],s[0x2b],s[0x2c],s[0x2d],s[0x2e],s[0x2f],
|
||||||
|
s[0x32],s[0x33],s[0x34], s[0x37],
|
||||||
|
s[0x30], s[0x35],
|
||||||
|
s[0x3a],s[0x3b],s[0x3c], s[0x3f])
|
||||||
|
world.spoiler.set_map('flute', text_output, new_spots, player)
|
||||||
|
create_dynamic_flute_exits(world, player)
|
||||||
|
|
||||||
|
|
||||||
|
def sort_flute_spots(world, player, flute_spots):
|
||||||
|
if world.owLayout[player] != 'grid':
|
||||||
|
flute_spots.sort(key=lambda id: flute_data[id][1] if id != 0x03 or not world.is_tile_swapped(0x03, player) else 0x04)
|
||||||
|
else:
|
||||||
|
world_layout = world.owgrid[player][0] if world.mode[player] != 'inverted' else world.owgrid[player][1]
|
||||||
|
layout_list = sum(world_layout, [])
|
||||||
|
layout_map = {id & 0xBF: i for i, id in enumerate(layout_list)}
|
||||||
|
flute_spots.sort(key=lambda id: layout_map[flute_data[id][1] if id != 0x03 or not world.is_tile_swapped(0x03, player) else 0x04])
|
||||||
|
|
||||||
|
|
||||||
|
def create_dynamic_flute_exits(world, player):
|
||||||
|
flute_in_pool = True if player not in world.customitemarray else any(i for i, n in world.customitemarray[player].items() if i == 'flute' and n > 0)
|
||||||
|
if not flute_in_pool:
|
||||||
|
return
|
||||||
|
for region in (r for r in world.regions if r.player == player and r.terrain == Terrain.Land and r.name not in ['Zoras Domain', 'Master Sword Meadow', 'Hobo Bridge']):
|
||||||
|
if region.type == (RegionType.LightWorld if world.mode[player] != 'inverted' else RegionType.DarkWorld):
|
||||||
|
exitname = 'Flute From ' + region.name
|
||||||
|
exit = Entrance(region.player, exitname, region)
|
||||||
|
exit.spot_type = 'Flute'
|
||||||
|
exit.connect(world.get_region('Flute Sky', player))
|
||||||
|
region.exits.append(exit)
|
||||||
|
world.initialize_regions()
|
||||||
|
|
||||||
|
|
||||||
|
default_flute_connections = [
|
||||||
|
0x03, 0x16, 0x18, 0x2c, 0x2f, 0x30, 0x3b, 0x3f
|
||||||
|
]
|
||||||
|
|
||||||
|
flute_data = {
|
||||||
|
#OWID LW Region DW Region Slot VRAM BG Y BG X Link Y Link X Cam Y Cam X Unk1 Unk2 IconY IconX AltY AltX AltVRAM AltBGY AltBGX AltCamY AltCamX AltUnk1 AltUnk2 AltIconY AltIconX
|
||||||
|
0x00: (['Lost Woods East Area', 'Skull Woods Forest'], 0x09, 0x1042, 0x022e, 0x0202, 0x0290, 0x0288, 0x029b, 0x028f, 0xfff2, 0x000e, 0x0290, 0x0288, 0x0290, 0x0290),
|
||||||
|
0x02: (['Lumberjack Area', 'Dark Lumberjack Area'], 0x02, 0x059c, 0x00d6, 0x04e6, 0x0138, 0x0558, 0x0143, 0x0563, 0xfffa, 0xfffa, 0x01d8, 0x0518),
|
||||||
|
0x03: (['West Death Mountain (Bottom)', 'West Dark Death Mountain (Top)'], 0x0b, 0x1600, 0x02ca, 0x060e, 0x0328, 0x0678, 0x0337, 0x0683, 0xfff6, 0xfff2, 0x03bb, 0x0680, 0x0118, 0x0860, 0x05c0, 0x00b8, 0x07ec, 0x0127, 0x086b, 0xfff8, 0x0004, 0x0148, 0x0850),
|
||||||
|
0x05: (['East Death Mountain (Bottom)', 'East Dark Death Mountain (Bottom)'], 0x0e, 0x1860, 0x031e, 0x0d00, 0x0388, 0x0da8, 0x038d, 0x0d7d, 0x0000, 0x0000, 0x03c8, 0x0d98),
|
||||||
|
0x07: (['Death Mountain TR Pegs Area', 'Turtle Rock Area'], 0x07, 0x0804, 0x0102, 0x0e1a, 0x0160, 0x0e90, 0x016f, 0x0e97, 0xfffe, 0x0006, 0x0150, 0x0ea0),
|
||||||
|
0x0a: (['Mountain Pass Area', 'Bumper Cave Area'], 0x0a, 0x0180, 0x0220, 0x0406, 0x0280, 0x0488, 0x028f, 0x0493, 0x0000, 0xfffa, 0x0390, 0x04d8),
|
||||||
|
0x0f: (['Zora Waterfall Area', 'Catfish Area'], 0x0f, 0x0316, 0x025c, 0x0eb2, 0x02c0, 0x0f28, 0x02cb, 0x0f2f, 0x0002, 0xfffe, 0x0360, 0x0f58),
|
||||||
|
0x10: (['Lost Woods Pass West Area', 'Skull Woods Pass West Area'], 0x10, 0x0080, 0x0400, 0x0000, 0x0448, 0x0058, 0x046f, 0x0085, 0x0000, 0x0000, 0x04f8, 0x0088),
|
||||||
|
0x11: (['Kakariko Fortune Area', 'Dark Fortune Area'], 0x11, 0x0912, 0x051e, 0x0292, 0x0588, 0x0318, 0x058d, 0x031f, 0x0000, 0xfffe, 0x05f8, 0x0318),
|
||||||
|
0x12: (['Kakariko Pond Area', 'Outcast Pond Area'], 0x12, 0x0890, 0x051a, 0x0476, 0x0578, 0x04f8, 0x0587, 0x0503, 0xfff6, 0x000a, 0x05b8, 0x04f8),
|
||||||
|
0x13: (['Sanctuary Area', 'Dark Chapel Area'], 0x13, 0x051c, 0x04aa, 0x06de, 0x0508, 0x0758, 0x0517, 0x0763, 0xfff6, 0x0002, 0x05b8, 0x0738),
|
||||||
|
0x14: (['Graveyard Area', 'Dark Graveyard Area'], 0x14, 0x089c, 0x051e, 0x08e6, 0x0580, 0x0958, 0x058b, 0x0963, 0x0000, 0xfffa, 0x05f0, 0x0918, 0x0580, 0x0948),
|
||||||
|
0x15: (['River Bend East Bank', 'Qirn Jump East Bank'], 0x15, 0x041a, 0x0486, 0x0ad2, 0x04e8, 0x0b48, 0x04f3, 0x0b4f, 0x0008, 0xfffe, 0x0548, 0x0b78),
|
||||||
|
0x16: (['Potion Shop Area', 'Dark Witch Area'], 0x16, 0x0888, 0x0516, 0x0c4e, 0x0578, 0x0cc8, 0x0583, 0x0cd3, 0xfffa, 0xfff2, 0x05e8, 0x0c9f),
|
||||||
|
0x17: (['Zora Approach Ledge', 'Catfish Approach Ledge'], 0x17, 0x039e, 0x047e, 0x0ef2, 0x04e0, 0x0f68, 0x04eb, 0x0f6f, 0x0000, 0xfffe, 0x0580, 0x0f48),
|
||||||
|
0x18: (['Kakariko Village', 'Village of Outcasts'], 0x18, 0x0b30, 0x0759, 0x017e, 0x07b7, 0x0200, 0x07c6, 0x020b, 0x0007, 0x0002, 0x0830, 0x0240, 0x07c8, 0x01f8),
|
||||||
|
0x1a: (['Forgotten Forest Area', 'Shield Shop Fence'], 0x1a, 0x081a, 0x070f, 0x04d2, 0x0770, 0x0548, 0x077c, 0x054f, 0xffff, 0xfffe, 0x0770, 0x0518),
|
||||||
|
0x1b: (['Hyrule Castle Courtyard', 'Pyramid Area'], 0x1b, 0x0c30, 0x077a, 0x0786, 0x07d8, 0x07f8, 0x07e7, 0x0803, 0x0006, 0xfffa, 0x07f8, 0x07f8),
|
||||||
|
0x1d: (['Wooden Bridge Area', 'Broken Bridge Northeast'], 0x1d, 0x0602, 0x06c2, 0x0a0e, 0x0720, 0x0a80, 0x072f, 0x0a8b, 0xfffe, 0x0002, 0x0750, 0x0a70),
|
||||||
|
0x1e: (['Eastern Palace Area', 'Palace of Darkness Area'], 0x26, 0x1802, 0x091e, 0x0c0e, 0x09c0, 0x0c80, 0x098b, 0x0c8b, 0x0000, 0x0002, 0x09a0, 0x0cb0),
|
||||||
|
0x22: (['Blacksmith Area', 'Hammer Pegs Area'], 0x22, 0x058c, 0x08aa, 0x0462, 0x0908, 0x04d8, 0x0917, 0x04df, 0x0006, 0xfffe, 0x0978, 0x04e8),
|
||||||
|
0x25: (['Sand Dunes Area', 'Dark Dunes Area'], 0x25, 0x030e, 0x085a, 0x0a76, 0x08b8, 0x0ae8, 0x08c7, 0x0af3, 0x0006, 0xfffa, 0x0918, 0x0b18),
|
||||||
|
0x28: (['Maze Race Area', 'Dig Game Area'], 0x28, 0x0908, 0x0b1e, 0x003a, 0x0b88, 0x00b8, 0x0b8d, 0x00bf, 0x0000, 0x0006, 0x0ba8, 0x00b8),
|
||||||
|
0x29: (['Kakariko Suburb Area', 'Frog Area'], 0x29, 0x0408, 0x0a7c, 0x0242, 0x0ae0, 0x02c0, 0x0aeb, 0x02c7, 0x0002, 0xfffe, 0x0b30, 0x02e0),
|
||||||
|
0x2a: (['Flute Boy Area', 'Stumpy Area'], 0x2a, 0x058e, 0x0aac, 0x046e, 0x0b10, 0x04e8, 0x0b1b, 0x04f3, 0x0002, 0x0002, 0x0b60, 0x04f8),
|
||||||
|
0x2b: (['Central Bonk Rocks Area', 'Dark Bonk Rocks Area'], 0x2b, 0x0620, 0x0acc, 0x0700, 0x0b30, 0x0790, 0x0b3b, 0x0785, 0xfff2, 0x0000, 0x0b80, 0x0760),
|
||||||
|
0x2c: (['Links House Area', 'Big Bomb Shop Area'], 0x2c, 0x0588, 0x0ab9, 0x0840, 0x0b17, 0x08b8, 0x0b26, 0x08bf, 0xfff7, 0x0000, 0x0bb0, 0x08a8),
|
||||||
|
0x2d: (['Stone Bridge South Area', 'Hammer Bridge South Area'], 0x2d, 0x0886, 0x0b1e, 0x0a2a, 0x0ba0, 0x0aa8, 0x0b8b, 0x0aaf, 0x0000, 0x0006, 0x0bf0, 0x0ab8),
|
||||||
|
0x2e: (['Tree Line Area', 'Dark Tree Line Area'], 0x2e, 0x0100, 0x0a1a, 0x0c00, 0x0a78, 0x0c30, 0x0a87, 0x0c7d, 0x0006, 0x0000, 0x0ac8, 0x0c70),
|
||||||
|
0x2f: (['Eastern Nook Area', 'Darkness Nook Area'], 0x2f, 0x0798, 0x0afa, 0x0eb2, 0x0b58, 0x0f30, 0x0b67, 0x0f37, 0xfff6, 0x000e, 0x0bc0, 0x0f00),
|
||||||
|
0x30: (['Desert Teleporter Ledge', 'Mire Teleporter Ledge'], 0x38, 0x1880, 0x0f1e, 0x0000, 0x0fa8, 0x0078, 0x0f8d, 0x008d, 0x0000, 0x0000, 0x0ff0, 0x0070),
|
||||||
|
0x32: (['Flute Boy Approach Area', 'Stumpy Approach Area'], 0x32, 0x03a0, 0x0c6c, 0x0500, 0x0cd0, 0x05a8, 0x0cdb, 0x0585, 0x0002, 0x0000, 0x0d00, 0x0528),
|
||||||
|
0x33: (['C Whirlpool Outer Area', 'Dark C Whirlpool Outer Area'], 0x33, 0x0180, 0x0c20, 0x0600, 0x0c80, 0x0628, 0x0c8f, 0x067d, 0x0000, 0x0000, 0x0ce0, 0x0688),
|
||||||
|
0x34: (['Statues Area', 'Hype Cave Area'], 0x34, 0x088e, 0x0d00, 0x0866, 0x0d60, 0x08d8, 0x0d6f, 0x08e3, 0x0000, 0x000a, 0x0dd0, 0x08e8),
|
||||||
|
#0x35: (['Lake Hylia Northwest Bank', 'Ice Lake Northwest Bank'], 0x35, 0x0d00, 0x0da6, 0x0a06, 0x0e08, 0x0a80, 0x0e13, 0x0a8b, 0xfffa, 0xfffa, 0x0dc8, 0x0a90),
|
||||||
|
0x35: (['Lake Hylia South Shore', 'Ice Lake Southeast Ledge'], 0x3e, 0x1860, 0x0f1e, 0x0d00, 0x0f98, 0x0da8, 0x0f8b, 0x0d85, 0x0000, 0x0000, 0x0fd8, 0x0da8),
|
||||||
|
0x37: (['Ice Cave Area', 'Shopping Mall Area'], 0x37, 0x0786, 0x0cf6, 0x0e2e, 0x0d58, 0x0ea0, 0x0d63, 0x0eab, 0x000a, 0x0002, 0x0d98, 0x0ed0),
|
||||||
|
0x3a: (['Desert Pass Area', 'Swamp Nook Area'], 0x3a, 0x001a, 0x0e08, 0x04c6, 0x0e70, 0x0540, 0x0e7d, 0x054b, 0x0006, 0x000a, 0x0ee0, 0x0570),
|
||||||
|
0x3b: (['Dam Area', 'Swamp Area'], 0x3b, 0x069e, 0x0edf, 0x06f2, 0x0f3d, 0x0778, 0x0f4c, 0x077f, 0xfff1, 0xfffe, 0x0fd0, 0x0770),
|
||||||
|
0x3c: (['South Pass Area', 'Dark South Pass Area'], 0x3c, 0x0584, 0x0ed0, 0x081e, 0x0f38, 0x0898, 0x0f45, 0x08a3, 0xfffe, 0x0002, 0x0fa8, 0x0898),
|
||||||
|
0x3f: (['Octoballoon Area', 'Bomber Corner Area'], 0x3f, 0x0810, 0x0f05, 0x0e75, 0x0f67, 0x0ef3, 0x0f72, 0x0efa, 0xfffb, 0x000b, 0x0fd0, 0x0ef0)
|
||||||
|
}
|
||||||
|
|
||||||
|
flute_spoiler_table = \
|
||||||
|
""" 0 1 2 3 4 5 6 7
|
||||||
|
+---+-+---+---+-+
|
||||||
|
01234567 A(00)| |s| | |s|
|
||||||
|
+--------+ | s +-+ s | s +-+
|
||||||
|
A(00)|s ss s s| B(08)| |s| | |s|
|
||||||
|
B(08)| s s| +-+-+-+-+-+-+-+-+
|
||||||
|
C(10)|ssssssss| C(10)|s|s|s|s|s|s|s|s|
|
||||||
|
D(18)|s ss ss | +-+-+-+-+-+-+-+-+
|
||||||
|
E(20)| s s | D(18)| |s| |s| |
|
||||||
|
F(28)|ssssssss| | s +-+ s +-+ s |
|
||||||
|
G(30)|s ssss s| E(20)| |s| |s| |
|
||||||
|
H(38)| sss s| +-+-+-+-+-+-+-+-+
|
||||||
|
+--------+ F(28)|s|s|s|s|s|s|s|s|
|
||||||
|
+-+-+-+-+-+-+-+-+
|
||||||
|
G(30)| |s|s|s| |s|
|
||||||
|
| s +-+-+-+ s +-+
|
||||||
|
H(38)| |s|s|s| |s|
|
||||||
|
+---+-+-+-+---+-+"""
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
import copy
|
import copy
|
||||||
import logging
|
import logging
|
||||||
import RaceRandom as random
|
|
||||||
import random as _random
|
import random as _random
|
||||||
from typing import List, Dict, Optional, Set, Tuple
|
from typing import Dict, List, Optional, Set, Tuple
|
||||||
from BaseClasses import OWEdge, World, Direction, Terrain
|
|
||||||
|
import RaceRandom as random
|
||||||
|
from BaseClasses import Direction, OWEdge, Terrain, World
|
||||||
from OverworldShuffle import connect_two_way, validate_layout
|
from OverworldShuffle import connect_two_way, validate_layout
|
||||||
|
|
||||||
ENABLE_KEEP_SIMILAR_SPECIAL_HANDLING = False
|
ENABLE_KEEP_SIMILAR_SPECIAL_HANDLING = False
|
||||||
|
|||||||
@@ -2,10 +2,13 @@ import logging
|
|||||||
import os
|
import os
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Dict, List
|
from typing import Dict, List
|
||||||
|
|
||||||
from PIL import Image, ImageDraw
|
from PIL import Image, ImageDraw
|
||||||
|
|
||||||
from BaseClasses import Direction, OWEdge
|
from BaseClasses import Direction, OWEdge
|
||||||
from source.overworld.LayoutGenerator import Screen
|
from source.overworld.LayoutGenerator import Screen
|
||||||
|
|
||||||
|
|
||||||
def get_edge_lists(grid: List[List[List[int]]],
|
def get_edge_lists(grid: List[List[List[int]]],
|
||||||
overworld_screens: Dict[int, Screen],
|
overworld_screens: Dict[int, Screen],
|
||||||
large_screen_quadrant_info: Dict[int, Dict]) -> Dict:
|
large_screen_quadrant_info: Dict[int, Dict]) -> Dict:
|
||||||
|
|||||||
11
uv.lock
generated
11
uv.lock
generated
@@ -49,6 +49,7 @@ dependencies = [
|
|||||||
{ name = "aenum" },
|
{ name = "aenum" },
|
||||||
{ name = "aioconsole", version = "0.6.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" },
|
{ name = "aioconsole", version = "0.6.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" },
|
||||||
{ name = "aioconsole", version = "0.8.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.8'" },
|
{ name = "aioconsole", version = "0.8.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.8'" },
|
||||||
|
{ name = "collections-extended" },
|
||||||
{ name = "colorama" },
|
{ name = "colorama" },
|
||||||
{ name = "distro" },
|
{ name = "distro" },
|
||||||
{ name = "fast-enum" },
|
{ name = "fast-enum" },
|
||||||
@@ -64,6 +65,7 @@ dependencies = [
|
|||||||
requires-dist = [
|
requires-dist = [
|
||||||
{ name = "aenum", specifier = ">=3.1.16" },
|
{ name = "aenum", specifier = ">=3.1.16" },
|
||||||
{ name = "aioconsole", specifier = ">=0.6.2" },
|
{ name = "aioconsole", specifier = ">=0.6.2" },
|
||||||
|
{ name = "collections-extended", specifier = ">=2.0.2" },
|
||||||
{ name = "colorama", specifier = ">=0.4.6" },
|
{ name = "colorama", specifier = ">=0.4.6" },
|
||||||
{ name = "distro", specifier = ">=1.9.0" },
|
{ name = "distro", specifier = ">=1.9.0" },
|
||||||
{ name = "fast-enum", specifier = ">=1.3.0" },
|
{ name = "fast-enum", specifier = ">=1.3.0" },
|
||||||
@@ -72,6 +74,15 @@ requires-dist = [
|
|||||||
{ name = "websockets", specifier = ">=11.0.3" },
|
{ name = "websockets", specifier = ">=11.0.3" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "collections-extended"
|
||||||
|
version = "2.0.2"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/62/40/e92bd575a99b7fb1fe9f3198ff4addc7d9f66e3d7e976b4d1b5bca6e049d/collections-extended-2.0.2.tar.gz", hash = "sha256:d68ebd1e2208444e392aa0016e5c085bd897f89296f5c1860787de5935994114", size = 22753, upload-time = "2022-01-23T18:58:38.99Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d5/65/8f409bbb1faf21714ebebfa553a34023a6657bbb2f85f43141be4e4ffe5e/collections_extended-2.0.2-py3-none-any.whl", hash = "sha256:bc096dda430b9455f51287943f7f63fbe53a4de78c444cc38c44e996af306ac5", size = 24330, upload-time = "2022-01-23T18:58:40.662Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "colorama"
|
name = "colorama"
|
||||||
version = "0.4.6"
|
version = "0.4.6"
|
||||||
|
|||||||
Reference in New Issue
Block a user