* Eine Testsuite um Mahrjong Ranking Berechnungen zu testen * Erste Arbeiten um die Workarounds aus dem "utils" Paket los zu werden. * Vieles am Code umformatiert für PEP8 conformität
639 lines
26 KiB
Python
639 lines
26 KiB
Python
# -*- encoding: utf-8 -*-
|
|
|
|
# TODO: Rankings archiv Flag erstellen, womit sie nicht mehr neuberechnet
|
|
# werden dürfen.
|
|
|
|
from __future__ import division
|
|
|
|
from django.conf import settings
|
|
from django.core.exceptions import ValidationError
|
|
from django.core.urlresolvers import reverse
|
|
from django.db import models
|
|
from django.utils import timezone
|
|
from django.utils.translation import ugettext as _
|
|
|
|
from events.models import Event
|
|
from . import KYU_RANKS, DAN_RANKS, DAN_RANKS_DICT, logger, set_dirty
|
|
from . import managers
|
|
|
|
kyu_dan_rankings = set()
|
|
ladder_rankings = set()
|
|
ladder_seasons = set()
|
|
|
|
|
|
class EventRanking(models.Model):
|
|
"""
|
|
Event Rankings funktionieren genauso wie Season Rankings.
|
|
Sie beschränken sich aber auf einen Event und werden nur dann angestossen,
|
|
wenn der Event als Turnier markiert wurde.
|
|
"""
|
|
user = models.ForeignKey(settings.AUTH_USER_MODEL)
|
|
event = models.ForeignKey(Event)
|
|
placement = models.PositiveIntegerField(blank=True, null=True)
|
|
avg_placement = models.FloatField(default=4)
|
|
avg_score = models.FloatField(default=0)
|
|
hanchan_count = models.PositiveIntegerField(default=0)
|
|
good_hanchans = models.PositiveIntegerField(default=0)
|
|
won_hanchans = models.PositiveIntegerField(default=0)
|
|
|
|
class Meta(object):
|
|
ordering = ('placement', 'avg_placement', '-avg_score',)
|
|
|
|
def get_absolute_url(self):
|
|
return reverse('event-ranking', args=[self.event_id])
|
|
|
|
def recalculate(self):
|
|
"""
|
|
Berechnet die durschnittliche Platzierung und Punkte, u.v.m. neu.
|
|
|
|
Diese Daten werden benötigt um die Platzierung zu erstellen. Sie
|
|
können zwar sehr leicht errechnet werden, es macht trotzdem Sinn
|
|
sie zwischen zu speichern.
|
|
"""
|
|
logger.info(
|
|
u'Recalculate EventRanking for Player %s in %s',
|
|
self.user, self.event.name
|
|
)
|
|
sum_placement = 0.0
|
|
sum_score = 0.0
|
|
event_hanchans = Hanchan.objects.confirmed_hanchans(
|
|
user=self.user_id,
|
|
event=self.event_id
|
|
)
|
|
self.hanchan_count = event_hanchans.count()
|
|
self.won_hanchans = 0
|
|
self.good_hanchans = 0
|
|
|
|
if self.hanchan_count <= 0:
|
|
self.delete()
|
|
else:
|
|
for hanchan in event_hanchans:
|
|
hanchan.get_playerdata(user=self.user)
|
|
sum_placement += hanchan.placement
|
|
sum_score += hanchan.game_score
|
|
self.won_hanchans += 1 if hanchan.placement == 1 else 0
|
|
self.good_hanchans += 1 if hanchan.placement == 2 else 0
|
|
self.avg_placement = sum_placement / self.hanchan_count
|
|
self.avg_score = sum_score / self.hanchan_count
|
|
self.save(force_update=True)
|
|
|
|
|
|
class Hanchan(models.Model):
|
|
"""
|
|
Ein komplette Runde Mahjong, die aus genau 4 Spielern bestehen muss.
|
|
Es werden aber noch andere Tests durchgeführt, ob sie gültig ist.
|
|
Außerdem gehört jede Hanchan zu einer Veranstaltung.
|
|
"""
|
|
event = models.ForeignKey(Event)
|
|
start = models.DateTimeField(
|
|
_('Start'),
|
|
help_text=_('This is crucial to get the right Hanchans that scores')
|
|
)
|
|
|
|
player1 = models.ForeignKey(
|
|
settings.AUTH_USER_MODEL,
|
|
on_delete=models.CASCADE,
|
|
related_name='user_hanchan+',
|
|
verbose_name=_('Player 1'))
|
|
player1_input_score = models.IntegerField(_('Score'))
|
|
player1_game_score = models.PositiveIntegerField(
|
|
_('Score'), default=0, editable=False)
|
|
player1_placement = models.PositiveSmallIntegerField(
|
|
default=0, editable=False)
|
|
player1_kyu_points = models.SmallIntegerField(
|
|
blank=True, null=True, editable=False)
|
|
player1_dan_points = models.SmallIntegerField(
|
|
blank=True, null=True, editable=False)
|
|
player1_bonus_points = models.SmallIntegerField(
|
|
blank=True, null=True, editable=False)
|
|
player1_comment = models.CharField(
|
|
_('Comment'), blank=True, max_length=255, editable=False)
|
|
|
|
player2 = models.ForeignKey(
|
|
settings.AUTH_USER_MODEL,
|
|
on_delete=models.CASCADE,
|
|
related_name='user_hanchan+',
|
|
verbose_name=_('Player 2'))
|
|
player2_input_score = models.IntegerField(_('Score'))
|
|
player2_game_score = models.PositiveIntegerField(
|
|
_('Score'), default=0, editable=False)
|
|
player2_placement = models.PositiveSmallIntegerField(
|
|
default=0, editable=False)
|
|
player2_kyu_points = models.SmallIntegerField(
|
|
blank=True, null=True, editable=False)
|
|
player2_dan_points = models.SmallIntegerField(
|
|
blank=True, null=True, editable=False)
|
|
player2_bonus_points = models.SmallIntegerField(
|
|
blank=True, null=True, editable=False)
|
|
player2_comment = models.CharField(
|
|
_('Comment'), blank=True, max_length=255, editable=False)
|
|
|
|
player3 = models.ForeignKey(
|
|
settings.AUTH_USER_MODEL,
|
|
on_delete=models.CASCADE,
|
|
related_name='user_hanchan+',
|
|
verbose_name=_('Player 3'))
|
|
player3_input_score = models.IntegerField(_('Score'))
|
|
player3_game_score = models.PositiveIntegerField(
|
|
_('Score'), default=0, editable=False)
|
|
player3_placement = models.PositiveSmallIntegerField(
|
|
default=0, editable=False)
|
|
player3_kyu_points = models.SmallIntegerField(
|
|
blank=True, null=True, editable=False)
|
|
player3_dan_points = models.SmallIntegerField(
|
|
blank=True, null=True, editable=False)
|
|
player3_bonus_points = models.SmallIntegerField(
|
|
blank=True, null=True, editable=False)
|
|
player3_comment = models.CharField(
|
|
_('Comment'), blank=True, max_length=255, editable=False)
|
|
|
|
player4 = models.ForeignKey(
|
|
settings.AUTH_USER_MODEL,
|
|
on_delete=models.CASCADE,
|
|
related_name='user_hanchan+',
|
|
verbose_name=_('Player 4'))
|
|
player4_input_score = models.IntegerField(_('Score'))
|
|
player4_game_score = models.PositiveIntegerField(
|
|
_('Score'), default=0, editable=False)
|
|
player4_placement = models.PositiveSmallIntegerField(
|
|
default=0, editable=False)
|
|
player4_kyu_points = models.SmallIntegerField(
|
|
blank=True, null=True, editable=False)
|
|
player4_dan_points = models.SmallIntegerField(
|
|
blank=True, null=True, editable=False)
|
|
player4_bonus_points = models.SmallIntegerField(
|
|
blank=True, null=True, editable=False)
|
|
player4_comment = models.CharField(
|
|
_('Comment'), blank=True, max_length=255, editable=False)
|
|
|
|
comment = models.TextField(_('Comment'), blank=True)
|
|
confirmed = models.BooleanField(_('Has been Confirmed'), default=True,
|
|
help_text=_(
|
|
'Only valid and confirmed Hanchans '
|
|
'will be counted in the rating.')
|
|
)
|
|
player_names = models.CharField(max_length=255, editable=False)
|
|
season = models.PositiveSmallIntegerField(
|
|
_('Season'), editable=False, db_index=True)
|
|
objects = managers.HanchanManager()
|
|
|
|
class Meta(object):
|
|
ordering = ('-start',)
|
|
verbose_name = _(u'Hanchan')
|
|
verbose_name_plural = _(u'Hanchans')
|
|
|
|
def __str__(self):
|
|
return _("Hanchan from {0:%Y-%m-%d} at {0:%H:%M} with {1}").format(
|
|
self.start, self.player_names
|
|
)
|
|
|
|
def clean(self):
|
|
"""
|
|
Check if 4 different Players are attending the Game,
|
|
if the Game between the opening hours and the Hanchan has a total of
|
|
100.000 Points.
|
|
"""
|
|
errors = {}
|
|
score_sum = 0
|
|
player_set = set()
|
|
|
|
try:
|
|
self.clean_fields()
|
|
except ValidationError as exception:
|
|
exception.update_error_dict(errors)
|
|
return
|
|
|
|
for i in range(1, 5):
|
|
player = getattr(self, 'player%d' % i)
|
|
input_score = getattr(self, 'player%d_input_score' % i)
|
|
score_sum += input_score
|
|
game_score = input_score if input_score > 0 else 0
|
|
if player in player_set:
|
|
errors['player%d' % i] = _(
|
|
"%s can't attend the same game multiple times") % player
|
|
else:
|
|
player_set.add(player)
|
|
setattr(self, 'player%d_game_score' % i, game_score)
|
|
|
|
# Check if the game was played during the event
|
|
if self.start > timezone.now():
|
|
errors['start'] = _(
|
|
"Games in the future may not be added, Dr. Brown")
|
|
elif not (self.event.start <= self.start <= self.event.end):
|
|
errors['start'] = _("Only games during the event are allowed")
|
|
elif score_sum < 100000:
|
|
errors['player4_input_score'] = _(
|
|
'Gamescore is lower then 100.000 Pt.')
|
|
elif score_sum > 100000:
|
|
errors['player4_input_score'] = _('Gamescore is over 100.000 Pt.')
|
|
|
|
if errors:
|
|
raise ValidationError(errors)
|
|
|
|
def compute_player_placements(self):
|
|
u"""
|
|
Bestimmt die Platzierung eines der Spieler einer Hanchan und speichert
|
|
diese beim jeweiligen Spieler ab.
|
|
"""
|
|
logger.debug("Berechne die Platzierungen neu...")
|
|
player_names = []
|
|
other_player_placement = 0
|
|
other_player_game_score = 0
|
|
placement = 1
|
|
player_nr = 1
|
|
for player in self.player_list:
|
|
if player['game_score'] == other_player_game_score:
|
|
player['placement'] = other_player_placement
|
|
else:
|
|
player['placement'] = placement
|
|
setattr(self, "player%d" % player_nr, player['user'])
|
|
setattr(self, "player%d_input_score" %
|
|
player_nr, player['input_score'])
|
|
setattr(self, "player%d_game_score" %
|
|
player_nr, player['game_score'])
|
|
setattr(self, "player%d_placement" %
|
|
player_nr, player['placement'])
|
|
setattr(self, "player%d_kyu_points" %
|
|
player_nr, player['kyu_points'])
|
|
setattr(self, "player%d_dan_points" %
|
|
player_nr, player['dan_points'])
|
|
setattr(self, "player%d_bonus_points" %
|
|
player_nr, player['bonus_points'])
|
|
setattr(self, "player%d_comment" % player_nr, player['comment'])
|
|
other_player_placement = player['placement']
|
|
other_player_game_score = player['game_score']
|
|
player_names.append(player['user'].username)
|
|
player_nr += 1
|
|
placement += 1
|
|
self.player_names = ', '.join(player_names)
|
|
|
|
def get_absolute_url(self):
|
|
return "{url:s}#{id:d}".format(
|
|
url=reverse('event-hanchan-list', kwargs={'event': self.event_id}),
|
|
id=self.pk
|
|
)
|
|
|
|
def get_playerdata(self, user):
|
|
"""small workaround to access score, placement and points of a
|
|
specific user prominent in the user templates"""
|
|
for player in ('player1', 'player2', 'player3', 'player4'):
|
|
if getattr(self, player) == user:
|
|
self.user = user
|
|
self.input_score = getattr(self, '%s_input_score' % player)
|
|
self.game_score = getattr(self, '%s_game_score' % player)
|
|
self.placement = getattr(self, '%s_placement' % player)
|
|
self.kyu_points = getattr(self, '%s_kyu_points' % player)
|
|
self.dan_points = getattr(self, '%s_dan_points' % player)
|
|
self.bonus_points = getattr(self, '%s_bonus_points' % player)
|
|
self.player_comment = getattr(self, '%s_comment' % player)
|
|
|
|
def update_playerdata(self, user, **kwargs):
|
|
"""i small workaround to access score, placement of a specific user
|
|
prominent from a in the user templates"""
|
|
for player in ('player1', 'player2', 'player3', 'player4'):
|
|
if getattr(self, player) == user:
|
|
setattr(self, '%s_input_score' % player, self.input_score)
|
|
setattr(self, '%s_game_score' % player, self.game_score)
|
|
setattr(self, '%s_placement' % player, self.placement)
|
|
setattr(self, '%s_kyu_points' % player, self.kyu_points)
|
|
setattr(self, '%s_dan_points' % player, self.dan_points)
|
|
setattr(self, '%s_bonus_points' % player, self.bonus_points)
|
|
setattr(self, '%s_comment' % player, self.player_comment)
|
|
|
|
@property
|
|
def player_list(self):
|
|
player_list = []
|
|
for i in range(1, 5):
|
|
player_list.append({
|
|
'user': getattr(self, 'player%d' % i),
|
|
'input_score': getattr(self, 'player%d_input_score' % i),
|
|
'game_score': getattr(self, 'player%d_game_score' % i),
|
|
'placement': getattr(self, 'player%d_placement' % i),
|
|
'kyu_points': getattr(self, 'player%d_kyu_points' % i),
|
|
'dan_points': getattr(self, 'player%d_dan_points' % i),
|
|
'bonus_points': getattr(self, 'player%d_bonus_points' % i),
|
|
'comment': getattr(self, 'player%d_comment' % i),
|
|
})
|
|
# sort player by Score:
|
|
return sorted(player_list, key=lambda player: player['input_score'],
|
|
reverse=True)
|
|
|
|
def save(self, recalculate=True, **kwargs):
|
|
self.season = self.event.mahjong_season or self.start.year
|
|
self.full_clean()
|
|
if recalculate:
|
|
self.compute_player_placements()
|
|
update_ranking(sender=Hanchan, instance=self)
|
|
return models.Model.save(self, **kwargs)
|
|
|
|
|
|
class KyuDanRanking(models.Model):
|
|
u"""
|
|
Die Einstufung des Spielers im Kyu bzw. Dan System.
|
|
Im Gegensatz zum Ladder Ranking ist das nicht Saison gebunden.
|
|
Deswegen läuft es getrennt.
|
|
"""
|
|
user = models.OneToOneField(settings.AUTH_USER_MODEL)
|
|
dan = models.PositiveSmallIntegerField(blank=True, null=True)
|
|
dan_points = models.PositiveIntegerField(default=0)
|
|
kyu = models.PositiveSmallIntegerField(default=10, blank=True, null=True)
|
|
kyu_points = models.PositiveIntegerField(default=0)
|
|
won_hanchans = models.PositiveIntegerField(default=0)
|
|
good_hanchans = models.PositiveIntegerField(default=0)
|
|
hanchan_count = models.PositiveIntegerField(default=0)
|
|
legacy_date = models.DateField(blank=True, null=True)
|
|
legacy_hanchan_count = models.PositiveIntegerField(default=0)
|
|
legacy_dan_points = models.PositiveIntegerField(default=0)
|
|
legacy_kyu_points = models.PositiveIntegerField(default=0)
|
|
wins_in_a_row = 0
|
|
|
|
class Meta(object):
|
|
ordering = ('-dan', '-dan_points', '-kyu_points',)
|
|
verbose_name = _(u'Kyū/Dan Ranking')
|
|
verbose_name_plural = _(u'Kyū/Dan Rankings')
|
|
|
|
def __unicode__(self):
|
|
if self.dan_points is not None:
|
|
return u"%s - %d. Dan" % (self.user.username, self.dan or 1)
|
|
else:
|
|
return u"%s - %d. Kyu" % (self.user.username, self.kyu or 10)
|
|
|
|
def append_3_in_a_row_bonuspoints(self, hanchan):
|
|
u"""
|
|
Wenn der Spieler 3 Siege in Folge hatte, bekommt er so viele Punkte
|
|
das er einen Dan Rang aufsteigt. Dies wird als Kommentar abgespeichert,
|
|
um es besser nachvollziehen zu können.
|
|
"""
|
|
if self.dan and hanchan.placement == 1:
|
|
self.wins_in_a_row += 1
|
|
else:
|
|
self.wins_in_a_row = 0
|
|
|
|
if self.dan and self.wins_in_a_row >= 3 and self.dan < 9:
|
|
logger.info(
|
|
'adding bonuspoints for 3 wins in a row for %s', self.user)
|
|
new_dan_rank = self.dan + 1
|
|
new_dan_points = DAN_RANKS_DICT[new_dan_rank] + 1
|
|
bonus_points = new_dan_points - self.dan_points
|
|
|
|
logger.debug("Stats for %s:", self.user)
|
|
logger.debug("current dan_points: %d", self.dan_points)
|
|
logger.debug("current dan: %d", self.dan)
|
|
logger.debug(
|
|
"min required points for the next dan: %d", new_dan_points)
|
|
logger.debug("bonus points to add: %d", bonus_points)
|
|
|
|
hanchan.dan_points += bonus_points
|
|
hanchan.bonus_points += bonus_points
|
|
hanchan.player_comment += '3 Siege in Folge: +%d Punkte auf den ' \
|
|
'%d. Dan. ' % (
|
|
bonus_points, new_dan_rank)
|
|
self.dan_points += bonus_points
|
|
self.wins_in_a_row = 0
|
|
|
|
# TODO: Komplett Überabreiten!
|
|
def append_tournament_bonuspoints(self, hanchan):
|
|
"""
|
|
Prüft ob es die letzte Hanchan in einem Turnier war. Wenn ja werden
|
|
bei Bedarf Bonuspunkte vergeben, falls der Spieler das Turnier
|
|
gewonnen hat.
|
|
:param hanchan: Ein Player Objekt
|
|
"""
|
|
bonus_points = 0
|
|
hanchans_this_event = Hanchan.objects.user_hanchans(
|
|
user=self.user, event=hanchan.event
|
|
).order_by('-start')
|
|
last_hanchan_this_event = hanchans_this_event[0]
|
|
if hanchan != last_hanchan_this_event:
|
|
# Das braucht nur am Ende eines Turnieres gemacht werden.
|
|
return False
|
|
else:
|
|
event_ranking = EventRanking.objects.get(
|
|
user=self.user,
|
|
event=hanchan.event
|
|
)
|
|
if event_ranking.placement == 1:
|
|
bonus_points += 4
|
|
hanchan.player_comment += u'+4 Punkte Turnier gewonnen. '
|
|
if event_ranking.avg_placement == 1:
|
|
bonus_points += 8
|
|
hanchan.player_comment += u'+8 Pkt: alle Spiele des Turnieres gewonnen. '
|
|
|
|
if bonus_points and self.dan:
|
|
hanchan.dan_points += bonus_points
|
|
self.dan_points += bonus_points
|
|
elif bonus_points:
|
|
hanchan.kyu_points += bonus_points
|
|
self.kyu_points += bonus_points
|
|
hanchan.bonus_points += bonus_points
|
|
return True
|
|
|
|
def get_absolute_url(self):
|
|
if self.dan or self.dan_points > 0:
|
|
return reverse('player-dan-score', args=[self.user.username])
|
|
else:
|
|
return reverse('player-kyu-score', args=[self.user.username])
|
|
|
|
def recalculate(self, hanchan_start=None):
|
|
"""
|
|
Fetches all valid Hanchans from this Player and recalculates his
|
|
Kyu/Dan Ranking.
|
|
"""
|
|
self.dan = None
|
|
self.dan_points = self.legacy_dan_points or 0
|
|
self.kyu = None
|
|
self.kyu_points = self.legacy_kyu_points or 0
|
|
self.hanchan_count = self.legacy_hanchan_count or 0
|
|
|
|
self.good_hanchans = 0
|
|
self.won_hanchans = 0
|
|
|
|
logger.info(
|
|
"recalculating Kyu/Dan points for %s since %s...",
|
|
self.user, str(hanchan_start)
|
|
)
|
|
self.update_rank()
|
|
valid_hanchans = Hanchan.objects.confirmed_hanchans(user=self.user)
|
|
valid_hanchans = valid_hanchans.order_by('start')
|
|
if self.legacy_date:
|
|
valid_hanchans = valid_hanchans.filter(start__gt=self.legacy_date)
|
|
""" TODO: Hanchan Punkte nur neu berechnen wenn sie vor hachan_start
|
|
lag. Es müssen aber alle durch die Schleife rennen, damit die Punkte
|
|
richtig gezählt werden."""
|
|
if hanchan_start:
|
|
valid_hanchans = valid_hanchans.filter(start__gte=hanchan_start)
|
|
self.hanchan_count += valid_hanchans.count()
|
|
for hanchan in valid_hanchans:
|
|
hanchan.get_playerdata(self.user)
|
|
hanchan.bonus_points = 0
|
|
hanchan.player_comment = u""
|
|
self.update_hanchan_points(hanchan)
|
|
if hanchan.event.mahjong_tournament:
|
|
self.append_tournament_bonuspoints(hanchan)
|
|
self.update_rank()
|
|
self.append_3_in_a_row_bonuspoints(hanchan)
|
|
self.update_rank()
|
|
self.won_hanchans += 1 if hanchan.placement == 1 else 0
|
|
self.good_hanchans += 1 if hanchan.placement == 2 else 0
|
|
hanchan.update_playerdata(self.user)
|
|
hanchan.save(recalculate=False)
|
|
logger.debug(
|
|
'id: %(id)d, start: %(start)s, placement: %(placement)d, '
|
|
'score: %(score)d, kyu points: %(kyu_points)d, dan points: '
|
|
'%(dan_points)d, bonus points: %(bonus_points)d',
|
|
{'id': hanchan.pk, 'start': hanchan.start,
|
|
'placement': hanchan.placement, 'score': hanchan.game_score,
|
|
'kyu_points': hanchan.kyu_points or 0,
|
|
'dan_points': hanchan.dan_points or 0,
|
|
'bonus_points': hanchan.bonus_points or 0}
|
|
)
|
|
self.save(force_update=True)
|
|
|
|
def update_hanchan_points(self, hanchan):
|
|
"""
|
|
Berechne die Kyu bzw. Dan Punkte für eine Hanchan neu.
|
|
:type hanchan: Hanchan
|
|
:param hanchan: Das Player Objekt das neuberechnet werden soll.
|
|
"""
|
|
hanchan.kyu_points = None
|
|
hanchan.dan_points = None
|
|
if hanchan.event.mahjong_tournament:
|
|
# Für Turniere gelten andere Regeln zur Punktevergabe:
|
|
# 1. Platz 4 Punkte
|
|
# 2. Platz 3 Punkte
|
|
# 3. Platz 2 Punkte
|
|
# 4. Platz 1 Punkt
|
|
tourney_points = 4 - hanchan.placement
|
|
if self.dan:
|
|
hanchan.dan_points = tourney_points
|
|
else:
|
|
hanchan.kyu_points = tourney_points
|
|
elif self.dan:
|
|
if hanchan.game_score >= 60000:
|
|
hanchan.dan_points = 3
|
|
elif hanchan.game_score == 0:
|
|
hanchan.dan_points = -3
|
|
elif hanchan.placement == 1:
|
|
hanchan.dan_points = 2
|
|
elif hanchan.placement == 2:
|
|
hanchan.dan_points = 1
|
|
elif hanchan.placement == 3:
|
|
hanchan.dan_points = -1
|
|
elif hanchan.placement == 4:
|
|
hanchan.dan_points = -2
|
|
elif hanchan.game_score >= 60000:
|
|
hanchan.kyu_points = 3
|
|
elif hanchan.game_score >= 30000:
|
|
hanchan.kyu_points = 1
|
|
elif hanchan.input_score < 10000:
|
|
hanchan.kyu_points = -1
|
|
else:
|
|
hanchan.kyu_points = 0
|
|
|
|
# Add the hanchans points to the players points
|
|
if self.dan:
|
|
# Only substract so much points that player has 0 Points:
|
|
if self.dan_points + hanchan.dan_points < 0:
|
|
hanchan.dan_points -= (self.dan_points + hanchan.dan_points)
|
|
self.dan_points += hanchan.dan_points
|
|
else:
|
|
# Only substract so much points that player has 0 Points:
|
|
if self.kyu_points + hanchan.kyu_points < 0:
|
|
hanchan.kyu_points -= (self.kyu_points + hanchan.kyu_points)
|
|
self.kyu_points += hanchan.kyu_points
|
|
|
|
# TODO: Merkwürdige Methode die zwar funktioniert aber nicht sehr
|
|
# aussagekräfig ist. Überarbeiten?
|
|
def update_rank(self):
|
|
if self.dan and self.dan_points < 0:
|
|
self.dan_points = 0
|
|
self.dan = 1
|
|
elif self.dan or self.dan_points > 0:
|
|
old_dan = self.dan
|
|
for min_points, dan_rank in DAN_RANKS:
|
|
if self.dan_points > min_points:
|
|
self.dan = dan_rank
|
|
break
|
|
if old_dan is None or self.dan > old_dan:
|
|
self.wins_in_a_row = 0
|
|
elif self.kyu_points < 1:
|
|
self.kyu_points = 0
|
|
self.kyu = 10
|
|
elif self.kyu_points > 50:
|
|
self.dan = 1
|
|
self.kyu = None
|
|
self.dan_points = 0
|
|
self.kyu_points = 0
|
|
else:
|
|
for min_points, kyu_rank in KYU_RANKS:
|
|
if self.kyu_points > min_points:
|
|
self.kyu = kyu_rank
|
|
break
|
|
|
|
|
|
class SeasonRanking(models.Model):
|
|
user = models.ForeignKey(settings.AUTH_USER_MODEL)
|
|
season = models.PositiveSmallIntegerField(_('Season'))
|
|
placement = models.PositiveIntegerField(blank=True, null=True)
|
|
avg_placement = models.FloatField(blank=True, null=True)
|
|
avg_score = models.FloatField(blank=True, null=True)
|
|
hanchan_count = models.PositiveIntegerField(default=0)
|
|
good_hanchans = models.PositiveIntegerField(default=0)
|
|
won_hanchans = models.PositiveIntegerField(default=0)
|
|
objects = managers.SeasonRankingManager()
|
|
|
|
class Meta(object):
|
|
ordering = ('placement', 'avg_placement', '-avg_score',)
|
|
|
|
def get_absolute_url(self):
|
|
return reverse('player-ladder-score', args=[self.user.username])
|
|
|
|
def recalculate(self):
|
|
season_hanchans = Hanchan.objects.season_hanchans(
|
|
user=self.user, season=self.season)
|
|
sum_placement = 0
|
|
sum_score = 0
|
|
self.placement = None
|
|
self.hanchan_count = season_hanchans.count()
|
|
self.good_hanchans = 0
|
|
self.won_hanchans = 0
|
|
|
|
logger.info(
|
|
u'Recalculate LadderRanking for Player %s in Season %s',
|
|
self.user, self.season)
|
|
for hanchan in season_hanchans:
|
|
sum_placement += hanchan.placement
|
|
sum_score += hanchan.game_score
|
|
self.won_hanchans += 1 if hanchan.placement == 1 else 0
|
|
self.good_hanchans += 1 if hanchan.placement == 2 else 0
|
|
try:
|
|
self.avg_placement = sum_placement / self.hanchan_count
|
|
self.avg_score = sum_score / self.hanchan_count
|
|
except ZeroDivisionError:
|
|
self.avg_placement = None
|
|
self.avg_score = None
|
|
self.save(force_update=True)
|
|
|
|
|
|
def update_ranking(sender, instance, **kwargs):
|
|
for user in (instance.player1, instance.player2, instance.player3, instance.player4):
|
|
logger.debug(
|
|
"marking %(user)s's kyu/dan for recalculation since %(start)s",
|
|
{'user': user, 'start': str(instance.start.date())}
|
|
)
|
|
set_dirty(user=user.id, hanchan_date=instance.start.date())
|
|
if instance.season:
|
|
logger.debug(
|
|
"marking %s's ladder %i season for recalculation.",
|
|
user, instance.season
|
|
)
|
|
set_dirty(user=user.id, season=instance.season)
|
|
logger.debug("marking season %d for recalculation.", instance.season)
|
|
set_dirty(season=instance.season)
|
|
logger.debug("marking event %s for recalculation.", instance.event)
|
|
set_dirty(event=instance.event_id, user=user.id)
|
|
|
|
|
|
models.signals.pre_delete.connect(update_ranking, sender=Hanchan)
|
|
# models.signals.post_save.connect(update_ranking, sender=Hanchan)
|