def move(self, next_position, units_by_position, spaces): """ >>> elf, goblin = Elf((0, 0)), Goblin((1, 0)) >>> units_by_position_a = {(0, 0): elf, (1, 0): goblin} >>> elf.move((0, 1), units_by_position_a, ((True, True), (True, True))) >>> units_by_position_a {(1, 0): Goblin(position=(1, 0), attack=3, health=200), (0, 1): Elf(position=(0, 1), attack=3, health=200)} """ if self.is_dead(): raise Exception(f"Unit {self} tried to move but it was dead") if units_by_position.get(self.position) != self: raise Exception( f"Unit {self} tried to move from {self.position} but it wasn't " f"there: instead {units_by_position.get(self.position)} was") if next_position in units_by_position: raise Exception( f"Unit {self} tried to move to {next_position} but " f"{units_by_position[next_position]} was there") manhattan_distance = utils.Point2D(*self.position)\ .manhattan_distance(utils.Point2D(*next_position)) if manhattan_distance != 1: raise Exception( f"Unit {self} at {self.position} tried to move to " f"{next_position} but the distance was {manhattan_distance}") if not self.is_position_space(next_position, spaces): raise Exception( f"Unit {self} tried to move to {next_position} but it was not " f"a space") del units_by_position[self.position] self.position = next_position units_by_position[self.position] = self
def processHollow(doc, parent, worldspawn, s, offset, hollow): '''Note: sets global style var.''' global style o = utils.getPoint(hollow.getAttribute('origin')) + offset e = utils.getPoint(hollow.getAttribute('extent')) style = hollow.getAttribute('style') holes = {} absentwalls = [] # FIXME the following is where we see if this hollow contains an # absentwalls element and a holes element before proceeding. It's # implemented in a bit of a hacky way; we ought to be using SAX but then it # would be a *lot* more work to create the output XML. This way we can # just change what's there a bit. for hollowChild in hollow.childNodes: if hollowChild.localName == 'absentwalls': # Get absent walls info... for absentwall in hollowChild.childNodes: wall = absentwall.getAttribute('value') absentwalls.append(wall) utils.insertPlaceholder(doc, hollow, hollowChild) elif hollowChild.localName == 'holes': # Get holes info... for hole in hollowChild.childNodes: wall = hole.getAttribute('wall') # If we've not added a hole to this wall yet then set up an empty array... if wall not in holes: holes[wall] = [] o_x, o_y = hole.getAttribute('origin').split() e_x, e_y = hole.getAttribute('extent').split() type = hole.getAttribute('type') if type == connector.DOOR: key = hole.getAttribute('key') button = hole.getAttribute('button') # FIXME test real map else: key = button = None # FIXME test real map # FIXME deal with other types holes[wall].append( utils.Hole2D(utils.Point2D(float(o_x), float(o_y)), utils.Point2D(float(e_x), float(e_y)), type, {'key': key})) # FIXME we shouldn't need to detect overlapping holes here because # they'll be detected higher up (by overlapping connected hollows) utils.insertPlaceholder(doc, hollow, hollowChild) # Now we have the required structural info (absent walls and holes), we can # turn this hollow into a series of textured brushes... io, ie = utils.makeHollow(doc, worldspawn, s, o, e, absentwalls, holes, style) # Contained solids, hollows and entities... for node in hollow.childNodes: processNode(doc, hollow, worldspawn, s, io, node) # We can't remove the child or we screw over tree traversal (urgh)... utils.insertPlaceholder(doc, parent, hollow)
def get_neighbours(self, position, tool): return [ (x, y) for x, y in utils.Point2D(*position).get_manhattan_neighbours() if x >= 0 and y >= 0 and tool in self.TOOLS_BY_TYPE[self.get_type(( x, y))] ]
def attack_enemy(self, enemy, units_by_position): """ >>> elf, goblin = Elf((0, 0)), Goblin((1, 0)) >>> units_by_position_a = {(0, 0): elf, (1, 0): goblin} >>> elf.attack_enemy(goblin, units_by_position_a) >>> units_by_position_a {(0, 0): Elf(position=(0, 0), attack=3, health=200), (1, 0): Goblin(position=(1, 0), attack=3, health=197)} >>> goblin.health = 3 >>> elf.attack_enemy(goblin, units_by_position_a) >>> units_by_position_a {(0, 0): Elf(position=(0, 0), attack=3, health=200)} >>> goblin.health = 1 >>> units_by_position_a = {(0, 0): elf, (1, 0): goblin} >>> elf.attack_enemy(goblin, units_by_position_a) >>> units_by_position_a {(0, 0): Elf(position=(0, 0), attack=3, health=200)} """ if self.is_dead(): raise Exception(f"Unit {self} tried to attack but it was dead") if enemy.is_dead(): raise Exception(f"Tried to attack {enemy} but it was dead") if units_by_position.get(self.position) != self: raise Exception( f"Unit {self} tried to attack from {self.position} but it " f"wasn't there: instead {units_by_position.get(self.position)} " f"was") if units_by_position.get(enemy.position) != enemy: raise Exception( f"Tried to attack {enemy} at {enemy.position} but it " f"wasn't there: instead " f"{units_by_position.get(enemy.position)} was") manhattan_distance = utils.Point2D(*self.position)\ .manhattan_distance(utils.Point2D(*enemy.position)) if manhattan_distance != 1: raise Exception( f"Unit {self} at {self.position} tried to attack {enemy} at " f"{enemy.position} but their distance was {manhattan_distance}" ) enemy.health -= self.attack if enemy.is_dead(): del units_by_position[enemy.position]
def get_cells(self): """ >>> sorted(set(Disk.from_hash_input('flqrgnkx').get_cells()) & { ... utils.Point2D(x, y) ... for x in range(3) ... for y in range(3) ... }) [Point2D(x=0, y=0), Point2D(x=1, y=0), Point2D(x=1, y=1)] """ return (utils.Point2D(x, y) for y, row in enumerate(self.cells) for x, cell in enumerate(row) if cell)
def get_point_for_index(self, index): """ >>> Grid().get_point_for_index(1) Point2D(x=0, y=0) >>> Grid().get_point_for_index(2) Point2D(x=1, y=0) >>> Grid().get_point_for_index(9) Point2D(x=1, y=1) """ ring = self.get_ring_for_index(index) if ring == 0: return utils.Point2D.ZERO_POINT side_index, side_offset = self.get_side_index_and_offset(ring, index) if side_index == 0: return utils.Point2D(ring, ring).offset((0, -(side_offset + 1))) elif side_index == 1: return utils.Point2D(ring, -ring).offset((-(side_offset + 1), 0)) elif side_index == 2: return utils.Point2D(-ring, -ring).offset((0, side_offset + 1)) elif side_index == 3: return utils.Point2D(-ring, ring).offset(((side_offset + 1), 0)) raise Exception(f"Mis-calculated side for {index}")
def _macro_stairs_core(self, full_length, full_height, full_depth, flip, step_length, step_height, t): '''Create stairs by iteration.''' parts = [] # Find closest match for step length and height... step_height = self._macro_stairs_match(full_height, step_height) num_steps = full_height / step_height step_length = full_length / num_steps # Create parts... length_iter = int(full_length / step_length) if flip: for i in range(length_iter): parts.append( utils.Region3D( utils.Point2D(step_length * i, 0), utils.Point2D(step_length, step_height * (length_iter - i)))) else: for i in range(length_iter): parts.append( utils.Region3D( utils.Point2D(step_length * i, 0), utils.Point2D(step_length, step_height * (i + 1)))) return parts
def from_diagram_text(cls, diagram_text): """ >>> diagram = Diagram.from_diagram_text( ... " | \\n" ... " | +--+ \\n" ... " A | C \\n" ... " F---|----E|--+ \\n" ... " | | | D \\n" ... " +B-+ +--+ \\n" ... ) >>> diagram.start_point Point2D(x=5, y=0) >>> sorted(diagram.letters.values()) ['A', 'B', 'C', 'D', 'E', 'F'] """ lines = diagram_text.strip('\n').splitlines() contents = { utils.Point2D(x, y): content for y, line in enumerate(lines) for x, content in enumerate(line) if cls.check_content(content) } neighbours = { point: cls.get_neighbours(contents, point) for point in contents } letters = { point: content for point, content in contents.items() if content in cls.CONTENT_LETTERS } start_points = { point for point in neighbours if point.y == 0 } if not start_points: raise Exception( f"Could not find start point, min y was " f"{min(point.y for point in neighbours)}") if len(start_points) > 1: raise Exception(f"Expected a single start point but got " f"{len(start_points)}: {sorted(start_points)}") start_point, = start_points return cls(neighbours, letters, start_point)
def from_grid_text(cls, grid_text, offset=None, position=utils.Point2D.ZERO_POINT, direction=DIRECTION_UP, infection_count=0): """ >>> Grid.from_grid_text('..#\\n#..\\n...') Grid(nodes={Point2D(x=1, y=-1): 'infected', Point2D(x=-1, y=0): 'infected'}, position=Point2D(x=0, y=0), direction=(0, -1), infection_count=0) """ lines = list(grid_text.strip().splitlines()) extra_characters = set("".join(lines)) - set(cls.PARSE_MAP) if extra_characters: raise Exception( f"Expected only {sorted(cls.PARSE_MAP)}, but also got " f"'{sorted(extra_characters)}'") x_sizes = set(map(len, lines)) if len(x_sizes) != 1: raise Exception( f"Got different length of lines: {sorted(x_sizes)}") x_size, = x_sizes y_size = len(lines) if offset is None: if x_size % 2 != 1 or y_size % 2 != 1: raise Exception( f"If a map is of odd size on both axis a position must be " f"provided") offset_x = -x_size // 2 + 1 offset_y = -y_size // 2 + 1 else: offset_x, offset_y = offset return cls( { utils.Point2D(x + offset_x, y + offset_y): cls.PARSE_MAP[content] for y, line in enumerate(lines) for x, content in enumerate(line) if cls.PARSE_MAP[content] != cls.STATE_CLEAN }, position, direction, infection_count)
def show(self, x_range_or_depth=None, y_range_or_depth=None, show_direction=True): """ >>> print(Grid.from_grid_text('..#\\n#..\\n...').show()) . . # #^.^. . . . """ x_range = self.get_axis_range(x_range_or_depth, 0, 'X') y_range = self.get_axis_range(y_range_or_depth, 1, 'Y') return "\n".join("".join( ("{{}}{}".format((self.DIRECTION_SHOW_MAP[self.direction][0] if show_direction else "["), ) if x + 1 in x_range and self.position == ( x + 1, y) else "{{}}{}".format((self.DIRECTION_SHOW_MAP[ self.direction][1] if show_direction else "]"), ) if x + 1 in x_range and self.position == (x, y) else "{} " if x + 1 in x_range else "{}" ).format(self.SHOW_MAP[self[utils.Point2D(x, y)]]) for x in x_range) for y in y_range)
def splitWall(brush, holes): '''Splits a brush up in 2D so that it appears to have a hole in it. This is achieved by taking the full brush size and the holes list and splitting the brush up into multiple parts.''' if not holes: return [brush] parts = [] # stores 2D parts returned by the main function finalparts = [] # stores expanded 3D parts extent2d = utils.Point2D(brush.extent.x, brush.extent.y) # Sort holes by x start coord, then width... sortedholes = qsortHoles(holes) # Check for errors and add doors into the returned list of parts, as they # need to be built... for hole in sortedholes: # FIXME check for other errors -- backtrack? if hole.origin.y + hole.extent.y > extent2d.y: utils.error('Hole is taller than containing wall.\n\thole: ' + str(hole) + '\n\textent2d: ' + str(extent2d)) if hole.type == connector.DOOR: finalparts.append( utils.Region2D( utils.Point2D(brush.origin.x + hole.origin.x, brush.origin.y + hole.origin.y), utils.Point2D(hole.extent.x, hole.extent.y), hole.type, hole.props)) # Go to main function... # FIXME this should not assume the origin is (0, 0) but it doesn't work if # we don't make this true... parts = splitWallCore(utils.Chunk2D(utils.Point2D(0, 0), extent2d), sortedholes) # Take list of brushes and add the z coord back on... for p in parts: finalparts.append( utils.Region2D( utils.Point2D(brush.origin.x + p.origin.x, brush.origin.y + p.origin.y), utils.Point2D(p.extent.x, p.extent.y), p.type, p.props)) # Now we can return them... return finalparts
def processSolid(doc, parent, worldspawn, sf, offset, solid): '''Note: uses style set in parent hollow.''' global style o = utils.getPoint(solid.getAttribute('origin')) + offset e = utils.getPoint(solid.getAttribute('extent')) t = solid.getAttribute('texture') type = solid.getAttribute('type') if not t: if not type: utils.error('solid with no type also has no texture attribute set') f = solid.getAttribute('holeface') # Get holes info... # FIXME this is repeated code from the hollow one -- any way we can # refactor it? props = {} holes = [] # Check if the solid has children (holes). # If so, split it up. # If not, just add it. if solid.hasChildNodes(): for hole in solid.childNodes: ho_x, ho_y = hole.getAttribute('origin').split() he_x, he_y = hole.getAttribute('extent').split() type = hole.getAttribute('type') if not type: pass elif type == connector.DOOR: props['key'] = hole.getAttribute('key') else: utils.warning( 'only doors allowed as hole types; not plats or others.') # FIXME deal with other types holes.append( utils.Hole2D(utils.Point2D(float(ho_x), float(ho_y)), utils.Point2D(float(he_x), float(he_y)), type, props)) # Built split (2D) parts into 3D brushes; mapping of coords to 3D # depends on which direction/face the hole was constructed in. if f == dcp.NORTH: parts = split.splitWall( utils.Region2D(utils.Point2D(o.x, o.z), utils.Point2D(e.x, e.z)), holes) for part in parts: part3d = utils.addDim(part, dims.Y, o.y, e.y) utils.makeBrush(doc, worldspawn, sf, style, part3d, f, t) elif f == dcp.UP: parts = split.splitWall( utils.Region2D(utils.Point2D(o.x, o.y), utils.Point2D(e.x, e.y)), holes) for part in parts: part3d = utils.addDim(part, dims.Z, o.z + prog.lip_small, e.z - prog.lip_small * 2) utils.makeBrush(doc, worldspawn, sf, style, part3d, f, t) else: utils.error('Unsupported holeface ' + f + ' requested for hole in solid.') else: # Doesn't have child nodes... if not type or type == connector.STEP: pass # no properties to set elif type == connector.DOOR: props['key'] = solid.getAttribute('key') elif type == connector.PLAT: props['position'] = solid.getAttribute('position') else: utils.warning('unknown type ' + type + ' specifed.') brush = utils.Region3D(Point(o.x, o.y, o.z), Point(e.x, e.y, e.z), type, props) utils.makeBrush(doc, worldspawn, sf, style, brush, type, t) # We can't remove the child or we screw over tree traversal (urgh)... utils.insertPlaceholder(doc, parent, solid)
def splitWallCore(chunk, holes): '''Take a given area of the face to be split and split it.''' global splitWallCoreLevel splitWallCoreLevel = splitWallCoreLevel + 1 parts = [] if len(holes) == 0: paddedPrint('sWC: 0. chunk:' + str(chunk)) parts.append(chunk) elif len(holes) == 1: hole = holes.pop() paddedPrint('sWC: 1. chunk: ' + str(chunk) + ' holes: ' + str(hole)) if hole.end.x < chunk.end.x: # The hole is not flush with one side of this chunk; split the # chunk so it is. # We do this by splitting the chunk so that the hole touches its # east side. # Anything left over after the east side is a single whole chunk. paddedPrint('sWC: 1. hole.end.x (' + str(hole.end.x) + ') < chunk.end.x (' + str(chunk.end.x) + ').') # Process part of chunk with hole in it... paddedPrint('sWC: 1. hole.end.x < chunk.end.x. HOLE CHUNK') addparts = splitWallCore( utils.Chunk2D( chunk.origin, utils.Point2D(hole.end.x - chunk.origin.x, chunk.extent.y)), [hole]) for ap in addparts: parts.append(ap) # Process the bit left at the east side... paddedPrint('sWC: 1. hole.end.x < chunk.end.x. SOLID CHUNK') addparts = splitWallCore( utils.Chunk2D( utils.Point2D(hole.end.x, chunk.origin.y), utils.Point2D(chunk.end.x - hole.end.x, chunk.extent.y)), []) for ap in addparts: parts.append(ap) else: # The end x-points of hole and chunk must be equal. # Add some parts around the hole... paddedPrint('sWC: 1. split flush.') # Under hole if (hole.origin.y - chunk.origin.y) > 0: parts.append( utils.Chunk2D( chunk.origin, utils.Point2D(hole.end.x - chunk.origin.x, hole.origin.y - chunk.origin.y))) # Left of hole if (hole.origin.x - chunk.origin.x) > 0: parts.append( utils.Chunk2D( chunk.origin + utils.Point2D(0, hole.origin.y - chunk.origin.y), utils.Point2D(hole.origin.x - chunk.origin.x, hole.extent.y))) # Above hole if (chunk.end.y - hole.end.y) > 0: parts.append( utils.Chunk2D( chunk.origin + utils.Point2D(0, hole.end.y - chunk.origin.y), utils.Point2D(hole.end.x - chunk.origin.x, chunk.end.y - hole.end.y))) paddedPrint('sWC: 1. split flush. results: ' + str([str(p) for p in parts])) else: # len(holes) > 1 paddedPrint('sWC: n. chunk: ' + str(chunk) + ' holes: ' + str([str(h) for h in holes])) '''Compare first two holes. If they do not overlap x-wise, split the chunk at the x value that represents the end of the first hole. If they do overlap x-wise, see if they overlap y-wise too. If they do, we're screwed (can't handle this yet). If they overlap y-wise See if there are any more nodes. If no, then we can just split this chunk at a given y-value to keep the holes seperate. If yes, then we need to see if we can split this chunk into two: one split vertically (at an x-value just after the 2nd hole) to seperate our two x-overlapping holes from the next one. and one split horizontally (at a y-value) to separate our overlapping holes ''' holeA = holes[0] holeB = holes[1] if holeA.end.x < holeB.origin.x: paddedPrint('sWC: n. holeA.end.x (' + str(holeA.end.x) + ') < holeB.origin.x (' + str(holeB.origin.x) + ')') # Our holes do not overlap x-wise; # split our chunk into two: # one chunk containing the first hole (flush to the edge) # another chunk containing all the other holes paddedPrint('sWC: n. holeA.end.x < holeB.origin.x. singleton') addparts = splitWallCore( utils.Chunk2D( chunk.origin, utils.Point2D(holeA.end.x - chunk.origin.x, chunk.extent.y)), [holeA]) for ap in addparts: parts.append(ap) paddedPrint('sWC: n. holeA.end.x < holeB.origin.x. the rest') addparts = splitWallCore( utils.Chunk2D( utils.Point2D(holeA.end.x, chunk.origin.y), utils.Point2D(chunk.end.x - holeA.end.x, chunk.extent.y)), holes[1:]) for ap in addparts: parts.append(ap) elif holeA.origin.y >= holeB.end.y \ or holeB.origin.y >= holeA.end.y: paddedPrint('sWC: n. Y. holeA.origin.y (' + str(holeA.origin.y) + ' >= holeB.end.y (' + str(holeB.end.y) + ')') # Our holes overlap x-wise, but they don't overlap y-wise. # Which one is on top? if holeA.origin.y >= holeB.origin.y: upper = holeA lower = holeB else: upper = holeB lower = holeA # Are there more holes? if not len(holes) > 2: paddedPrint('sWC: n. Y. no more holes') # No more holes; just split this chunk y-wise... paddedPrint('sWC: n. Y. no more holes. LOWER.') addparts = splitWallCore( utils.Chunk2D( chunk.origin, utils.Point2D(chunk.extent.x, upper.origin.y)), [lower]) for ap in addparts: parts.append(ap) paddedPrint('sWC: n. Y. no more holes. UPPER.') addparts = splitWallCore( utils.Chunk2D( utils.Point2D(chunk.origin.x, upper.origin.y), utils.Point2D(chunk.extent.x, chunk.extent.y - upper.origin.y)), [upper]) for ap in addparts: parts.append(ap) else: # There are more holes; split both y- and x-wise. # Use the x-value of the next hole (FIXME could break things?) xcutoff = holes[2].origin.x - chunk.origin.x paddedPrint('sWC: n. Y. more holes; xcutoff = ' + str(xcutoff)) paddedPrint('sWC: n. Y. more holes. LOWER.') addparts = splitWallCore( utils.Chunk2D(chunk.origin, utils.Point2D(xcutoff, upper.origin.y)), [lower]) for ap in addparts: parts.append(ap) paddedPrint('sWC: n. Y. more holes. UPPER.') addparts = splitWallCore( utils.Chunk2D( utils.Point2D(chunk.origin.x, upper.origin.y), utils.Point2D(xcutoff, chunk.extent.y - upper.origin.y)), [upper]) for ap in addparts: parts.append(ap) paddedPrint('sWC: n. Y. more holes. REST-OF-X.') addparts = splitWallCore( utils.Chunk2D( utils.Point2D(chunk.origin.x + xcutoff, chunk.origin.y), utils.Point2D(chunk.extent.x - xcutoff, chunk.extent.y)), holes[2:]) for ap in addparts: parts.append(ap) else: # Our holes overlap both x- and y-wise; for now, we're screwed. utils.error( "Oh dear: the segmentation algorithm can't cope with holes that overlap " 'both x- and y-wise!') splitWallCoreLevel = splitWallCoreLevel - 1 return parts