684 lines
28 KiB
Python
684 lines
28 KiB
Python
# -*- encoding: utf-8 -*-
|
|
|
|
# TODO: Rankings archiv Flag erstellen, womit sie nicht mehr neuberechnet
|
|
# werden dürfen.
|
|
|
|
from __future__ import division
|
|
|
|
from datetime import datetime, time
|
|
|
|
from django.conf import settings
|
|
from django.core.exceptions import ValidationError
|
|
from django.db import models
|
|
from django.urls import reverse
|
|
from django.utils import timezone
|
|
from django.utils.translation import gettext as _
|
|
|
|
|
|
from events.models import Event
|
|
from . import 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,
|
|
on_delete=models.PROTECT)
|
|
event = models.ForeignKey(Event, on_delete=models.CASCADE)
|
|
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 durchschnittliche 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 zwischenzuspeichern.
|
|
"""
|
|
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(
|
|
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, on_delete=models.CASCADE)
|
|
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.PROTECT,
|
|
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.PROTECT,
|
|
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.PROTECT,
|
|
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.PROTECT,
|
|
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_player_data(self, user, **kwargs):
|
|
"""to access scores and placement of a specific user from 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,
|
|
on_delete=models.CASCADE)
|
|
dan = models.PositiveSmallIntegerField(blank=True, null=True)
|
|
dan_points = models.PositiveIntegerField(default=0)
|
|
max_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_dan = models.PositiveSmallIntegerField(blank=True, null=True)
|
|
legacy_dan_points = models.PositiveIntegerField(blank=True, null=True)
|
|
legacy_max_dan_points = models.PositiveIntegerField(default=0)
|
|
legacy_kyu = models.PositiveSmallIntegerField(blank=True, null=True)
|
|
legacy_kyu_points = models.PositiveIntegerField(blank=True, null=True)
|
|
legacy_hanchan_count = models.PositiveIntegerField(blank=True, null=True)
|
|
legacy_good_hanchans = models.PositiveIntegerField(blank=True, null=True)
|
|
legacy_won_hanchans = models.PositiveIntegerField(blank=True, null=True)
|
|
wins_in_a_row = models.PositiveIntegerField(default=0)
|
|
last_hanchan_date = models.DateTimeField(blank=True, null=True)
|
|
objects = managers.KyuDanRankingManager()
|
|
|
|
class Meta(object):
|
|
ordering = (models.F("dan").desc(nulls_last=True),
|
|
'-dan_points', '-kyu_points',
|
|
'-won_hanchans', '-good_hanchans',
|
|
'-last_hanchan_date')
|
|
verbose_name = _(u'Kyū/Dan Ranking')
|
|
verbose_name_plural = _(u'Kyū/Dan Rankings')
|
|
|
|
@property
|
|
def rank(self):
|
|
if self.dan is not None:
|
|
return "{0:d}. Dan".format(self.dan)
|
|
else:
|
|
return "{0:d}. Kyū".format(self.kyu or 10)
|
|
|
|
@property
|
|
def points(self):
|
|
return self.dan_points if self.dan is not None else self.kyu_points
|
|
|
|
def __str__(self):
|
|
if self.dan is not None:
|
|
return u"%s - %d. Dan" % (self.user.username, self.dan or 1)
|
|
else:
|
|
return u"%s - %d. Kyū" % (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 not self.dan or not settings.DAN_3_WINS_IN_A_ROW:
|
|
return
|
|
if hanchan.placement == 1:
|
|
self.wins_in_a_row += 1
|
|
else:
|
|
self.wins_in_a_row = 0
|
|
return
|
|
if 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
|
|
self.update_rank()
|
|
|
|
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]
|
|
# Das braucht nur am Ende eines Turnieres gemacht werden.
|
|
if hanchan != last_hanchan_this_event:
|
|
return False
|
|
event_ranking = EventRanking.objects.get(
|
|
user=self.user,
|
|
event=hanchan.event
|
|
)
|
|
if event_ranking.placement == 1:
|
|
bonus_points += settings.TOURNAMENT_WIN_BONUSPOINTS
|
|
hanchan.player_comment += u'+{0:d} Punkte Turnier gewonnen. '.format(
|
|
settings.TOURNAMENT_WIN_BONUSPOINTS)
|
|
if event_ranking.avg_placement == 1:
|
|
bonus_points += settings.TOURNAMENT_FLAWLESS_VICTORY_BONUSPOINTS
|
|
hanchan.player_comment += u'+{0:d} Pkt: alle Spiele des Turnieres gewonnen. '.format(
|
|
settings.TOURNAMENT_FLAWLESS_VICTORY_BONUSPOINTS)
|
|
|
|
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 calculate(self, since=None, until=None, force_recalc=False):
|
|
"""
|
|
Fetches all valid Hanchans from this Player and recalculates his
|
|
Kyu/Dan Ranking.
|
|
"""
|
|
valid_hanchans = Hanchan.objects.confirmed(user=self.user)
|
|
valid_hanchans = valid_hanchans.order_by('start')
|
|
if since and self.last_hanchan_date and since < self.last_hanchan_date:
|
|
force_recalc = True
|
|
if until and self.last_hanchan_date and until < self.last_hanchan_date:
|
|
force_recalc = True
|
|
if force_recalc:
|
|
# Setze alles auf die legacy Werte und berechne alles von neuem.
|
|
self.dan = self.legacy_dan
|
|
self.dan_points = self.legacy_dan_points or 0
|
|
self.max_dan_points = self.legacy_max_dan_points or 0
|
|
self.kyu = self.legacy_kyu
|
|
self.kyu_points = self.legacy_kyu_points or 0
|
|
self.hanchan_count = self.legacy_hanchan_count or 0
|
|
self.good_hanchans = self.legacy_good_hanchans or 0
|
|
self.won_hanchans = self.legacy_won_hanchans or 0
|
|
self.last_hanchan_date = None
|
|
self.update_rank()
|
|
since = timezone.make_aware(datetime.combine(
|
|
self.legacy_date,
|
|
time(23, 59, 59))) if self.legacy_date else None
|
|
elif self.last_hanchan_date:
|
|
since = self.last_hanchan_date
|
|
elif self.legacy_date:
|
|
since = timezone.make_aware(
|
|
datetime.combine(self.legacy_date, time(0, 0, 0))
|
|
)
|
|
if since:
|
|
valid_hanchans = valid_hanchans.filter(start__gt=since)
|
|
else:
|
|
since = valid_hanchans.aggregate(since=models.Min("start"))["since"]
|
|
if until:
|
|
valid_hanchans = valid_hanchans.filter(start__lte=until)
|
|
else:
|
|
until = valid_hanchans.aggregate(until=models.Max("start"))["until"]
|
|
if valid_hanchans.count() > 0:
|
|
LOGGER.info(f"recalculating Kyu/Dan points for {self.user} ({since:%Y-%m-%d} - {until:%Y-%m-%d})...")
|
|
else:
|
|
LOGGER.info(f"No new valid Hanchans for {self.user}...")
|
|
for hanchan in valid_hanchans:
|
|
self.hanchan_count += 1
|
|
LOGGER.info(f"{self.user} Hanchan no. {self.hanchan_count} from {hanchan.start}")
|
|
hanchan.get_playerdata(self.user)
|
|
if since and hanchan.start < since:
|
|
LOGGER.info(hanchan, "<", since, "no recalc")
|
|
self.dan_points += hanchan.dan_points or 0
|
|
self.kyu_points += hanchan.kyu_points or 0
|
|
self.update_rank()
|
|
else:
|
|
hanchan.bonus_points = 0
|
|
hanchan.player_comment = ""
|
|
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)
|
|
hanchan.update_player_data(self.user)
|
|
hanchan.save(recalculate=False)
|
|
self.won_hanchans += 1 if hanchan.placement == 1 else 0
|
|
self.good_hanchans += 1 if hanchan.placement == 2 else 0
|
|
self.last_hanchan_date = hanchan.start
|
|
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, welches neu berechnet werden soll.
|
|
"""
|
|
hanchan.kyu_points = None
|
|
hanchan.dan_points = None
|
|
if hanchan.event.mahjong_tournament and settings.TOURNAMENT_POINT_SYSTEM:
|
|
"""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
|
|
# otherwise player must be in the kyu ranking
|
|
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.player_comment = 'Spieler unterschreitet 0 Punkte.' \
|
|
'(Original {} Punkte)'.format(hanchan.dan_points)
|
|
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.player_comment = 'Spieler unterschreitet 0 Punkte.' \
|
|
'(Original {} Punkte)'.format(hanchan.kyu_points)
|
|
hanchan.kyu_points -= (self.kyu_points + hanchan.kyu_points)
|
|
self.kyu_points += hanchan.kyu_points
|
|
|
|
def update_rank(self):
|
|
# Update Dan ranking:
|
|
if self.dan or self.dan_points > 0:
|
|
if settings.DAN_ALLOW_DROP_DOWN:
|
|
self.dan = max((dan for min_points, dan in settings.DAN_RANKS
|
|
if self.dan_points > min_points))
|
|
else:
|
|
self.max_dan_points = max(self.max_dan_points, self.dan_points)
|
|
self.dan = max((dan for min_points, dan in settings.DAN_RANKS
|
|
if self.max_dan_points > min_points))
|
|
|
|
# jump from Kyu to Dan
|
|
elif self.kyu_points > 50:
|
|
self.dan = 1
|
|
self.dan_points = 0
|
|
self.kyu = None
|
|
self.kyu_points = 0
|
|
self.wins_in_a_row = 0
|
|
# update Kyu ranking_
|
|
else:
|
|
self.kyu = min((kyu for min_points, kyu in settings.KYU_RANKS
|
|
if self.kyu_points > min_points))
|
|
|
|
|
|
class SeasonRanking(models.Model):
|
|
user = models.ForeignKey(settings.AUTH_USER_MODEL,
|
|
on_delete=models.PROTECT)
|
|
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, until=None):
|
|
season_hanchans = Hanchan.objects.season_hanchans(
|
|
user=self.user, season=self.season, until=until)
|
|
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)}
|
|
)
|
|
set_dirty(user=user.id, hanchan_date=instance.start)
|
|
LOGGER.debug("marking event %s for recalculation.", instance.event)
|
|
set_dirty(event=instance.event_id, user=user.id)
|
|
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)
|
|
|
|
|
|
models.signals.pre_delete.connect(update_ranking, sender=Hanchan)
|