Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions djangofiles/frisbeer/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ class TeamAdmin(admin.ModelAdmin):

admin.site.register(Player)
admin.site.register(Game, GameAdmin)
admin.site.register(GameRules)
admin.site.register(Location)
admin.site.register(Rank)
admin.site.register(Season)
admin.site.register(SeasonRules)
admin.site.register(Team, TeamAdmin)
75 changes: 75 additions & 0 deletions djangofiles/frisbeer/migrations/0032_auto_20200613_1432.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# Generated by Django 3.0.7 on 2020-06-13 14:32

import django.core.validators
from django.db import migrations, models
import django.db.models.deletion


def set_frisbeer_rules(apps, schema_editor):
GameRules = apps.get_model('frisbeer', 'GameRules')

frisbeer_rules = GameRules(name='Frisbeer',
min_players=6,
max_players=6,
min_rounds=2,
max_rounds=3)
frisbeer_rules.save()

Season = apps.get_model('frisbeer', 'Season')
Game = apps.get_model('frisbeer', 'Game')

Season.objects.filter(game_rules__isnull=True).update(game_rules=frisbeer_rules)
Game.objects.filter(rules__isnull=True, season__isnull=True).update(rules=frisbeer_rules)


def unset_rules(apps, schem_editor):
Season = apps.get_model('frisbeer', 'Season')
Game = apps.get_model('frisbeer', 'Game')

Season.objects.all().update(game_rules=None)
Game.objects.all().update(rules=None)


class Migration(migrations.Migration):

dependencies = [
('frisbeer', '0031_auto_20190630_1123'),
]

operations = [
migrations.CreateModel(
name='GameRules',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=50, unique=True)),
('min_players', models.IntegerField(default=6, validators=[django.core.validators.MinValueValidator(0)])),
('max_players', models.IntegerField(default=6, validators=[django.core.validators.MinValueValidator(0)])),
('min_rounds', models.IntegerField(default=2, validators=[django.core.validators.MinValueValidator(0)])),
('max_rounds', models.IntegerField(default=3, validators=[django.core.validators.MinValueValidator(0)])),
],
),
migrations.AlterField(
model_name='game',
name='location',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='frisbeer.Location'),
),
migrations.AlterField(
model_name='game',
name='season',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='frisbeer.Season'),
),
migrations.AddField(
model_name='game',
name='rules',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='frisbeer.GameRules'),
),
migrations.AddField(
model_name='season',
name='game_rules',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='frisbeer.GameRules'),
),
migrations.RunPython(
set_frisbeer_rules,
unset_rules
)
]
22 changes: 22 additions & 0 deletions djangofiles/frisbeer/migrations/0033_auto_20200614_0910.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Generated by Django 3.0.7 on 2020-06-14 09:10

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
('frisbeer', '0032_auto_20200613_1432'),
]

operations = [
migrations.AlterModelOptions(
name='gamerules',
options={'verbose_name': 'Ruleset'},
),
migrations.RenameField(
model_name='game',
old_name='rules',
new_name='_rules',
),
]
56 changes: 56 additions & 0 deletions djangofiles/frisbeer/migrations/0034_season_rules.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Generated by Django 3.0.7 on 2020-06-17 19:40

from django.db import migrations, models
import django.db.models.deletion

def algorithm_to_season_rules(apps, schema_editor):
SeasonRules = apps.get_model('frisbeer', 'SeasonRules')
Season = apps.get_model('frisbeer', 'Season')

for season in Season.objects.all():
name = season.get_score_algorithm_display()
algorithm = season.score_algorithm
season.rules, _ = SeasonRules.objects.get_or_create(name=name, score_algorithm=algorithm)
season.save()


def season_rules_to_algorithm(apps, schema_editor):
Season = apps.get_model('frisbeer', 'Season')
for season in Season.objects.all():
season.score_algorithm = season.rules.score_algorithm
season.save()


class Migration(migrations.Migration):

dependencies = [
('frisbeer', '0033_auto_20200614_0910'),
]

operations = [
migrations.CreateModel(
name='SeasonRules',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=100)),
('elo_decay', models.BooleanField(default=True)),
('score_algorithm', models.CharField(choices=[('2017', 'Season 2017'), ('2018', 'Season 2018'), ('elo', 'Elo'), ('top_elo', 'Best elo')], default='elo', max_length=16)),
('rank_statistic', models.CharField(choices=[('RP', 'Rounds played'), ('RW', 'Rounds won'), ('GP', 'Games played'), ('GW', 'Games won')], default='RW', max_length=3)),
('rank_min_value', models.IntegerField(default=5)),
],
),
migrations.AddField(
model_name='season',
name='rules',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='frisbeer.SeasonRules'),
),
migrations.RunPython(
algorithm_to_season_rules,
season_rules_to_algorithm
),
migrations.AlterField(
model_name='season',
name='game_rules',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='frisbeer.GameRules'),
),
]
144 changes: 99 additions & 45 deletions djangofiles/frisbeer/models.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import itertools
from operator import itemgetter
from math import exp
from math import exp, ceil
from datetime import date

from django.core.exceptions import ValidationError
from django.core.validators import MinValueValidator
from django.utils.translation import ugettext_lazy as _
from django.utils.timezone import now
from django.db import models

from frisbeer import utils


class Rank(models.Model):
name = models.CharField(max_length=100, blank=True, unique=True)
Expand All @@ -30,29 +35,52 @@ def __str__(self):
return self.name


class Season(models.Model):
ALGORITHM_2017 = '2017'
ALGORITHM_2018 = '2018'
ALGORITHM_TOP_ELO = 'elo'
ALGORITHM_CHOICES = (
(ALGORITHM_2017, '2017'),
(ALGORITHM_2018, '2018'),
(ALGORITHM_TOP_ELO, 'Best elo')
)
class GameRules(models.Model):
name = models.CharField(max_length=50, unique=True)
min_players = models.IntegerField(default=6, validators=[MinValueValidator(0)])
max_players = models.IntegerField(default=6, validators=[MinValueValidator(0)])
min_rounds = models.IntegerField(default=2, validators=[MinValueValidator(0)])
max_rounds = models.IntegerField(default=3, validators=[MinValueValidator(0)])

name = models.CharField(max_length=255, unique=True)
start_date = models.DateField(default=now)
end_date = models.DateField(null=True, blank=True)
score_algorithm = models.CharField(max_length=255, choices=ALGORITHM_CHOICES)
class Meta:
verbose_name = 'Ruleset'

def clean(self):
if self.min_players > self.max_players:
self.max_players = self.min_players
if self.min_rounds > self.max_rounds:
self.max_rounds = self.min_rounds

def __str__(self):
return self.name

@staticmethod
def current():
return Season.objects.filter(start_date__lte=date.today()).order_by('-start_date').first()

def score(self, *args, **kwargs):
class ScoreAlgorithm(models.TextChoices):
S_2017 = '2017', _('Season 2017')
S_2018 = '2018', _('Season 2018')
ELO = 'elo', _('Elo')
TOP_ELO = 'top_elo', _('Best elo')


class PlayerStatistic(models.TextChoices):
ROUNDS_PLAYED = 'RP', _('Rounds played')
ROUNDS_WON = 'RW', _('Rounds won')
GAMES_PLAYED = 'GP', _('Games played')
GAMES_WON = 'GW', _('Games won')


class SeasonRules(models.Model):
name = models.CharField(max_length=100)
elo_decay = models.BooleanField(default=True)
score_algorithm = models.CharField(max_length=16, choices=ScoreAlgorithm.choices, default=ScoreAlgorithm.ELO)
rank_statistic = models.CharField(max_length=3, choices=PlayerStatistic.choices, default=PlayerStatistic.ROUNDS_WON)
rank_min_value = models.IntegerField(default=5)

def __str__(self):
return self.name or self.score_algorithm

@property
def algorithm(self):
def score_2017(games_played, rounds_played, rounds_won, *args, **kwargs):
win_rate = rounds_won / rounds_played if rounds_played != 0 else 0
return int(win_rate * (1 - exp(-games_played / 4)) * 1000)
Expand All @@ -61,15 +89,40 @@ def score_2018(games_played, rounds_played, rounds_won, *args, **kwargs):
win_rate = rounds_won / rounds_played if rounds_played != 0 else 0
return int(rounds_won + win_rate * (1 / (1 + exp(3 - games_played / 2.5))) * 1000)

def score_best_elo(player, *args, **kwargs):
return player.season_best

def score_elo(player, *args, **kwargs):
return player.elo

if self.score_algorithm == Season.ALGORITHM_2017:
return score_2017(*args, **kwargs)
elif self.score_algorithm == Season.ALGORITHM_2018:
return score_2018(*args, **kwargs)
if self.score_algorithm == ScoreAlgorithm.S_2017:
return score_2017
elif self.score_algorithm == ScoreAlgorithm.S_2018:
return score_2018
elif self.score_algorithm == ScoreAlgorithm.TOP_ELO:
return score_best_elo
else:
return score_elo(*args, **kwargs)
return score_elo


class Season(models.Model):

name = models.CharField(max_length=255, unique=True)
start_date = models.DateField(default=now)
end_date = models.DateField(null=True, blank=True)
rules = models.ForeignKey(SeasonRules, null=True, on_delete=models.PROTECT)
score_algorithm = models.CharField(max_length=255, choices=ScoreAlgorithm.choices)
game_rules = models.ForeignKey(GameRules, null=True, blank=True, on_delete=models.PROTECT)

def __str__(self):
return self.name

@staticmethod
def current():
return Season.objects.filter(start_date__lte=date.today()).order_by('-start_date').first()

def score(self, *args, **kwargs):
return self.rules.algorithm(*args, **kwargs)


class Team(models.Model):
Expand Down Expand Up @@ -129,7 +182,8 @@ class Game(models.Model):
(PLAYED, "Played"),
(APPROVED, "Approved"))

season = models.ForeignKey(Season, on_delete=models.SET_NULL, null=True)
season = models.ForeignKey(Season, null=True, blank=True, on_delete=models.SET_NULL)
_rules = models.ForeignKey(GameRules, null=True, blank=True, on_delete=models.SET_NULL)

players = models.ManyToManyField(Player, related_name='games', through='GamePlayerRelation')
date = models.DateTimeField(default=now)
Expand Down Expand Up @@ -157,6 +211,14 @@ def _team(self, side):
except GameTeamRelation.DoesNotExist:
return None

def clean(self):
if self.rules is None and (self.season is None or self.season.game_rules is None):
raise ValidationError({'rules': _('Rules or season with rules must be set')})

@property
def rules(self):
return self._rules or self.season.game_rules

@property
def team1(self):
return self._team(1)
Expand All @@ -181,35 +243,27 @@ def __str__(self):
)

def can_create_teams(self):
return self.state == Game.READY \
and self.players.count() == 6 \
and self.players.filter(gameplayerrelation__team=0).count() == 6
player_count = self.players.count()
return self.rules.min_players <= player_count <= self.rules.max_players \
and player_count == self.players.filter(gameplayerrelation__team=0).count()

def can_score(self):
return self.state >= Game.APPROVED and (self.team1_score == 2 or self.team2_score == 2) \
and self.players.count() == 6 \
and self.players.filter(gameplayerrelation__team=1).count() == 3 \
and self.players.filter(gameplayerrelation__team=2).count() == 3
players_count = self.players.count()
players_per_team = ceil(players_count / 2)
rounds_played = self.team1_score + self.team2_score
return self.state >= Game.APPROVED \
and self.rules.min_rounds <= rounds_played <= self.rules.max_rounds \
and self.rules.min_players <= players_count <= self.rules.max_players \
and self.players.filter(gameplayerrelation__team=0).count() == 0 \
and self.players.filter(gameplayerrelation__team=1).count() <= players_per_team \
and self.players.filter(gameplayerrelation__team=2).count() <= players_per_team

def create_teams(self):
def calculate_team_elo(team):
return int(sum([player.elo for player in team]) / len(team))

elo_list = []
players = set(self.players.all())
possibilities = itertools.combinations(players, 3)
for possibility in possibilities:
team1 = possibility
team2 = players - set(team1)
elo1 = calculate_team_elo(team1)
elo2 = calculate_team_elo(team2)
elo_list.append((abs(elo1 - elo2), team1, team2))
ideal_teams = sorted(elo_list, key=itemgetter(0))[0]
ideal_teams = utils.create_equal_teams(set(self.players.all()))
self.gameplayerrelation_set \
.filter(player__id__in=[player.id for player in ideal_teams[1]]).update(team=GamePlayerRelation.Team1)
self.gameplayerrelation_set \
.filter(player__id__in=[player.id for player in ideal_teams[2]]).update(team=GamePlayerRelation.Team2)
print(ideal_teams[0])
self.save()


Expand Down
Loading