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
예제 #2
0
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]
예제 #5
0
 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)
예제 #6
0
    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}")
예제 #7
0
 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
예제 #8
0
    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)
예제 #9
0
 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)
예제 #10
0
    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)
예제 #11
0
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
예제 #12
0
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)
예제 #13
0
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