class DistributedIceGameAI(DistributedMinigameAI.DistributedMinigameAI):
    notify = directNotify.newCategory('DistributedIceGameAI')

    def __init__(self, air, minigameId):
        try:
            self.DistributedIceGameAI_initialized
        except:
            self.DistributedIceGameAI_initialized = 1
            DistributedMinigameAI.DistributedMinigameAI.__init__(self, air, minigameId)
            self.gameFSM = ClassicFSM.ClassicFSM('DistributedIceGameAI', [State.State('off', self.enterOff, self.exitOff, ['waitClientsChoices']),
             State.State('waitClientsChoices', self.enterWaitClientsChoices, self.exitWaitClientsChoices, ['cleanup', 'processChoices']),
             State.State('processChoices', self.enterProcessChoices, self.exitProcessChoices, ['waitEndingPositions', 'cleanup']),
             State.State('waitEndingPositions', self.enterWaitEndingPositions, self.exitWaitEndingPositions, ['processEndingPositions', 'cleanup']),
             State.State('processEndingPositions', self.enterProcessEndingPositions, self.exitProcessEndingPositions, ['waitClientsChoices', 'scoreMatch', 'cleanup']),
             State.State('scoreMatch', self.enterScoreMatch, self.exitScoreMatch, ['waitClientsChoices', 'finalResults', 'cleanup']),
             State.State('finalResults', self.enterFinalResults, self.exitFinalResults, ['cleanup']),
             State.State('cleanup', self.enterCleanup, self.exitCleanup, ['off'])], 'off', 'off')
            self.addChildGameFSM(self.gameFSM)
            self.avatarChoices = {}
            self.avatarEndingPositions = {}
            self.curRound = 0
            self.curMatch = 0
            self.finalEndingPositions = [Point3(IceGameGlobals.StartingPositions[0]),
             Point3(IceGameGlobals.StartingPositions[1]),
             Point3(IceGameGlobals.StartingPositions[2]),
             Point3(IceGameGlobals.StartingPositions[3])]

    def generate(self):
        self.notify.debug('generate')
        DistributedMinigameAI.DistributedMinigameAI.generate(self)

    def delete(self):
        self.notify.debug('delete')
        taskMgr.remove(self.taskName('wait-choices-timeout'))
        taskMgr.remove(self.taskName('endingPositionsTimeout'))
        del self.gameFSM
        DistributedMinigameAI.DistributedMinigameAI.delete(self)

    def setGameReady(self):
        self.notify.debug('setGameReady')
        DistributedMinigameAI.DistributedMinigameAI.setGameReady(self)
        self.numTreasures = IceGameGlobals.NumTreasures[self.getSafezoneId()]
        self.numTreasuresTaken = 0
        self.takenTreasuresTable = [0] * self.numTreasures
        self.numPenalties = IceGameGlobals.NumPenalties[self.getSafezoneId()]
        self.numPenaltiesTaken = 0
        self.takenPenaltiesTable = [0] * self.numPenalties

    def setGameStart(self, timestamp):
        self.notify.debug('setGameStart')
        DistributedMinigameAI.DistributedMinigameAI.setGameStart(self, timestamp)
        self.gameFSM.request('waitClientsChoices')

    def setGameAbort(self):
        self.notify.debug('setGameAbort')
        if self.gameFSM.getCurrentState():
            self.gameFSM.request('cleanup')
        DistributedMinigameAI.DistributedMinigameAI.setGameAbort(self)

    def gameOver(self):
        self.notify.debug('gameOver')
        self.gameFSM.request('cleanup')
        DistributedMinigameAI.DistributedMinigameAI.gameOver(self)

    def enterOff(self):
        self.notify.debug('enterOff')

    def exitOff(self):
        pass

    def enterCleanup(self):
        self.notify.debug('enterCleanup')
        self.gameFSM.request('off')

    def exitCleanup(self):
        pass

    def enterWaitClientsChoices(self):
        self.notify.debug('enterWaitClientsChoices')
        self.resetChoices()
        self.sendUpdate('setMatchAndRound', [self.curMatch, self.curRound])
        self.sendUpdate('setNewState', ['inputChoice'])
        taskMgr.doMethodLater(IceGameGlobals.InputTimeout, self.waitClientsChoicesTimeout, self.taskName('wait-choices-timeout'))
        self.sendUpdate('setTimerStartTime', [globalClockDelta.getFrameNetworkTime()])

    def exitWaitClientsChoices(self):
        self.notify.debug('exitWaitClientsChoices')
        taskMgr.remove(self.taskName('wait-choices-timeout'))

    def enterProcessChoices(self):
        forceAndHeading = []
        for avId in self.avIdList:
            force = self.avatarChoices[avId][0]
            heading = self.avatarChoices[avId][1]
            forceAndHeading.append([force, heading])

        self.notify.debug('tireInputs = %s' % forceAndHeading)
        self.sendUpdate('setTireInputs', [forceAndHeading])
        self.gameFSM.request('waitEndingPositions')

    def exitProcessChoices(self):
        pass

    def enterWaitEndingPositions(self):
        if self.curRound == 0:
            self.takenTreasuresTable = [0] * self.numTreasures
            self.takenPenaltiesTable = [0] * self.numPenalties
        taskMgr.doMethodLater(IceGameGlobals.InputTimeout, self.waitClientsChoicesTimeout, self.taskName('endingPositionsTimeout'))
        self.avatarEndingPositions = {}

    def exitWaitEndingPositions(self):
        taskMgr.remove(self.taskName('endingPositionsTimeout'))

    def enterProcessEndingPositions(self):
        averagePos = [Point3(0, 0, 0),
         Point3(0, 0, 0),
         Point3(0, 0, 0),
         Point3(0, 0, 0)]
        divisor = 0
        for avId in self.avatarEndingPositions.keys():
            divisor += 1
            oneClientEndingPositions = self.avatarEndingPositions[avId]
            avIndex = self.avIdList.index(avId)
            for index in xrange(len(oneClientEndingPositions)):
                pos = oneClientEndingPositions[index]
                averagePos[index] += Point3(pos[0], pos[1], pos[2])
                self.notify.debug('index = %d averagePos = %s' % (index, averagePos))

        sentPos = []
        if divisor:
            for newPos in averagePos:
                newPos /= divisor
                newPos.setZ(IceGameGlobals.TireRadius)
                sentPos.append([newPos[0], newPos[1], newPos[2]])

        else:
            sentPos = self.finalEndingPositions
        self.sendUpdate('setFinalPositions', [sentPos])
        self.finalEndingPositions = sentPos
        if self.curMatch == IceGameGlobals.NumMatches - 1 and self.curRound == IceGameGlobals.NumRounds - 1:
            self.gameFSM.request('scoreMatch')
        elif self.curRound == IceGameGlobals.NumRounds - 1:
            self.gameFSM.request('scoreMatch')
        else:
            self.curRound += 1
            self.sendUpdate('setMatchAndRound', [self.curMatch, self.curRound])
            self.gameFSM.request('waitClientsChoices')

    def exitProcessEndingPositions(self):
        pass

    def enterScoreMatch(self):
        sortedByDistance = []
        for avId in self.avIdList:
            index = self.avIdList.index(avId)
            pos = Point3(*self.finalEndingPositions[index])
            pos.setZ(0)
            sortedByDistance.append((avId, pos.length()))

        def compareDistance(x, y):
            if x[1] - y[1] > 0:
                return 1
            elif x[1] - y[1] < 0:
                return -1
            else:
                return 0

        sortedByDistance.sort(cmp=compareDistance)
        self.scoresAsList = []
        totalPointsAdded = 0
        for index in xrange(len(self.avIdList)):
            pos = Point3(*self.finalEndingPositions[index])
            pos.setZ(0)
            length = pos.length()
            points = length / IceGameGlobals.FarthestLength * (IceGameGlobals.PointsInCorner - IceGameGlobals.PointsDeadCenter[self.numPlayers])
            points += IceGameGlobals.PointsDeadCenter[self.numPlayers]
            self.notify.debug('length = %s points=%s avId=%d' % (length, points, avId))
            avId = self.avIdList[index]
            bonusIndex = 0
            for sortIndex in xrange(len(sortedByDistance)):
                if sortedByDistance[sortIndex][0] == avId:
                    bonusIndex = sortIndex

            bonusIndex += 4 - len(self.avIdList)
            pointsToAdd = int(points + 0.5) + IceGameGlobals.BonusPointsForPlace[bonusIndex]
            totalPointsAdded += pointsToAdd
            self.scoreDict[avId] += pointsToAdd
            self.scoresAsList.append(self.scoreDict[avId])

        self.curMatch += 1
        self.curRound = 0
        self.sendUpdate('setScores', [self.curMatch, self.curRound, self.scoresAsList])
        self.sendUpdate('setNewState', ['scoring'])

        def allToonsScoringMovieDone(self = self):
            self.notify.debug('allToonsScoringMovieDone')
            if self.curMatch == IceGameGlobals.NumMatches:
                self.gameFSM.request('finalResults')
            else:
                self.gameFSM.request('waitClientsChoices')

        def handleTimeout(avIds, self = self):
            self.notify.debug('handleTimeout: avatars %s did not report "done"' % avIds)
            if self.curMatch == IceGameGlobals.NumMatches:
                self.gameFSM.request('finalResults')
            else:
                self.gameFSM.request('waitClientsChoices')

        scoreMovieDuration = IceGameGlobals.FarthestLength * IceGameGlobals.ExpandFeetPerSec
        scoreMovieDuration += totalPointsAdded * IceGameGlobals.ScoreCountUpRate
        self.scoringMovieDoneBarrier = ToonBarrier('waitScoringMovieDone', self.uniqueName('waitScoringMovieDone'), self.avIdList, scoreMovieDuration + MinigameGlobals.latencyTolerance, allToonsScoringMovieDone, handleTimeout)

    def exitScoreMatch(self):
        self.scoringMovieDoneBarrier.cleanup()
        self.scoringMovieDoneBarrier = None
        return

    def enterFinalResults(self):
        self.checkScores()
        self.sendUpdate('setNewState', ['finalResults'])
        taskMgr.doMethodLater(IceGameGlobals.ShowScoresDuration, self.__doneShowingScores, self.taskName('waitShowScores'))

    def exitFinalResults(self):
        taskMgr.remove(self.taskName('waitShowScores'))

    def __doneShowingScores(self, task):
        self.notify.debug('doneShowingScores')
        self.gameOver()
        return Task.done

    def waitClientsChoicesTimeout(self, task):
        self.notify.debug('waitClientsChoicesTimeout: did not hear from all clients')
        for avId in self.avatarChoices.keys():
            if self.avatarChoices[avId] == (-1, 0):
                self.avatarChoices[avId] = (0, 0)

        self.gameFSM.request('processChoices')
        return Task.done

    def resetChoices(self):
        for avId in self.avIdList:
            self.avatarChoices[avId] = (-1, 0)

    def setAvatarChoice(self, force, direction):
        avatarId = self.air.getAvatarIdFromSender()
        self.notify.debug('setAvatarChoice: avatar: ' + str(avatarId) + ' votes: ' + str(force) + ' direction: ' + str(direction))
        self.avatarChoices[avatarId] = self.checkChoice(avatarId, force, direction)
        if self.allAvatarsChosen():
            self.notify.debug('setAvatarChoice: all avatars have chosen')
            self.gameFSM.request('processChoices')
        else:
            self.notify.debug('setAvatarChoice: still waiting for more choices')

    def checkChoice(self, avId, force, direction):
        retForce = force
        retDir = direction
        if retForce < 0:
            retForce = 0
        if retForce > 100:
            retForce = 100
        return (retForce, retDir)

    def allAvatarsChosen(self):
        for avId in self.avatarChoices.keys():
            choice = self.avatarChoices[avId]
            if choice[0] == -1 and not self.stateDict[avId] == DistributedMinigameAI.EXITED:
                return False

        return True

    def endingPositions(self, positions):
        if not self.gameFSM or not self.gameFSM.getCurrentState() or self.gameFSM.getCurrentState().getName() != 'waitEndingPositions':
            return
        self.notify.debug('got endingPositions from client %s' % positions)
        avId = self.air.getAvatarIdFromSender()
        self.avatarEndingPositions[avId] = positions
        if self.allAvatarsSentEndingPositions():
            self.gameFSM.request('processEndingPositions')

    def allAvatarsSentEndingPositions(self):
        if len(self.avatarEndingPositions) == len(self.avIdList):
            return True
        return False

    def endingPositionsTimeout(self, task):
        self.notify.debug('endingPositionsTimeout : did not hear from all clients')
        self.gameFSM.request('processEndingPositions')
        return Task.done

    def reportScoringMovieDone(self):
        if not self.gameFSM or not self.gameFSM.getCurrentState() or self.gameFSM.getCurrentState().getName() != 'scoreMatch':
            return
        avId = self.air.getAvatarIdFromSender()
        self.notify.debug('reportScoringMovieDone: avatar %s is done' % avId)
        self.scoringMovieDoneBarrier.clear(avId)

    def claimTreasure(self, treasureNum):
        if not self.gameFSM or not self.gameFSM.getCurrentState() or self.gameFSM.getCurrentState().getName() != 'waitEndingPositions':
            return
        avId = self.air.getAvatarIdFromSender()
        if not self.scoreDict.has_key(avId):
            self.notify.warning('PROBLEM: avatar %s called claimTreasure(%s) but he is not in the scoreDict: %s. avIdList is: %s' % (avId,
             treasureNum,
             self.scoreDict,
             self.avIdList))
            return
        if treasureNum < 0 or treasureNum >= self.numTreasures:
            self.air.writeServerEvent('warning', treasureNum, 'MazeGameAI.claimTreasure treasureNum out of range')
            return
        if self.takenTreasuresTable[treasureNum]:
            return
        self.takenTreasuresTable[treasureNum] = 1
        avId = self.air.getAvatarIdFromSender()
        self.sendUpdate('setTreasureGrabbed', [avId, treasureNum])
        self.scoreDict[avId] += 1
        self.numTreasuresTaken += 1

    def claimPenalty(self, penaltyNum):
        if not self.gameFSM or not self.gameFSM.getCurrentState() or self.gameFSM.getCurrentState().getName() != 'waitEndingPositions':
            return
        avId = self.air.getAvatarIdFromSender()
        if not self.scoreDict.has_key(avId):
            self.notify.warning('PROBLEM: avatar %s called claimPenalty(%s) but he is not in the scoreDict: %s. avIdList is: %s' % (avId,
             penaltyNum,
             self.scoreDict,
             self.avIdList))
            return
        if penaltyNum < 0 or penaltyNum >= self.numPenalties:
            self.air.writeServerEvent('warning', penaltyNum, 'IceGameAI.claimPenalty penaltyNum out of range')
            return
        if self.takenPenaltiesTable[penaltyNum]:
            return
        self.takenPenaltiesTable[penaltyNum] = 1
        avId = self.air.getAvatarIdFromSender()
        self.sendUpdate('setPenaltyGrabbed', [avId, penaltyNum])
        self.scoreDict[avId] -= 1
        self.numPenaltiesTaken += 1

    def checkScores(self):
        self.scoresAsList = []
        for index in xrange(len(self.avIdList)):
            avId = self.avIdList[index]
            if self.scoreDict[avId] < 0:
                self.scoreDict[avId] = 1
            self.scoresAsList.append(self.scoreDict[avId])
Example #2
0
class DistributedIceGameAI(DistributedMinigameAI.DistributedMinigameAI):
    """AI side class for the ice game."""
    notify = directNotify.newCategory("DistributedIceGameAI")

    def __init__(self, air, minigameId):
        try:
            self.DistributedIceGameAI_initialized
        except:
            self.DistributedIceGameAI_initialized = 1
            DistributedMinigameAI.DistributedMinigameAI.__init__(
                self, air, minigameId)

            self.gameFSM = ClassicFSM.ClassicFSM(
                'DistributedIceGameAI',
                [
                    State.State('off', self.enterOff, self.exitOff,
                                ['waitClientsChoices']),
                    State.State('waitClientsChoices',
                                self.enterWaitClientsChoices,
                                self.exitWaitClientsChoices,
                                ['cleanup', 'processChoices']),
                    State.State('processChoices', self.enterProcessChoices,
                                self.exitProcessChoices,
                                ['waitEndingPositions', 'cleanup']),
                    State.State('waitEndingPositions',
                                self.enterWaitEndingPositions,
                                self.exitWaitEndingPositions,
                                ['processEndingPositions', 'cleanup']),
                    State.State(
                        'processEndingPositions',
                        self.enterProcessEndingPositions,
                        self.exitProcessEndingPositions,
                        ['waitClientsChoices', 'scoreMatch', 'cleanup']),
                    State.State(
                        'scoreMatch', self.enterScoreMatch,
                        self.exitScoreMatch,
                        ['waitClientsChoices', 'finalResults', 'cleanup']),
                    State.State('finalResults', self.enterFinalResults,
                                self.exitFinalResults, ['cleanup']),
                    State.State('cleanup', self.enterCleanup, self.exitCleanup,
                                ['off']),
                ],
                # Initial State
                'off',
                # Final State
                'off',
            )

            # Add our game ClassicFSM to the framework ClassicFSM
            self.addChildGameFSM(self.gameFSM)

            # for each avatar, keep track of force and which direction he wants to go
            self.avatarChoices = {}

            # for each avatar, keep track of the ending positions of the tires
            self.avatarEndingPositions = {}

            self.curRound = 0
            self.curMatch = 0

            # a list of the definitive AI arbitrated positions of the tires,
            self.finalEndingPositions = [
                Point3(IceGameGlobals.StartingPositions[0]),
                Point3(IceGameGlobals.StartingPositions[1]),
                Point3(IceGameGlobals.StartingPositions[2]),
                Point3(IceGameGlobals.StartingPositions[3]),
            ]

    def generate(self):
        self.notify.debug("generate")
        DistributedMinigameAI.DistributedMinigameAI.generate(self)

    # Disable is never called on the AI so we do not define one

    def delete(self):
        self.notify.debug("delete")
        taskMgr.remove(self.taskName("wait-choices-timeout"))
        taskMgr.remove(self.taskName("endingPositionsTimeout"))
        del self.gameFSM
        DistributedMinigameAI.DistributedMinigameAI.delete(self)

    # override some network message handlers
    def setGameReady(self):
        self.notify.debug("setGameReady")
        DistributedMinigameAI.DistributedMinigameAI.setGameReady(self)

        self.numTreasures = IceGameGlobals.NumTreasures[self.getSafezoneId()]
        self.numTreasuresTaken = 0
        self.takenTreasuresTable = [0] * self.numTreasures

        self.numPenalties = IceGameGlobals.NumPenalties[self.getSafezoneId()]
        self.numPenaltiesTaken = 0
        self.takenPenaltiesTable = [0] * self.numPenalties

    def setGameStart(self, timestamp):
        self.notify.debug("setGameStart")
        # base class will cause gameFSM to enter initial state
        DistributedMinigameAI.DistributedMinigameAI.setGameStart(
            self, timestamp)
        # all of the players are ready to start playing the game
        # transition to the appropriate ClassicFSM state
        self.gameFSM.request('waitClientsChoices')

    def setGameAbort(self):
        self.notify.debug("setGameAbort")
        # this is called when the minigame is unexpectedly
        # ended (a player got disconnected, etc.)
        if self.gameFSM.getCurrentState():
            self.gameFSM.request('cleanup')
        DistributedMinigameAI.DistributedMinigameAI.setGameAbort(self)

    def gameOver(self):
        self.notify.debug("gameOver")

        # clean things up in this class
        self.gameFSM.request('cleanup')
        # tell the base class to wrap things up
        DistributedMinigameAI.DistributedMinigameAI.gameOver(self)

    def enterOff(self):
        self.notify.debug("enterOff")

    def exitOff(self):
        pass

##     def enterPlay(self):
##         self.notify.debug("enterPlay")

##         # when the game is done, call gameOver()
##         #self.gameOver()
##         # set up a barrier to wait for the 'game done' msgs
##         def allToonsDone(self=self):
##             self.notify.debug('allToonsDone')
##             #self.sendUpdate('setEveryoneDone')
##             #if not PairingGameGlobals.EndlessGame:
##             self.gameOver()

##         def handleTimeout(avIds, self=self):
##             self.notify.debug(
##                 'handleTimeout: avatars %s did not report "done"' %
##                 avIds)
##             self.setGameAbort()

##         self.gameDuration = 300
##         self.doneBarrier = ToonBarrier(
##             'waitClientsDone',
##             self.uniqueName('waitClientsDone'),
##             self.avIdList,
##             self.gameDuration + MinigameGlobals.latencyTolerance,
##             allToonsDone, handleTimeout)

##     def exitPlay(self):
##         self.doneBarrier.cleanup()
##         del self.doneBarrier
##         pass

    def enterCleanup(self):
        """Enter cleanup state."""
        self.notify.debug("enterCleanup")
        self.gameFSM.request('off')

    def exitCleanup(self):
        """Exit cleanup state."""
        pass

    def enterWaitClientsChoices(self):
        """Wait for the clients to choose force and direction."""
        self.notify.debug("enterWaitClientsChoices")
        # Clear out choices for this round
        self.resetChoices()
        self.sendUpdate('setMatchAndRound', [self.curMatch, self.curRound])
        # tell the clients their new state
        self.sendUpdate('setNewState', ['inputChoice'])
        # Start the timeout
        taskMgr.doMethodLater(IceGameGlobals.InputTimeout,
                              self.waitClientsChoicesTimeout,
                              self.taskName("wait-choices-timeout"))
        self.sendUpdate("setTimerStartTime",
                        [globalClockDelta.getFrameNetworkTime()])

        pass

    def exitWaitClientsChoices(self):
        """Exit waiting for the clients to choose force and direction."""
        self.notify.debug("exitWaitClientsChoices")
        taskMgr.remove(self.taskName("wait-choices-timeout"))
        pass

    def enterProcessChoices(self):
        """Enter Process Choices State.

        For now let each machine do its own simulation.  If that's clunky,
        do it similar to golf, have one machine do everything then send,
        the movie out.  Difference now is that we have 4 objects moving
        around instead of 1.  Making message size a problem
        """
        forceAndHeading = []
        for avId in self.avIdList:
            force = self.avatarChoices[avId][0]
            heading = self.avatarChoices[avId][1]
            forceAndHeading.append([force, heading])
        self.notify.debug('tireInputs = %s' % forceAndHeading)
        self.sendUpdate("setTireInputs", [forceAndHeading])
        self.gameFSM.request('waitEndingPositions')
        pass

    def exitProcessChoices(self):
        """Exit Process Choices State."""
        pass

    def enterWaitEndingPositions(self):
        """Wait for the clients to send their finished positions."""
        #TODO start a task to force this state to end
        if self.curRound == 0:
            self.takenTreasuresTable = [0] * self.numTreasures
            self.takenPenaltiesTable = [0] * self.numPenalties
        taskMgr.doMethodLater(IceGameGlobals.InputTimeout,
                              self.waitClientsChoicesTimeout,
                              self.taskName("endingPositionsTimeout"))
        self.avatarEndingPositions = {}
        pass

    def exitWaitEndingPositions(self):
        """Exit Wait Ending Positions State."""
        taskMgr.remove(self.taskName("endingPositionsTimeout"))

    def enterProcessEndingPositions(self):
        """We've received the final positions from each client, arbitrate."""
        # TODO the ending positions should be very close to each other
        # The total number of physics steps taken tends to be +- 3
        # of the others
        # TODO detect if someone is wildly divergent from the others
        # TODO detect if someone is dead center
        averagePos = [
            Point3(0, 0, 0),
            Point3(0, 0, 0),
            Point3(0, 0, 0),
            Point3(0, 0, 0)
        ]
        divisor = 0
        for avId in self.avatarEndingPositions.keys():
            divisor += 1
            oneClientEndingPositions = self.avatarEndingPositions[avId]
            avIndex = self.avIdList.index(avId)
            for index in xrange(len(oneClientEndingPositions)):
                pos = oneClientEndingPositions[index]
                averagePos[index] += Point3(pos[0], pos[1], pos[2])
                self.notify.debug('index = %d averagePos = %s' %
                                  (index, averagePos))
        sentPos = []

        if divisor:
            for newPos in averagePos:
                newPos /= divisor
                newPos.setZ(IceGameGlobals.TireRadius)  # always ground them
                sentPos.append([newPos[0], newPos[1], newPos[2]])
        else:
            sentPos = self.finalEndingPositions
        self.sendUpdate('setFinalPositions', [sentPos])

        self.finalEndingPositions = sentPos

        # from here we can go to several different states
        # if the match is not over, go to waitClientsChoices
        # if the match is over, but not the game, go to scoring
        # if the game is over, go to finalResults
        if (self.curMatch == IceGameGlobals.NumMatches -1) and \
           (self.curRound == IceGameGlobals.NumRounds - 1):
            # end the game
            self.gameFSM.request('scoreMatch')
            pass
        elif self.curRound == IceGameGlobals.NumRounds - 1:
            # match is ending, score it
            self.gameFSM.request('scoreMatch')
            pass
        else:
            # match is not yet over
            self.curRound += 1
            self.sendUpdate('setMatchAndRound', [self.curMatch, self.curRound])
            self.gameFSM.request('waitClientsChoices')
        pass

    def exitProcessEndingPositions(self):
        """Exit Process Ending Positions State."""
        pass

    def enterScoreMatch(self):
        """Showing the clients final score from the 3 rounds."""
        #self.scoreCopy = self.scoreDict.deepCopy()
        sortedByDistance = []
        for avId in self.avIdList:
            # center is 0,0,0, so distance is pos.length()
            index = self.avIdList.index(avId)
            pos = Point3(*self.finalEndingPositions[index])
            pos.setZ(0)
            sortedByDistance.append((avId, pos.length()))

        def compareDistance(x, y):
            if x[1] - y[1] > 0:
                return 1
            elif x[1] - y[1] < 0:
                return -1
            else:
                return 0

        sortedByDistance.sort(cmp=compareDistance)

        self.scoresAsList = []
        totalPointsAdded = 0
        for index in xrange(len(self.avIdList)):
            # since the center is at 0,0,0 just query the length
            pos = Point3(*self.finalEndingPositions[index])
            pos.setZ(0)
            length = pos.length()
            points = length / IceGameGlobals.FarthestLength * \
                     (IceGameGlobals.PointsInCorner - \
                      IceGameGlobals.PointsDeadCenter[self.numPlayers])
            points += IceGameGlobals.PointsDeadCenter[self.numPlayers]
            self.notify.debug('length = %s points=%s avId=%d' %
                              (length, points, avId))
            avId = self.avIdList[index]
            bonusIndex = 0
            for sortIndex in xrange(len(sortedByDistance)):
                if sortedByDistance[sortIndex][0] == avId:
                    bonusIndex = sortIndex
            bonusIndex += 4 - len(self.avIdList)
            pointsToAdd = int(
                points + 0.5) + IceGameGlobals.BonusPointsForPlace[bonusIndex]
            totalPointsAdded += pointsToAdd
            self.scoreDict[avId] += pointsToAdd
            self.scoresAsList.append(self.scoreDict[avId])
        self.curMatch += 1
        self.curRound = 0
        self.sendUpdate('setScores',
                        [self.curMatch, self.curRound, self.scoresAsList])
        self.sendUpdate('setNewState', ['scoring'])

        # set up a barrier to wait for the 'game done' msgs
        def allToonsScoringMovieDone(self=self):
            self.notify.debug('allToonsScoringMovieDone')
            if self.curMatch == IceGameGlobals.NumMatches:
                self.gameFSM.request('finalResults')
            else:
                self.gameFSM.request('waitClientsChoices')

        def handleTimeout(avIds, self=self):
            self.notify.debug(
                'handleTimeout: avatars %s did not report "done"' % avIds)
            #self.setGameAbort()
            if self.curMatch == IceGameGlobals.NumMatches:
                self.gameFSM.request('finalResults')
            else:
                self.gameFSM.request('waitClientsChoices')

        scoreMovieDuration = IceGameGlobals.FarthestLength * IceGameGlobals.ExpandFeetPerSec
        #scoreMovieDuration += len(self.avIdList) * IceGameGlobals.ScoreIncreaseDuration
        scoreMovieDuration += totalPointsAdded * IceGameGlobals.ScoreCountUpRate

        self.scoringMovieDoneBarrier = ToonBarrier(
            'waitScoringMovieDone', self.uniqueName('waitScoringMovieDone'),
            self.avIdList,
            scoreMovieDuration + MinigameGlobals.latencyTolerance,
            allToonsScoringMovieDone, handleTimeout)

        pass

    def exitScoreMatch(self):
        """Exit Score Match state."""
        self.scoringMovieDoneBarrier.cleanup()
        self.scoringMovieDoneBarrier = None
        pass

    def enterFinalResults(self):
        """Showing the clients final results from the 3 matches."""
        self.checkScores()
        self.sendUpdate('setNewState', ['finalResults'])
        taskMgr.doMethodLater(IceGameGlobals.ShowScoresDuration,
                              self.__doneShowingScores,
                              self.taskName("waitShowScores"))
        pass

    def exitFinalResults(self):
        """Exit  Final Results state."""
        taskMgr.remove(self.taskName("waitShowScores"))
        pass

    def __doneShowingScores(self, task):
        self.notify.debug('doneShowingScores')
        self.gameOver()
        return Task.done

    def waitClientsChoicesTimeout(self, task):
        self.notify.debug(
            "waitClientsChoicesTimeout: did not hear from all clients")
        # Anybody who did not choose gets a 0 for their input
        for avId in self.avatarChoices.keys():
            if self.avatarChoices[avId] == (-1, 0):
                self.avatarChoices[avId] = (0, 0)

        # Go ahead and process choices
        self.gameFSM.request('processChoices')
        return Task.done

    def resetChoices(self):
        """Reset all avatar choices."""
        for avId in self.avIdList:
            # Initialize toons to -1 so we can tell they have not picked yet
            self.avatarChoices[avId] = (-1, 0)

    def setAvatarChoice(self, force, direction):
        """Handle getting 1 players choice for force and direction."""
        avatarId = self.air.getAvatarIdFromSender()
        self.notify.debug("setAvatarChoice: avatar: " + str(avatarId) +
                          " votes: " + str(force) + " direction: " +
                          str(direction))

        # Record this choice in the choices dictionary
        # Check to make sure it is valid first
        self.avatarChoices[avatarId] = self.checkChoice(
            avatarId, force, direction)

        # See if everybody has chosen
        if self.allAvatarsChosen():
            self.notify.debug("setAvatarChoice: all avatars have chosen")
            self.gameFSM.request('processChoices')
        else:
            self.notify.debug(
                "setAvatarChoice: still waiting for more choices")

    def checkChoice(self, avId, force, direction):
        """Make sure the avatar's choices are legal."""
        retForce = force
        retDir = direction
        if retForce < 0:
            retForce = 0
        if retForce > 100:
            retForce = 100

        return (retForce, retDir)

    def allAvatarsChosen(self):
        """
        Returns true if all avatars playing have chosen their votes
        """
        for avId in self.avatarChoices.keys():
            choice = self.avatarChoices[avId]
            if (choice[0] == -1) and not (self.stateDict[avId]
                                          == DistributedMinigameAI.EXITED):
                return False
        # If you get here, all avatars must have chosen
        return True

    def endingPositions(self, positions):
        """Aribitrate on the ending positions."""
        if self.gameFSM.getCurrentState().getName() != 'waitEndingPositions':
            return
        self.notify.debug('got endingPositions from client %s' % positions)
        avId = self.air.getAvatarIdFromSender()
        self.avatarEndingPositions[avId] = positions
        if self.allAvatarsSentEndingPositions():
            self.gameFSM.request('processEndingPositions')

    def allAvatarsSentEndingPositions(self):
        """
        Returns true if all avatars playing have sent their ending positions
        """
        if len(self.avatarEndingPositions) == len(self.avIdList):
            return True
        return False

    def endingPositionsTimeout(self, task):
        """Handle the case when we clients didn't send ending positions."""
        self.notify.debug(
            "endingPositionsTimeout : did not hear from all clients")
        # Go ahead and process choices
        self.gameFSM.request('processEndingPositions')
        return Task.done

    def reportScoringMovieDone(self):
        if self.gameFSM.getCurrentState().getName() != 'scoreMatch':
            return

        avId = self.air.getAvatarIdFromSender()
        # all of the objects on this avatar's client have landed
        # or been caught
        self.notify.debug('reportScoringMovieDone: avatar %s is done' % avId)
        self.scoringMovieDoneBarrier.clear(avId)

    def claimTreasure(self, treasureNum):
        # if the game just ended, ignore this message
        if self.gameFSM.getCurrentState().getName() != 'waitEndingPositions':
            return
        # we're getting strange AI crashes where a toon claims
        # a treasure, and the toon is not listed in the scoreDict
        avId = self.air.getAvatarIdFromSender()
        if not self.scoreDict.has_key(avId):
            self.notify.warning(
                'PROBLEM: avatar %s called claimTreasure(%s) '
                'but he is not in the scoreDict: %s. avIdList is: %s' %
                (avId, treasureNum, self.scoreDict, self.avIdList))
            return

        #self.notify.debug("treasure %s claimed by %s" % \
        #                  (treasureNum, self.air.getAvatarIdFromSender()))

        # give the treasure to the first toon that claims it
        if treasureNum < 0 or treasureNum >= self.numTreasures:
            self.air.writeServerEvent(
                'warning', treasureNum,
                'MazeGameAI.claimTreasure treasureNum out of range')
            return
        if self.takenTreasuresTable[treasureNum]:
            return
        self.takenTreasuresTable[treasureNum] = 1

        avId = self.air.getAvatarIdFromSender()
        self.sendUpdate("setTreasureGrabbed", [avId, treasureNum])
        self.scoreDict[avId] += 1
        self.numTreasuresTaken += 1

    def claimPenalty(self, penaltyNum):
        # if the game just ended, ignore this message
        if self.gameFSM.getCurrentState().getName() != 'waitEndingPositions':
            return
        # we're getting strange AI crashes where a toon claims
        # a penalty, and the toon is not listed in the scoreDict
        avId = self.air.getAvatarIdFromSender()
        if not self.scoreDict.has_key(avId):
            self.notify.warning(
                'PROBLEM: avatar %s called claimPenalty(%s) '
                'but he is not in the scoreDict: %s. avIdList is: %s' %
                (avId, penaltyNum, self.scoreDict, self.avIdList))
            return

        #self.notify.debug("penalty %s claimed by %s" % \
        #                  (penaltyNum, self.air.getAvatarIdFromSender()))

        # give the penalty to the first toon that claims it
        if penaltyNum < 0 or penaltyNum >= self.numPenalties:
            self.air.writeServerEvent(
                'warning', penaltyNum,
                'IceGameAI.claimPenalty penaltyNum out of range')
            return
        if self.takenPenaltiesTable[penaltyNum]:
            return
        self.takenPenaltiesTable[penaltyNum] = 1

        avId = self.air.getAvatarIdFromSender()
        self.sendUpdate("setPenaltyGrabbed", [avId, penaltyNum])
        self.scoreDict[avId] -= 1
        self.numPenaltiesTaken += 1

    def checkScores(self):
        """Force everyone to have at least a score of at least 1."""
        self.scoresAsList = []
        for index in xrange(len(self.avIdList)):
            # since the center is at 0,0,0 just query the length
            avId = self.avIdList[index]
            if self.scoreDict[avId] < 0:
                self.scoreDict[avId] = 1
            self.scoresAsList.append(self.scoreDict[avId])
Example #3
0
class DistributedPairingGameAI(DistributedMinigameAI):
    notify = directNotify.newCategory('DistributedPairingGameAI')
    OneCardInMultiplayer = True
    TurnDownTwoAtATime = True
    
    def __init__(self, air, minigameId):
        
        try:
            pass
        except:
            self.DistributedPairingGameAI_initialized = 1
            DistributedMinigameAI.__init__(self, air, minigameId)
            self.gameFSM = ClassicFSM.ClassicFSM('DistributedPairingGameAI', [
                State.State('inactive', self.enterInactive, self.exitInactive, [
                    'play']),
                State.State('play', self.enterPlay, self.exitPlay, [
                    'cleanup']),
                State.State('cleanup', self.enterCleanup, self.exitCleanup, [
                    'inactive'])], 'inactive', 'inactive')
            self.addChildGameFSM(self.gameFSM)
            self.gameFSM.enterInitialState()
            self.deckSeed = random.randint(0, 4000000)
            self.faceUpDict = { }
            self.inactiveList = []
            self.maxOpenCards = 2
            self.points = 0
            self.flips = 0
            self.matches = 0
            self.cards = []
            self.gameDuration = 90


    
    def generate(self):
        self.notify.debug('generate')
        DistributedMinigameAI.generate(self)

    
    def delete(self):
        self.notify.debug('delete')
        del self.gameFSM
        DistributedMinigameAI.delete(self)

    
    def setGameReady(self):
        self.notify.debug('setGameReady')
        if self.OneCardInMultiplayer and len(self.avIdList) > 1:
            self.maxOpenCards = 1
        
        self.sendUpdate('setMaxOpenCards', [
            self.maxOpenCards])
        DistributedMinigameAI.setGameReady(self)
        for avId in self.avIdList:
            self.faceUpDict[avId] = []
        
        self.deck = PairingGameGlobals.createDeck(self.deckSeed, self.numPlayers)
        for index in xrange(len(self.deck.cards)):
            cardValue = self.deck.cards[index]
            oneCard = PlayingCardBase(cardValue)
            self.cards.append(oneCard)
        

    
    def setGameStart(self, timestamp):
        self.notify.debug('setGameStart')
        DistributedMinigameAI.setGameStart(self, timestamp)
        self.gameFSM.request('play')

    
    def setGameAbort(self):
        self.notify.debug('setGameAbort')
        if self.gameFSM.getCurrentState():
            self.gameFSM.request('cleanup')
        
        DistributedMinigameAI.setGameAbort(self)

    
    def calcLowFlipBonus(self):
        lowFlipModifier = PairingGameGlobals.calcLowFlipModifier(self.matches, self.flips)
        bonus = lowFlipModifier * self.matches
        self.notify.debug('low flip bonus = %d' % bonus)
        return bonus

    
    def gameOver(self):
        self.notify.debug('gameOver')
        lowFlipBonus = 0
        for avId in self.avIdList:
            self.scoreDict[avId] = max(1, self.points)
            lowFlipBonus = self.calcLowFlipBonus()
            self.scoreDict[avId] += lowFlipBonus
            if self.matches == len(self.cards) / 2:
                self.scoreDict[avId] += round(len(self.cards) / 4.0)
                self.logAllPerfect()
                continue
        
        logAvId = self.avIdList[0]
        self.air.writeServerEvent('minigame_pairing', self.doId, '%s|%s|%s|%s|%s|%s|%s|%s' % (ToontownGlobals.PairingGameId, self.getSafezoneId(), self.avIdList, self.scoreDict[logAvId], self.gameDuration, self.matches, self.flips, lowFlipBonus))
        self.gameFSM.request('cleanup')
        DistributedMinigameAI.gameOver(self)

    
    def enterInactive(self):
        self.notify.debug('enterInactive')

    
    def exitInactive(self):
        pass

    
    def enterPlay(self):
        self.notify.debug('enterPlay')
        
        def allToonsDone(self = self):
            self.notify.debug('allToonsDone')
            self.sendUpdate('setEveryoneDone')
            if not PairingGameGlobals.EndlessGame:
                self.gameOver()
            

        
        def handleTimeout(avIds, self = self):
            self.notify.debug('handleTimeout: avatars %s did not report "done"' % avIds)
            self.setGameAbort()

        self.gameDuration = PairingGameGlobals.calcGameDuration(self.getDifficulty())
        self.doneBarrier = ToonBarrier('waitClientsDone', self.uniqueName('waitClientsDone'), self.avIdList, self.gameDuration + MinigameGlobals.latencyTolerance, allToonsDone, handleTimeout)

    
    def exitPlay(self):
        self.doneBarrier.cleanup()
        del self.doneBarrier

    
    def enterCleanup(self):
        self.notify.debug('enterCleanup')
        self.gameFSM.request('inactive')

    
    def exitCleanup(self):
        pass

    
    def getDeckSeed(self):
        return self.deckSeed

    
    def isCardFaceUp(self, deckOrderIndex):
        retval = False
        for key in self.faceUpDict.keys():
            if deckOrderIndex in self.faceUpDict[key]:
                retval = True
                break
                continue
        
        return retval

    
    def isCardFaceDown(self, deckOrderIndex):
        return not self.isCardFaceUp(deckOrderIndex)

    
    def checkForMatch(self):
        faceUpList = []
        for oneToonFaceUpList in self.faceUpDict.values():
            faceUpList += oneToonFaceUpList
        
        for i in range(len(faceUpList)):
            cardA = faceUpList[i]
            for j in range(i + 1, len(faceUpList)):
                cardB = faceUpList[j]
                if self.cards[cardA].rank == self.cards[cardB].rank:
                    return (cardA, cardB)
                    continue
            
        
        return (-1, -1)

    
    def handleMatch(self, cardA, cardB):
        self.notify.debug('we got a match %d %d' % (cardA, cardB))
        for key in self.faceUpDict.keys():
            if cardA in self.faceUpDict[key]:
                self.faceUpDict[key].remove(cardA)
            
            if cardB in self.faceUpDict[key]:
                self.faceUpDict[key].remove(cardB)
                continue
        
        self.inactiveList.append(cardA)
        self.inactiveList.append(cardB)

    
    def turnDownCard(self, cardA):
        self.notify.debug('turning down card %d' % cardA)
        for key in self.faceUpDict.keys():
            if cardA in self.faceUpDict[key]:
                self.faceUpDict[key].remove(cardA)
                continue
        

    
    def openCardRequest(self, deckOrderIndex, bonusGlowCard):
        if self.isCardFaceUp(deckOrderIndex):
            return None
        
        if self.gameFSM.getCurrentState().getName() != 'play':
            return None
        
        avId = self.air.getAvatarIdFromSender()
        if avId not in self.avIdList:
            self.air.writeServerEvent('suspicious', avId, 'openCardRequest from non-player av %s' % avId)
            return None
        
        if deckOrderIndex < 0 or deckOrderIndex >= len(self.cards):
            self.logSuspicious(avId, 'openCardRequest: invalid deckOrderIndex: %s' % deckOrderIndex)
            return None
        
        if bonusGlowCard < 0 or bonusGlowCard >= len(self.cards):
            self.logSuspicious(avId, 'openCardRequest: invalid bonusGlowCard: %s' % bonusGlowCard)
            return None
        
        cardsToTurnDown = []
        faceUpList = self.faceUpDict[avId]
        numCardsFaceUpAtStart = len(faceUpList)
        if len(faceUpList) >= self.maxOpenCards:
            oldestCard = faceUpList.pop(0)
            cardsToTurnDown.append(oldestCard)
        
        if self.TurnDownTwoAtATime and numCardsFaceUpAtStart == 2:
            secondOldestCard = faceUpList.pop(0)
            cardsToTurnDown.append(secondOldestCard)
        
        cardToTurnUp = deckOrderIndex
        self.faceUpDict[avId].append(cardToTurnUp)
        (cardA, cardB) = self.checkForMatch()
        matchingCard = -1
        if cardA > -1:
            self.handleMatch(cardA, cardB)
            if cardA == deckOrderIndex:
                matchingCard = cardB
            else:
                matchingCard = cardA
            pointsToGive = 1
            if bonusGlowCard in [
                cardA,
                cardB]:
                pointsToGive += 1
            
            self.points += pointsToGive
            self.matches += 1
        
        self.flips += 1
        self.sendUpdate('openCardResult', [
            cardToTurnUp,
            avId,
            matchingCard,
            self.points,
            cardsToTurnDown])

    
    def reportDone(self):
        if self.gameFSM.getCurrentState().getName() != 'play':
            return None
        
        avId = self.air.getAvatarIdFromSender()
        self.notify.debug('reportDone: avatar %s is done' % avId)
        self.doneBarrier.clear(avId)
class DistributedIceGameAI(DistributedMinigameAI.DistributedMinigameAI):
    notify = directNotify.newCategory('DistributedIceGameAI')

    def __init__(self, air, minigameId):
        try:
            self.DistributedIceGameAI_initialized
            return
        except:
            self.DistributedIceGameAI_initialized = 1

        DistributedMinigameAI.DistributedMinigameAI.__init__(
            self, air, minigameId)
        self.gameFSM = ClassicFSM.ClassicFSM('DistributedIceGameAI', [
            State.State('off', self.enterOff, self.exitOff,
                        ['waitClientsChoices']),
            State.State('waitClientsChoices', self.enterWaitClientsChoices,
                        self.exitWaitClientsChoices,
                        ['cleanup', 'processChoices']),
            State.State('processChoices', self.enterProcessChoices,
                        self.exitProcessChoices,
                        ['waitEndingPositions', 'cleanup']),
            State.State('waitEndingPositions', self.enterWaitEndingPositions,
                        self.exitWaitEndingPositions,
                        ['processEndingPositions', 'cleanup']),
            State.State('processEndingPositions',
                        self.enterProcessEndingPositions,
                        self.exitProcessEndingPositions,
                        ['waitClientsChoices', 'scoreMatch', 'cleanup']),
            State.State('scoreMatch', self.enterScoreMatch,
                        self.exitScoreMatch,
                        ['waitClientsChoices', 'finalResults', 'cleanup']),
            State.State('finalResults', self.enterFinalResults,
                        self.exitFinalResults, ['cleanup']),
            State.State('cleanup', self.enterCleanup, self.exitCleanup,
                        ['off'])
        ], 'off', 'off')
        self.addChildGameFSM(self.gameFSM)
        self.avatarChoices = {}
        self.avatarEndingPositions = {}
        self.curRound = 0
        self.curMatch = 0
        self.finalEndingPositions = [
            Point3(IceGameGlobals.StartingPositions[0]),
            Point3(IceGameGlobals.StartingPositions[1]),
            Point3(IceGameGlobals.StartingPositions[2]),
            Point3(IceGameGlobals.StartingPositions[3])
        ]

    def generate(self):
        self.notify.debug('generate')
        DistributedMinigameAI.DistributedMinigameAI.generate(self)

    def delete(self):
        self.notify.debug('delete')
        taskMgr.remove(self.taskName('wait-choices-timeout'))
        taskMgr.remove(self.taskName('endingPositionsTimeout'))
        del self.gameFSM
        DistributedMinigameAI.DistributedMinigameAI.delete(self)

    def setGameReady(self):
        self.notify.debug('setGameReady')
        DistributedMinigameAI.DistributedMinigameAI.setGameReady(self)
        self.numTreasures = IceGameGlobals.NumTreasures[self.getSafezoneId()]
        self.numTreasuresTaken = 0
        self.takenTreasuresTable = [0] * self.numTreasures
        self.numPenalties = IceGameGlobals.NumPenalties[self.getSafezoneId()]
        self.numPenaltiesTaken = 0
        self.takenPenaltiesTable = [0] * self.numPenalties

    def setGameStart(self, timestamp):
        self.notify.debug('setGameStart')
        DistributedMinigameAI.DistributedMinigameAI.setGameStart(
            self, timestamp)
        self.gameFSM.request('waitClientsChoices')

    def setGameAbort(self):
        self.notify.debug('setGameAbort')
        if self.gameFSM.getCurrentState():
            self.gameFSM.request('cleanup')
        DistributedMinigameAI.DistributedMinigameAI.setGameAbort(self)

    def gameOver(self):
        self.notify.debug('gameOver')
        self.gameFSM.request('cleanup')
        DistributedMinigameAI.DistributedMinigameAI.gameOver(self)

    def enterOff(self):
        self.notify.debug('enterOff')

    def exitOff(self):
        pass

    def enterCleanup(self):
        self.notify.debug('enterCleanup')
        self.gameFSM.request('off')

    def exitCleanup(self):
        pass

    def enterWaitClientsChoices(self):
        self.notify.debug('enterWaitClientsChoices')
        self.resetChoices()
        self.sendUpdate('setMatchAndRound', [self.curMatch, self.curRound])
        self.sendUpdate('setNewState', ['inputChoice'])
        taskMgr.doMethodLater(IceGameGlobals.InputTimeout,
                              self.waitClientsChoicesTimeout,
                              self.taskName('wait-choices-timeout'))
        self.sendUpdate('setTimerStartTime',
                        [globalClockDelta.getFrameNetworkTime()])

    def exitWaitClientsChoices(self):
        self.notify.debug('exitWaitClientsChoices')
        taskMgr.remove(self.taskName('wait-choices-timeout'))

    def enterProcessChoices(self):
        forceAndHeading = []
        for avId in self.avIdList:
            force = self.avatarChoices[avId][0]
            heading = self.avatarChoices[avId][1]
            forceAndHeading.append([force, heading])

        self.notify.debug('tireInputs = %s' % forceAndHeading)
        self.sendUpdate('setTireInputs', [forceAndHeading])
        self.gameFSM.request('waitEndingPositions')

    def exitProcessChoices(self):
        pass

    def enterWaitEndingPositions(self):
        if self.curRound == 0:
            self.takenTreasuresTable = [0] * self.numTreasures
            self.takenPenaltiesTable = [0] * self.numPenalties
        taskMgr.doMethodLater(IceGameGlobals.InputTimeout,
                              self.waitClientsChoicesTimeout,
                              self.taskName('endingPositionsTimeout'))
        self.avatarEndingPositions = {}

    def exitWaitEndingPositions(self):
        taskMgr.remove(self.taskName('endingPositionsTimeout'))

    def enterProcessEndingPositions(self):
        averagePos = [
            Point3(0, 0, 0),
            Point3(0, 0, 0),
            Point3(0, 0, 0),
            Point3(0, 0, 0)
        ]
        divisor = 0
        for avId in self.avatarEndingPositions.keys():
            divisor += 1
            oneClientEndingPositions = self.avatarEndingPositions[avId]
            avIndex = self.avIdList.index(avId)
            for index in xrange(len(oneClientEndingPositions)):
                pos = oneClientEndingPositions[index]
                averagePos[index] += Point3(pos[0], pos[1], pos[2])
                self.notify.debug('index = %d averagePos = %s' %
                                  (index, averagePos))

        sentPos = []
        if divisor:
            for newPos in averagePos:
                newPos /= divisor
                newPos.setZ(IceGameGlobals.TireRadius)
                sentPos.append([newPos[0], newPos[1], newPos[2]])

        else:
            sentPos = self.finalEndingPositions
        self.sendUpdate('setFinalPositions', [sentPos])
        self.finalEndingPositions = sentPos
        if self.curMatch == IceGameGlobals.NumMatches - 1 and self.curRound == IceGameGlobals.NumRounds - 1:
            self.gameFSM.request('scoreMatch')
        elif self.curRound == IceGameGlobals.NumRounds - 1:
            self.gameFSM.request('scoreMatch')
        else:
            self.curRound += 1
            self.sendUpdate('setMatchAndRound', [self.curMatch, self.curRound])
            self.gameFSM.request('waitClientsChoices')

    def exitProcessEndingPositions(self):
        pass

    def enterScoreMatch(self):
        sortedByDistance = []
        for avId in self.avIdList:
            index = self.avIdList.index(avId)
            pos = Point3(*self.finalEndingPositions[index])
            pos.setZ(0)
            sortedByDistance.append((avId, pos.length()))

        def compareDistance(x, y):
            if x[1] - y[1] > 0:
                return 1
            elif x[1] - y[1] < 0:
                return -1
            else:
                return 0

        sortedByDistance.sort(cmp=compareDistance)
        self.scoresAsList = []
        totalPointsAdded = 0
        for index in xrange(len(self.avIdList)):
            pos = Point3(*self.finalEndingPositions[index])
            pos.setZ(0)
            length = pos.length()
            points = length / IceGameGlobals.FarthestLength * (
                IceGameGlobals.PointsInCorner -
                IceGameGlobals.PointsDeadCenter[self.numPlayers])
            points += IceGameGlobals.PointsDeadCenter[self.numPlayers]
            self.notify.debug('length = %s points=%s avId=%d' %
                              (length, points, avId))
            avId = self.avIdList[index]
            bonusIndex = 0
            for sortIndex in xrange(len(sortedByDistance)):
                if sortedByDistance[sortIndex][0] == avId:
                    bonusIndex = sortIndex

            bonusIndex += 4 - len(self.avIdList)
            pointsToAdd = int(
                points + 0.5) + IceGameGlobals.BonusPointsForPlace[bonusIndex]
            totalPointsAdded += pointsToAdd
            self.scoreDict[avId] += pointsToAdd
            self.scoresAsList.append(self.scoreDict[avId])

        self.curMatch += 1
        self.curRound = 0
        self.sendUpdate('setScores',
                        [self.curMatch, self.curRound, self.scoresAsList])
        self.sendUpdate('setNewState', ['scoring'])

        def allToonsScoringMovieDone(self=self):
            self.notify.debug('allToonsScoringMovieDone')
            if self.curMatch == IceGameGlobals.NumMatches:
                self.gameFSM.request('finalResults')
            else:
                self.gameFSM.request('waitClientsChoices')

        def handleTimeout(avIds, self=self):
            self.notify.debug(
                'handleTimeout: avatars %s did not report "done"' % avIds)
            if self.curMatch == IceGameGlobals.NumMatches:
                self.gameFSM.request('finalResults')
            else:
                self.gameFSM.request('waitClientsChoices')

        scoreMovieDuration = IceGameGlobals.FarthestLength * IceGameGlobals.ExpandFeetPerSec
        scoreMovieDuration += totalPointsAdded * IceGameGlobals.ScoreCountUpRate
        self.scoringMovieDoneBarrier = ToonBarrier(
            'waitScoringMovieDone', self.uniqueName('waitScoringMovieDone'),
            self.avIdList,
            scoreMovieDuration + MinigameGlobals.latencyTolerance,
            allToonsScoringMovieDone, handleTimeout)

    def exitScoreMatch(self):
        self.scoringMovieDoneBarrier.cleanup()
        self.scoringMovieDoneBarrier = None
        return

    def enterFinalResults(self):
        self.checkScores()
        self.sendUpdate('setNewState', ['finalResults'])
        taskMgr.doMethodLater(IceGameGlobals.ShowScoresDuration,
                              self.__doneShowingScores,
                              self.taskName('waitShowScores'))

    def exitFinalResults(self):
        taskMgr.remove(self.taskName('waitShowScores'))

    def __doneShowingScores(self, task):
        self.notify.debug('doneShowingScores')
        self.gameOver()
        return Task.done

    def waitClientsChoicesTimeout(self, task):
        self.notify.debug(
            'waitClientsChoicesTimeout: did not hear from all clients')
        for avId in self.avatarChoices.keys():
            if self.avatarChoices[avId] == (-1, 0):
                self.avatarChoices[avId] = (0, 0)

        self.gameFSM.request('processChoices')
        return Task.done

    def resetChoices(self):
        for avId in self.avIdList:
            self.avatarChoices[avId] = (-1, 0)

    def setAvatarChoice(self, force, direction):
        avatarId = self.air.getAvatarIdFromSender()
        self.notify.debug('setAvatarChoice: avatar: ' + str(avatarId) +
                          ' votes: ' + str(force) + ' direction: ' +
                          str(direction))
        self.avatarChoices[avatarId] = self.checkChoice(
            avatarId, force, direction)
        if self.allAvatarsChosen():
            self.notify.debug('setAvatarChoice: all avatars have chosen')
            self.gameFSM.request('processChoices')
        else:
            self.notify.debug(
                'setAvatarChoice: still waiting for more choices')

    def checkChoice(self, avId, force, direction):
        retForce = force
        retDir = direction
        if retForce < 0:
            retForce = 0
        if retForce > 100:
            retForce = 100
        return (retForce, retDir)

    def allAvatarsChosen(self):
        for avId in self.avatarChoices.keys():
            choice = self.avatarChoices[avId]
            if choice[0] == -1 and not self.stateDict[
                    avId] == DistributedMinigameAI.EXITED:
                return False

        return True

    def endingPositions(self, positions):
        if not self.gameFSM or not self.gameFSM.getCurrentState(
        ) or self.gameFSM.getCurrentState().getName() != 'waitEndingPositions':
            return
        self.notify.debug('got endingPositions from client %s' % positions)
        avId = self.air.getAvatarIdFromSender()
        self.avatarEndingPositions[avId] = positions
        if self.allAvatarsSentEndingPositions():
            self.gameFSM.request('processEndingPositions')

    def allAvatarsSentEndingPositions(self):
        if len(self.avatarEndingPositions) == len(self.avIdList):
            return True
        return False

    def endingPositionsTimeout(self, task):
        self.notify.debug(
            'endingPositionsTimeout : did not hear from all clients')
        self.gameFSM.request('processEndingPositions')
        return Task.done

    def reportScoringMovieDone(self):
        if not self.gameFSM or not self.gameFSM.getCurrentState(
        ) or self.gameFSM.getCurrentState().getName() != 'scoreMatch':
            return
        avId = self.air.getAvatarIdFromSender()
        self.notify.debug('reportScoringMovieDone: avatar %s is done' % avId)
        self.scoringMovieDoneBarrier.clear(avId)

    def claimTreasure(self, treasureNum):
        if not self.gameFSM or not self.gameFSM.getCurrentState(
        ) or self.gameFSM.getCurrentState().getName() != 'waitEndingPositions':
            return
        avId = self.air.getAvatarIdFromSender()
        if avId not in self.scoreDict:
            self.notify.warning(
                'PROBLEM: avatar %s called claimTreasure(%s) but he is not in the scoreDict: %s. avIdList is: %s'
                % (avId, treasureNum, self.scoreDict, self.avIdList))
            return
        if treasureNum < 0 or treasureNum >= self.numTreasures:
            self.air.writeServerEvent(
                'warning', treasureNum,
                'MazeGameAI.claimTreasure treasureNum out of range')
            return
        if self.takenTreasuresTable[treasureNum]:
            return
        self.takenTreasuresTable[treasureNum] = 1
        avId = self.air.getAvatarIdFromSender()
        self.sendUpdate('setTreasureGrabbed', [avId, treasureNum])
        self.scoreDict[avId] += 1
        self.numTreasuresTaken += 1

    def claimPenalty(self, penaltyNum):
        if not self.gameFSM or not self.gameFSM.getCurrentState(
        ) or self.gameFSM.getCurrentState().getName() != 'waitEndingPositions':
            return
        avId = self.air.getAvatarIdFromSender()
        if avId not in self.scoreDict:
            self.notify.warning(
                'PROBLEM: avatar %s called claimPenalty(%s) but he is not in the scoreDict: %s. avIdList is: %s'
                % (avId, penaltyNum, self.scoreDict, self.avIdList))
            return
        if penaltyNum < 0 or penaltyNum >= self.numPenalties:
            self.air.writeServerEvent(
                'warning', penaltyNum,
                'IceGameAI.claimPenalty penaltyNum out of range')
            return
        if self.takenPenaltiesTable[penaltyNum]:
            return
        self.takenPenaltiesTable[penaltyNum] = 1
        avId = self.air.getAvatarIdFromSender()
        self.sendUpdate('setPenaltyGrabbed', [avId, penaltyNum])
        self.scoreDict[avId] -= 1
        self.numPenaltiesTaken += 1

    def checkScores(self):
        self.scoresAsList = []
        for index in xrange(len(self.avIdList)):
            avId = self.avIdList[index]
            if self.scoreDict[avId] < 0:
                self.scoreDict[avId] = 1
            self.scoresAsList.append(self.scoreDict[avId])
class DistributedPairingGameAI(DistributedMinigameAI):
    notify = directNotify.newCategory('DistributedPairingGameAI')
    OneCardInMultiplayer = True
    TurnDownTwoAtATime = True

    def __init__(self, air, minigameId):
        try:
            self.DistributedPairingGameAI_initialized
        except:
            self.DistributedPairingGameAI_initialized = 1
            DistributedMinigameAI.__init__(self, air, minigameId)
            self.gameFSM = ClassicFSM.ClassicFSM('DistributedPairingGameAI', [State.State('inactive', self.enterInactive, self.exitInactive, ['play']), State.State('play', self.enterPlay, self.exitPlay, ['cleanup']), State.State('cleanup', self.enterCleanup, self.exitCleanup, ['inactive'])], 'inactive', 'inactive')
            self.addChildGameFSM(self.gameFSM)
            self.gameFSM.enterInitialState()
            self.deckSeed = random.randint(0, 4000000)
            self.faceUpDict = {}
            self.inactiveList = []
            self.maxOpenCards = 2
            self.points = 0
            self.flips = 0
            self.matches = 0
            self.cards = []
            self.gameDuration = 90

    def generate(self):
        self.notify.debug('generate')
        DistributedMinigameAI.generate(self)

    def delete(self):
        self.notify.debug('delete')
        del self.gameFSM
        DistributedMinigameAI.delete(self)

    def setGameReady(self):
        self.notify.debug('setGameReady')
        if self.OneCardInMultiplayer and len(self.avIdList) > 1:
            self.maxOpenCards = 1
        self.sendUpdate('setMaxOpenCards', [self.maxOpenCards])
        DistributedMinigameAI.setGameReady(self)
        for avId in self.avIdList:
            self.faceUpDict[avId] = []

        self.deck = PairingGameGlobals.createDeck(self.deckSeed, self.numPlayers)
        for index in xrange(len(self.deck.cards)):
            cardValue = self.deck.cards[index]
            oneCard = PlayingCardBase(cardValue)
            self.cards.append(oneCard)

    def setGameStart(self, timestamp):
        self.notify.debug('setGameStart')
        DistributedMinigameAI.setGameStart(self, timestamp)
        self.gameFSM.request('play')

    def setGameAbort(self):
        self.notify.debug('setGameAbort')
        if self.gameFSM.getCurrentState():
            self.gameFSM.request('cleanup')
        DistributedMinigameAI.setGameAbort(self)

    def calcLowFlipBonus(self):
        lowFlipModifier = PairingGameGlobals.calcLowFlipModifier(self.matches, self.flips)
        bonus = lowFlipModifier * self.matches
        self.notify.debug('low flip bonus = %d' % bonus)
        return bonus

    def gameOver(self):
        self.notify.debug('gameOver')
        lowFlipBonus = 0
        for avId in self.avIdList:
            self.scoreDict[avId] = max(1, self.points)
            lowFlipBonus = self.calcLowFlipBonus()
            self.scoreDict[avId] += lowFlipBonus
            if self.matches == len(self.cards) / 2:
                self.scoreDict[avId] += round(len(self.cards) / 4.0)
                self.logAllPerfect()

        logAvId = self.avIdList[0]
        self.air.writeServerEvent('minigame_pairing', self.doId, '%s|%s|%s|%s|%s|%s|%s|%s' % (ToontownGlobals.PairingGameId,
         self.getSafezoneId(),
         self.avIdList,
         self.scoreDict[logAvId],
         self.gameDuration,
         self.matches,
         self.flips,
         lowFlipBonus))
        self.gameFSM.request('cleanup')
        DistributedMinigameAI.gameOver(self)

    def enterInactive(self):
        self.notify.debug('enterInactive')

    def exitInactive(self):
        pass

    def enterPlay(self):
        self.notify.debug('enterPlay')

        def allToonsDone(self = self):
            self.notify.debug('allToonsDone')
            self.sendUpdate('setEveryoneDone')
            if not PairingGameGlobals.EndlessGame:
                self.gameOver()

        def handleTimeout(avIds, self = self):
            self.notify.debug('handleTimeout: avatars %s did not report "done"' % avIds)
            self.setGameAbort()

        self.gameDuration = PairingGameGlobals.calcGameDuration(self.getDifficulty())
        self.doneBarrier = ToonBarrier('waitClientsDone', self.uniqueName('waitClientsDone'), self.avIdList, self.gameDuration + MinigameGlobals.latencyTolerance, allToonsDone, handleTimeout)

    def exitPlay(self):
        self.doneBarrier.cleanup()
        del self.doneBarrier

    def enterCleanup(self):
        self.notify.debug('enterCleanup')
        self.gameFSM.request('inactive')

    def exitCleanup(self):
        pass

    def getDeckSeed(self):
        return self.deckSeed

    def isCardFaceUp(self, deckOrderIndex):
        retval = False
        for key in self.faceUpDict.keys():
            if deckOrderIndex in self.faceUpDict[key]:
                retval = True
                break

        return retval

    def isCardFaceDown(self, deckOrderIndex):
        return not self.isCardFaceUp(deckOrderIndex)

    def checkForMatch(self):
        faceUpList = []
        for oneToonFaceUpList in self.faceUpDict.values():
            faceUpList += oneToonFaceUpList

        for i in xrange(len(faceUpList)):
            cardA = faceUpList[i]
            for j in xrange(i + 1, len(faceUpList)):
                cardB = faceUpList[j]
                if self.cards[cardA].rank == self.cards[cardB].rank:
                    return (cardA, cardB)

        return (-1, -1)

    def handleMatch(self, cardA, cardB):
        self.notify.debug('we got a match %d %d' % (cardA, cardB))
        for key in self.faceUpDict.keys():
            if cardA in self.faceUpDict[key]:
                self.faceUpDict[key].remove(cardA)
            if cardB in self.faceUpDict[key]:
                self.faceUpDict[key].remove(cardB)

        self.inactiveList.append(cardA)
        self.inactiveList.append(cardB)

    def turnDownCard(self, cardA):
        self.notify.debug('turning down card %d' % cardA)
        for key in self.faceUpDict.keys():
            if cardA in self.faceUpDict[key]:
                self.faceUpDict[key].remove(cardA)

    def openCardRequest(self, deckOrderIndex, bonusGlowCard):
        if self.isCardFaceUp(deckOrderIndex):
            return
        if self.gameFSM.getCurrentState().getName() != 'play':
            return
        avId = self.air.getAvatarIdFromSender()
        if avId not in self.avIdList:
            self.air.writeServerEvent('suspicious', avId, 'openCardRequest from non-player av %s' % avId)
            return
        if deckOrderIndex < 0 or deckOrderIndex >= len(self.cards):
            self.logSuspicious(avId, 'openCardRequest: invalid deckOrderIndex: %s' % deckOrderIndex)
            return
        if bonusGlowCard < 0 or bonusGlowCard >= len(self.cards):
            self.logSuspicious(avId, 'openCardRequest: invalid bonusGlowCard: %s' % bonusGlowCard)
            return
        cardsToTurnDown = []
        faceUpList = self.faceUpDict[avId]
        numCardsFaceUpAtStart = len(faceUpList)
        if len(faceUpList) >= self.maxOpenCards:
            oldestCard = faceUpList.pop(0)
            cardsToTurnDown.append(oldestCard)
        if self.TurnDownTwoAtATime and numCardsFaceUpAtStart == 2:
            secondOldestCard = faceUpList.pop(0)
            cardsToTurnDown.append(secondOldestCard)
        cardToTurnUp = deckOrderIndex
        self.faceUpDict[avId].append(cardToTurnUp)
        cardA, cardB = self.checkForMatch()
        matchingCard = -1
        if cardA > -1:
            self.handleMatch(cardA, cardB)
            if cardA == deckOrderIndex:
                matchingCard = cardB
            else:
                matchingCard = cardA
            pointsToGive = 1
            if bonusGlowCard in [cardA, cardB]:
                pointsToGive += 1
            self.points += pointsToGive
            self.matches += 1
        self.flips += 1
        self.sendUpdate('openCardResult', [cardToTurnUp,
         avId,
         matchingCard,
         self.points,
         cardsToTurnDown])

    def reportDone(self):
        if self.gameFSM.getCurrentState().getName() != 'play':
            return
        avId = self.air.getAvatarIdFromSender()
        self.notify.debug('reportDone: avatar %s is done' % avId)
        self.doneBarrier.clear(avId)
Example #6
0
class DistributedPairingGameAI(DistributedMinigameAI):

    notify = directNotify.newCategory("DistributedPairingGameAI")

    OneCardInMultiplayer = True  # do we restrict it to one card open
    TurnDownTwoAtATime = True  # when we open a 3rd card, do we close down the other 2

    def __init__(self, air, minigameId):
        try:
            self.DistributedPairingGameAI_initialized
        except:
            self.DistributedPairingGameAI_initialized = 1
            DistributedMinigameAI.__init__(self, air, minigameId)

            self.gameFSM = ClassicFSM.ClassicFSM(
                'DistributedPairingGameAI',
                [
                    State.State('inactive', self.enterInactive,
                                self.exitInactive, ['play']),
                    State.State('play', self.enterPlay, self.exitPlay,
                                ['cleanup']),
                    State.State('cleanup', self.enterCleanup, self.exitCleanup,
                                ['inactive']),
                ],
                # Initial State
                'inactive',
                # Final State
                'inactive',
            )

            # Add our game ClassicFSM to the framework ClassicFSM
            self.addChildGameFSM(self.gameFSM)

            self.gameFSM.enterInitialState()

            self.deckSeed = random.randint(0, 4000000)
            #self.deckSeed = 0
            self.faceUpDict = {}  # which cards are face up per toon
            self.inactiveList = []  # which cards are out of play
            self.maxOpenCards = 2
            self.points = 0
            self.flips = 0
            self.matches = 0
            self.cards = []
            self.gameDuration = 90

    def generate(self):
        self.notify.debug("generate")
        DistributedMinigameAI.generate(self)

    # Disable is never called on the AI so we do not define one

    def delete(self):
        self.notify.debug("delete")
        del self.gameFSM
        DistributedMinigameAI.delete(self)

    # override some network message handlers
    def setGameReady(self):
        self.notify.debug("setGameReady")

        # make sure we broadcast how many cards open per toon
        if self.OneCardInMultiplayer and len(self.avIdList) > 1:
            self.maxOpenCards = 1
        self.sendUpdate('setMaxOpenCards', [self.maxOpenCards])

        DistributedMinigameAI.setGameReady(self)
        # all of the players have checked in
        # they will now be shown the rules

        # init face up dict and other fields
        for avId in self.avIdList:
            self.faceUpDict[avId] = []
        self.deck = PairingGameGlobals.createDeck(self.deckSeed,
                                                  self.numPlayers)
        for index in range(len(self.deck.cards)):
            cardValue = self.deck.cards[index]
            oneCard = PlayingCardBase(cardValue)
            self.cards.append(oneCard)

    def setGameStart(self, timestamp):
        self.notify.debug("setGameStart")
        # base class will cause gameFSM to enter initial state
        DistributedMinigameAI.setGameStart(self, timestamp)
        # all of the players are ready to start playing the game
        # transition to the appropriate ClassicFSM state
        self.gameFSM.request('play')

    def setGameAbort(self):
        self.notify.debug("setGameAbort")
        # this is called when the minigame is unexpectedly
        # ended (a player got disconnected, etc.)
        if self.gameFSM.getCurrentState():
            self.gameFSM.request('cleanup')
        DistributedMinigameAI.setGameAbort(self)

    def calcLowFlipBonus(self):
        lowFlipModifier = PairingGameGlobals.calcLowFlipModifier(
            self.matches, self.flips)
        bonus = lowFlipModifier * self.matches
        self.notify.debug('low flip bonus = %d' % bonus)
        return bonus

    def gameOver(self):
        self.notify.debug("gameOver")
        # call this when the game is done
        lowFlipBonus = 0
        for avId in self.avIdList:
            self.scoreDict[avId] = max(1, self.points)
            # we know the flips and the matches, calculate low
            lowFlipBonus = self.calcLowFlipBonus()
            self.scoreDict[avId] += lowFlipBonus

            if self.matches == len(self.cards) / 2:
                self.scoreDict[avId] += round(len(self.cards) / 4.)
                self.logAllPerfect()

        # Log balancing variables to the event server
        logAvId = self.avIdList[0]
        self.air.writeServerEvent(
            'minigame_pairing', self.doId, '%s|%s|%s|%s|%s|%s|%s|%s' %
            (ToontownGlobals.PairingGameId, self.getSafezoneId(),
             self.avIdList, self.scoreDict[logAvId], self.gameDuration,
             self.matches, self.flips, lowFlipBonus))

        # clean things up in this class
        self.gameFSM.request('cleanup')
        # tell the base class to wrap things up
        DistributedMinigameAI.gameOver(self)

    def enterInactive(self):
        self.notify.debug("enterInactive")

    def exitInactive(self):
        pass

    def enterPlay(self):
        self.notify.debug("enterPlay")

        # when the game is done, call gameOver()
        #self.gameOver()
        # set up a barrier to wait for the 'game done' msgs
        def allToonsDone(self=self):
            self.notify.debug('allToonsDone')
            self.sendUpdate('setEveryoneDone')
            if not PairingGameGlobals.EndlessGame:
                self.gameOver()

        def handleTimeout(avIds, self=self):
            self.notify.debug(
                'handleTimeout: avatars %s did not report "done"' % avIds)
            self.setGameAbort()

        self.gameDuration = PairingGameGlobals.calcGameDuration(
            self.getDifficulty())
        self.doneBarrier = ToonBarrier(
            'waitClientsDone', self.uniqueName('waitClientsDone'),
            self.avIdList,
            self.gameDuration + MinigameGlobals.latencyTolerance, allToonsDone,
            handleTimeout)

    def exitPlay(self):
        self.doneBarrier.cleanup()
        del self.doneBarrier
        pass

    def enterCleanup(self):
        self.notify.debug("enterCleanup")
        self.gameFSM.request('inactive')

    def exitCleanup(self):
        pass

    def getDeckSeed(self):
        return self.deckSeed

    def isCardFaceUp(self, deckOrderIndex):
        retval = False
        for key in list(self.faceUpDict.keys()):
            if deckOrderIndex in self.faceUpDict[key]:
                retval = True
                break
        return retval

    def isCardFaceDown(self, deckOrderIndex):
        return not self.isCardFaceUp(deckOrderIndex)

    def checkForMatch(self):
        faceUpList = []
        for oneToonFaceUpList in list(self.faceUpDict.values()):
            faceUpList += oneToonFaceUpList
        for i in range(len(faceUpList)):
            cardA = faceUpList[i]
            for j in range(i + 1, len(faceUpList)):
                cardB = faceUpList[j]
                if self.cards[cardA].rank == self.cards[cardB].rank:
                    return cardA, cardB
        return -1, -1

    def handleMatch(self, cardA, cardB):
        self.notify.debug('we got a match %d %d' % (cardA, cardB))

        for key in list(self.faceUpDict.keys()):
            if cardA in self.faceUpDict[key]:
                self.faceUpDict[key].remove(cardA)
            if cardB in self.faceUpDict[key]:
                self.faceUpDict[key].remove(cardB)

        self.inactiveList.append(cardA)
        self.inactiveList.append(cardB)

    def turnDownCard(self, cardA):
        self.notify.debug('turning down card %d' % cardA)
        for key in list(self.faceUpDict.keys()):
            if cardA in self.faceUpDict[key]:
                self.faceUpDict[key].remove(cardA)

    def openCardRequest(self, deckOrderIndex, bonusGlowCard):
        # some other toon opened the card slightly ahead
        if self.isCardFaceUp(deckOrderIndex):
            return

        if self.gameFSM.getCurrentState().getName() != 'play':
            return

        #import pdb; pdb.set_trace()
        avId = self.air.getAvatarIdFromSender()
        if avId not in self.avIdList:
            self.air.writeServerEvent(
                'suspicious', avId,
                'openCardRequest from non-player av %s' % avId)
            return
        cardsToTurnDown = []
        faceUpList = self.faceUpDict[avId]
        numCardsFaceUpAtStart = len(faceUpList)
        if len(faceUpList) >= self.maxOpenCards:
            oldestCard = faceUpList.pop(0)
            cardsToTurnDown.append(oldestCard)
        if self.TurnDownTwoAtATime and numCardsFaceUpAtStart == 2:
            secondOldestCard = faceUpList.pop(0)
            cardsToTurnDown.append(secondOldestCard)
        cardToTurnUp = deckOrderIndex
        self.faceUpDict[avId].append(cardToTurnUp)
        cardA, cardB = self.checkForMatch()
        matchingCard = -1
        if cardA > -1:
            self.handleMatch(cardA, cardB)
            if cardA == deckOrderIndex:
                matchingCard = cardB
            else:
                matchingCard = cardA
            pointsToGive = 1
            if bonusGlowCard in [cardA, cardB]:
                # TODO do some sanity checking that the bonus was legit
                pointsToGive += 1
            self.points += pointsToGive
            self.matches += 1

        self.flips += 1
        self.sendUpdate(
            'openCardResult',
            [cardToTurnUp, avId, matchingCard, self.points, cardsToTurnDown])

    def reportDone(self):
        if self.gameFSM.getCurrentState().getName() != 'play':
            return

        avId = self.air.getAvatarIdFromSender()
        # all of the objects on this avatar's client have landed
        # or been caught
        self.notify.debug('reportDone: avatar %s is done' % avId)
        self.doneBarrier.clear(avId)