Intermediate commit to switch from work to home
This commit is contained in:
@@ -867,6 +867,7 @@ class Door(object):
|
||||
self.smallKey = False # There's a small key door on this side
|
||||
self.bigKey = False # There's a big key door on this side
|
||||
self.ugly = False # Indicates that it can't be seen from the front (e.g. back of a big key door)
|
||||
self.landing = False
|
||||
|
||||
def getAddress(self):
|
||||
if self.type == DoorType.Normal:
|
||||
@@ -908,22 +909,32 @@ class Sector(object):
|
||||
|
||||
def __init__(self):
|
||||
self.regions = []
|
||||
self.oustandings_doors = []
|
||||
self.outstanding_doors = []
|
||||
# todo: make these lazy init? - when do you invalidate them
|
||||
|
||||
def polarity(self):
|
||||
polarity = [0, 0, 0]
|
||||
for door in self.oustandings_doors:
|
||||
idx, inc = pol_idx[door.direction]
|
||||
polarity[idx] = pol_inc[inc](polarity[idx])
|
||||
for door in self.outstanding_doors:
|
||||
if not door.landing:
|
||||
idx, inc = pol_idx[door.direction]
|
||||
polarity[idx] = pol_inc[inc](polarity[idx])
|
||||
return polarity
|
||||
|
||||
def magnitude(self):
|
||||
magnitude = [0, 0, 0]
|
||||
for door in self.oustandings_doors:
|
||||
idx, inc = pol_idx[door.direction]
|
||||
magnitude[idx] = magnitude[idx] + 1
|
||||
for door in self.outstanding_doors:
|
||||
if not door.landing:
|
||||
idx, inc = pol_idx[door.direction]
|
||||
magnitude[idx] = magnitude[idx] + 1
|
||||
return magnitude
|
||||
|
||||
def outflow(self):
|
||||
outflow = 0
|
||||
for door in self.outstanding_doors:
|
||||
if not door.blocked:
|
||||
outflow = outflow + 1
|
||||
return outflow
|
||||
|
||||
|
||||
class Boss(object):
|
||||
def __init__(self, name, enemizer_name, defeat_rule, player):
|
||||
|
||||
211
DoorShuffle.py
211
DoorShuffle.py
@@ -11,8 +11,8 @@ def link_doors(world, player):
|
||||
for exitName, regionName in mandatory_connections:
|
||||
connect_simple_door(world, exitName, regionName, player)
|
||||
# These should all be connected for now as normal connections
|
||||
for edge_a, edge_b in intratile_doors:
|
||||
connect_intertile_door(world, edge_a, edge_b, player)
|
||||
for edge_a, edge_b in interior_doors:
|
||||
connect_two_way(world, edge_a, edge_b, player)
|
||||
|
||||
# These connection are here because they are currently unable to be shuffled
|
||||
if world.doorShuffle not in ['basic', 'experimental']: # these modes supports spirals
|
||||
@@ -110,20 +110,6 @@ def connect_simple_door(world, exit_name, region_name, player):
|
||||
d.dest = region
|
||||
|
||||
|
||||
def connect_intertile_door(world, edge_1, edge_2, player):
|
||||
ent_a = world.get_entrance(edge_1, player)
|
||||
ent_b = world.get_entrance(edge_2, player)
|
||||
|
||||
# if these were already connected somewhere, remove the backreference
|
||||
if ent_a.connected_region is not None:
|
||||
ent_a.connected_region.entrances.remove(ent_a)
|
||||
if ent_b.connected_region is not None:
|
||||
ent_b.connected_region.entrances.remove(ent_b)
|
||||
|
||||
ent_a.connect(ent_b.parent_region)
|
||||
ent_b.connect(ent_a.parent_region)
|
||||
|
||||
|
||||
def connect_two_way(world, entrancename, exitname, player):
|
||||
entrance = world.get_entrance(entrancename, player)
|
||||
ext = world.get_entrance(exitname, player)
|
||||
@@ -134,7 +120,6 @@ def connect_two_way(world, entrancename, exitname, player):
|
||||
if ext.connected_region is not None:
|
||||
ext.connected_region.entrances.remove(ext)
|
||||
|
||||
# todo - access rules for the doors...
|
||||
entrance.connect(ext.parent_region)
|
||||
ext.connect(entrance.parent_region)
|
||||
if entrance.parent_region.dungeon:
|
||||
@@ -237,7 +222,7 @@ def shuffle_dungeon(world, player, start_region_names, dungeon_region_names):
|
||||
available_doors.remove(connect_door)
|
||||
else:
|
||||
# If there's no available region with a door, use an internal connection
|
||||
connect_door = find_compatible_door_in_list(world, door, available_doors, player)
|
||||
connect_door = find_compatible_door_in_list(ugly_regions, world, door, available_doors, player)
|
||||
if connect_door is not None:
|
||||
logger.info(' Adding loop via %s', connect_door.name)
|
||||
maybe_connect_two_way(world, door, connect_door, player)
|
||||
@@ -735,22 +720,23 @@ def experiment(world, player):
|
||||
hc = convert_to_sectors(hyrule_castle_regions, world, player)
|
||||
ep = convert_to_sectors(eastern_regions, world, player)
|
||||
dp = convert_to_sectors(desert_regions, world, player)
|
||||
dungeon_sectors = [hc, ep, dp]
|
||||
dungeon_sectors = [hc, ep]
|
||||
dp_split = split_up_sectors(dp, desert_default_entrance_sets)
|
||||
dungeon_sectors.extend(dp_split)
|
||||
|
||||
for sector_list in dungeon_sectors:
|
||||
for sector in sector_list:
|
||||
for region in sector.regions:
|
||||
print(region.name)
|
||||
for door in sector.oustandings_doors:
|
||||
for door in sector.outstanding_doors:
|
||||
print(door.name)
|
||||
print('pol: ' + str(sector.polarity()))
|
||||
print('mag: ' + str(sector.magnitude()))
|
||||
print()
|
||||
print()
|
||||
split_up_sectors(dp, desert_default_entrance_sets)
|
||||
|
||||
dungeon_region_lists = [hyrule_castle_regions, eastern_regions, desert_regions]
|
||||
for region_list in dungeon_region_lists:
|
||||
shuffle_dungeon_no_repeats(world, player, region_list)
|
||||
for sector_list in dungeon_sectors:
|
||||
shuffle_dungeon_no_repeats(world, player, sector_list)
|
||||
# for ent, ext in experimental_connections:
|
||||
# if world.get_door(ent, player).blocked:
|
||||
# connect_one_way(world, ext, ent, player)
|
||||
@@ -792,7 +778,7 @@ def convert_to_sectors(region_names, world, player):
|
||||
outstanding_doors.append(door)
|
||||
sector = Sector()
|
||||
sector.regions.extend(region_chunk)
|
||||
sector.oustandings_doors.extend(outstanding_doors)
|
||||
sector.outstanding_doors.extend(outstanding_doors)
|
||||
sectors.append(sector)
|
||||
return sectors
|
||||
|
||||
@@ -817,18 +803,17 @@ def split_up_sectors(sector_list, entrance_sets):
|
||||
print('mag:'+str(sum_vector(s_list, lambda s: s.magnitude())))
|
||||
print('pol:'+str(sum_vector(leftover_sectors, lambda s: s.polarity())))
|
||||
print('mag:'+str(sum_vector(leftover_sectors, lambda s: s.magnitude())))
|
||||
|
||||
|
||||
assignment(new_sector_grid, leftover_sectors)
|
||||
return new_sector_grid
|
||||
|
||||
|
||||
def sum_vector(sector_list, func):
|
||||
sum = [0, 0, 0]
|
||||
result = [0, 0, 0]
|
||||
for sector in sector_list:
|
||||
vector = func(sector)
|
||||
for i in range(len(sum)):
|
||||
sum[i] = sum[i] + vector[i]
|
||||
return sum
|
||||
for i in range(len(result)):
|
||||
result[i] = result[i] + vector[i]
|
||||
return result
|
||||
|
||||
|
||||
# def add_vectors(vector_one, vector_two):
|
||||
@@ -839,10 +824,10 @@ def sum_vector(sector_list, func):
|
||||
|
||||
|
||||
def is_polarity_neutral(polarity):
|
||||
neutral = 0
|
||||
for value in polarity:
|
||||
neutral = neutral + value
|
||||
return neutral == 0
|
||||
if value != 0:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def is_proposal_valid(proposal, buckets, candidates):
|
||||
@@ -850,9 +835,9 @@ def is_proposal_valid(proposal, buckets, candidates):
|
||||
for i in range(len(proposal)):
|
||||
if proposal[i] is -1:
|
||||
return False # indicates an incomplete proposal
|
||||
test_bucket = [[]]*len(buckets)
|
||||
test_bucket = []
|
||||
for bucket_idx in range(len(buckets)):
|
||||
test_bucket[bucket_idx].extend(buckets[bucket_idx])
|
||||
test_bucket.append(list(buckets[bucket_idx]))
|
||||
for i in range(len(proposal)):
|
||||
test_bucket[proposal[i]].append(candidates[i])
|
||||
for test in test_bucket:
|
||||
@@ -863,28 +848,41 @@ def is_proposal_valid(proposal, buckets, candidates):
|
||||
|
||||
|
||||
def assignment(buckets, candidates):
|
||||
random.shuffle(buckets)
|
||||
# for a faster search - instead of random - put the most likely culprits to cause problems at the end, least likely at the front
|
||||
# unless we start checking for failures earlier in the algo
|
||||
random.shuffle(candidates)
|
||||
proposal = [-1]*len(candidates)
|
||||
|
||||
solution = next_proposal(proposal, buckets, candidates)
|
||||
solution = find_proposal(proposal, buckets, candidates)
|
||||
if solution is None:
|
||||
raise Exception('Unable to find a proposal')
|
||||
# todo: use solution to assign to buckets and candidates
|
||||
# buckets =
|
||||
for i in range(len(solution)):
|
||||
buckets[solution[i]].append(candidates[i])
|
||||
|
||||
|
||||
def next_proposal(proposal, buckets, candidates):
|
||||
if is_proposal_valid(proposal, buckets, candidates):
|
||||
return proposal
|
||||
# this is a DFS search
|
||||
def find_proposal(proposal, buckets, candidates):
|
||||
size = len(candidates)
|
||||
combination_grid = []
|
||||
for i in range(size):
|
||||
combination_grid.append(list(range(len(buckets))))
|
||||
# randomize which bucket
|
||||
for possible_buckets in combination_grid:
|
||||
random.shuffle(possible_buckets)
|
||||
|
||||
next_candidate_idx = proposal.index(-1)
|
||||
for i in range(len(buckets)): # todo: this produces a weighted solution unfortunately, good for a mode called OneBigHappyDungeon in crossed, not good for a balanced, or random approach
|
||||
proposal[next_candidate_idx] = i
|
||||
found_proposal = next_proposal(proposal, buckets, candidates)
|
||||
if found_proposal is not None:
|
||||
return found_proposal
|
||||
return None # there was no valid assignment
|
||||
idx = 0
|
||||
while idx != size or not is_proposal_valid(proposal, buckets, candidates):
|
||||
if idx == size:
|
||||
idx = idx - 1
|
||||
while len(combination_grid[idx]) == 0:
|
||||
if idx == -1: # this is the failure case - we shouldn't hit it
|
||||
return None
|
||||
combination_grid[idx] = list(range(len(buckets)))
|
||||
idx = idx - 1
|
||||
proposal[idx] = combination_grid[idx].pop()
|
||||
# can we detect a bad choice at this stage
|
||||
idx = idx + 1
|
||||
return proposal
|
||||
|
||||
|
||||
# code below is for an algorithm without restarts
|
||||
@@ -898,23 +896,19 @@ def next_proposal(proposal, buckets, candidates):
|
||||
# Nuts, normal loop
|
||||
|
||||
|
||||
def shuffle_dungeon_no_repeats(world, player, dungeon_region_names):
|
||||
def shuffle_dungeon_no_repeats(world, player, available_sectors):
|
||||
logger = logging.getLogger('')
|
||||
available_regions = []
|
||||
for name in dungeon_region_names:
|
||||
available_regions.append(world.get_region(name, player))
|
||||
random.shuffle(available_regions)
|
||||
|
||||
random.shuffle(available_sectors)
|
||||
|
||||
available_doors = []
|
||||
# this is interesting but I want to ensure connectedness
|
||||
# Except for Desert1/2 and Skull 1/2/3 - treat them as separate dungeons?
|
||||
while len(available_regions) > 0:
|
||||
while len(available_sectors) > 0:
|
||||
# Pick a random region and make its doors the open set
|
||||
region = available_regions.pop()
|
||||
logger.info("Starting in %s", region.name)
|
||||
regions = find_connected_regions(region, available_regions, logger)
|
||||
for region in regions:
|
||||
available_doors.extend(get_doors_ex(world, region, player))
|
||||
sector = available_sectors.pop()
|
||||
current_sector = sector
|
||||
available_doors.extend(sector.outstanding_doors)
|
||||
|
||||
# Loop until all available doors are used
|
||||
while len(available_doors) > 0:
|
||||
@@ -923,62 +917,75 @@ def shuffle_dungeon_no_repeats(world, player, dungeon_region_names):
|
||||
door = available_doors.pop()
|
||||
logger.info('Linking %s', door.name)
|
||||
# Find an available region that has a compatible door
|
||||
connect_region, connect_door = find_compatible_door_in_regions_ex(world, door, available_regions, player)
|
||||
if connect_region is not None:
|
||||
logger.info(' Found new region %s via %s', connect_region.name, connect_door.name)
|
||||
connect_sector, connect_door = find_compatible_door_in_sectors_ex(world, door, available_sectors, player)
|
||||
if connect_sector is not None:
|
||||
logger.info(' Found new sector via %s', connect_door.name)
|
||||
if door not in current_sector.outstanding_doors:
|
||||
raise Exception('Door %s was not in sector', door.name)
|
||||
# Check if valid
|
||||
if not is_valid(door, connect_door, current_sector, connect_sector, len(available_sectors) == 1):
|
||||
logger.info(' Not Linking %s to %s', door.name, connect_door.name)
|
||||
available_doors.insert(0, door)
|
||||
continue
|
||||
# Apply connection and add the new region's doors to the available list
|
||||
maybe_connect_two_way(world, door, connect_door, player)
|
||||
c_regions = find_connected_regions(connect_region, available_regions, logger)
|
||||
for region in c_regions:
|
||||
available_doors.extend(get_doors_ex(world, region, player))
|
||||
# We've used this region and door, so don't use them again
|
||||
available_doors.remove(connect_door)
|
||||
available_regions.remove(connect_region)
|
||||
current_sector.outstanding_doors.remove(door)
|
||||
connect_sector.outstanding_doors.remove(connect_door)
|
||||
available_doors.extend(connect_sector.outstanding_doors)
|
||||
available_sectors.remove(connect_sector)
|
||||
current_sector.outstanding_doors.extend(connect_sector.outstanding_doors)
|
||||
current_sector.regions.extend(connect_sector.regions)
|
||||
else:
|
||||
# If there's no available region with a door, use an internal connection
|
||||
connect_door = find_compatible_door_in_list(world, door, available_doors, player)
|
||||
connect_door = find_compatible_door_in_list_old(world, door, available_doors, player)
|
||||
if connect_door is not None:
|
||||
logger.info(' Adding loop via %s', connect_door.name)
|
||||
if door not in current_sector.outstanding_doors:
|
||||
raise Exception('Door %s was not in sector', door.name)
|
||||
# Check if valid
|
||||
if not is_loop_valid(door, connect_door, current_sector):
|
||||
logger.info(' Not Linking %s to %s', door.name, connect_door.name)
|
||||
available_doors.insert(0, door)
|
||||
continue
|
||||
maybe_connect_two_way(world, door, connect_door, player)
|
||||
available_doors.remove(connect_door)
|
||||
current_sector.outstanding_doors.remove(door)
|
||||
current_sector.outstanding_doors.remove(connect_door)
|
||||
else:
|
||||
raise Exception ('Something has gone terribly wrong')
|
||||
# Check that we used everything, we failed otherwise
|
||||
if len(available_regions) > 0 or len(available_doors) > 0:
|
||||
if len(available_sectors) > 0 or len(available_doors) > 0:
|
||||
logger.warning('Failed to add all regions/doors to dungeon, generation will likely fail.')
|
||||
|
||||
|
||||
def find_connected_regions(region, available_regions, logger):
|
||||
region_chunk = [region]
|
||||
exits = []
|
||||
exits.extend(region.exits)
|
||||
while len(exits) > 0:
|
||||
ext = exits.pop()
|
||||
if ext.connected_region is not None:
|
||||
connect_region = ext.connected_region
|
||||
if connect_region not in region_chunk and connect_region in available_regions:
|
||||
# door = world.check_for_door(ext.name, player)
|
||||
# if door is None or door.type not in [DoorType.Normal, DoorType.SpiralStairs]:
|
||||
logger.info(' Found new region %s via %s', connect_region.name, ext.name)
|
||||
available_regions.remove(connect_region)
|
||||
region_chunk.append(connect_region)
|
||||
exits.extend(connect_region.exits)
|
||||
return region_chunk
|
||||
|
||||
|
||||
def find_compatible_door_in_regions_ex(world, door, regions, player):
|
||||
for region in regions:
|
||||
for proposed_door in get_doors_ex(world, region, player):
|
||||
def find_compatible_door_in_sectors_ex(world, door, sectors, player):
|
||||
for sector in sectors:
|
||||
for proposed_door in sector.outstanding_doors:
|
||||
if doors_compatible(door, proposed_door):
|
||||
return region, proposed_door
|
||||
return sector, proposed_door
|
||||
return None, None
|
||||
|
||||
def get_doors_ex(world, region, player):
|
||||
res = []
|
||||
for exit in region.exits:
|
||||
door = world.check_for_door(exit.name, player)
|
||||
if door is not None and door.type in [DoorType.Normal, DoorType.SpiralStairs]:
|
||||
res.append(door)
|
||||
return res
|
||||
|
||||
def find_compatible_door_in_list_old(world, door, doors, player):
|
||||
for proposed_door in doors:
|
||||
if doors_compatible(door, proposed_door):
|
||||
return proposed_door
|
||||
|
||||
|
||||
# todo: path checking needed?
|
||||
def is_valid(door_a, door_b, sector_a, sector_b, last_sector):
|
||||
if last_sector:
|
||||
return True
|
||||
elif door_a.blocked and door_b.blocked: # todo, I can't see this going well unless we are in loop generation...
|
||||
return False
|
||||
elif not door_a.blocked and not door_b.blocked:
|
||||
return sector_a.outflow() + sector_b.outflow() - 2 > 0
|
||||
elif door_a.blocked or door_b.blocked:
|
||||
return sector_a.outflow() + sector_b.outflow() - 1 > 0
|
||||
return False # not sure how we got here, but it's a bad idea
|
||||
|
||||
|
||||
def is_loop_valid(door_a, door_b, sector):
|
||||
return True # todo: is this always true?
|
||||
|
||||
|
||||
# DATA GOES DOWN HERE
|
||||
|
||||
9
Doors.py
9
Doors.py
@@ -103,9 +103,9 @@ def create_doors(world, player):
|
||||
create_dir_door(player, 'Eastern Courtyard EN', DoorType.Normal, Direction.East, 0xa9, Top, Low),
|
||||
big_key(create_dir_door(player, 'Eastern Courtyard N', DoorType.Normal, Direction.North, 0xa9, Mid, High)),
|
||||
create_door(player, 'Eastern Courtyard Potholes', DoorType.Hole),
|
||||
create_door(player, 'Eastern Fairy Landing', DoorType.Hole),
|
||||
landing(create_door(player, 'Eastern Fairy Landing', DoorType.Hole)),
|
||||
create_door(player, 'Eastern Fairies\' Warp', DoorType.Warp),
|
||||
create_door(player, 'Eastern Courtyard Warp End', DoorType.Warp),
|
||||
landing(create_door(player, 'Eastern Courtyard Warp End', DoorType.Warp)),
|
||||
create_dir_door(player, 'Eastern Map Valley WN', DoorType.Normal, Direction.West, 0xaa, Top, Low),
|
||||
create_dir_door(player, 'Eastern Map Valley SW', DoorType.Normal, Direction.South, 0xaa, Left, High),
|
||||
create_dir_door(player, 'Eastern Dark Square NW', DoorType.Normal, Direction.North, 0xba, Left, High),
|
||||
@@ -226,3 +226,8 @@ def toggle(door):
|
||||
def blocked(door):
|
||||
door.blocked = True
|
||||
return door
|
||||
|
||||
|
||||
def landing(door):
|
||||
door.landing = True
|
||||
return door
|
||||
|
||||
@@ -171,10 +171,10 @@ hyrule_castle_regions = [
|
||||
'Hyrule Castle Lobby', 'Hyrule Castle West Lobby', 'Hyrule Castle East Lobby', 'Hyrule Castle East Hall',
|
||||
'Hyrule Castle West Hall', 'Hyrule Castle Back Hall', 'Hyrule Castle Throne Room', 'Hyrule Dungeon Map Room',
|
||||
'Hyrule Dungeon North Abyss', 'Hyrule Dungeon North Abyss Catwalk', 'Hyrule Dungeon South Abyss',
|
||||
'Hyrule Dungeon South Abyss Catwalk', 'Hyrule Dungeon Guardroom', 'Hyrule Dungeon Armory',
|
||||
'Hyrule Dungeon Staircase', 'Hyrule Dungeon Cellblock', 'Sewers Behind Tapestry', 'Sewers Rope Room',
|
||||
'Sewers Dark Cross', 'Sewers Water', 'Sewers Key Rat', 'Sewers Secret Room', 'Sewers Secret Room Blocked Path',
|
||||
'Sewers Pull Switch', 'Sanctuary'
|
||||
'Hyrule Dungeon South Abyss Catwalk', 'Hyrule Dungeon Guardroom', 'Hyrule Dungeon Armory Main',
|
||||
'Hyrule Dungeon Armory North Branch', 'Hyrule Dungeon Staircase', 'Hyrule Dungeon Cellblock',
|
||||
'Sewers Behind Tapestry', 'Sewers Rope Room', 'Sewers Dark Cross', 'Sewers Water', 'Sewers Key Rat',
|
||||
'Sewers Secret Room', 'Sewers Secret Room Blocked Path', 'Sewers Pull Switch', 'Sanctuary'
|
||||
]
|
||||
|
||||
eastern_regions = [
|
||||
|
||||
Reference in New Issue
Block a user