def rotate_track(blocks: list, rotation: int) -> list: ''' Rotates the entire track by a given cardinal rotation. The rotation happens inside the standard stadium arena size (32, 32, 32). The rotation factors in the fact that blocks may occupy more than one spot on the map, by rotating the block offsets first and then adding the maximum X and Z component offset to the original position rotation. Args: blocks (list): a list of pygbx.MapBlock's rotation (int): the cardinal rotation to rotate by Returns: list: the rotated blocks ''' for block in blocks: rotated, _, _ = rotate_block_offsets( [block.position, [0, 0, 0], [31, 0, 31]], rotation) rotated = rotated[0] try: offsets = STADIUM_BLOCK_OFFSETS[block.name] offsets, _, _ = rotate_block_offsets(offsets, block.rotation) _, xoff, zoff = rotate_block_offsets(offsets, rotation) block.position = Vector3(rotated[0] + xoff, rotated[1], rotated[2] + zoff) except KeyError: block.position = Vector3(rotated[0], rotated[1], rotated[2]) block.rotation = (block.rotation + rotation) % 4 return blocks
def read_vec3(self): """Reads 12 bytes as 3 floats from the buffer and packs them into a Vector3. Returns: the vector read from the buffer """ return Vector3(self.read_float(), self.read_float(), self.read_float())
def occupied_track_positions(track): positions = {} for block in track: name = get_block_name(block[BID], STADIUM_BLOCKS) if not name: continue try: if is_on_ground(block) and name in DYNAMIC_GROUND_OFFSETS: offsets = DYNAMIC_GROUND_OFFSETS[name] else: offsets = STADIUM_BLOCK_OFFSETS[name] except KeyError: continue offsets, _, _ = rotate_block_offsets(offsets, block[BROT]) block_positions = [] for offset in offsets: block_positions.append( Vector3(block[BX] + offset[0], block[BY] + offset[1], block[BZ] + offset[2])) positions[block] = block_positions return positions
def block_from_tup(tup): block = MapBlock() block.name = get_block_name(tup[BID], STADIUM_BLOCKS) block.rotation = tup[BROT] block.position = Vector3(tup[BX], tup[BY], tup[BZ]) if len(tup) > 5: block.flags = 0x1000 if tup[BFLAGS] == 1 else 0 return block
def rotate_track(blocks, rotation): for block in blocks: rotated, _, _ = rotate_block_offsets( [block.position, [0, 0, 0], [31, 0, 31]], rotation) rotated = rotated[0] try: offsets = STADIUM_BLOCK_OFFSETS[block.name] offsets, _, _ = rotate_block_offsets(offsets, block.rotation) _, xoff, zoff = rotate_block_offsets(offsets, rotation) block.position = Vector3(rotated[0] + xoff, rotated[1], rotated[2] + zoff) except KeyError: block.position = Vector3(rotated[0], rotated[1], rotated[2]) block.rotation = (block.rotation + rotation) % 4 return blocks
def block_from_tup(tup: tuple) -> MapBlock: ''' Converts a tuple to a MapBlock instance. Args: block (tuple): the tuple to convert Returns: MapBlock: the converted block ''' block = MapBlock() block.name = get_block_name(tup[BID], STADIUM_BLOCKS) block.rotation = tup[BROT] block.position = Vector3(tup[BX], tup[BY], tup[BZ]) if len(tup) > 5: block.flags = 0x1000 if tup[BFLAGS] == 1 else 0 return block
def occupied_track_positions(track: list) -> dict: ''' Produces a dict of each block and its occupied positions. Some blocks may have different offsets depending on whether they are on the ground or not. Args: track (list): the list of block tuples Returns: dict: blocks as keys and their occupied positions as values ''' positions = {} for block in track: name = get_block_name(block[BID], STADIUM_BLOCKS) if not name: continue try: if is_on_ground(block) and name in DYNAMIC_GROUND_OFFSETS: offsets = DYNAMIC_GROUND_OFFSETS[name] else: offsets = STADIUM_BLOCK_OFFSETS[name] except KeyError: continue offsets, _, _ = rotate_block_offsets(offsets, block[BROT]) block_positions = [] for offset in offsets: block_positions.append( Vector3(block[BX] + offset[0], block[BY] + offset[1], block[BZ] + offset[2])) positions[block] = block_positions return positions
def build(self, track_len: int, use_seed: bool=False, failsafe: bool=True, verbose: bool=True, put_finish: bool=True, progress_callback=None, map_size: tuple=(20, 8, 20)): ''' Builds the track according to the parameters. Args: track_len (int): the track length, in blocks use_seed (bool): whether to use a random seed from the seed data failsafe (bool): whether to enable various checking heuristics verbose (bool): print additional information while building put_finish (bool): whether to put a finish as the last block progress_callback: a function that is called whenever a new block is placed map_size (tuple): the map size to build the track in Returns: list: the resulting track ''' self.running = True fixed_y = random.randrange(1, 7) if not self.gmap: self.gmap = GameMap(Vector3(map_size[0], map_size[1], map_size[2]), Vector3(0, fixed_y, 0)) if use_seed and self.seed_data: self.gmap.track = self.sample_seed(3) elif len(self.gmap) == 0: self.gmap.add(self.random_start_block()) self.gmap.update() blacklist = [] current_block_preds = None while len(self.gmap) < track_len: if not self.running: return None end = len(self.gmap) == track_len - 1 if len(blacklist) >= 10 or (len(blacklist) == 1 and end): if verbose: print('More than 10 fails, going back.') if len(self.gmap) > track_len - 5: back = 5 elif end: back = 10 else: back = random.randrange(2, 6) end_idx = min(len(self.gmap) - 1, back) if end_idx > 0: del self.gmap.track[-end_idx:len(self.gmap)] blacklist = [] current_block_preds = None X_block, X_position = self.prepare_inputs() block_override = FINISH_LINE_BLOCK if end and put_finish else -1 next_block, current_block_preds = self.predict_next_block( X_block[:], X_position[:], block_override=block_override, blacklist=blacklist, block_preds=current_block_preds ) self.gmap.add(next_block) decoded = self.gmap.decoded if failsafe: # Do not exceed map size if self.gmap.exceeds_map_size(): blacklist.append(next_block[BID]) self.gmap.pop() continue occ = occupied_track_vectors([decoded[-1]]) if len(occ) > 0: min_y_block = min(occ, key=lambda pos: pos.y).y else: min_y_block = decoded[-1][BY] # If we are above the ground if min_y_block > 1 and next_block[BID] in GROUND_BLOCKS: blacklist.extend(GROUND_BLOCKS) self.gmap.pop() continue if (intersects(decoded[:-1], decoded[-1]) or # Overlaps the track (next_block[BID] == FINISH_LINE_BLOCK and not end)): # Tries to put finish before desired track length blacklist.append(next_block[BID]) self.gmap.pop() continue if self.score_prediction(self.gmap[-2], next_block) < 5: blacklist.append(next_block[BID]) self.gmap.pop() continue blacklist = [] current_block_preds = None next_block = (next_block[BID], next_block[BX], next_block[BY], next_block[BZ], next_block[BROT]) if progress_callback: progress_callback(len(self.gmap), track_len) if verbose: print(len(self.gmap)) result_track = self.gmap.center() result_track = [block for block in result_track if block[BID] != STADIUM_BLOCKS['StadiumGrass']] return result_track
def build(self, track_len, use_seed=False, failsafe=True, verbose=True, save=True, put_finish=True, progress_callback=None, map_size=(20, 8, 20)): self.running = True fixed_y = random.randrange(1, 7) if not self.gmap or self.reset: self.gmap = GameMap(Vector3(map_size[0], map_size[1], map_size[2]), Vector3(0, fixed_y, 0)) if use_seed and self.seed_data: self.gmap.track = self.sample_seed(3) elif len(self.gmap) == 0: self.gmap.add(self.random_start_block()) print(self.gmap.track) self.gmap.update() blacklist = [] current_block_preds = None while len(self.gmap) < track_len: if not self.running: return None end = len(self.gmap) == track_len - 1 if len(blacklist) >= 10 or (len(blacklist) == 1 and end) and self.reset: if verbose: print('More than 10 fails, going back.') if len(self.gmap) > track_len - 5: back = 5 elif end: back = 10 else: back = random.randrange(2, 6) end_idx = min(len(self.gmap) - 1, back) if end_idx > 0: del self.gmap.track[-end_idx:len(self.gmap)] blacklist = [] current_block_preds = None X_block, X_position = self.prepare_inputs() override_block = FINISH_LINE_BLOCK if end and put_finish else -1 next_block, current_block_preds = self.predict_next_block( X_block[:], X_position[:], override_block, blacklist=blacklist, block_preds=current_block_preds) self.gmap.add(next_block) decoded = self.gmap.decoded if failsafe: # Do not exceed map size if self.gmap.exceeds_map_size(): blacklist.append(next_block[BID]) self.gmap.pop() continue occ = occupied_track_vectors([decoded[-1]]) if len(occ) > 0: min_y_block = min(occ, key=lambda pos: pos.y).y else: min_y_block = decoded[-1][BY] # If we are above the ground if min_y_block > 1 and next_block[BID] in GROUND_BLOCKS: blacklist.extend(GROUND_BLOCKS) self.gmap.pop() continue if (intersects(decoded[:-1], decoded[-1]) or # Overlaps the track (next_block[BID] == FINISH_LINE_BLOCK and not end) ): # Tries to put finish before desired track length blacklist.append(next_block[BID]) self.gmap.pop() continue if self.score_prediction(self.gmap[-2], next_block) < 5: blacklist.append(next_block[BID]) self.gmap.pop() continue blacklist = [] current_block_preds = None next_block = (next_block[BID], next_block[BX], next_block[BY], next_block[BZ], next_block[BROT]) if progress_callback: progress_callback(len(self.gmap), track_len) if verbose: print(len(self.gmap)) result_track = self.gmap.center() result_track = [ block for block in result_track if block[BID] != STADIUM_BLOCKS['StadiumGrass'] ] return result_track