Fehlerbereinigung.
Mahjong Ranking berechnet nun die richtigen Ränge zu den legendären Kyu/Dan Punkten. Stablie Version bevor die Datenstruktur des Mahjongrankings vereinfacht wird.
This commit is contained in:
committed by
Christian Berg
parent
bafbf38612
commit
79eaeb34ad
@@ -1,5 +1,7 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
|
||||
# TODO: Rankings archiv Flag erstellen, womit sie nicht mehr neuberechnet werden dürfen.
|
||||
|
||||
from datetime import date, timedelta
|
||||
|
||||
from django.conf import settings
|
||||
@@ -33,7 +35,6 @@ class EventRanking(models.Model):
|
||||
hanchan_count = models.PositiveIntegerField(default=0)
|
||||
good_hanchans = models.PositiveIntegerField(default=0)
|
||||
won_hanchans = models.PositiveIntegerField(default=0)
|
||||
dirty = models.BooleanField(default=True, editable=False)
|
||||
|
||||
class Meta(object):
|
||||
ordering = ('placement', 'avg_placement', '-avg_score',)
|
||||
@@ -48,10 +49,6 @@ class EventRanking(models.Model):
|
||||
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.
|
||||
|
||||
Das Eigenschaft dirty ist ein altes Überbleibsel, um das Objekt
|
||||
zur neuberrechnung zu markieren. Mittlerweile wird ein lokaler
|
||||
Cache dafür verwendet, das ist schneller.
|
||||
"""
|
||||
logger.info(u'Recalculate EventRanking for Player %s in %s', self.user,
|
||||
self.event.name) # @IgnorePep8
|
||||
@@ -59,14 +56,13 @@ class EventRanking(models.Model):
|
||||
event=self.event_id) # @IgnorePep8
|
||||
aggregator = event_hanchans.aggregate(
|
||||
models.Avg('placement'),
|
||||
models.Avg('score'),
|
||||
models.Avg('game_score'),
|
||||
models.Count('pk'))
|
||||
self.avg_placement = aggregator['placement__avg']
|
||||
self.avg_score = aggregator['score__avg']
|
||||
self.hanchan_count = aggregator['pk__count']
|
||||
self.good_hanchans = event_hanchans.filter(placement__lt=3).count()
|
||||
self.won_hanchans = event_hanchans.filter(placement=1).count()
|
||||
self.dirty = False
|
||||
if self.hanchan_count <= 0:
|
||||
self.delete()
|
||||
else:
|
||||
@@ -80,20 +76,16 @@ class Hanchan(models.Model):
|
||||
Außerdem gehört jede Hanchan zu einer Veranstaltung.
|
||||
"""
|
||||
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.')) # @IgnorePep8
|
||||
confirmed = models.BooleanField(_('Has been Confirmed'), default=True, help_text=_('Only valid and confirmed Hanchans will be counted in the rating.')) # @IgnorePep8
|
||||
event = models.ForeignKey(Event)
|
||||
player_names = models.CharField(max_length=127, editable=False)
|
||||
player_names = models.CharField(max_length=255, editable=False)
|
||||
players = models.ManyToManyField(
|
||||
settings.AUTH_USER_MODEL,
|
||||
through='Player',
|
||||
verbose_name=_('Players')
|
||||
)
|
||||
season = models.ForeignKey('LadderSeason', blank=True, null=True,
|
||||
editable=False) # @IgnorePep8
|
||||
start = models.DateTimeField(_('Start'), help_text=_(
|
||||
'This is crucial to get the right Hanchans that scores')) # @IgnorePep8
|
||||
season = models.ForeignKey('LadderSeason', blank=True, null=True, editable=False)
|
||||
start = models.DateTimeField(_('Start'), help_text=_('This is crucial to get the right Hanchans that scores'))
|
||||
valid = models.BooleanField(_('Is Valid'), default=False)
|
||||
|
||||
class Meta(object):
|
||||
@@ -122,22 +114,26 @@ class Hanchan(models.Model):
|
||||
self.valid = False
|
||||
return _('For a Hanchan exactly 4 players are needed.')
|
||||
|
||||
score_sum = self.player_set.aggregate(Sum('score'))['score__sum']
|
||||
score_sum = self.player_set.aggregate(Sum('input_score'))['input_score__sum']
|
||||
if score_sum == 100000:
|
||||
self.valid = True
|
||||
return '4 Spieler, 100.000 Endpunktestand, die Hanchan ist \
|
||||
korrekt!'
|
||||
elif score_sum > 100000 and self.player_set.filter(score=0):
|
||||
self.valid = True
|
||||
return 'Endpunktestand über 100.000, aber jemand ist auf 0 \
|
||||
gefallen. Die Hanchan stimmt.'
|
||||
# elif score_sum > 100000 and self.player_set.filter(score=0):
|
||||
# self.valid = True
|
||||
# return 'Endpunktestand über 100.000, aber jemand ist auf 0 \
|
||||
# gefallen. Die Hanchan stimmt.'
|
||||
elif score_sum < 100000:
|
||||
self.valid = False
|
||||
return 'Endpunktestand weniger als 100.000 Punkte.'
|
||||
elif score_sum > 100000 and not self.player_set.filter(score=0):
|
||||
elif score_sum > 100000:
|
||||
self.valid = False
|
||||
return 'Endpunktestand über 100.000, aber niemand ist auf 0 \
|
||||
gefallen.'
|
||||
return 'Endpunktestand mehr als 100.000 Punkte.'
|
||||
|
||||
#elif score_sum > 100000 and not self.player_set.filter(score=0):
|
||||
# self.valid = False
|
||||
# return 'Endpunktestand über 100.000, aber niemand ist auf 0 \
|
||||
# gefallen.'
|
||||
else:
|
||||
self.valid = False
|
||||
return 'Wir wissen nicht warum, aber das kann nicht passen...'
|
||||
@@ -177,7 +173,7 @@ class Hanchan(models.Model):
|
||||
"""
|
||||
logger.debug("Berechne die Platzierungen neu...")
|
||||
attending_players = self.player_set.select_related('hanchan', 'user')
|
||||
attending_players = attending_players.order_by('-score')
|
||||
attending_players = attending_players.order_by('-game_score')
|
||||
other_player_placement = 0
|
||||
other_player_score = 0
|
||||
placement = 1
|
||||
@@ -185,15 +181,15 @@ class Hanchan(models.Model):
|
||||
logger.info("Compute player pacements for Hanchan Nr. %d", self.pk)
|
||||
for player in attending_players:
|
||||
player_list.append(player.user.username)
|
||||
if player.score <= 0:
|
||||
if player.game_score <= 0:
|
||||
player.placement = 4
|
||||
elif player.score == other_player_score:
|
||||
elif player.game_score == other_player_score:
|
||||
player.placement = other_player_placement
|
||||
else:
|
||||
player.placement = placement
|
||||
placement += 1
|
||||
other_player_placement = player.placement
|
||||
other_player_score = player.score
|
||||
other_player_score = player.game_score
|
||||
player.save(season_id=self.season_id, mark_dirty=True)
|
||||
|
||||
def get_absolute_url(self):
|
||||
@@ -206,21 +202,20 @@ class Hanchan(models.Model):
|
||||
def save(self, **kwargs):
|
||||
logger.debug("Hanchan save() wurde getriggert!")
|
||||
|
||||
self.season = self.season or LadderSeason.objects.get_by_date(
|
||||
self.start)
|
||||
self.season = self.season or LadderSeason.objects.get_by_date(self.start)
|
||||
if self.pk:
|
||||
self.check_validity()
|
||||
self.compute_player_placements()
|
||||
self.player_names = ', '.join(self.player_set.values_list(
|
||||
'user__username', flat=True).order_by('-score'))
|
||||
'user__username', flat=True).order_by('-input_score'))
|
||||
return models.Model.save(self, **kwargs)
|
||||
|
||||
|
||||
class KyuDanRanking(models.Model):
|
||||
u"""
|
||||
Die Einstufung des Spieles im Kyu bzw. Dan System.
|
||||
Die Einstufung des Spielers im Kyu bzw. Dan System.
|
||||
Im Gegensatz zum Ladder Ranking ist das nicht Saison gebunden.
|
||||
daher läuft es getrennt.
|
||||
Deswegen läuft es getrennt.
|
||||
"""
|
||||
user = models.OneToOneField(settings.AUTH_USER_MODEL)
|
||||
dan = models.PositiveSmallIntegerField(blank=True, null=True)
|
||||
@@ -230,8 +225,10 @@ class KyuDanRanking(models.Model):
|
||||
won_hanchans = models.PositiveIntegerField(default=0)
|
||||
good_hanchans = models.PositiveIntegerField(default=0)
|
||||
hanchan_count = models.PositiveIntegerField(default=0)
|
||||
dirty = models.BooleanField(default=True, editable=False)
|
||||
wins_in_a_row = 0
|
||||
legacy_date = models.DateField(blank=True, null=True)
|
||||
legacy_dan_points = models.PositiveIntegerField(default=0)
|
||||
legacy_kyu_points = models.PositiveIntegerField(default=0)
|
||||
|
||||
class Meta(object):
|
||||
ordering = ('-dan_points', '-kyu_points',)
|
||||
@@ -239,10 +236,10 @@ class KyuDanRanking(models.Model):
|
||||
verbose_name_plural = _(u'Kyū/Dan Rankings')
|
||||
|
||||
def __unicode__(self):
|
||||
if self.dan_points:
|
||||
return u"%s - %d. Dan" % (self.user.username, self.dan)
|
||||
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)
|
||||
return u"%s - %d. Kyu" % (self.user.username, self.kyu or 10)
|
||||
|
||||
def append_3_in_a_row_bonuspoints(self, hanchan):
|
||||
u"""
|
||||
@@ -312,7 +309,7 @@ class KyuDanRanking(models.Model):
|
||||
self.kyu_points += bonus_points
|
||||
|
||||
def get_absolute_url(self):
|
||||
if self.dan:
|
||||
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])
|
||||
@@ -324,80 +321,90 @@ class KyuDanRanking(models.Model):
|
||||
"""
|
||||
logger.debug("recalculating Kyu/Dan punkte for %s...", self.user)
|
||||
valid_hanchans = Player.objects.valid_hanchans(user=self.user_id)
|
||||
if self.legacy_date:
|
||||
valid_hanchans = valid_hanchans.filter(hanchan__start__gt=self.legacy_date)
|
||||
valid_hanchans = valid_hanchans.order_by('hanchan__start')
|
||||
self.kyu_points = 0
|
||||
self.dan_points = 0
|
||||
self.kyu_points = self.legacy_kyu_points or 0
|
||||
self.dan_points = self.legacy_dan_points or 0
|
||||
self.dan = None
|
||||
self.kyu = 10
|
||||
self.update_rank()
|
||||
self.hanchan_count = valid_hanchans.count()
|
||||
self.won_hanchans = valid_hanchans.filter(placement=1).count()
|
||||
self.good_hanchans = valid_hanchans.filter(placement=2).count()
|
||||
logger.info("Neuberechnung der Punkte von %s", self.user)
|
||||
|
||||
for hanchan in valid_hanchans:
|
||||
self.update_points(hanchan)
|
||||
self.append_tournament_bonuspoints(hanchan)
|
||||
for played_hanchan in valid_hanchans:
|
||||
self.update_hanchan_points(played_hanchan)
|
||||
self.append_tournament_bonuspoints(played_hanchan)
|
||||
self.update_rank()
|
||||
self.append_3_in_a_row_bonuspoints(hanchan)
|
||||
self.append_3_in_a_row_bonuspoints(played_hanchan)
|
||||
self.update_rank()
|
||||
hanchan.save(force_update=True, mark_dirty=False)
|
||||
played_hanchan.save(force_update=True, mark_dirty=False)
|
||||
self.save(force_update=True)
|
||||
|
||||
def update_points(self, player):
|
||||
def update_hanchan_points(self, played_hanchan):
|
||||
"""
|
||||
Berechne die Kyu bzw. Dan Punkte für ein Spiel neu.
|
||||
:param player: Das Player Objekt das neuberechnet werden soll.
|
||||
Berechne die Kyu bzw. Dan Punkte für eine Hanchan neu.
|
||||
Für Turniere gelten andere Regeln zur Punktevergabe:
|
||||
1. Platz 4 Punkte
|
||||
2. Platz 3 Punkte
|
||||
3. Platz 2 Punkte
|
||||
4. Platz 1 Punkt
|
||||
:param played_hanchan: Das Player Objekt das neuberechnet werden soll.
|
||||
"""
|
||||
player.bonus_points = 0
|
||||
player.comment = ""
|
||||
player.dan_points = None
|
||||
player.kyu_points = None
|
||||
|
||||
if player.hanchan.event.is_tournament:
|
||||
tourney_points = 4 - player.placement
|
||||
played_hanchan.bonus_points = 0
|
||||
played_hanchan.comment = ""
|
||||
played_hanchan.dan_points = None
|
||||
played_hanchan.kyu_points = None
|
||||
print self.user, played_hanchan.input_score, played_hanchan.game_score
|
||||
if played_hanchan.hanchan.event.is_tournament:
|
||||
tourney_points = 4 - played_hanchan.placement
|
||||
if self.dan:
|
||||
player.dan_points = tourney_points
|
||||
played_hanchan.dan_points = tourney_points
|
||||
else:
|
||||
player.kyu_points = tourney_points
|
||||
played_hanchan.kyu_points = tourney_points
|
||||
elif self.dan:
|
||||
if player.score >= 60000:
|
||||
player.dan_points = 3
|
||||
elif player.score == 0:
|
||||
player.dan_points = -3
|
||||
elif player.placement == 1:
|
||||
player.dan_points = 2
|
||||
elif player.placement == 2:
|
||||
player.dan_points = 1
|
||||
elif player.placement == 3:
|
||||
player.dan_points = -1
|
||||
elif player.placement == 4:
|
||||
player.dan_points = -2
|
||||
elif player.score >= 60000:
|
||||
player.kyu_points = 3
|
||||
elif player.score >= 30000:
|
||||
player.kyu_points = 1
|
||||
elif player.score < 10000:
|
||||
player.kyu_points = -1
|
||||
if played_hanchan.game_score >= 60000:
|
||||
played_hanchan.dan_points = 3
|
||||
elif played_hanchan.game_score == 0:
|
||||
played_hanchan.dan_points = -3
|
||||
elif played_hanchan.placement == 1:
|
||||
played_hanchan.dan_points = 2
|
||||
elif played_hanchan.placement == 2:
|
||||
played_hanchan.dan_points = 1
|
||||
elif played_hanchan.placement == 3:
|
||||
played_hanchan.dan_points = -1
|
||||
elif played_hanchan.placement == 4:
|
||||
played_hanchan.dan_points = -2
|
||||
#Kein Turnier, Spieler nicht im Dan, dann muss er Sie Kyu Punkte bekommen
|
||||
elif played_hanchan.game_score >= 60000:
|
||||
played_hanchan.kyu_points = 3
|
||||
elif played_hanchan.game_score >= 30000:
|
||||
played_hanchan.kyu_points = 1
|
||||
elif played_hanchan.game_score < 10000:
|
||||
played_hanchan.kyu_points = -1
|
||||
else:
|
||||
player.kyu_points = 0
|
||||
played_hanchan.kyu_points = 0
|
||||
|
||||
# Add the hanchans points to the players score
|
||||
if player.dan_points:
|
||||
if self.dan_points + player.dan_points < 0:
|
||||
# Add the hanchans points to the players points
|
||||
if self.dan:
|
||||
if self.dan_points + played_hanchan.dan_points < 0:
|
||||
# Only substract so much points that player has 0 Points:
|
||||
player.dan_points -= (self.dan_points + player.dan_points)
|
||||
self.dan_points += player.dan_points
|
||||
elif player.kyu_points:
|
||||
if self.kyu_points + player.kyu_points < 0:
|
||||
played_hanchan.dan_points -= (self.dan_points + played_hanchan.dan_points)
|
||||
self.dan_points += played_hanchan.dan_points
|
||||
print "Danpunkte des Spielers: {}, Danpunkte der Hanchan {}".format(self.dan_points, played_hanchan.dan_points)
|
||||
else:
|
||||
if self.kyu_points + played_hanchan.kyu_points < 0:
|
||||
# Only substract so much points that player has 0 Points:
|
||||
player.kyu_points -= (self.kyu_points + player.kyu_points)
|
||||
self.kyu_points += player.kyu_points
|
||||
played_hanchan.kyu_points -= (self.kyu_points + played_hanchan.kyu_points)
|
||||
self.kyu_points += played_hanchan.kyu_points
|
||||
|
||||
def update_rank(self):
|
||||
if self.dan and self.dan_points < 0:
|
||||
self.dan_points = 0
|
||||
self.dan = 1
|
||||
elif self.dan:
|
||||
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:
|
||||
@@ -429,7 +436,6 @@ class LadderRanking(models.Model):
|
||||
hanchan_count = models.PositiveIntegerField(default=0)
|
||||
good_hanchans = models.PositiveIntegerField(default=0)
|
||||
won_hanchans = models.PositiveIntegerField(default=0)
|
||||
dirty = models.BooleanField(default=True, editable=False)
|
||||
|
||||
class Meta(object):
|
||||
ordering = ('placement', 'avg_placement', '-avg_score',)
|
||||
@@ -443,15 +449,14 @@ class LadderRanking(models.Model):
|
||||
ladder_hanchans = Player.objects.ladder_hanchans(
|
||||
self.user_id, self.season_id)
|
||||
aggregate = ladder_hanchans.aggregate(
|
||||
models.Avg('placement'),
|
||||
models.Avg('score'),
|
||||
models.Count('pk')
|
||||
avg_placement = models.Avg('placement'),
|
||||
avg_score = models.Avg('game_score'),
|
||||
hanchan_count = models.Count('pk')
|
||||
)
|
||||
self.dirty = False
|
||||
self.placement = None
|
||||
self.hanchan_count = aggregate['pk__count']
|
||||
self.avg_placement = aggregate['placement__avg']
|
||||
self.avg_score = aggregate['score__avg']
|
||||
self.hanchan_count = aggregate['hanchan_count']
|
||||
self.avg_placement = aggregate['avg_placement']
|
||||
self.avg_score = aggregate['avg_score']
|
||||
self.good_hanchans = ladder_hanchans.filter(placement=2).count()
|
||||
self.won_hanchans = ladder_hanchans.filter(placement=1).count()
|
||||
self.save(force_update=True)
|
||||
@@ -464,12 +469,12 @@ class LadderSeasonManager(models.Manager):
|
||||
"""
|
||||
current_season = cache.get('current_mahjong_season')
|
||||
if not current_season:
|
||||
try:
|
||||
today = date.today()
|
||||
current_season = self.filter(start__lte=today, end__gte=today)
|
||||
current_season = current_season[0]
|
||||
except IndexError:
|
||||
current_season = None
|
||||
current_year = date.today().year
|
||||
current_season = self.get_or_create(name=current_year, defaults={
|
||||
'name': current_year,
|
||||
'start': date(year=current_year,month=1, day=1),
|
||||
'end': date(year=current_year, month=12, day=31)
|
||||
})[0]
|
||||
cache.set('current_mahjong_season', current_season, 4320)
|
||||
return current_season
|
||||
|
||||
@@ -489,11 +494,10 @@ class LadderSeason(models.Model):
|
||||
u"""
|
||||
Eine Saison für das Kasu interne Ladder-Ranking.
|
||||
"""
|
||||
name = models.CharField(max_length=100)
|
||||
name = models.PositiveSmallIntegerField()
|
||||
start = models.DateField()
|
||||
end = models.DateField()
|
||||
objects = LadderSeasonManager()
|
||||
dirty = models.BooleanField(default=True, editable=False)
|
||||
|
||||
class Meta(object):
|
||||
ordering = ('start',)
|
||||
@@ -501,7 +505,7 @@ class LadderSeason(models.Model):
|
||||
verbose_name_plural = _('Ladder Seasons')
|
||||
|
||||
def __unicode__(self):
|
||||
return self.name
|
||||
return str(self.name)
|
||||
|
||||
def get_absolute_url(self):
|
||||
"""
|
||||
@@ -526,7 +530,6 @@ class LadderSeason(models.Model):
|
||||
ranking.placement = placement
|
||||
ranking.save(force_update=True)
|
||||
placement += 1
|
||||
self.dirty = False
|
||||
self.save(force_update=True)
|
||||
|
||||
|
||||
@@ -544,7 +547,7 @@ class PlayerManager(models.Manager):
|
||||
return queryset
|
||||
|
||||
def ladder_hanchans(self, user=None, season=None, num_hanchans=None,
|
||||
max_age=None): # @IgnorePep8
|
||||
max_age=None):
|
||||
queryset = self.valid_hanchans(user).order_by('-hanchan__start')
|
||||
queryset = queryset.select_related()
|
||||
season = season or LadderSeason.objects.current()
|
||||
@@ -589,18 +592,19 @@ class PlayerManager(models.Manager):
|
||||
class Player(models.Model):
|
||||
hanchan = models.ForeignKey(Hanchan)
|
||||
user = models.ForeignKey(settings.AUTH_USER_MODEL)
|
||||
score = models.PositiveIntegerField(default=0)
|
||||
input_score = models.IntegerField(default=0)
|
||||
game_score = models.PositiveIntegerField(default=0)
|
||||
placement = models.PositiveSmallIntegerField(
|
||||
blank=True,
|
||||
null=True,
|
||||
default=None
|
||||
)
|
||||
kyu_points = models.PositiveSmallIntegerField(
|
||||
kyu_points = models.SmallIntegerField(
|
||||
blank=True,
|
||||
null=True,
|
||||
default=None
|
||||
)
|
||||
dan_points = models.PositiveSmallIntegerField(
|
||||
dan_points = models.SmallIntegerField(
|
||||
blank=True,
|
||||
null=True,
|
||||
default=None
|
||||
@@ -615,13 +619,14 @@ class Player(models.Model):
|
||||
|
||||
class Meta(object):
|
||||
unique_together = ('hanchan', 'user')
|
||||
ordering = ['-score']
|
||||
ordering = ['-input_score']
|
||||
|
||||
def __str__(self):
|
||||
return "{0}'s Punkte vom {1: %d.%m.%Y um %H:%M}".format(
|
||||
self.user.username, self.hanchan.start)
|
||||
|
||||
def save(self, mark_dirty=True, season_id=None, *args, **kwargs):
|
||||
self.game_score = self.input_score if self.input_score > 0 else 0
|
||||
season_id = season_id or self.hanchan.season_id
|
||||
super(Player, self).save(*args, **kwargs)
|
||||
|
||||
@@ -634,7 +639,7 @@ class Player(models.Model):
|
||||
if season_id:
|
||||
logger.debug(
|
||||
"Marking %s's season no. %i ranking for recalculation.",
|
||||
self.user, season_id) # @IgnorePep8
|
||||
self.user, season_id)
|
||||
set_dirty(season=season_id, user=self.user_id)
|
||||
logger.debug("Marking season no %i for recalculation.", season_id)
|
||||
set_dirty(season=season_id)
|
||||
|
||||
Reference in New Issue
Block a user