text: import random
  from datetime import datetime, time, timedelta, date
  from random import shuffle
  import pickle as pk
  import sys
  from django.db import models
  from django.db.models import Q, Avg
  from django.utils.translation import ugettext_noop, ugettext as _
  from django.core.urlresolvers import reverse
  from django.shortcuts import get_object_or_404
  from wouso.core.user.models import Player
  from wouso.core.magic.manager import InsufficientAmount
  from wouso.core.qpool.models import Question
  from wouso.core.qpool import get_questions_with_category, register_category
  from wouso.core.game.models import Game
  from wouso.core import scoring, signals
  from wouso.core.god import God
  from wouso.interface.apps.messaging.models import Message
  import logging
  
  
  class ChallengeException(Exception):
      pass
  
  
  class ChallengeUser(Player):
      """ Extension of the userprofile, customized for challenge """
  
      last_launched = models.DateTimeField(blank=True, null=True)
  
      def is_eligible(self):
          return God.user_is_eligible(self, ChallengeGame)
  
      def can_launch(self):
          """ Check if 1 challenge per day restriction apply
          """
          now = datetime.now()
          today_start = datetime.combine(now, time())
          today_end = datetime.combine(now, time(23, 59, 59))
          logging.info("today_start: %s, today_end: %s, self.last_launched: %s" % (today_start, today_end, self.last_launched))
          if self.magic.has_modifier('challenge-cannot-challenge'):
              return False
          if not self.last_launched:
              return True
          if today_start <= self.last_launched <= today_end:
              return False
          return True
  
      def has_enough_points(self):
          """ Check if the user has 30 points to challenge
          """
          REQ_POINTS = 30
          return self.points >= REQ_POINTS
  
      def in_same_division(self, user):
          from wouso.interface.top.models import TopUser
          position_diff = abs(self.get_extension(TopUser).position - user.get_extension(TopUser).position)
          if position_diff <= 20:
              return True
          return False
  
      def can_challenge(self, user):
          """ Check if the target user is available.
          """
          user = user.get_extension(ChallengeUser)
          if self.user == user.user:
              # Cannot challenge myself
              logging.info("User cannot challenge because it is the same user.")
              return False
          if user.magic.has_modifier('challenge-cannot-be-challenged'):
              logging.info("User cannot challenge due to magic modifier.")
              return False
          return God.user_can_interact_with(self, user, game=ChallengeGame)
  
      def has_one_more(self):
          return self.magic.has_modifier('challenge-one-more')
  
      def do_one_more(self):
          try:
              modifier = self.magic.use_modifier('challenge-one-more', 1)
          except InsufficientAmount:
              return False
          self.set_last_launched(None)
          self.save()
  
          signal_msg = ugettext_noop('used {artifact} to enable one more challenge.')
          signals.addActivity.send(sender=None, user_from=self,
                                   user_to=self,
                                   message=signal_msg,
                                   arguments=dict(artifact=modifier.artifact.title),
                                   game=ChallengeGame.get_instance())
          return True
  
      def can_play(self, challenge):
          return challenge.can_play(self)
  
      def launch_against(self, destination):
          destination = destination.get_extension(ChallengeUser)
  
          if destination.id == self.id:
              raise ChallengeException('Cannot launch against myself')
  
          if not self.can_launch():
              raise ChallengeException('Player cannot launch')
  
          if not self.in_same_division(destination):
              raise ChallengeException('You are not in the same division')
  
          if not self.can_challenge(destination):
              raise ChallengeException('Player cannot launch against this opponent')
  
          return Challenge.create(user_from=self, user_to=destination)
  
      def set_last_launched(self, value):
          logging.info("set last launched of %s to %s" % (hex(id(self)), value))
          self.last_launched = value
          self.save()
  
      def get_all_challenges(self):
          return Challenge.objects.exclude(status=u'L').filter(Q(user_from__user=self) | Q(user_to__user=self))
  
      def get_won_challenges(self):
          return self.get_all_challenges().filter(winner=self)
  
      def get_lost_challenges(self):
          return self.get_all_challenges().exclude(winner=self).exclude(status=u'R').exclude(status=u'D')
  
      def get_draw_challenges(self):
          return self.get_all_challenges().filter(status=u'D')
  
      def get_refused_challenges(self):
          return self.get_all_challenges().filter(status=u'R')
  
      def get_win_percentage(self):
          w = self.get_won_challenges().count()
          d = self.get_draw_challenges().count()
          l = self.get_lost_challenges().count()
          # 1 draw counts as 1/2 win, 1/2 loss
          return 0 if w + d == 0 else (w + d / 2.0) / (w + l + d) * 100
  
      def get_random_opponent(self):
          players = ChallengeUser.objects.exclude(user=self.user)
          players = players.exclude(race__can_play=False)
          players = [p for p in players if self.can_challenge(p) and self.in_same_division(p)]
          if not players:
              return False
          # selects the user to be challenged
          import random
          i = random.randrange(0, len(players))
          return players[i]
  
      def get_related_challenges(self, target_user):
          # Gets the challenges between self and target_user
          chall_total = Challenge.objects.filter(Q(user_from__user=self) |
                                                 Q(user_to__user=self)).exclude(status=u'L')
          chall_total = chall_total.filter(Q(user_from__user=target_user) |
                                           Q(user_to__user=target_user)).order_by('-date')
          return chall_total
  
      def get_stats(self):
          chall_total = self.get_all_challenges()
          chall_won = self.get_won_challenges()
          chall_sent = chall_total.filter(user_from__user=self)
          chall_rec = chall_total.filter(user_to__user=self)
  
          n_chall_sent = chall_sent.count()
          n_chall_rec = chall_rec.count()
          n_chall_played = chall_sent.count() + chall_rec.count()
          n_chall_won = chall_won.count()
          n_chall_ref = self.get_refused_challenges().count()
          all_participation = Participant.objects.filter(user=self)
  
          opponents_from = list(set(map(lambda x: x.user_to.user, chall_sent)))
          opponents_to = list(set(map(lambda x: x.user_from.user, chall_rec)))
          opponents = list(set(opponents_from + opponents_to))
  
          result = []
          for op in opponents:
              chall_against_op = chall_total.filter(Q(user_to__user=op) | Q(user_from__user=op))
              won = chall_against_op.filter(Q(status=u'P') & Q(winner=self)).count()
              lost = chall_against_op.filter(Q(status=u'P') & Q(winner=op)).count()
              draw = chall_against_op.filter(Q(status=u'D')).count()
              refused = chall_against_op.filter(Q(status=u'R')).count()
              total = won + lost + draw + refused
              result.append((op, won, lost, draw, refused, total))
  
          # Sort results by 'total'
          result = sorted(result, key=lambda by: by[5], reverse=True)
  
          average_time = all_participation.aggregate(Avg('seconds_took'))['seconds_took__avg']
          average_score = all_participation.aggregate(Avg('score'))['score__avg']
  
          if average_time is None:
              average_time = 0
          if average_score is None:
              average_score = 0
  
          win_percentage = self.get_win_percentage()
  
          stats = dict(n_chall_played=n_chall_played, n_chall_won=n_chall_won,
                       n_chall_sent=n_chall_sent, n_chall_rec=n_chall_rec,
                       n_chall_ref=n_chall_ref, current_player=self,
                       average_time=average_time, average_score=average_score,
                       win_percentage=win_percentage, opponents=result)
          return stats
  
  Player.register_extension('challenge', ChallengeUser)
  
  
  class Participant(models.Model):
      user = models.ForeignKey(ChallengeUser)
      start = models.DateTimeField(null=True, blank=True)
      seconds_took = models.IntegerField(null=True, blank=True)
      played = models.BooleanField(default=False)
      responses = models.TextField(default='', blank=True, null=True)
      # score = models.FloatField(null=True, blank=True)
      score = models.IntegerField(null=True, blank=True)
  
      @property
      def challenge(self):
          try:
              return Challenge.objects.get(Q(user_from=self) | Q(user_to=self))
          except Challenge.DoesNotExist:
              return None
  
      def __unicode__(self):
          return unicode(self.user)
  
  
  class Challenge(models.Model):
      STATUS = (
          ('L', 'Launched'),
          ('A', 'Accepted'),
          ('R', 'Refused'),
          ('P', 'Played'),
          ('D', 'Draw'),
      )
      user_from = models.ForeignKey(Participant, related_name="user_from")
      user_to = models.ForeignKey(Participant, related_name="user_to")
      date = models.DateTimeField()
      status = models.CharField(max_length=1, choices=STATUS, default='L')
      winner = models.ForeignKey(ChallengeUser, related_name="winner", null=True, blank=True)
      questions = models.ManyToManyField(Question)
      owner = models.ForeignKey(Game, null=True, blank=True)
  
      nr_q = 0
      LIMIT = 5
      TIME_LIMIT = 300  # seconds
      TIME_SAFE = 10    # seconds more
      WARRANTY = True   # on/off switch
      SCORING = True    # on/off switch for rewarding challenges
  
      @classmethod
      def create(cls, user_from, user_to, ignore_questions=False):
          """ Assigns questions, and returns the number of assigned q """
          questions = [q for q in get_questions_with_category('challenge')]
          if (len(questions) < cls.LIMIT) and not ignore_questions:
              raise ChallengeException('Too few questions')
          shuffle(questions)
  
          questions_qs = questions[:cls.LIMIT]
          challenge = cls.create_custom(user_from, user_to, questions_qs)
  
          # set last_launched
          user_from = user_from.get_extension(ChallengeUser)
          user_from.set_last_launched(datetime.now())
  
          return challenge
  
      @classmethod
      def create_custom(cls, player_from, player_to, questions_qs, game=None):
          user_from, user_to = player_from.get_extension(ChallengeUser), player_to.get_extension(ChallengeUser)
          uf, ut = Participant.objects.create(user=user_from), Participant.objects.create(user=user_to)
  
          c = Challenge.objects.create(user_from=uf, user_to=ut, date=datetime.now(), owner=game)
          for q in questions_qs:
              c.questions.add(q)
  
          c.manager.created()
          return c
  
      @staticmethod
      def get_expired(today):
          """
          Return all expired candidate challenges at given date.
          """
          yesterday = today + timedelta(days=-1)
          return Challenge.objects.filter(date__lt=yesterday).filter(Q(status='A') | Q(status='L'))
  
      @classmethod
      def exist_last_day(cls, today, user_from, user_to):
          """
          Return true if there are any challenges between the two users in the last day
          """
          yesterday = today + timedelta(days=-1)
          return Challenge.objects.filter(user_from__user=user_from, user_to__user=user_to, date__gt=yesterday).count() > 0
  
      @classmethod
      def last_between(cls, user_from, user_to):
          try:
              return Challenge.objects.filter(user_from__user=user_from, user_to__user=user_to).exclude(status='R').order_by('-date')[0]
          except IndexError:
              return None
  
      @property
      def participants(self):
          return (self.user_from, self.user_to)
  
      @property
      def manager(self):
          if self.owner is None or self.owner.name == 'ChallengeGame':
              return ChallengeGame.get_manager(self)
          return self.owner.get_real_object().get_manager(self)
  
      def participant_for_player(self, player):
          player = player.get_extension(ChallengeUser)
          if self.user_from.user == player:
              return self.user_from
          elif self.user_to.user == player:
              return self.user_to
          raise ValueError('Not participating in this challenge')
  
      def accept(self):
          self.status = 'A'
          self.save()
          self.manager.accept()
  
      def refuse(self, auto=False):
          self.status = 'R'
          self.save()
          self.manager.refuse(auto)
  
      def cancel(self):
          self.manager.cancel()
          self.user_from.user.set_last_launched(None)
          self.delete()
  
      def set_start(self, user):
          """ Update user.start and set playing time to now
          This is called when one of the participants sees the challenge for the
          first time.
          After this call, challenge will be visible to him for 5 minutes
          TODO: check it hasn't been already started in the past"""
  
          partic = self.participant_for_player(user)
  
          partic.start = datetime.now()
          partic.save()
  
      def set_won_by_player(self, player):
          if self.user_from.user == player:
              self.user_from.score = 1
              self.user_to.score = 0
          else:
              self.user_from.score = 0
              self.user_to.score = 1
  
          self.user_from.played = True
          self.user_to.played = True
  
          # process expired activity
          self.played()
  
      def set_expired(self):
          if not self.user_from.played:
              self.user_from.score = 0.0
              self.user_from.played = True
  
          if not self.user_to.played:
              self.user_to.score = 0.0
              self.user_to.played = True
  
          # send expired activity
          self.played()
  
      def time_for_user(self, user):
          now = datetime.now()
          partic = self.participant_for_player(user)
  
          tlimit = scoring.timer(user, ChallengeGame, 'chall-timer', level=user.level_no)
  
          return tlimit - (now - partic.start).seconds
  
      def is_expired(self, participant):
          """ This function assumes that seconds_took has been set.
          If the user didn't submit the challenge, this will return False
          which might be incorrect.
          TODO: fix this to first check if user has submitted, and
          if not, verify with datetime.now - participant.start.
          """
          if participant.start is None:
              return False
          if participant.seconds_took < Challenge.TIME_LIMIT + Challenge.TIME_SAFE:
              return False
          return True
  
      def is_expired_for_user(self, user):
          """ Check if the challenge has expired for the user
          """
          partic = self.participant_for_player(user)
          return self.is_expired(partic)
  
      def is_started_for_user(self, user):
          """ Check if the challenge has already started for the given user"""
          partic = self.participant_for_player(user)
          if partic.start is None:
              return False
          return True
  
      def extraInfo(self, user_won, user_lost):
          '''returns a string with extra info for a string such as User 1 finished the challenge in $SECONDS seconds
          (or $MINUTES minutes and seconds) and scored X points)'''
  
          def formatTime(seconds):
              if seconds is None:
                  return _('expired')
              ret = ''
              if seconds < 60:
                  ret = _("{seconds} seconds").format(seconds=seconds)
              elif seconds == 60:
                  ret = _('1 minute')
              elif seconds % 60 == 0:
                  ret = _('{minutes} minutes').format(minutes=(seconds/60))
              elif 60 < seconds < 120:
                  ret = _('1 minute and {s} seconds').format(s=seconds % 60)
              else:
                  ret = _('{m} minutes and {s} seconds').format(m=seconds / 60, s=seconds % 60)
              return _('(in {time})').format(time=ret)
  
          return '%dp %s - %dp %s' % (user_won.score, formatTime(user_won.seconds_took),
                                      user_lost.score, formatTime(user_lost.seconds_took))
  
      def played(self):
          """ Both players have played, save and score
          Notice the fact this is the only function where the scoring is affected
          """
          result = self.manager.get_result()
  
          if result == 'draw':
              self.status = 'D'
          else:
              self.status = 'P'
              self.user_won, self.user_lost = result
              self.winner = self.user_won.user
          self.save()
  
          self.manager.handle_result()
          self.manager.score()
  
      @classmethod
      def _calculate_points(cls, responses):
          """ Response contains a dict with question id and checked answers ids.
          Example:
              {1 : [14,], ...}, - has answered answer with id 14 at the question with id 1
          """
          points = 0.0
          results = {}
          for r, v in responses.iteritems():
              checked, missed, wrong = 0, 0, 0
              q = Question.objects.get(id=r)
              for a in q.answers.all():
                  if a.correct:
                      if a.id in v:
                          checked += 1
                      else:
                          missed += 1
                  elif a.id in v:
                      wrong += 1
              correct_count = len([a for a in q.answers if a.correct])
              wrong_count = len([a for a in q.answers if not a.correct])
              if correct_count == 0:
                  qpoints = 1 if (len(v) == 0) else 0
              elif wrong_count == 0:
                  qpoints = 1 if (len(v) == q.answers.count()) else 0
              else:
                  qpoints = float(checked) / correct_count - float(wrong) / wrong_count
              qpoints = qpoints if qpoints > 0 else 0
              points += qpoints
              results[r] = ((checked, correct_count))
          return {'points': int(100.0 * points), 'results': results}
  
      def set_played(self, user, responses):
          """ Set user's results. If both users have played, also update self and activity. """
          user_played = self.participant_for_player(user)
  
          user_played.seconds_took = (datetime.now() - user_played.start).seconds
          user_played.played = True
          user_played.responses = pk.dumps(responses)
          exp = False
          if self.is_expired(user_played):
              exp = True
              user_played.score = 0.0
          else:
              results = Challenge._calculate_points(responses)
              user_played.score = results['points']
          user_played.save()
  
          if self.user_to.played and self.user_from.played:
              self.played()
  
          if exp:
              results = {}
              results['results'] = {}
              results['points'] = '0.0 (expired)'
          return results
  
      def can_play(self, user):
          """ Check if user can play this challenge"""
          try:
              partic = self.participant_for_player(user)
          except ValueError:
              return False
  
          if partic.played:
              return False
  
          return self.is_runnable()
  
      def is_launched(self):
          return self.status == 'L'
  
      def is_runnable(self):
          return self.status == 'A'
  
      def is_refused(self):
          return self.status == 'R'
  
      def is_draw(self):
          return self.status == 'D'
  
      def is_played(self):
          return self.status == 'P'
  
      def title(self):
          return u"%s vs %s" % (self.user_from, self.user_to)
  
      def __unicode__(self):
          return "%s vs %s (%s) - %s [%d] " % (
              unicode(self.user_from),
              unicode(self.user_to),
              self.date,
              self.status,
              self.questions.count())
  
  
  class ChallengeManager(object):
      def __init__(self, challenge):
          self.challenge = challenge
  
      def created(self):
          pass
  
      def accept(self):
          pass
  
      def refuse(self):
          raise NotImplementedError
  
      def cancel(self):
          raise NotImplementedError
  
      def get_result(self):
          raise NotImplementedError
  
      def handle_result(self):
          """ Called after status is one of: 'D', 'P'
          """
          pass
  
      def score(self):
          raise NotImplementedError
  
  
  class DefaultChallengeManager(ChallengeManager):
      def created(self):
          if self.challenge.WARRANTY:
              # take 3 points from user_from
              scoring.score(self.challenge.user_from.user, ChallengeGame, 'chall-warranty', external_id=self.challenge.id)
  
      def accept(self):
          if self.challenge.WARRANTY:
              # take warranty from user_to
              scoring.score(self.challenge.user_to.user, ChallengeGame, 'chall-warranty', external_id=self.challenge.id)
  
      def refuse(self, auto):
          self.challenge.user_from.user.set_last_launched(None)
  
          # send activity signal
          if auto:
              signal_msg = ugettext_noop('has refused challenge from {chall_from} (expired)')
          else:
              signal_msg = ugettext_noop('has refused challenge from {chall_from}')
          action_msg = 'chall-refused'
          signals.addActivity.send(sender=None,
                                   user_from=self.challenge.user_to.user,
                                   user_to=self.challenge.user_from.user,
                                   message=signal_msg,
                                   arguments=dict(chall_from=self.challenge.user_from),
                                   action=action_msg,
                                   game=ChallengeGame.get_instance())
          self.challenge.save()
          if self.challenge.WARRANTY:
              # give warranty back to initiator
              scoring.unset(self.challenge.user_from.user, ChallengeGame, 'chall-warranty', external_id=self.challenge.id)
  
      def cancel(self):
          if self.challenge.WARRANTY:
              # give warranty back to initiator
              scoring.unset(self.challenge.user_from.user, ChallengeGame, 'chall-warranty', external_id=self.challenge.id)
  
      def get_result(self):
          for u in (self.challenge.user_to, self.challenge.user_from):
              if u.user.magic.has_modifier('challenge-always-lose'):
                  u.score = -1
  
          if self.challenge.user_to.score > self.challenge.user_from.score:
              result = (self.challenge.user_to, self.challenge.user_from)
          elif self.challenge.user_from.score > self.challenge.user_to.score:
              result = (self.challenge.user_from, self.challenge.user_to)
          else:  # draw game
              result = 'draw'
  
          return result
  
      def handle_result(self):
          if self.challenge.status == 'D':
              action_msg = "chall-draw"
              signal_msg = ugettext_noop('draw result between {user_from} and {user_to}:\n{extra}')
              signals.addActivity.send(sender=None, user_from=self.challenge.user_to.user,
                                       user_to=self.challenge.user_from.user,
                                       message=signal_msg,
                                       arguments=dict(user_to=self.challenge.user_to, user_from=self.challenge.user_from,
                                                      extra=self.challenge.extraInfo(self.challenge.user_from, self.challenge.user_to)),
                                       action=action_msg,
                                       game=ChallengeGame.get_instance())
          elif self.challenge.status == 'P':
              action_msg = "chall-won"
              signal_msg = ugettext_noop('won challenge with {user_lost}: {extra}')
              signals.addActivity.send(sender=None, user_from=self.challenge.user_won.user,
                                       user_to=self.challenge.user_lost.user,
                                       message=signal_msg,
                                       arguments=dict(user_lost=self.challenge.user_lost, id=self.challenge.id,
                                                      extra=self.challenge.extraInfo(self.challenge.user_won, self.challenge.user_lost)),
                                       action=action_msg,
                                       game=ChallengeGame.get_instance())
  
      def score(self):
          if not self.challenge.SCORING:
              return
  
          for u in (self.challenge.user_to, self.challenge.user_from):
                  u.percents = 100
  
          if self.challenge.status == 'D':
              scoring.score(self.challenge.user_to.user, ChallengeGame, 'chall-draw', percents=self.challenge.user_to.percents)
              scoring.score(self.challenge.user_from.user, ChallengeGame, 'chall-draw', percents=self.challenge.user_from.percents)
          else:
              diff_race = self.challenge.user_won.user.race != self.challenge.user_lost.user.race
              diff_class = self.challenge.user_won.user.group != self.challenge.user_lost.user.group
              diff_race = 1 if diff_race else 0
              diff_class = 1 if diff_class else 0
              winner_points = self.challenge.user_won.user.points
              loser_points = self.challenge.user_lost.user.points
  
              # Check for charge
              if self.challenge.user_won.user.magic.has_modifier('challenge-affect-scoring-won'):
                  self.challenge.user_won.percents += self.challenge.user_won.user.magic.modifier_percents('challenge-affect-scoring-won') - 100
  
              # Check for weakness
              if self.challenge.user_won.user.magic.has_modifier('challenge-affect-scoring-lost'):
                  self.challenge.user_won.percents += self.challenge.user_won.user.magic.modifier_percents('challenge-affect-scoring-lost') - 100
  
              # Check for frenzy on the winning player
              if self.challenge.user_won.user.magic.has_modifier('challenge-affect-scoring'):
                  self.challenge.user_won.percents += self.challenge.user_won.user.magic.modifier_percents('challenge-affect-scoring') - 100
  
              if self.challenge.WARRANTY:
                  # warranty not affected by percents
                  scoring.score(self.challenge.user_won.user, ChallengeGame, 'chall-warranty-return', external_id=self.challenge.id)
  
              scoring.score(self.challenge.user_won.user, ChallengeGame, 'chall-won',
                            external_id=self.challenge.id, percents=self.challenge.user_won.percents,
                            points=self.challenge.user_won.score, points2=self.challenge.user_lost.score,
                            different_race=diff_race, different_class=diff_class,
                            winner_points=winner_points, loser_points=loser_points)
  
              # Check for frenzy on the losing player
              if self.challenge.user_lost.user.magic.has_modifier('challenge-affect-scoring'):
                  self.challenge.user_lost.percents += self.challenge.user_lost.user.magic.modifier_percents('challenge-affect-scoring') - 100
  
              # Check for spell evade
              if self.challenge.user_lost.user.magic.has_modifier('challenge-evade'):
                  random.seed()
                  if random.random() < 0.20:
                      # He's lucky, no penalty, return warranty
                      scoring.score(self.challenge.user_lost.user, ChallengeGame, 'chall-warranty-return', external_id=self.challenge.id)
                      Message.send(sender=None, receiver=self.challenge.user_lost.user, subject="Challenge evaded", text="You have just evaded losing points in a challenge")
  
              scoring.score(self.challenge.user_lost.user, ChallengeGame, 'chall-lost',
                            external_id=self.challenge.id, percents=self.challenge.user_lost.percents,
                            points=self.challenge.user_lost.score, points2=self.challenge.user_lost.score)
  
  
  class ChallengeGame(Game):
      """ Each game must extend Game """
      class Meta:
          proxy = True
  
      QPOOL_CATEGORY = 'challenge'
  
      def __init__(self, *args, **kwargs):
          # Set parent's fields
          self._meta.get_field('verbose_name').default = "Challenges"
          self._meta.get_field('short_name').default = ""
          # the url field takes as value only a named url from module's urls.py
          self._meta.get_field('url').default = "challenge_index_view"
          super(ChallengeGame, self).__init__(*args, **kwargs)
  
      @staticmethod
      def get_active(user):
          """ Return a list of active (runnable) challenges for a user """
          user = user.get_extension(ChallengeUser)
          try:
              challs = [p.challenge for p in Participant.objects.filter(
                  Q(user_id=user.id, played=False)).order_by('-id') if p.challenge.is_launched() or p.challenge.is_runnable()]
          except Participant.DoesNotExist:
              challs = []
          return challs
  
      @staticmethod
      def get_played(user):
          """ Return a list of played (scored TODO) challenges for a user """
          try:
              challs = [p.challenge for p in Participant.objects.filter(
                  Q(user_id=user.id, played=True)).order_by('-id')]
          except Participant.DoesNotExist:
              challs = []
          return challs
  
      @staticmethod
      def get_expired(user):
          """ Return a list of status != L/A challenges, where played = False """
          # TODO
          return []
  
      @classmethod
      def get_formulas(kls):
          """ Returns a list of formulas used by qotd """
          fs = []
          chall_game = kls.get_instance()
          fs.append(dict(name='chall-won', expression='points=6+{different_race}+{different_class}',
                         owner=chall_game.game,
                         description='Points earned when winning a challenge. Arguments: different_race (int 0,1), different_class (int 0,1)'))
          fs.append(dict(name='chall-lost', expression='points=2',
                         owner=chall_game.game,
                         description='Points earned when losing a challenge'))
          fs.append(dict(name='chall-draw', expression='points=4',
                         owner=chall_game.game,
                         description='Points earned when drawing a challenge'))
          fs.append(dict(name='chall-warranty', expression='points=-3',
                         owner=chall_game.game,
                         description='Points taken as a warranty for challenge'))
          fs.append(dict(name='chall-warranty-return', expression='points=3',
                         owner=chall_game.game,
                         description='Points given back as a warranty taken for challenge'))
          fs.append(dict(name='chall-timer',
                         expression='tlimit=300 - 5 * ({level} - 1)', owner=chall_game.game,
                         description='Seconds left for a user in challenge'))
          return fs
  
      @classmethod
      def get_modifiers(kls):
          """
          Challenge game modifiers
          """
          return ['challenge-one-more',  # challenge twice a day, positive
                  'challenge-cannot-be-challenged',  # reject incoming challenges, negative
                  'challenge-cannot-challenge',  # reject outgoing challenges, negative
                  'challenge-always-lose',  # lose regardless the result, negative
                  'challenge-affect-scoring',  # affect scoring by positive/negative percent
                  'challenge-affect-scoring-won',  # affect scoring by positive/negative percent for win challenges
                  'challenge-evade',  # 33% chance player does not lose points in a challenge
                  ]
  
      @classmethod
      def management_task(cls, now=None, stdout=sys.stdout):
          now = now if now else datetime.now()
          today = now.date()
          challenges = Challenge.get_expired(today)
          stdout.write(' Updating %d expired challenges (at %s)\n' % (len(challenges), today))
          for c in challenges:
              if c.is_launched():
                  # launched before yesterday, automatically refuse
                  c.refuse(auto=True)
              else:
                  # launched and accepted before yesterday, but not played by both
                  c.set_expired()
  
      @classmethod
      def get_api(kls):
          from api import ChallengesHandler, ChallengeLaunch, ChallengeHandler, ChallengeGetRandom
  
          return {r'^challenge/list/$': ChallengesHandler,
                  r'^challenge/random/$': ChallengeGetRandom,
                  r'^challenge/launch/(?P<player_id>\d+)/$': ChallengeLaunch,
                  r'^challenge/(?P<challenge_id>\d+)/$': ChallengeHandler,
                  r'^challenge/(?P<challenge_id>\d+)/(?P<action>refuse|cancel|accept)/$': ChallengeHandler,
                  }
  
      @classmethod
      def get_manager(cls, challenge):
          return DefaultChallengeManager(challenge)
  
  
  register_category(ChallengeGame.QPOOL_CATEGORY, ChallengeGame)
  
  
  # Hack for having participants in sync
  def challenge_post_delete(sender, instance, **kwargs):
      """ For some reason, this is called twice. Needs investigantion
      Also, in django devele, on_delete=cascade will fix this hack
      """
      try:
          instance.user_from.delete()
          instance.user_to.delete()
      except:
          pass
  models.signals.post_delete.connect(challenge_post_delete, Challenge)
  