def scheduleDrops(self, genId = None):
        if genId is None:
            genId = self.getCurGeneration()
        gen = self._id2gen[genId]
        if gen.hasBeenScheduled:
            return
        fruitIndex = int((gen.startTime + 0.5 * self.DropPeriod) / PartyGlobals.CatchActivityDuration)
        fruitNames = ['apple',
         'orange',
         'pear',
         'coconut',
         'watermelon',
         'pineapple']
        fruitName = fruitNames[fruitIndex % len(fruitNames)]
        rng = RandomNumGen(genId + self._generationSeedBase)
        gen.droppedObjNames = [fruitName] * self.numFruits + ['anvil'] * self.numAnvils
        rng.shuffle(gen.droppedObjNames)
        dropPlacer = PartyRegionDropPlacer(self, gen.numPlayers, genId, gen.droppedObjNames, startTime=gen.startTime)
        gen.numItemsDropped = 0
        tIndex = gen.startTime % PartyGlobals.CatchActivityDuration
        tPercent = float(tIndex) / PartyGlobals.CatchActivityDuration
        gen.numItemsDropped += dropPlacer.skipPercent(tPercent)
        while not dropPlacer.doneDropping(continuous=True):
            nextDrop = dropPlacer.getNextDrop()
            gen.dropSchedule.append(nextDrop)

        gen.hasBeenScheduled = True
        return
Esempio n. 2
0
    def scheduleDrops(self, genId = None):
        if genId is None:
            genId = self.getCurGeneration()
        gen = self._id2gen[genId]
        if gen.hasBeenScheduled:
            return
        fruitIndex = int((gen.startTime + 0.5 * self.DropPeriod) / PartyGlobals.CatchActivityDuration)
        fruitNames = ['apple',
         'orange',
         'pear',
         'coconut',
         'watermelon',
         'pineapple']
        fruitName = fruitNames[fruitIndex % len(fruitNames)]
        rng = RandomNumGen(genId + self._generationSeedBase)
        gen.droppedObjNames = [fruitName] * self.numFruits + ['anvil'] * self.numAnvils
        rng.shuffle(gen.droppedObjNames)
        dropPlacer = PartyRegionDropPlacer(self, gen.numPlayers, genId, gen.droppedObjNames, startTime=gen.startTime)
        gen.numItemsDropped = 0
        tIndex = gen.startTime % PartyGlobals.CatchActivityDuration
        tPercent = float(tIndex) / PartyGlobals.CatchActivityDuration
        gen.numItemsDropped += dropPlacer.skipPercent(tPercent)
        while not dropPlacer.doneDropping(continuous=True):
            nextDrop = dropPlacer.getNextDrop()
            gen.dropSchedule.append(nextDrop)

        gen.hasBeenScheduled = True
        return
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
Esempio n. 4
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
Esempio n. 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)