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
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
def testInitialize(self): m = Matrix((2, 3), 42) self.assertTrue(all([m.at(c) == 42 for c in m]))
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]))
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)