def executeMovement(self,
                        move: DiscreteMove,
                        targetPosition: np.ndarray,
                        restrictedDuration: int = None,
                        acceptableSinguityCount: int = None):
        def getLastingMoveDuration(moveDuration: int) -> int:
            minimumBoundDuration = moveDuration if move.minimumMoveTime is None else max(
                moveDuration, move.minimumMoveTime)
            maximumBoundDuration = minimumBoundDuration if restrictedDuration is None else min(
                minimumBoundDuration, restrictedDuration)
            return maximumBoundDuration

        originalMovementDuration = PhysicsEstimator.estimateMovementDuration(
            self.singuitiesMeanPosition,
            targetPosition,
            clusterStd=self.singuitiesStd,
            acceptableSinguityCount=None if acceptableSinguityCount is None
            else (self.singuityCount, acceptableSinguityCount))
        constrainedMovementDuration = getLastingMoveDuration(
            originalMovementDuration)

        if constrainedMovementDuration == 0:
            return 0, self

        targetIsWithinStd = PhysicsEstimator.distance2(
            self.singuitiesMeanPosition,
            targetPosition) <= self.singuitiesStd**2

        if targetIsWithinStd:
            ratioOfTimeSpentInside = min(
                constrainedMovementDuration *
                Singuity.MAXIMUM_SPEED_UNITS_PER_FRAME,
                self.singuitiesStd * 2) / (self.singuitiesStd * 2)
            newStd = min(
                PhysicsEstimator.getMinimumStd(self.singuityCount) *
                ratioOfTimeSpentInside + self.singuitiesStd *
                (1 - ratioOfTimeSpentInside), self.singuitiesStd)
        else:
            # maximum duration on a 5000 unit map
            newStd = self.singuitiesStd * (1 +
                                           constrainedMovementDuration / 1768)

        return constrainedMovementDuration,\
               DiscretePlayer(
                   self.isCurrentPlayer,
                   self.id,
                   self.singuityCount,
                   self.singuitiesMeanPosition + (targetPosition - self.singuitiesMeanPosition) * (constrainedMovementDuration / constrainedMovementDuration),
                   newStd,
                   self.singuitiesAverageHealth
               )
    def quickAttackDuration(gameState: DiscreteGameState,
                            playerId: str) -> int:
        ownPlayer = gameState.playerDictionary[playerId]
        ownSpawners = [
            s for s in gameState.spawners
            if s.isClaimed() and s.isAllegedToPlayer(playerId)
        ]

        if len(ownSpawners) == 0:
            return -1

        ownGestationFrames = [
            s.frameCountBeforeGestationIsDone(gameState.frameCount)
            for s in ownSpawners
        ]

        claimedEnemySpawners = [
            s for s in gameState.spawners
            if s.isClaimed() and not s.isAllegedToPlayer(playerId)
        ]
        totalEnemySpawnerHealthPoints = np.sum(
            [s.getHealthPoints() for s in claimedEnemySpawners])
        totalEnemyCount = np.sum([
            p.singuityCount for p in gameState.playerDictionary.values()
            if p.id != playerId
        ])
        virtualSinguityCount = np.max(
            [ownPlayer.singuityCount - totalEnemyCount * 1.1, 1])

        if ownPlayer.singuityCount == 0:
            ownForceMeanPosition = np.mean([s.position for s in ownSpawners],
                                           axis=0)
            ownForceStd = 1000
        else:
            ownForceMeanPosition = ownPlayer.singuitiesMeanPosition
            ownForceStd = ownPlayer.singuitiesStd

        return PhysicsEstimator.estimateSpawnerToZeroHealthDurationIntegral(virtualSinguityCount, totalEnemySpawnerHealthPoints, [], False, 1.0) * 5\
               + np.sum([PhysicsEstimator.estimateMovementDuration(ownForceMeanPosition, s.position, ownForceStd) for s in claimedEnemySpawners]) * 5\
               + gameState.frameCount