Intermediate commit to switch from work to home

This commit is contained in:
randall.rupper
2019-09-16 21:12:55 -06:00
parent 6f5121c028
commit 49e782b050
4 changed files with 138 additions and 115 deletions

View File

@@ -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):

View File

@@ -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

View File

@@ -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

View File

@@ -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 = [