def startSuitWalkTask(self):
        ival = Parallel(name='catchGameMetaSuitWalk')
        rng = RandomNumGen(self.randomNumGen)
        delay = 0.0
        while delay < CatchGameGlobals.GameDuration:
            delay += lerp(self.SuitPeriodRange[0], self.SuitPeriodRange[0], rng.random())
            walkIval = Sequence(name='catchGameSuitWalk')
            walkIval.append(Wait(delay))

            def pickY(self = self, rng = rng):
                return lerp(-self.StageHalfHeight, self.StageHalfHeight, rng.random())

            m = [2.5,
             2.5,
             2.3,
             2.1][self.getNumPlayers() - 1]
            startPos = Point3(-(self.StageHalfWidth * m), pickY(), 0)
            stopPos = Point3(self.StageHalfWidth * m, pickY(), 0)
            if rng.choice([0, 1]):
                startPos, stopPos = stopPos, startPos
            walkIval.append(self.getSuitWalkIval(startPos, stopPos, rng))
            ival.append(walkIval)

        ival.start()
        self.suitWalkIval = ival
Esempio n. 2
0
    def startSuitWalkTask(self):
        ival = Parallel(name='catchGameMetaSuitWalk')
        rng = RandomNumGen(self.randomNumGen)
        delay = 0.0
        while delay < CatchGameGlobals.GameDuration:
            delay += lerp(self.SuitPeriodRange[0], self.SuitPeriodRange[0],
                          rng.random())
            walkIval = Sequence(name='catchGameSuitWalk')
            walkIval.append(Wait(delay))

            def pickY(self=self, rng=rng):
                return lerp(-(self.StageHalfHeight), self.StageHalfHeight,
                            rng.random())

            m = [2.5, 2.5, 2.2999999999999998,
                 2.1000000000000001][self.getNumPlayers() - 1]
            startPos = Point3(-(self.StageHalfWidth * m), pickY(), 0)
            stopPos = Point3(self.StageHalfWidth * m, pickY(), 0)
            if rng.choice([0, 1]):
                startPos = stopPos
                stopPos = startPos

            walkIval.append(self.getSuitWalkIval(startPos, stopPos, rng))
            ival.append(walkIval)
        ival.start()
        self.suitWalkIval = ival
Esempio n. 3
0
class MazeSuit(DirectObject):
    COLL_SPHERE_NAME = 'MazeSuitSphere'
    COLLISION_EVENT_NAME = 'MazeSuitCollision'
    MOVE_IVAL_NAME = 'moveMazeSuit'
    DIR_UP = 0
    DIR_DOWN = 1
    DIR_LEFT = 2
    DIR_RIGHT = 3
    oppositeDirections = [DIR_DOWN, DIR_UP, DIR_RIGHT, DIR_LEFT]
    directionHs = [0, 180, 90, 270]
    DEFAULT_SPEED = 4.0
    SUIT_Z = 0.1

    def __init__(
            self,
            serialNum,
            maze,
            randomNumGen,
            cellWalkPeriod,
            difficulty,
            suitDnaName='f',
            startTile=None,
            ticFreq=MazeGameGlobals.SUIT_TIC_FREQ,
            walkSameDirectionProb=MazeGameGlobals.WALK_SAME_DIRECTION_PROB,
            walkTurnAroundProb=MazeGameGlobals.WALK_TURN_AROUND_PROB,
            uniqueRandomNumGen=True,
            walkAnimName=None):
        self.serialNum = serialNum
        self.maze = maze
        if uniqueRandomNumGen:
            self.rng = RandomNumGen(randomNumGen)
        else:
            self.rng = randomNumGen
        self.difficulty = difficulty
        self._walkSameDirectionProb = walkSameDirectionProb
        self._walkTurnAroundProb = walkTurnAroundProb
        self._walkAnimName = walkAnimName or 'walk'
        self.suit = Suit.Suit()
        d = SuitDNA.SuitDNA()
        d.newSuit(suitDnaName)
        self.suit.setDNA(d)
        self.suit.nametag3d.stash()
        self.suit.nametag.destroy()
        if startTile is None:
            defaultStartPos = MazeGameGlobals.SUIT_START_POSITIONS[
                self.serialNum]
            self.startTile = (defaultStartPos[0] * self.maze.width,
                              defaultStartPos[1] * self.maze.height)
        else:
            self.startTile = startTile
        self.ticFreq = ticFreq
        self.ticPeriod = int(cellWalkPeriod)
        self.cellWalkDuration = float(self.ticPeriod) / float(self.ticFreq)
        self.turnDuration = 0.6 * self.cellWalkDuration
        return

    def destroy(self):
        self.suit.delete()

    def uniqueName(self, str):
        return str + ` (self.serialNum) `

    def gameStart(self, gameStartTime):
        self.gameStartTime = gameStartTime
        self.initCollisions()
        self.startWalkAnim()
        self.occupiedTiles = [(self.nextTX, self.nextTY)]
        n = 20
        self.nextThinkTic = self.serialNum * self.ticFreq / n
        self.fromPos = Point3(0, 0, 0)
        self.toPos = Point3(0, 0, 0)
        self.fromHpr = Point3(0, 0, 0)
        self.toHpr = Point3(0, 0, 0)
        self.moveIval = WaitInterval(1.0)

    def gameEnd(self):
        self.moveIval.pause()
        del self.moveIval
        self.shutdownCollisions()
        self.suit.loop('neutral')

    def initCollisions(self):
        self.collSphere = CollisionSphere(0, 0, 0, 2.0)
        self.collSphere.setTangible(0)
        self.collNode = CollisionNode(self.uniqueName(self.COLL_SPHERE_NAME))
        self.collNode.setIntoCollideMask(ToontownGlobals.WallBitmask)
        self.collNode.addSolid(self.collSphere)
        self.collNodePath = self.suit.attachNewNode(self.collNode)
        self.collNodePath.hide()
        self.accept(self.uniqueName('enter' + self.COLL_SPHERE_NAME),
                    self.handleEnterSphere)

    def shutdownCollisions(self):
        self.ignore(self.uniqueName('enter' + self.COLL_SPHERE_NAME))
        del self.collSphere
        self.collNodePath.removeNode()
        del self.collNodePath
        del self.collNode

    def handleEnterSphere(self, collEntry):
        messenger.send(self.COLLISION_EVENT_NAME, [self.serialNum])

    def __getWorldPos(self, sTX, sTY):
        wx, wy = self.maze.tile2world(sTX, sTY)
        return Point3(wx, wy, self.SUIT_Z)

    def onstage(self):
        sTX = int(self.startTile[0])
        sTY = int(self.startTile[1])
        c = 0
        lim = 0
        toggle = 0
        direction = 0
        while not self.maze.isWalkable(sTX, sTY):
            if 0 == direction:
                sTX -= 1
            elif 1 == direction:
                sTY -= 1
            elif 2 == direction:
                sTX += 1
            elif 3 == direction:
                sTY += 1
            c += 1
            if c > lim:
                c = 0
                direction = (direction + 1) % 4
                toggle += 1
                if not toggle & 1:
                    lim += 1

        self.TX = sTX
        self.TY = sTY
        self.direction = self.DIR_DOWN
        self.lastDirection = self.direction
        self.nextTX = self.TX
        self.nextTY = self.TY
        self.suit.setPos(self.__getWorldPos(self.TX, self.TY))
        self.suit.setHpr(self.directionHs[self.direction], 0, 0)
        self.suit.reparentTo(render)
        self.suit.pose(self._walkAnimName, 0)
        self.suit.loop('neutral')

    def offstage(self):
        self.suit.reparentTo(hidden)

    def startWalkAnim(self):
        self.suit.loop(self._walkAnimName)
        speed = float(self.maze.cellWidth) / self.cellWalkDuration
        self.suit.setPlayRate(speed / self.DEFAULT_SPEED, self._walkAnimName)

    def __applyDirection(self, dir, TX, TY):
        if self.DIR_UP == dir:
            TY += 1
        elif self.DIR_DOWN == dir:
            TY -= 1
        elif self.DIR_LEFT == dir:
            TX -= 1
        elif self.DIR_RIGHT == dir:
            TX += 1
        return (TX, TY)

    def __chooseNewWalkDirection(self, unwalkables):
        if not self.rng.randrange(self._walkSameDirectionProb):
            newTX, newTY = self.__applyDirection(self.direction, self.TX,
                                                 self.TY)
            if self.maze.isWalkable(newTX, newTY, unwalkables):
                return self.direction
        if self.difficulty >= 0.5:
            if not self.rng.randrange(self._walkTurnAroundProb):
                oppositeDir = self.oppositeDirections[self.direction]
                newTX, newTY = self.__applyDirection(oppositeDir, self.TX,
                                                     self.TY)
                if self.maze.isWalkable(newTX, newTY, unwalkables):
                    return oppositeDir
        candidateDirs = [
            self.DIR_UP, self.DIR_DOWN, self.DIR_LEFT, self.DIR_RIGHT
        ]
        candidateDirs.remove(self.oppositeDirections[self.direction])
        while len(candidateDirs):
            dir = self.rng.choice(candidateDirs)
            newTX, newTY = self.__applyDirection(dir, self.TX, self.TY)
            if self.maze.isWalkable(newTX, newTY, unwalkables):
                return dir
            candidateDirs.remove(dir)

        return self.oppositeDirections[self.direction]

    def getThinkTimestampTics(self, curTic):
        if curTic < self.nextThinkTic:
            return []
        else:
            r = range(self.nextThinkTic, curTic + 1, self.ticPeriod)
            self.lastTicBeforeRender = r[-1]
            return r

    def prepareToThink(self):
        self.occupiedTiles = [(self.nextTX, self.nextTY)]

    def think(self, curTic, curT, unwalkables):
        self.TX = self.nextTX
        self.TY = self.nextTY
        self.lastDirection = self.direction
        self.direction = self.__chooseNewWalkDirection(unwalkables)
        self.nextTX, self.nextTY = self.__applyDirection(
            self.direction, self.TX, self.TY)
        self.occupiedTiles = [(self.TX, self.TY), (self.nextTX, self.nextTY)]
        if curTic == self.lastTicBeforeRender:
            fromCoords = self.maze.tile2world(self.TX, self.TY)
            toCoords = self.maze.tile2world(self.nextTX, self.nextTY)
            self.fromPos.set(fromCoords[0], fromCoords[1], self.SUIT_Z)
            self.toPos.set(toCoords[0], toCoords[1], self.SUIT_Z)
            self.moveIval = LerpPosInterval(self.suit,
                                            self.cellWalkDuration,
                                            self.toPos,
                                            startPos=self.fromPos,
                                            name=self.uniqueName(
                                                self.MOVE_IVAL_NAME))
            if self.direction != self.lastDirection:
                self.fromH = self.directionHs[self.lastDirection]
                toH = self.directionHs[self.direction]
                if self.fromH == 270 and toH == 0:
                    self.fromH = -90
                elif self.fromH == 0 and toH == 270:
                    self.fromH = 360
                self.fromHpr.set(self.fromH, 0, 0)
                self.toHpr.set(toH, 0, 0)
                turnIval = LerpHprInterval(
                    self.suit,
                    self.turnDuration,
                    self.toHpr,
                    startHpr=self.fromHpr,
                    name=self.uniqueName('turnMazeSuit'))
                self.moveIval = Parallel(self.moveIval,
                                         turnIval,
                                         name=self.uniqueName(
                                             self.MOVE_IVAL_NAME))
            else:
                self.suit.setH(self.directionHs[self.direction])
            moveStartT = float(self.nextThinkTic) / float(self.ticFreq)
            self.moveIval.start(curT - (moveStartT + self.gameStartTime))
        self.nextThinkTic += self.ticPeriod

    @staticmethod
    def thinkSuits(suitList, startTime, ticFreq=MazeGameGlobals.SUIT_TIC_FREQ):
        curT = globalClock.getFrameTime() - startTime
        curTic = int(curT * float(ticFreq))
        suitUpdates = []
        for i in xrange(len(suitList)):
            updateTics = suitList[i].getThinkTimestampTics(curTic)
            suitUpdates.extend(zip(updateTics, [i] * len(updateTics)))

        suitUpdates.sort(lambda a, b: a[0] - b[0])
        if len(suitUpdates) > 0:
            curTic = 0
            for i in xrange(len(suitUpdates)):
                update = suitUpdates[i]
                tic = update[0]
                suitIndex = update[1]
                suit = suitList[suitIndex]
                if tic > curTic:
                    curTic = tic
                    j = i + 1
                    while j < len(suitUpdates):
                        if suitUpdates[j][0] > tic:
                            break
                        suitList[suitUpdates[j][1]].prepareToThink()
                        j += 1

                unwalkables = []
                for si in xrange(suitIndex):
                    unwalkables.extend(suitList[si].occupiedTiles)

                for si in xrange(suitIndex + 1, len(suitList)):
                    unwalkables.extend(suitList[si].occupiedTiles)

                suit.think(curTic, curT, unwalkables)
Esempio n. 4
0
class MazeSuit(DirectObject):
    COLL_SPHERE_NAME = 'MazeSuitSphere'
    COLLISION_EVENT_NAME = 'MazeSuitCollision'
    MOVE_IVAL_NAME = 'moveMazeSuit'
    DIR_UP = 0
    DIR_DOWN = 1
    DIR_LEFT = 2
    DIR_RIGHT = 3
    oppositeDirections = [
        DIR_DOWN,
        DIR_UP,
        DIR_RIGHT,
        DIR_LEFT]
    directionHs = [
        0,
        180,
        90,
        270]
    DEFAULT_SPEED = 4.0
    SUIT_Z = 0.10000000000000001
    
    def __init__(self, serialNum, maze, randomNumGen, cellWalkPeriod, difficulty, suitDnaName = 'f', startTile = None, ticFreq = MazeGameGlobals.SUIT_TIC_FREQ, walkSameDirectionProb = MazeGameGlobals.WALK_SAME_DIRECTION_PROB, walkTurnAroundProb = MazeGameGlobals.WALK_TURN_AROUND_PROB, uniqueRandomNumGen = True, walkAnimName = None):
        self.serialNum = serialNum
        self.maze = maze
        if uniqueRandomNumGen:
            self.rng = RandomNumGen(randomNumGen)
        else:
            self.rng = randomNumGen
        self.difficulty = difficulty
        self._walkSameDirectionProb = walkSameDirectionProb
        self._walkTurnAroundProb = walkTurnAroundProb
        if not walkAnimName:
            pass
        self._walkAnimName = 'walk'
        self.suit = Suit.Suit()
        d = SuitDNA.SuitDNA()
        d.newSuit(suitDnaName)
        self.suit.setDNA(d)
        if startTile is None:
            defaultStartPos = MazeGameGlobals.SUIT_START_POSITIONS[self.serialNum]
            self.startTile = (defaultStartPos[0] * self.maze.width, defaultStartPos[1] * self.maze.height)
        else:
            self.startTile = startTile
        self.ticFreq = ticFreq
        self.ticPeriod = int(cellWalkPeriod)
        self.cellWalkDuration = float(self.ticPeriod) / float(self.ticFreq)
        self.turnDuration = 0.59999999999999998 * self.cellWalkDuration

    
    def destroy(self):
        self.suit.delete()

    
    def uniqueName(self, str):
        return str + `self.serialNum`

    
    def gameStart(self, gameStartTime):
        self.gameStartTime = gameStartTime
        self.initCollisions()
        self.startWalkAnim()
        self.occupiedTiles = [
            (self.nextTX, self.nextTY)]
        n = 20
        self.nextThinkTic = self.serialNum * self.ticFreq / n
        self.fromPos = Point3(0, 0, 0)
        self.toPos = Point3(0, 0, 0)
        self.fromHpr = Point3(0, 0, 0)
        self.toHpr = Point3(0, 0, 0)
        self.moveIval = WaitInterval(1.0)

    
    def gameEnd(self):
        self.moveIval.pause()
        del self.moveIval
        self.shutdownCollisions()
        self.suit.loop('neutral')

    
    def initCollisions(self):
        self.collSphere = CollisionSphere(0, 0, 0, 2.0)
        self.collSphere.setTangible(0)
        self.collNode = CollisionNode(self.uniqueName(self.COLL_SPHERE_NAME))
        self.collNode.setIntoCollideMask(ToontownGlobals.WallBitmask)
        self.collNode.addSolid(self.collSphere)
        self.collNodePath = self.suit.attachNewNode(self.collNode)
        self.collNodePath.hide()
        self.accept(self.uniqueName('enter' + self.COLL_SPHERE_NAME), self.handleEnterSphere)

    
    def shutdownCollisions(self):
        self.ignore(self.uniqueName('enter' + self.COLL_SPHERE_NAME))
        del self.collSphere
        self.collNodePath.removeNode()
        del self.collNodePath
        del self.collNode

    
    def handleEnterSphere(self, collEntry):
        messenger.send(self.COLLISION_EVENT_NAME, [
            self.serialNum])

    
    def _MazeSuit__getWorldPos(self, sTX, sTY):
        (wx, wy) = self.maze.tile2world(sTX, sTY)
        return Point3(wx, wy, self.SUIT_Z)

    
    def onstage(self):
        sTX = int(self.startTile[0])
        sTY = int(self.startTile[1])
        c = 0
        lim = 0
        toggle = 0
        direction = 0
        while not self.maze.isWalkable(sTX, sTY):
            if 0 == direction:
                sTX -= 1
            elif 1 == direction:
                sTY -= 1
            elif 2 == direction:
                sTX += 1
            elif 3 == direction:
                sTY += 1
            
            c += 1
            if c > lim:
                c = 0
                direction = (direction + 1) % 4
                toggle += 1
                if not toggle & 1:
                    lim += 1
                
        self.TX = sTX
        self.TY = sTY
        self.direction = self.DIR_DOWN
        self.lastDirection = self.direction
        self.nextTX = self.TX
        self.nextTY = self.TY
        self.suit.setPos(self._MazeSuit__getWorldPos(self.TX, self.TY))
        self.suit.setHpr(self.directionHs[self.direction], 0, 0)
        self.suit.reparentTo(render)
        self.suit.pose(self._walkAnimName, 0)
        self.suit.loop('neutral')

    
    def offstage(self):
        self.suit.reparentTo(hidden)

    
    def startWalkAnim(self):
        self.suit.loop(self._walkAnimName)
        speed = float(self.maze.cellWidth) / self.cellWalkDuration
        self.suit.setPlayRate(speed / self.DEFAULT_SPEED, self._walkAnimName)

    
    def _MazeSuit__applyDirection(self, dir, TX, TY):
        if self.DIR_UP == dir:
            TY += 1
        elif self.DIR_DOWN == dir:
            TY -= 1
        elif self.DIR_LEFT == dir:
            TX -= 1
        elif self.DIR_RIGHT == dir:
            TX += 1
        
        return (TX, TY)

    
    def _MazeSuit__chooseNewWalkDirection(self, unwalkables):
        if not self.rng.randrange(self._walkSameDirectionProb):
            (newTX, newTY) = self._MazeSuit__applyDirection(self.direction, self.TX, self.TY)
            if self.maze.isWalkable(newTX, newTY, unwalkables):
                return self.direction
            
        
        if self.difficulty >= 0.5:
            if not self.rng.randrange(self._walkTurnAroundProb):
                oppositeDir = self.oppositeDirections[self.direction]
                (newTX, newTY) = self._MazeSuit__applyDirection(oppositeDir, self.TX, self.TY)
                if self.maze.isWalkable(newTX, newTY, unwalkables):
                    return oppositeDir
                
            
        
        candidateDirs = [
            self.DIR_UP,
            self.DIR_DOWN,
            self.DIR_LEFT,
            self.DIR_RIGHT]
        candidateDirs.remove(self.oppositeDirections[self.direction])
        while len(candidateDirs):
            dir = self.rng.choice(candidateDirs)
            (newTX, newTY) = self._MazeSuit__applyDirection(dir, self.TX, self.TY)
            if self.maze.isWalkable(newTX, newTY, unwalkables):
                return dir
            
            candidateDirs.remove(dir)
        return self.oppositeDirections[self.direction]

    
    def getThinkTimestampTics(self, curTic):
        if curTic < self.nextThinkTic:
            return []
        else:
            r = range(self.nextThinkTic, curTic + 1, self.ticPeriod)
            self.lastTicBeforeRender = r[-1]
            return r

    
    def prepareToThink(self):
        self.occupiedTiles = [
            (self.nextTX, self.nextTY)]

    
    def think(self, curTic, curT, unwalkables):
        self.TX = self.nextTX
        self.TY = self.nextTY
        self.lastDirection = self.direction
        self.direction = self._MazeSuit__chooseNewWalkDirection(unwalkables)
        (self.nextTX, self.nextTY) = self._MazeSuit__applyDirection(self.direction, self.TX, self.TY)
        self.occupiedTiles = [
            (self.TX, self.TY),
            (self.nextTX, self.nextTY)]
        if curTic == self.lastTicBeforeRender:
            fromCoords = self.maze.tile2world(self.TX, self.TY)
            toCoords = self.maze.tile2world(self.nextTX, self.nextTY)
            self.fromPos.set(fromCoords[0], fromCoords[1], self.SUIT_Z)
            self.toPos.set(toCoords[0], toCoords[1], self.SUIT_Z)
            self.moveIval = LerpPosInterval(self.suit, self.cellWalkDuration, self.toPos, startPos = self.fromPos, name = self.uniqueName(self.MOVE_IVAL_NAME))
            if self.direction != self.lastDirection:
                self.fromH = self.directionHs[self.lastDirection]
                toH = self.directionHs[self.direction]
                if self.fromH == 270 and toH == 0:
                    self.fromH = -90
                elif self.fromH == 0 and toH == 270:
                    self.fromH = 360
                
                self.fromHpr.set(self.fromH, 0, 0)
                self.toHpr.set(toH, 0, 0)
                turnIval = LerpHprInterval(self.suit, self.turnDuration, self.toHpr, startHpr = self.fromHpr, name = self.uniqueName('turnMazeSuit'))
                self.moveIval = Parallel(self.moveIval, turnIval, name = self.uniqueName(self.MOVE_IVAL_NAME))
            else:
                self.suit.setH(self.directionHs[self.direction])
            moveStartT = float(self.nextThinkTic) / float(self.ticFreq)
            self.moveIval.start(curT - moveStartT + self.gameStartTime)
        
        self.nextThinkTic += self.ticPeriod

    
    def thinkSuits(suitList, startTime, ticFreq = MazeGameGlobals.SUIT_TIC_FREQ):
        curT = globalClock.getFrameTime() - startTime
        curTic = int(curT * float(ticFreq))
        suitUpdates = []
        for i in xrange(len(suitList)):
            updateTics = suitList[i].getThinkTimestampTics(curTic)
            suitUpdates.extend(zip(updateTics, [
                i] * len(updateTics)))
        
        suitUpdates.sort(lambda a, b: a[0] - b[0])
        if len(suitUpdates) > 0:
            curTic = 0
            for i in xrange(len(suitUpdates)):
                update = suitUpdates[i]
                tic = update[0]
                suitIndex = update[1]
                suit = suitList[suitIndex]
                if tic > curTic:
                    curTic = tic
                    j = i + 1
                    while j < len(suitUpdates):
                        if suitUpdates[j][0] > tic:
                            break
                        
                        suitList[suitUpdates[j][1]].prepareToThink()
                        j += 1
                
                unwalkables = []
                for si in xrange(suitIndex):
                    unwalkables.extend(suitList[si].occupiedTiles)
                
                for si in xrange(suitIndex + 1, len(suitList)):
                    unwalkables.extend(suitList[si].occupiedTiles)
                
                suit.think(curTic, curT, unwalkables)
            
        

    thinkSuits = staticmethod(thinkSuits)
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)
class DistributedNPCPrizeClerk(DistributedNPCToonBase):
    def __init__(self, cr):
        DistributedNPCToonBase.__init__(self, cr)
        self.rng = None
        self.gui = None
        self.isLocalToon = 0
        self.av = None
        self.numHouseItems = None
        return

    def announceGenerate(self):
        DistributedNPCToonBase.announceGenerate(self)
        self.rng = RandomNumGen(self.doId)
        self.setHat(59, 0, 0)
        if self.style.gender == 'm':
            self.setGlasses(22, 0, 0)

    def disable(self):
        self.ignoreAll()
        taskMgr.remove(self.uniqueName('popupPrizeGUI'))
        taskMgr.remove(self.uniqueName('lerpCamera'))
        if self.gui:
            self.gui.exit()
            self.gui = None
        self.av = None
        base.localAvatar.posCamera(0, 0)
        DistributedNPCToonBase.disable(self)
        return

    def allowedToEnter(self):
        if hasattr(base,
                   'ttAccess') and base.ttAccess and base.ttAccess.canAccess():
            return True
        return False

    def handleOkTeaser(self):
        self.dialog.destroy()
        del self.dialog
        place = base.cr.playGame.getPlace()
        if place:
            place.fsm.request('walk')

    def handleCollisionSphereEnter(self, collEntry):
        if self.allowedToEnter():
            base.cr.playGame.getPlace().fsm.request('purchase')
            self.sendUpdate('avatarEnter', [])
        else:
            place = base.cr.playGame.getPlace()
            if place:
                place.fsm.request('stopped')
            self.dialog = TeaserPanel.TeaserPanel(pageName='otherGags',
                                                  doneFunc=self.handleOkTeaser)

    def initToonState(self):
        self.setAnimState('neutral', 1.05, None, None)
        npcOrigin = self.cr.playGame.hood.loader.geom.find(
            '**/npc_prizeclerk_origin_%s;+s' % self.posIndex)
        if not npcOrigin.isEmpty():
            self.reparentTo(npcOrigin)
            self.clearMat()
        else:
            self.notify.warning(
                'announceGenerate: Could not find npc_prizeclerk_origin_' +
                str(self.posIndex))
        return

    def __handleUnexpectedExit(self):
        self.notify.warning('unexpected exit')
        self.av = None
        return

    def resetClerk(self):
        self.ignoreAll()
        taskMgr.remove(self.uniqueName('popupPrizeGUI'))
        taskMgr.remove(self.uniqueName('lerpCamera'))
        if self.gui:
            self.gui.exit()
            self.gui = None
        self.clearMat()
        self.startLookAround()
        self.detectAvatars()
        if self.isLocalToon:
            self.freeAvatar()
        return Task.done

    def setLimits(self, numHouseItems):
        self.numHouseItems = numHouseItems

    def setMovie(self, mode, npcId, avId, timestamp):
        timeStamp = ClockDelta.globalClockDelta.localElapsedTime(timestamp)
        self.remain = NPCToons.CLERK_COUNTDOWN_TIME - timeStamp
        self.isLocalToon = avId == base.localAvatar.doId
        if mode == NPCToons.PURCHASE_MOVIE_CLEAR:
            return
        if mode == NPCToons.PURCHASE_MOVIE_START:
            self.av = base.cr.doId2do.get(avId)
            if self.av is None:
                self.notify.warning('Avatar %d not found in doId' % avId)
                return
            self.accept(self.av.uniqueName('disable'),
                        self.__handleUnexpectedExit)
            self.setupAvatars(self.av)
            if self.isLocalToon:
                camera.wrtReparentTo(render)
                self.cameraLerp = LerpPosQuatInterval(
                    camera,
                    1,
                    Point3(-4, 16,
                           self.getHeight() - 0.5),
                    Point3(-150, -2, 0),
                    other=self,
                    blendType='easeInOut')
                self.cameraLerp.start()
            self.setChatAbsolute(
                self.rng.choice(TTLocalizer.TF_STOREOWNER_GREETING),
                CFSpeech | CFTimeout)
            if self.isLocalToon:
                taskMgr.doMethodLater(1.0, self.popupPrizeGUI,
                                      self.uniqueName('popupPrizeGUI'))
        else:
            if MODE_TO_PHRASE.has_key(mode):
                self.setChatAbsolute(self.rng.choice(MODE_TO_PHRASE[mode]),
                                     CFSpeech | CFTimeout)
            else:
                if mode == NPCToons.PURCHASE_MOVIE_TIMEOUT:
                    self.setChatAbsolute(
                        self.rng.choice(TTLocalizer.TF_STOREOWNER_TOOKTOOLONG),
                        CFSpeech | CFTimeout)
                    self.resetClerk()
                else:
                    if mode == NPCToons.PURCHASE_MOVIE_COMPLETE:
                        self.setChatAbsolute(
                            self.rng.choice(TTLocalizer.TF_STOREOWNER_GOODBYE),
                            CFSpeech | CFTimeout)
                        self.resetClerk()
        return

    def popupPrizeGUI(self, task):
        self.accept(DONE_EVENT, self.__handleGuiDone)
        self.gui = ToonfestPrizeCollect.ToonfestPrizeCollect(
            npc=self, random=self.rng, doneEvent=DONE_EVENT)
        self.gui.show()
        return Task.done

    def __handleGuiDone(self):
        self.ignore(DONE_EVENT)
        self.d_requestFinished()
        self.gui = None
        return

    def requestPurchase(self, item, callback, optional=-1):
        blob = item.getBlob(store=CatalogItem.Customization)
        context = self.getCallbackContext(callback, [item])
        self.d_requestPrize(context, blob, optional)

    def d_requestPrize(self, context, blob, optional):
        self.sendUpdate('requestPrize', [context, blob, optional])

    def d_requestPrizeInfo(self, context, retcode):
        self.doCallbackContext(context, [retcode])

    def d_requestFinished(self):
        self.sendUpdate('requestFinished', [])