def test_levels(self): #same orientation orientation = 'tall_wide' block1 = Block(block_mesh, orientation, (0, 0, 0)) block2 = Block(block_mesh, orientation, (2, 1, 0)) print(block1.get_top_level()) print(block2.get_top_level()) self.assertTrue(block1.get_top_level() == block2.get_top_level()) print(block1.get_bottom_level()) print(block2.get_bottom_level()) self.assertTrue(block1.get_bottom_level() == block2.get_bottom_level()) # differing orientations block1 = Block(block_mesh, 'flat_wide', ( 0, 0, 0)) block2 = Block(block_mesh, 'tall_thin', ( 5, 3, -7)) print(block1.get_top_level()) print(block2.get_top_level()) self.assertTrue(block1.get_top_level() == block2.get_top_level()) print(block1.get_bottom_level()) print(block2.get_bottom_level()) self.assertTrue(block1.get_bottom_level() > block2.get_bottom_level())
def is_stable(tower_state, new_block: Block): """ Boolean recursive function that calculates if the new block to be places in the scheme of the current block state will stand, or topple. :param tower_state: A state of the block tower. :param new_block: potential block to add, organized in a dictionary with keys as levels and values list of blocks level -> [block1, block2, ...] :return: True iff the new arrangement still stands """ bottom_level = new_block.get_bottom_level() top_level = new_block.get_top_level() # blocks_by_top_level= tower_state.get_blocks_by_top_level() # blocks_by_bottom_level= tower_state.get_blocks_by_bottom_level() # Initiate the relation to surrounding blocks in the tower # Short-circuit the expensive calculation if can be skipped. blocks_below = calculate_below(new_block, tower_state.get_by_top(bottom_level - 1)) \ if (bottom_level - 1) in tower_state \ else set() tower_state.set_blocks_below(new_block, blocks_below) blocks_above = calculate_above(new_block, tower_state.get_by_bottom(top_level + 1)) \ if (top_level + 1) in tower_state \ else set() tower_state.set_blocks_above(new_block, blocks_above) # Last attempt to find if this situation was already stored as a unstable combination if tower_state.is_bad_block( tower_state.stringify_block_neighbors(new_block)): return False # connect the new block to the blocks above and below by making changes to their neighbor setting. tower_state.connect_block_to_neighbors(new_block) if is_stable_helper((tower_state, new_block)): return True else: # We can stringify this block's failure, contingent on it's neighbors. # This will allow to quickly check in the future if this block is stable, relative to its surroundings tower_state.add_bad_block_state(new_block) # release the relation of this block on it neighbors tower_state.disconnect_block_from_neighbors(new_block) return False
def is_overlapping(block_tower, new_block: Block): """ Check if new block clashes (physically overlaps) with the rest of the block tower :param block_tower: A tower state :param new_block: either a new object or just it's string representation :return: """ if block_tower.is_bad_block(new_block): return True bottom_level: int = new_block.get_bottom_level() top_level: int = new_block.get_top_level() """ Only scan blocks with top levels above my bottom level. X - Block. B - Bottom layer. T - Top layer XXXXXXX new block XXXXXXX others TTTTTTTT others BBBBBBB in XXXXXXXX out TTTT XXXX """ for level in filter(lambda l: l >= bottom_level, block_tower.keys()): for other_block in block_tower.get_by_top(level): """ Diqualify for overlap any block with a bottom above our top X - Block. B - Bottom layer. T - Top layer others XXXXXXXX others XXXXX in XXXXXXXX out BBBBB TTTTTTT BBBBBBBB new block XXXXXXX XXXXXXX """ if other_block.get_bottom_level( ) <= top_level and other_block.is_overlapping(new_block): # perform a memoization of bad blocks we've seen block_tower.add_bad_block(new_block) return True return False