class CogdoMazeFactory:

    def __init__(self, randomNumGen, width, height, frameWallThickness = Globals.FrameWallThickness, cogdoMazeData = CogdoMazeData):
        self._rng = RandomNumGen(randomNumGen)
        self.width = width
        self.height = height
        self.frameWallThickness = frameWallThickness
        self._cogdoMazeData = cogdoMazeData
        self.quadrantSize = self._cogdoMazeData.QuadrantSize
        self.cellWidth = self._cogdoMazeData.QuadrantCellWidth

    def getMazeData(self):
        if not hasattr(self, '_data'):
            self._generateMazeData()
        return self._data

    def createCogdoMaze(self, flattenModel = True):
        if not hasattr(self, '_maze'):
            self._loadAndBuildMazeModel(flatten=flattenModel)
        return CogdoMaze(self._model, self._data, self.cellWidth)

    def _gatherQuadrantData(self):
        self.openBarriers = []
        barrierItems = range(Globals.TotalBarriers)
        self._rng.shuffle(barrierItems)
        for i in barrierItems[0:len(barrierItems) - Globals.NumBarriers]:
            self.openBarriers.append(i)

        self.quadrantData = []
        quadrantKeys = self._cogdoMazeData.QuadrantCollisions.keys()
        self._rng.shuffle(quadrantKeys)
        i = 0
        for y in range(self.height):
            for x in range(self.width):
                key = quadrantKeys[i]
                collTable = self._cogdoMazeData.QuadrantCollisions[key]
                angle = self._cogdoMazeData.QuadrantAngles[self._rng.randint(0, len(self._cogdoMazeData.QuadrantAngles) - 1)]
                self.quadrantData.append((key, collTable[angle], angle))
                i += 1
                if x * y >= self._cogdoMazeData.NumQuadrants:
                    i = 0

    def _generateBarrierData(self):
        data = []
        for y in range(self.height):
            data.append([])
            for x in range(self.width):
                if x == self.width - 1:
                    ax = -1
                else:
                    ax = 1
                if y == self.height - 1:
                    ay = -1
                else:
                    ay = 1
                data[y].append([ax, ay])

        dirUp = 0
        dirDown = 1
        dirLeft = 2
        dirRight = 3

        def getAvailableDirections(ax, ay, ignore = None):
            dirs = []
            if ax - 1 >= 0 and data[ay][ax - 1][BARRIER_DATA_RIGHT] == 1 and (ax, ay) != ignore:
                dirs.append(dirLeft)
            if ax + 1 < self.width and data[ay][ax][BARRIER_DATA_RIGHT] == 1 and (ax, ay) != ignore:
                dirs.append(dirRight)
            if ay - 1 >= 0 and data[ay - 1][ax][BARRIER_DATA_TOP] == 1 and (ax, ay) != ignore:
                dirs.append(dirDown)
            if ay + 1 < self.height and data[ay][ax][BARRIER_DATA_TOP] == 1 and (ax, ay) != ignore:
                dirs.append(dirUp)
            return dirs

        visited = []

        def tryVisitNeighbor(ax, ay, ad):
            if ad == dirUp:
                if data[ay][ax] in visited:
                    return None
                visited.append(data[ay][ax])
                data[ay][ax][BARRIER_DATA_TOP] = 0
                ay += 1
            elif ad == dirDown:
                if data[ay - 1][ax] in visited:
                    return None
                visited.append(data[ay - 1][ax])
                data[ay - 1][ax][BARRIER_DATA_TOP] = 0
                ay -= 1
            elif ad == dirLeft:
                if data[ay][ax - 1] in visited:
                    return None
                visited.append(data[ay][ax - 1])
                data[ay][ax - 1][BARRIER_DATA_RIGHT] = 0
                ax -= 1
            elif ad == dirRight:
                if data[ay][ax] in visited:
                    return None
                visited.append(data[ay][ax])
                data[ay][ax][BARRIER_DATA_RIGHT] = 0
                ax += 1
            return ax, ay

        def openBarriers(x, y):
            dirs = getAvailableDirections(x, y)
            for dir in dirs:
                next = tryVisitNeighbor(x, y, dir)
                if next is not None:
                    openBarriers(*next)

        x = self._rng.randint(0, self.width - 1)
        y = self._rng.randint(0, self.height - 1)
        openBarriers(x, y)
        self._barrierData = data

    def _generateMazeData(self):
        if not hasattr(self, 'quadrantData'):
            self._gatherQuadrantData()
        self._data = {'width': (self.width + 1) * self.frameWallThickness + self.width * self.quadrantSize,
                      'height': (self.height + 1) * self.frameWallThickness + self.height * self.quadrantSize}
        self._data['originX'] = int(self._data['width'] / 2)
        self._data['originY'] = int(self._data['height'] / 2)
        collisionTable = []
        horizontalWall = [ 1 for x in range(self._data['width']) ]
        collisionTable.append(horizontalWall)
        for i in range(0, len(self.quadrantData), self.width):
            for y in range(self.quadrantSize):
                row = [1]
                for x in range(i, i + self.width):
                    if x == 1 and y < self.quadrantSize / 2 - 2:
                        newData = []
                        for j in self.quadrantData[x][1][y]:
                            if j == 0:
                                newData.append(2)
                            else:
                                newData.append(j + 0)

                        row += newData + [1]
                    else:
                        row += self.quadrantData[x][1][y] + [1]

                collisionTable.append(row)

            collisionTable.append(horizontalWall[:])

        barriers = Globals.MazeBarriers
        for i in range(len(barriers)):
            for coords in barriers[i]:
                collisionTable[coords[1]][coords[0]] = 0

        y = self._data['originY']
        for x in range(len(collisionTable[y])):
            if collisionTable[y][x] == 0:
                collisionTable[y][x] = 2

        x = self._data['originX']
        for y in range(len(collisionTable)):
            if collisionTable[y][x] == 0:
                collisionTable[y][x] = 2

        self._data['collisionTable'] = collisionTable

    def _loadAndBuildMazeModel(self, flatten = False):
        self.getMazeData()
        self._model = NodePath('CogdoMazeModel')
        levelModel = CogdoUtil.loadMazeModel('level')
        self.quadrants = []
        quadrantUnitSize = int(self.quadrantSize * self.cellWidth)
        frameActualSize = self.frameWallThickness * self.cellWidth
        size = quadrantUnitSize + frameActualSize
        halfWidth = int(self.width / 2)
        halfHeight = int(self.height / 2)
        i = 0
        for y in range(self.height):
            for x in range(self.width):
                ax = (x - halfWidth) * size
                ay = (y - halfHeight) * size
                extension = ''
                if hasattr(getBase(), 'air'):
                    extension = '.bam'
                filepath = self.quadrantData[i][0] + extension
                angle = self.quadrantData[i][2]
                m = self._createQuadrant(filepath, i, angle, quadrantUnitSize)
                m.setPos(ax, ay, 0)
                m.reparentTo(self._model)
                self.quadrants.append(m)
                i += 1

        quadrantHalfUnitSize = quadrantUnitSize * 0.5
        barrierModel = CogdoUtil.loadMazeModel('grouping_blockerDivider').find('**/divider')
        y = 3
        for x in range(self.width):
            if x == (self.width - 1) / 2:
                continue
            ax = (x - halfWidth) * size
            ay = (y - halfHeight) * size - quadrantHalfUnitSize - (self.cellWidth - 0.5)
            b = NodePath('barrier')
            barrierModel.instanceTo(b)
            b.setPos(ax, ay, 0)
            b.reparentTo(self._model)

        offset = self.cellWidth - 0.5
        for x in (0, 3):
            for y in range(self.height):
                ax = (x - halfWidth) * size - quadrantHalfUnitSize - frameActualSize + offset
                ay = (y - halfHeight) * size
                b = NodePath('barrier')
                barrierModel.instanceTo(b)
                b.setPos(ax, ay, 0)
                b.setH(90)
                b.reparentTo(self._model)

            offset -= 2.0

        barrierModel.removeNode()
        levelModel.getChildren().reparentTo(self._model)
        for np in self._model.findAllMatches('**/*lightCone*'):
            CogdoUtil.initializeLightCone(np, 'fixed', 3)

        if flatten:
            self._model.flattenStrong()
        return self._model

    def _createQuadrant(self, filepath, serialNum, angle, size):
        root = NodePath('QuadrantRoot-%i' % serialNum)
        quadrant = loader.loadModel(filepath)
        quadrant.getChildren().reparentTo(root)
        root.setH(angle)
        return root
Пример #2
0
class CogdoMazeFactory:

    def __init__(self, randomNumGen, width, height, frameWallThickness=Globals.FrameWallThickness, cogdoMazeData=CogdoMazeData):
        self._rng = RandomNumGen(randomNumGen)
        self.width = width
        self.height = height
        self.frameWallThickness = frameWallThickness
        self._cogdoMazeData = cogdoMazeData
        self.quadrantSize = self._cogdoMazeData.QuadrantSize
        self.cellWidth = self._cogdoMazeData.QuadrantCellWidth

    def getMazeData(self):
        if not hasattr(self, '_data'):
            self._generateMazeData()
        return self._data

    def createCogdoMaze(self, flattenModel=True):
        if not hasattr(self, '_maze'):
            self._loadAndBuildMazeModel(flatten=flattenModel)
        return CogdoMaze(self._model, self._data, self.cellWidth)

    def _gatherQuadrantData(self):
        self.openBarriers = []
        barrierItems = range(Globals.TotalBarriers)
        self._rng.shuffle(barrierItems)
        for i in barrierItems[0:len(barrierItems) - Globals.NumBarriers]:
            self.openBarriers.append(i)

        self.quadrantData = []
        quadrantKeys = self._cogdoMazeData.QuadrantCollisions.keys()
        self._rng.shuffle(quadrantKeys)
        i = 0
        for y in xrange(self.height):
            for x in xrange(self.width):
                key = quadrantKeys[i]
                collTable = self._cogdoMazeData.QuadrantCollisions[key]
                angle = self._cogdoMazeData.QuadrantAngles[self._rng.randint(0, len(self._cogdoMazeData.QuadrantAngles) - 1)]
                self.quadrantData.append((key, collTable[angle], angle))
                i += 1
                if x * y >= self._cogdoMazeData.NumQuadrants:
                    i = 0

    def _generateBarrierData(self):
        data = []
        for y in xrange(self.height):
            data.append([])
            for x in xrange(self.width):
                if x == self.width - 1:
                    ax = -1
                else:
                    ax = 1
                if y == self.height - 1:
                    ay = -1
                else:
                    ay = 1
                data[y].append([ax, ay])

        dirUp = 0
        dirDown = 1
        dirLeft = 2
        dirRight = 3

        def getAvailableDirections(ax, ay, ignore=None):
            dirs = []
            if ax - 1 >= 0 and data[ay][(ax - 1)][BARRIER_DATA_RIGHT] == 1 and (ax, ay) != ignore:
                dirs.append(dirLeft)
            if ax + 1 < self.width and data[ay][ax][BARRIER_DATA_RIGHT] == 1 and (ax, ay) != ignore:
                dirs.append(dirRight)
            if ay - 1 >= 0 and data[(ay - 1)][ax][BARRIER_DATA_TOP] == 1 and (ax, ay) != ignore:
                dirs.append(dirDown)
            if ay + 1 < self.height and data[ay][ax][BARRIER_DATA_TOP] == 1 and (ax, ay) != ignore:
                dirs.append(dirUp)
            return dirs

        visited = []

        def tryVisitNeighbor(ax, ay, ad):
            if ad == dirUp:
                if data[ay][ax] in visited:
                    return None
                visited.append(data[ay][ax])
                data[ay][ax][BARRIER_DATA_TOP] = 0
                ay += 1
            elif ad == dirDown:
                if data[(ay - 1)][ax] in visited:
                    return None
                visited.append(data[(ay - 1)][ax])
                data[(ay - 1)][ax][BARRIER_DATA_TOP] = 0
                ay -= 1
            elif ad == dirLeft:
                if data[ay][(ax - 1)] in visited:
                    return None
                visited.append(data[ay][(ax - 1)])
                data[ay][(ax - 1)][BARRIER_DATA_RIGHT] = 0
                ax -= 1
            elif ad == dirRight:
                if data[ay][ax] in visited:
                    return None
                visited.append(data[ay][ax])
                data[ay][ax][BARRIER_DATA_RIGHT] = 0
                ax += 1
            return (ax, ay)

        def openBarriers(x, y):
            dirs = getAvailableDirections(x, y)
            for dir in dirs:
                next = tryVisitNeighbor(x, y, dir)
                if next is not None:
                    openBarriers(*next)

            return

        x = self._rng.randint(0, self.width - 1)
        y = self._rng.randint(0, self.height - 1)
        openBarriers(x, y)
        self._barrierData = data
        return

    def _generateMazeData(self):
        if not hasattr(self, 'quadrantData'):
            self._gatherQuadrantData()
        self._data = {}
        self._data['width'] = (self.width + 1) * self.frameWallThickness + self.width * self.quadrantSize
        self._data['height'] = (self.height + 1) * self.frameWallThickness + self.height * self.quadrantSize
        self._data['originX'] = int(self._data['width'] / 2)
        self._data['originY'] = int(self._data['height'] / 2)
        collisionTable = []
        horizontalWall = [ 1 for x in xrange(self._data['width']) ]
        collisionTable.append(horizontalWall)
        for i in xrange(0, len(self.quadrantData), self.width):
            for y in xrange(self.quadrantSize):
                row = [
                 1]
                for x in xrange(i, i + self.width):
                    if x == 1 and y < self.quadrantSize / 2 - 2:
                        newData = []
                        for j in self.quadrantData[x][1][y]:
                            if j == 0:
                                newData.append(2)
                            else:
                                newData.append(j + 0)

                        row += newData + [1]
                    else:
                        row += self.quadrantData[x][1][y] + [1]

                collisionTable.append(row)

            collisionTable.append(horizontalWall[:])

        barriers = Globals.MazeBarriers
        for i in xrange(len(barriers)):
            for coords in barriers[i]:
                collisionTable[coords[1]][coords[0]] = 0

        y = self._data['originY']
        for x in xrange(len(collisionTable[y])):
            if collisionTable[y][x] == 0:
                collisionTable[y][x] = 2

        x = self._data['originX']
        for y in xrange(len(collisionTable)):
            if collisionTable[y][x] == 0:
                collisionTable[y][x] = 2

        self._data['collisionTable'] = collisionTable

    def _loadAndBuildMazeModel(self, flatten=False):
        self.getMazeData()
        self._model = NodePath('CogdoMazeModel')
        levelModel = CogdoUtil.loadMazeModel('level')
        self.quadrants = []
        quadrantUnitSize = int(self.quadrantSize * self.cellWidth)
        frameActualSize = self.frameWallThickness * self.cellWidth
        size = quadrantUnitSize + frameActualSize
        halfWidth = int(self.width / 2)
        halfHeight = int(self.height / 2)
        i = 0
        for y in xrange(self.height):
            for x in xrange(self.width):
                ax = (x - halfWidth) * size
                ay = (y - halfHeight) * size
                extension = ''
                if hasattr(getBase(), 'air'):
                    extension = '.bam'
                filepath = self.quadrantData[i][0] + extension
                angle = self.quadrantData[i][2]
                m = self._createQuadrant(filepath, i, angle, quadrantUnitSize)
                m.setPos(ax, ay, 0)
                m.reparentTo(self._model)
                self.quadrants.append(m)
                i += 1

        quadrantHalfUnitSize = quadrantUnitSize * 0.5
        barrierModel = CogdoUtil.loadMazeModel('grouping_blockerDivider').find('**/divider')
        y = 3
        for x in xrange(self.width):
            if x == (self.width - 1) / 2:
                continue
            ax = (x - halfWidth) * size
            ay = (y - halfHeight) * size - quadrantHalfUnitSize - (self.cellWidth - 0.5)
            b = NodePath('barrier')
            barrierModel.instanceTo(b)
            b.setPos(ax, ay, 0)
            b.reparentTo(self._model)

        offset = self.cellWidth - 0.5
        for x in (0, 3):
            for y in xrange(self.height):
                ax = (x - halfWidth) * size - quadrantHalfUnitSize - frameActualSize + offset
                ay = (y - halfHeight) * size
                b = NodePath('barrier')
                barrierModel.instanceTo(b)
                b.setPos(ax, ay, 0)
                b.setH(90)
                b.reparentTo(self._model)

            offset -= 2.0

        barrierModel.removeNode()
        levelModel.getChildren().reparentTo(self._model)
        for np in self._model.findAllMatches('**/*lightCone*'):
            CogdoUtil.initializeLightCone(np, 'fixed', 3)

        if flatten:
            self._model.flattenStrong()
        return self._model

    def _createQuadrant(self, filepath, serialNum, angle, size):
        root = NodePath('QuadrantRoot-%i' % serialNum)
        quadrant = loader.loadModel(filepath)
        quadrant.getChildren().reparentTo(root)
        root.setH(angle)
        return root
class CogdoFlyingLevelFactory:
    def __init__(self,
                 parent,
                 quadLengthUnits,
                 quadVisibilityAhead,
                 quadVisibiltyBehind,
                 rng=None):
        self.parent = parent
        self.quadLengthUnits = quadLengthUnits
        self.quadVisibiltyAhead = quadVisibilityAhead
        self.quadVisibiltyBehind = quadVisibiltyBehind
        if not rng:
            pass
        self._rng = RandomNumGen(1)
        self._level = None

    def loadAndBuildLevel(self, safezoneId):
        levelNode = NodePath('level')
        frameModel = CogdoUtil.loadFlyingModel('level')
        startPlatformModel = CogdoUtil.loadFlyingModel('levelStart')
        endPlatformModel = CogdoUtil.loadFlyingModel('levelEnd')
        for fan in frameModel.findAllMatches('**/*wallFan'):
            fan.flattenStrong()

        frameModel.find('**/fogOpaque').setBin('background', 1)
        frameModel.find('**/ceiling').setBin('background', 2)
        frameModel.find('**/fogTranslucent_bm').setBin('fixed', 1)
        frameModel.find('**/wallR').setBin('opaque', 2)
        frameModel.find('**/wallL').setBin('opaque', 2)
        frameModel.find('**/fogTranslucent_top').setBin('fixed', 2)
        frameModel.getChildren().reparentTo(levelNode)
        levelNode.hide()
        self._level = CogdoFlyingLevel(self.parent, levelNode,
                                       startPlatformModel, endPlatformModel,
                                       self.quadLengthUnits,
                                       self.quadVisibiltyAhead,
                                       self.quadVisibiltyBehind)
        if Globals.Dev.WantTempLevel:
            quads = Globals.Dev.DevQuadsOrder
        else:
            levelInfo = Globals.Level.DifficultyOrder[safezoneId]
            quads = []
            for difficulty in levelInfo:
                quadList = Globals.Level.QuadsByDifficulty[difficulty]
                quads.append(quadList[self._rng.randint(0, len(quadList) - 1)])

        for i in quads:
            filePath = CogdoUtil.getModelPath('quadrant%i' % i, 'flying')
            quadModel = loader.loadModel(filePath)
            for np in quadModel.findAllMatches('**/*lightCone*'):
                CogdoUtil.initializeLightCone(np, 'fixed', 3)

            self._level.appendQuadrant(quadModel)

        self._level.ready()

    def createLevel(self, safezoneId=2000):
        if self._level is None:
            self.loadAndBuildLevel(safezoneId)

        return self._level

    def createLevelFog(self):
        if self._level is None:
            self.loadAndBuildLevel()

        return CogdoFlyingLevelFog(self._level)
Пример #4
0
class ADBakery(Bakery):

    """
    A factory for tiles based on panda3d's perlin noise
    """

    def __init__(self, editorFile, bakeryFolder):
        #id is a seed for the map and unique name for any cached heightmap images

        self.dice = RandomNumGen(TimeVal().getUsec())
        self.id = self.dice.randint(2, 1000000)

        # the overall smoothness/roughness of the terrain
        smoothness = 80
        # how quickly altitude and roughness shift
        self.consistency = smoothness * 8
        # waterHeight is expressed as a multiplier to the max height
        self.waterHeight = 0.3
        # for realism the flatHeight should be at or very close to waterHeight
        self.flatHeight = self.waterHeight + 0.04

        #creates noise objects that will be used by the getHeight function
        """Create perlin noise."""

        # See getHeight() for more details....

        # where perlin 1 is low terrain will be mostly low and flat
        # where it is high terrain will be higher and slopes will be exagerrated
        # increase perlin1 to create larger areas of geographic consistency
        self.perlin1 = StackedPerlinNoise2()
        perlin1a = PerlinNoise2(0, 0, 256, seed=self.id)
        perlin1a.setScale(self.consistency)
        self.perlin1.addLevel(perlin1a)
        perlin1b = PerlinNoise2(0, 0, 256, seed=self.id * 2 + 123)
        perlin1b.setScale(self.consistency / 2)
        self.perlin1.addLevel(perlin1b, 1 / 2)


        # perlin2 creates the noticeable noise in the terrain
        # without perlin2 everything would look unnaturally smooth and regular
        # increase perlin2 to make the terrain smoother
        self.perlin2 = StackedPerlinNoise2()
        frequencySpread = 3.0
        amplitudeSpread = 3.4
        perlin2a = PerlinNoise2(0, 0, 256, seed=self.id * 2)
        perlin2a.setScale(smoothness)
        self.perlin2.addLevel(perlin2a)
        perlin2b = PerlinNoise2(0, 0, 256, seed=self.id * 3 + 3)
        perlin2b.setScale(smoothness / frequencySpread)
        self.perlin2.addLevel(perlin2b, 1 / amplitudeSpread)
        perlin2c = PerlinNoise2(0, 0, 256, seed=self.id * 4 + 4)
        perlin2c.setScale(smoothness / (frequencySpread * frequencySpread))
        self.perlin2.addLevel(perlin2c, 1 / (amplitudeSpread * amplitudeSpread))
        perlin2d = PerlinNoise2(0, 0, 256, seed=self.id * 5 + 5)
        perlin2d.setScale(smoothness / (math.pow(frequencySpread, 3)))
        self.perlin2.addLevel(perlin2d, 1 / (math.pow(amplitudeSpread, 3)))
        perlin2e = PerlinNoise2(0, 0, 256, seed=self.id * 6 + 6)
        perlin2e.setScale(smoothness / (math.pow(frequencySpread, 4)))
        self.perlin2.addLevel(perlin2e, 1 / (math.pow(amplitudeSpread, 4)))

    def hasTile(self, xStart, yStart, tileSize):
        """
        If one is using a cashed tile source instead of a live bakery, this would be sometimes be false
        """
        return True

    def getTile(self, xStart, yStart, scale):
        """
        returns a tile for the specified positions and size
        """
        sizeY = tileMapSize
        sizeX = tileMapSize
        getHeight = self.getHeight
        
        noiseTex=Texture("NoiseTex")
        noiseTex.setup2dTexture(sizeX, sizeY, Texture.TUnsignedByte, Texture.FRgb)
        p=noiseTex.modifyRamImage()
        step=noiseTex.getNumComponents()*noiseTex.getComponentWidth()
        scalar=.4
        for y in range(sizeY):
            yPos=scalar*(1.0*y*scale/(sizeY-1)+yStart)
            for x in range(sizeX):
                height = getHeight(scalar*(1.0*x*scale/(sizeX-1) + xStart), yPos)
                r=min(255,max(0,height*256))
                g=r*256
                b=g*256
                index = (sizeX * y + x)*step
                p.setElement(index, b%256)#Blue
                p.setElement(index+1, g%256)#Green
                p.setElement(index+2, r)#Red
        
        return Tile({"height":Map("height", noiseTex)},[], xStart, yStart, scale)

    def asyncGetTile(self, xStart, yStart, scale, callback, callbackParams=[]):
        """
        like getTile, but calls callback(tile,*callbackParams) when done
        """
        callback(self.getTile(xStart, yStart, scale), *callbackParams)
    
    def getHeight(self, x, y):
        """Returns the height at the specified terrain coordinates.

        The values returned should be between 0 and 1 and use the full range.
        Heights should be the smoothest and flatest at flatHeight.

        """

        # all of these should be in the range of 0 to 1
        p1 = (self.perlin1(x, y) + 1) / 2 # low frequency
        p2 = (self.perlin2(x, y) + 1) / 2 # high frequency
        fh = self.flatHeight

        # p1 varies what kind of terrain is in the area, p1 alone would be smooth
        # p2 introduces the visible noise and roughness
        # when p1 is high the altitude will be high overall
        # when p1 is close to fh most of the visible noise will be muted
        return (p1 - fh + (p1 - fh) * (p2 - fh)) / 2 + fh
        # if p1 = fh, the whole equation simplifies to...
        # 1. (fh - fh + (fh - fh) * (p2 - fh)) / 2 + fh
        # 2. ( 0 + 0 * (p2 - fh)) / 2 + fh
        # 3. (0 + 0 ) / 2 + fh
        # 4. fh
        # The important part to understanding the equation is at step 2.
        # The closer p1 is to fh, the smaller the mutiplier for p2 becomes.
        # As p2 diminishes, so does the roughness.
Пример #5
0
class MazeAI(DistributedObjectAI):

    notify = directNotify.newCategory("MazeAI")

    TurnChance = 75
    ForkChance = 20
    StopChance = 10

    def __init__(self, air, gameId):
        DistributedObjectAI.__init__(self, air)

        self.gameId = gameId
        self.xsize = 0
        self.ysize = 0
        self.numWalls = 0
        self.map = None
        self.root = None

        self.prevPlayers = {}

    def getSize(self):
        return self.xsize, self.ysize

    def getNumWalls(self):
        return self.numWalls

    def deleteMaze(self):
        if self.map:
            for row in self.map:
                for cell in row:
                    self.air.zoneAllocator.free(cell.zoneId)
                    cell.requestDelete()

    def generateMaze(self, xsize, ysize, 
                     prevMaze = None, seed = None):
        if seed is None:
            seed = int(time.time())
        self.random = RandomNumGen(seed)

        # Delete the old maze, if any
        self.deleteMaze()

        self.xsize = xsize
        self.ysize = ysize
        self.map = []
        for sy in range(self.ysize):
            row = []
            for sx in range(self.xsize):
                zoneId = self.air.zoneAllocator.allocate()
                cell = CellAI(self.air)
                cell.setGeometry(Globals.AllDirs, sx, sy)
                cell.generateWithRequired(zoneId)
                row.append(cell)
            self.map.append(row)

        # Start by choosing a random square and a random direction.
        self.numSquares = self.xsize * self.ysize - 1
        self.paths = []
        nextStep = self.__findEmptySquare()
        self.paths.append(nextStep)
        while self.numSquares > 0:
            self.__generateMazeSteps()

        # Count up the number of walls.
        walls = []
        for row in self.map:
            for cell in row:
                walls += cell.getWalls()
        self.numWalls = len(walls)
        
        random.shuffle(walls)

        if prevMaze:
            # Put our previous art paintings up on the walls,
            # including any poster data.
            for i in range(len(prevMaze.artPaintings)):
                dir, name, color, posterData, imgData = prevMaze.artPaintings[i]
                sx, sy, wallDir = walls[i]
                cell = self.map[sy][sx]
                while posterData[0] and cell.posterDir:
                    # We've already placed a poster in this cell.  Go
                    # on to the next one.
                    del walls[i]
                    sx, sy, wallDir = walls[i]
                    cell = self.map[sy][sx]
                
                if dir & Globals.AllDirs != 0:
                    # It's not a floor or ceiling, so use the wall we
                    # picked, instead of the wall it was on in the
                    # previous maze.
                    dir = wallDir
                else:
                    # It is a floor or ceiling, so keep it there.
                    pass

                cell.preloadPrevPainting(dir, posterData, imgData)

                # Record the player's color so we can identify him
                # when he comes back in and give him the points for
                # this paint immediately.
                self.prevPlayers.setdefault(color, []).append((cell, dir))

##         # Temp hack for debugging.
##         painted = PNMImage()
##         painted.read('paint.rgb')
##         for sx, sy, dir in walls:
##             cell = self.map[sy][sx]
##             for dir in Globals.AllDirsList:
##                 if cell.bits & dir:
##                     cell.painted[dir] = PNMImage(painted)

        self.drawMap()
        self.determineVisibility()

    def getPosterData(self):
        return self.posterData
        
    def determineVisibility(self):
        """ Determines which cells can be seen from which other
        cells, based on straight-line visibility. """

        self.visCollWalls = self.makeCollisionWalls()
        self.visTrav = CollisionTraverser('visTrav')
        self.visQueue = CollisionHandlerQueue()
        self.visNode = CollisionNode('visNode')
        self.visNP = self.visCollWalls.attachNewNode(self.visNode)
        self.visNP.setCollideMask(0)
        self.visTrav.addCollider(self.visNP, self.visQueue)

        for sy in range(self.ysize):
            self.notify.info("%s determineVisibility: %d%%" % (
                self.gameId, 100.0 * sy / self.ysize))
            row = self.map[sy]
            for cell in row:
                self.__determineVisibilityForCell(cell, 1, Globals.AllDirs)

        # Now build the expanded visibility, including neighbor
        # visibility, for smooth transitions.
        for row in self.map:
            for cell in row:
                self.__expandVisibilityForCell(cell)
        self.notify.info("%s done" % (self.gameId))

    def __expandVisibilityForCell(self, cell):
        """ Build cell.expandedZoneIds, representing the union of
        visibleZoneIds for the cell and all of its eight neigbors. """

        cell.expandedZoneIds = set()
        for sy in range(max(cell.sy - 1, 0), min(cell.sy + 2, self.ysize)):
            for sx in range(max(cell.sx - 1, 0), min(cell.sx + 2, self.xsize)):
                c2 = self.map[sy][sx]
                cell.expandedZoneIds |= c2.visibleZoneIds
        assert cell.expandedZoneIds | cell.visibleZoneIds == cell.expandedZoneIds

    def __determineVisibilityForCell(self, cell, radius, dirBits):
        """ Determines the set of visible cells that can be seen from
        this cell, for the given radius. """
        cell.visibleZoneIds.add(cell.zoneId)
        
        numVisible = 0
        nextDirs = 0
        for i in range(-radius, radius):
            if dirBits & Globals.North:
                # North wall
                sx = cell.sx - i
                sy = cell.sy + radius
                if self.__checkCellVisibility(cell, sx, sy):
                    nextDirs |= Globals.North

            if dirBits & Globals.East:
                # East wall
                sx = cell.sx + radius
                sy = cell.sy + i
                if self.__checkCellVisibility(cell, sx, sy):
                    nextDirs |= Globals.East

            if dirBits & Globals.South:
                # South wall
                sx = cell.sx + i
                sy = cell.sy - radius
                if self.__checkCellVisibility(cell, sx, sy):
                    nextDirs |= Globals.South

            if dirBits & Globals.West:
                # West wall
                sx = cell.sx - radius
                sy = cell.sy - i
                if self.__checkCellVisibility(cell, sx, sy):
                    nextDirs |= Globals.West

        if nextDirs:
            # Recursively check the higher radius
            self.__determineVisibilityForCell(cell, radius + 1, nextDirs)

    def __checkCellVisibility(self, source, sx, sy):
        """ Looks up the target cell, and returns true if the target
        can be seen from source, false otherwise. """
        if sx < 0 or sx >= self.xsize or sy < 0 or sy >= self.ysize:
            # Not even a real cell.
            return False
        target = self.map[sy][sx]
        if not self.__doVisibilityTest(source, target):
            return False

        # target is visible.
        source.visibleZoneIds.add(target.zoneId)
        return True
            
    def __doVisibilityTest(self, source, target):
        """ Returns true if target can be seen from source, false
        otherwise. """

        # We naively check 16 lines: each of the four corners of
        # target from the four corners of source.  If any of them has
        # a line of sight--that is, any one of them does *not*
        # generate a collision event--we're in.

        # We use a point just inside each corner, so we don't have to
        # deal with ambiguities at the precise corner.

        self.visQueue.clearEntries()
        self.visNode.clearSolids()

        segs = {}
        for ax, ay in [(source.sx + 0.1, source.sy + 0.1),
                       (source.sx + 0.9, source.sy + 0.1),
                       (source.sx + 0.9, source.sy + 0.9),
                       (source.sx + 0.1, source.sy + 0.9)]:
            for bx, by in [(target.sx + 0.1, target.sy + 0.1),
                           (target.sx + 0.9, target.sy + 0.1),
                           (target.sx + 0.9, target.sy + 0.9),
                           (target.sx + 0.1, target.sy + 0.9)]:
                seg = CollisionSegment(ax, ay, 0.5, bx, by, 0.5)
                self.visNode.addSolid(seg)
                segs[seg] = True

        self.visTrav.traverse(self.visCollWalls)

        # Now see which segments detected a collision.
        for entry in self.visQueue.getEntries():
            seg = entry.getFrom()
            if seg in segs:
                del segs[seg]

        # If any are left, we've got a line of sight.
        if segs:
            return True

        # If none are left, we're blocked.
        return False

    def __generateMazeSteps(self):
        """ Moves all of the active paths forward one step. """

        if not self.paths:
            # Ran out of open paths.  Go find a likely square to pick
            # up from again.
            nextStep = self.__findEmptySquare()
            self.paths.append(nextStep)

        paths = self.paths
        self.paths = []
        for sx, sy, dir in paths:
            self.__generateMazeOneStep(sx, sy, dir)

    def __generateMazeOneStep(self, sx, sy, dir):
        """ Moves this path forward one step. """

        if self.random.randint(0, 99) < self.StopChance:
            # Abandon this path.
            return

        numNextSteps = 1
        while numNextSteps < 4 and self.random.randint(0, 99) < self.ForkChance:
            # Consider a fork.
            numNextSteps += 1

        nextDirs = Globals.AllDirsList[:]
        nextDirs.remove(dir)
        self.random.shuffle(nextDirs)

        if self.random.randint(0, 99) < self.TurnChance:
            # Consider a turn.  Put the current direction at the end.
            nextDirs.append(dir)
        else:
            # Don't consider a turn.  Put the current direction at the
            # front.
            nextDirs = [dir] + nextDirs

        for dir in nextDirs:
            nextStep = self.__makeStep(sx, sy, dir)
            if nextStep:
                # That step was valid, save the current path for next
                # pass.
                self.numSquares -= 1
                self.paths.append(nextStep)
                numNextSteps -= 1
                if numNextSteps == 0:
                    return
                
            # Try the next direction.

        # Couldn't go anywhere else.  We're done.
        return
                
    def __makeStep(self, sx, sy, dir):
        """ Attempts to move this path forward in the indicated
        direction.  Returns the new sx, sy, dir if successful, or None
        on failure. """
        if dir == Globals.South:
            if sy == 0:
                return None
            if self.map[sy][sx].bits & Globals.South == 0:
                return None
            if self.map[sy - 1][sx].bits != Globals.AllDirs:
                return None
            self.map[sy][sx].bits &= ~Globals.South
            self.map[sy - 1][sx].bits &= ~Globals.North
            return (sx, sy - 1, dir)

        elif dir == Globals.West:
            if sx == 0:
                return None
            if self.map[sy][sx].bits & Globals.West == 0:
                return None
            if self.map[sy][sx - 1].bits != Globals.AllDirs:
                return None
            self.map[sy][sx].bits &= ~Globals.West
            self.map[sy][sx - 1].bits &= ~Globals.East
            return (sx - 1, sy, dir)

        elif dir == Globals.North:
            if sy == self.ysize - 1:
                return None
            if self.map[sy][sx].bits & Globals.North == 0:
                return None
            if self.map[sy + 1][sx].bits != Globals.AllDirs:
                return None
            self.map[sy][sx].bits &= ~Globals.North
            self.map[sy + 1][sx].bits &= ~Globals.South
            return (sx, sy + 1, dir)

        elif dir == Globals.East:
            if sx == self.xsize - 1:
                return None
            if self.map[sy][sx].bits & Globals.East == 0:
                return None
            if self.map[sy][sx + 1].bits != Globals.AllDirs:
                return None
            self.map[sy][sx].bits &= ~Globals.East
            self.map[sy][sx + 1].bits &= ~Globals.West
            return (sx + 1, sy, dir)

        assert False

    def __findEmptySquare(self):
        """ Finds an empty square next door to a non-empty square, and
        starts a new path. """

        if self.numSquares == self.xsize * self.ysize - 1:
            # All squares are still empty.
            sx = self.random.randint(0, self.xsize - 1)
            sy = self.random.randint(0, self.ysize - 1)
            dir = self.random.choice(Globals.AllDirsList)
            return (sx, sy, dir)

        # First, get the map squares in random order.
        ylist = list(range(self.ysize))
        xlist = list(range(self.xsize))
        self.random.shuffle(ylist)
        self.random.shuffle(xlist)

        for sy in ylist:
            for sx in xlist:
                if self.map[sy][sx].bits != Globals.AllDirs:
                    continue
                if sy > 0 and self.map[sy - 1][sx].bits != Globals.AllDirs:
                    return (sx, sy - 1, Globals.North)
                elif sy < self.ysize - 1 and self.map[sy + 1][sx].bits != Globals.AllDirs:
                    return (sx, sy + 1, Globals.South)
                elif sx > 0 and self.map[sy][sx - 1].bits != Globals.AllDirs:
                    return (sx - 1, sy, Globals.East)
                elif sx < self.xsize - 1 and self.map[sy][sx + 1].bits != Globals.AllDirs:
                    return (sx + 1, sy, Globals.West)

        self.drawMap()
        assert False

    def makeCollisionWalls(self):
        """ Creates and returns a scene graph that contains a
        collision wall for each West and South wall in the maze, for
        the purposes of determining visibility. """
        root = self.__makeCollisionQuadtree(0, 0, self.xsize, self.ysize)
        root.flattenLight()
        return root

    def __makeCollisionQuadtree(self, ax, ay, bx, by):
        # We recursively create a quadtree hierarchy, in an attempt to
        # ensure the collisions remain scalable with very large maps.
        xsize = bx - ax
        ysize = by - ay
        if xsize > 1 or ysize > 1:
            # Recurse.
            root = NodePath('%s_%s__%s_%s' % (ax, ay, bx, by))
            xsize = max((xsize + 1) / 2, 1)
            ysize = max((ysize + 1) / 2, 1)
            py = ay
            while py < by:
                px = ax
                while px < bx:
                    np = self.__makeCollisionQuadtree(px, py, min(px + xsize, bx), min(py + ysize, by))
                    np.reparentTo(root)
                    px += xsize
                py += ysize
            return root

        # One cell.  Handle it.
        node = CollisionNode('%s_%s' % (ax, ay))
        cell = self.map[ay][ax]
        if cell.bits & Globals.West:
            node.addSolid(CollisionPolygon(Point3(0, 0, 0), Point3(0, 1, 0),
                                           Point3(0, 1, 1), Point3(0, 0, 1)))
        if cell.bits & Globals.South:
            node.addSolid(CollisionPolygon(Point3(1, 0, 0), Point3(0, 0, 0),
                                           Point3(0, 0, 1), Point3(1, 0, 1)))
        np = NodePath(node)
        np.setPos(cell.sx, cell.sy, 0)
        return np

    def drawMap(self):
        line = '+'
        for xi in range(self.xsize):
            line += '---+'
        print line

        for yi in range(self.ysize - 1, -1, -1):
            row = self.map[yi]
            line = '|'
            for cell in row:
                if cell.bits & Globals.East:
                    line += '   |' 
                else:
                    line += '    ' 
            print line
            line = '+'
            for cell in row:
                if cell.bits & Globals.South:
                    line += '---+'
                else:
                    line += '   +'
            print line
        
    def makeGeom(self):
        """ Generates a set of renderable geometry. """

        if self.root:
            self.root.removeNode()

        self.root = NodePath('root')
        for sy in range(self.ysize):
            geomRow = []
            for sx in range(self.xsize):
                nodeGeom = self.map[sy][sx].makeGeomCell(sx, sy)
                nodeGeom.reparentTo(self.root)
                geomRow.append(nodeGeom)
        return self.root
        

    def chooseArtPaintings(self, game):
        """ Give score bonuses to the players with the 3 "best" art
        paintings. """

        # Now score each wall painting.
        anyPosters = False
        artPaintings = []
        for row in self.map:
            for cell in row:
                for dir, score in cell.artScore.items():
                    # Normally, the player with the most paint gets
                    # the bonus for the art painting.
                    player = cell.wonPlayers.get(dir, None)

                    # But if there's a poster on this wall, the player
                    # who owns the poster gets that bonus instead.
                    if dir == cell.posterDir:
                        anyPosters = True
                        pp = self.cr.doId2do.get(cell.posterPlayerId)
                        if pp:
                            player = pp
                        
                    p = cell.painted.get(dir, None)
                    if p and player and not player.isDeleted():
                        artPaintings.append((-score, cell, dir, player, p))

        # Sort the list so that the highest scores appear at the top.
        artPaintings.sort()

        self.artPaintings = []

        # We must have at least one poster in the list.
        needPoster = True

        # Unless there are no posters with paint on them, in which
        # case never mind.
        if not anyPosters:
            needPoster = False
        
        for i in range(len(artPaintings)):
            score, cell, dir, player, p = artPaintings[i]

            if dir == cell.posterDir:
                needPoster = False

            if len(self.artPaintings) == len(Globals.ArtPaintingBonus) - 1 and needPoster:
                # If we've reached the end of the list and we haven't
                # yet met our poster need, don't consider any
                # art paintings that aren't made on posters.
                continue

            bonus = Globals.ArtPaintingBonus[len(self.artPaintings)]
            player.artBonus += bonus
            player.score += bonus
            
            data = StringStream()
            p.write(data, Globals.ImageFormat)
            imgData = data.getData()

            posterData = ('', 0)
            if dir == cell.posterDir:
                posterData = cell.posterData
            
            self.artPaintings.append((dir, player.name, player.color, posterData, imgData))
            if len(self.artPaintings) >= len(Globals.ArtPaintingBonus):
                break

    def chooseRandomPosterCell(self, player):
        """ Selects a cell without a poster already applied, and
        applies this player's poster to it. """

        cells = []
        for row in self.map:
            cells += row[:]

        while True:
            if not cells:
                print "No cell available for user's poster."
                return
            
            cell = random.choice(cells)
            cells.remove(cell)
            if cell.posterDir:
                # Already taken.
                continue
            if cell.bits & Globals.AllDirs == 0:
                # No walls to hold a poster.
                continue
            break

        dirs = Globals.AllDirsList[:]
        while True:
            dir = random.choice(dirs)
            if cell.bits & dir:
                break
            dirs.remove(dir)

        cell.posterDir = dir
        cell.posterPlayerId = player.doId
        player.posterCell = cell

        print "posterData = %s, %s" % (len(player.posterData[0]), player.posterData[1])
        cell.updatePosterData(player.posterData)