Exemplo n.º 1
0
class Maze(object):
    def __init__(self, size):
        self._map = Matrix(size, Tile.NULL)
        self._area = [Rect((0, 0), size)]
        self._roomSize = RoomSizeRange((7, 5), (13, 7))
        self._entranceMax = 4
        self._deadend = 2
        self._room = []
        self._entrance = []
        self._diged = set()
        self._deadends = []

    def __iter__(self):
        for c in self._map:
            yield (c, self._map.at(c))

    @property
    def area(self):
        return self._area[:]

    @property
    def room(self):
        return self._room[:]

    @property
    def deadends(self):
        return self._deadends[:]

    def setEntranceMax(self, value):
        self._entranceMax = value
        return self

    def setDeadEnd(self, value):
        self._deadend = value
        return self

    def setRoomSizeRange(self, small, big):
        self._roomSize = RoomSizeRange(small, big)
        return self

    def toString(self):
        return '\n'.join(
            [''.join([t.ch for t in line]) for line in self._map.lines])

    def generate(self):
        return self.splitAreas().makeRoom().digCorridor().removeDeadEnd()

    def splitAreas(self):
        lastSize = 0
        while lastSize != len(self._area):
            lastSize = len(self._area)
            for area in self._area:
                if self.verticalSplitArea(area): continue
                self.horizontalSplitArea(area)
        return self

    def verticalSplitArea(self, area):
        if area.width < self._roomSize.maxWidth * 2: return False
        self._area.remove(area)
        min = self._roomSize.minWidth + 2
        max = area.width - self._roomSize.minWidth - 2
        split = random.choice([v for v in range(min, max + 1) if v % 2])
        self._area.extend(area.verticalSplit(split))
        return True

    def horizontalSplitArea(self, area):
        if area.height < self._roomSize.maxHeight * 2: return False
        self._area.remove(area)
        min = self._roomSize.minHeight + 1
        max = area.height - self._roomSize.minHeight - 1
        split = random.choice([v for v in range(min, max + 1) if v % 2])
        self._area.extend(area.horizontalSplit(split))
        return True

    def makeRoom(self):
        for area in self._area:
            self.makeRoomInArea(area)
        for room in self._room:
            self.putRoom(room)
            self.makeRoomEntrance(room)
        return self

    def makeRoomInArea(self, area):
        roomArea = area.reduce(1)
        (w, h) = RoomSizeRange(self._roomSize.min, roomArea.size).randomSize()
        x = random.choice([
            x for x in range(roomArea.x, roomArea.right - w + 2) if x % 2 == 0
        ])
        y = random.choice([
            y for y in range(roomArea.y, roomArea.bottom - h + 2) if y % 2 == 0
        ])
        room = Rect((x, y), (w, h))
        self._room.append(room)

    def putRoom(self, room):
        for c in room:
            self._map.put(c, Tile.ROOM)
        for c in room.frame:
            self._map.put(c, Tile.WALL)

    def makeRoomEntrance(self, room):
        candidate = []
        for (x, y) in room.frame:
            if x == 0 or y == 0: continue
            if x == self._map.width - 1 or y == self._map.height - 1: continue
            if x % 2 == 0 and y % 2 == 0: continue
            candidate.append((x, y))
        for c in range(random.randrange(1, self._entranceMax + 1)):
            e = random.choice(candidate)
            self._entrance.append(e)
            self._map.put(e, Tile.DOOR)
            candidate.remove(e)

    def digCorridor(self):
        for y in range(1, self._map.height, 2):
            for x in range(1, self._map.width, 2):
                self.digAround((x, y))
        return self

    def startDigCorridor(self, start, dir):
        step = (coord.sum(start, dir), coord.sum(start, dir, dir))
        if any([self._map.isOutBound(c) for c in step]): return
        if any([self._map.at(c).isStopDig for c in step]): return
        if any([c in self._diged for c in step]): return
        for c in step:
            self._diged.add(c)
            if self._map.at(c) == Tile.NULL:
                self._map.put(c, Tile.CORRIDOR)
        self.digAround(step[1])

    def digAround(self, start):
        dirs = list(direction.CROSS)
        random.shuffle(dirs)
        for d in dirs:
            self.startDigCorridor(start, d)

    def removeDeadEnd(self):
        self.updateDeadEnd()
        while (self._deadend < len(self._deadends)):
            de = random.choice(self._deadends)
            self._deadends.remove(de)
            self.fillDeadEnd(de)
        return self

    def updateDeadEnd(self):
        for y in range(1, self._map.height, 2):
            for x in range(1, self._map.width, 2):
                c = (x, y)
                if self._map.at(c).isWall: continue
                if self.isDeadEnd(c):
                    self._deadends.append(c)

    def fillDeadEnd(self, point):
        if not self.isDeadEnd(point): return
        self._map.put(point, Tile.NULL)
        for d in direction.CROSS:
            step = coord.sum(point, d)
            if self._map.at(step).isWall: continue
            self.fillDeadEnd(step)
            break
        for de in self._deadends:
            self._map.put(de, Tile.DEADEND)

    def isDeadEnd(self, point):
        wall = 0
        for d in direction.CROSS:
            if self._map.at(coord.sum(point, d)).isWall:
                wall += 1
        return wall == 3
Exemplo n.º 2
0
class BTPChromosome:
    
    # Cost functions
    function = {
        'linear':
            (lambda i, j, u: float(abs(i * j - 17) * u)),
        'quadratic':
            (lambda i, j, u: float(((i * i - j * j + 1) ** 2) * (u * u))),
        'general1':
            (lambda i, j, u: float(abs(i * j - 11) * math.sqrt(u))),
        'general2':
            (lambda i, j, u: float(abs(i * j - 17) * u + u ** 4)),
        }

    def __init__(self, source, destination, init=None, cost='linear'):
        self.source = source
        self.destination = destination
        self.cost = cost

        self.btp_matrix = Matrix(len(source), len(destination))

        # Initialize if requested
        if init:
            init_kind = {
                'simple': 0,
                'complex': 3
                }
            self.init([], init_kind[init])

    def __str__(self):
        str = (" " * 10)

        for i in xrange(len(self.destination)):
            str += "{0:10.2}".format(float(self.destination[i]))

        str += "\n"

        for i in xrange(self.btp_matrix.rows):
            str += "{0:10.2}".format(float(self.source[i]))
            for j in xrange(self.btp_matrix.cols):
                num = float(self.btp_matrix.matrix[i][j])
                str += "{0:10.2}".format(num)
            str += "\n"

        return str

    # Returns the fitness of this individual
    def fitness(self):
        
        mat = self.btp_matrix

        # Evaluate and accumulate results of the fitness function
        # across the matrix
        return sum([sum([BTPChromosome.function[self.cost](i, j, mat.at(i, j))
                         for j in xrange(mat.cols)])
                    for i in xrange(mat.rows)])
    
    # Check that the BTP matrix complies with the constraints
    def check_constraints(self):

        tolerance = 1E-6

        # Returns true if we can tolerate the error
        is_tolerable = lambda a, b: math.fabs(a - b) < tolerance
        # This sequence say for rows and cols if the
        # current solution is acceptable
        row_is_ok = map(is_tolerable,
                      self.btp_matrix.marginal_row(), self.source)
        col_is_ok = map(is_tolerable,
                      self.btp_matrix.marginal_col(), self.destination)

        and_op = lambda x, y: x and y

        return (functools.reduce(and_op, row_is_ok) and
                functools.reduce(and_op, col_is_ok))

    # For an element of the BTP matrix returns the maximum value
    # to which it can be set within constraints
    def delta(self, i, j):
        row = self.btp_matrix.marginal_row()
        col = self.btp_matrix.marginal_col()
        
        return min(self.source[i] - row[i],
                   self.destination[j] - col[j])

    # Initialization procedure: traverse the matrix in a random
    # order and assign, within constraints, a portion of the maximum
    # possible value. The portion depends on the value of the depth parameter
    def init(self, index_seq=[], depth=0):
        if index_seq:
            traversal = index_seq
        else:
            traversal = self.random_traversal()

        # Choose portion
        if depth:
            portion = random.uniform(0.0, 1.0)
        else:
            portion = 1.0

        # Assign always the maximum possible value
        for (i, j) in traversal:
            value = self.btp_matrix.at(i, j) + self.delta(i, j) * portion
            self.btp_matrix.set(i, j, value)

        if depth:
            self.init(traversal, depth - 1)
            return

        if not self.check_constraints():
            print sum(self.btp_matrix.marginal_row())
            print sum(self.btp_matrix.marginal_col())
            raise BTPConstraintViolationException()

    # Mutation operator. If parameter boundary is True an element
    # is changed to its maximum allowed value. Otherwise, it is changed
    # to a portion of its maximum allowed value
    def fixed_mutation(self, boundary=True):
        cell = (i, j) = self.random_element()

        # If boundary mutation set to maximum, otherwise set to a portion
        if boundary:
            portion = 1.0
            depth = 0
            traversal = self.random_traversal()
            traversal.remove(cell)
        else:
            portion = random.uniform(0.0, 1.0)
            depth = 3
            traversal = []
        
        self.btp_matrix.set_all(0)
        new_value = self.delta(i, j) * portion
        self.btp_matrix.set(i, j, new_value)

        # Rerun initialization with zero depth, ignoring that element
        self.init(traversal, 0)

    # This mutation operator selects a random submatrix and
    # reinitialize its values
    def submatrix_mutation(self, mutation_rate):

        selected_rows = [i for i in xrange(self.btp_matrix.rows)
                         if random.uniform(0.0, 1.0) < mutation_rate]
        selected_cols = [i for i in xrange(self.btp_matrix.cols)
                         if random.uniform(0.0, 1.0) < mutation_rate]

        # If no rows or columns were selected then return
        if not selected_rows or not selected_cols:
            return

        sources = [0 for i in selected_rows]
        destinations = [0 for i in selected_cols]

        # Calculate sources and destinations
        for i in xrange(len(selected_rows)):
            for j in xrange(len(selected_cols)):
                cell = (selected_rows[i], selected_cols[j])
                sources[i] += self.btp_matrix.at(*cell)
                destinations[j] += self.btp_matrix.at(*cell)

        small_chromosome = BTPChromosome(tuple(sources), tuple(destinations),
                                         'complex', self.cost)

        # Reset corresponding values
        for i in xrange(len(selected_rows)):
            for j in xrange(len(selected_cols)):
                args = (selected_rows[i], selected_cols[j],
                        small_chromosome.btp_matrix.at(i, j))
                self.btp_matrix.set(*args)
        
        if not self.check_constraints():
            raise BTPConstraintViolationException()

    # Crossover operator, the offspring is a convex combination of
    # the parents. It supports more than two parents
    def crossover(self, others):
        offspring = BTPChromosome(self.source, self.destination,
                                  False, self.cost)
        others.append(self)

        coeff = [random.uniform(0.3, 0.6) for i in others]
        total = sum(coeff)
        coeff = [c / total for c in coeff]

        mul = lambda a, b: a.btp_matrix * float(b)
        add = lambda a, b: a + b

        offspring.btp_matrix = functools.reduce(add, map(mul, others, coeff))

        if not offspring.check_constraints():
            raise BTPConstraintViolationException()

        return offspring

    # Picks a random element from the matrix
    def random_element(self):
        return (random.randrange(self.btp_matrix.rows),
                random.randrange(self.btp_matrix.cols))

    # Random sequence of indexes for visiting all elements of the matrix
    def random_traversal(self):
            
        traversal = []
        total_elements = self.btp_matrix.rows * self.btp_matrix.cols
        
        # Pick a random cell, if it hasn't been visited yet
        # then add it to the sequence
        while(total_elements):
            cell = self.random_element()
            
            if cell not in traversal:
                total_elements -= 1
                traversal.append(cell)
                
        return traversal
Exemplo n.º 3
0
 def testInitialize(self):
     m = Matrix((2, 3), 42)
     self.assertTrue(all([m.at(c) == 42 for c in m]))
Exemplo n.º 4
0
 def testFill(self):
     m = Matrix((2, 3))
     self.assertTrue(all([m.at(c) == None for c in m]))
     m.fill(42)
     self.assertTrue(all([m.at(c) == 42 for c in m]))
Exemplo n.º 5
0
 def testPutAndAt(self):
     m = Matrix((2, 3))
     c = (1, 2)
     self.assertEqual(m.at((1, 2)), None)
     m.put(c, 42)
     self.assertEqual(m.at((1, 2)), 42)