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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ __pycache__
db.sqlite3
*.pyc
log.txt
.vscode
20 changes: 20 additions & 0 deletions delete_data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import os
import sys

'''
Simply deletes all data from tables
'''

sys.path.append(os.path.join(os.path.dirname(__file__), 'djangofiles'))
os.environ.setdefault('DJANGO_SETTINGS_MODULE', "server.settings")

import django
django.setup()
from frisbeer.models import *

Game.objects.all().delete()
Player.objects.all().delete()
Season.objects.all().delete()
Location.objects.all().delete()
GamePlayerRelation.objects.all().delete()
Team.objects.all().delete()
1 change: 1 addition & 0 deletions djangofiles/frisbeer/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ class PlayerInGameInline(admin.TabularInline):

class GameAdmin(admin.ModelAdmin):
inlines = [PlayerInGameInline, ]
exclude = ('elo_change', '_rules')

def get_changeform_initial_data(self, request):
return {'season': Season.current().id }
Expand Down
23 changes: 23 additions & 0 deletions djangofiles/frisbeer/migrations/0035_game_elo_change.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Generated by Django 3.0.14 on 2021-12-27 21:43

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('frisbeer', '0034_season_rules'),
]

operations = [
migrations.AddField(
model_name='game',
name='elo_change',
field=models.IntegerField(default=0),
),
migrations.AlterField(
model_name='season',
name='score_algorithm',
field=models.CharField(choices=[('2017', 'Season 2017'), ('2018', 'Season 2018'), ('elo', 'Elo'), ('top_elo', 'Best elo')], max_length=255),
),
]
6 changes: 6 additions & 0 deletions djangofiles/frisbeer/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@ class Game(models.Model):

team1_score = models.IntegerField(default=0, choices=((0, 0), (1, 1), (2, 2)))
team2_score = models.IntegerField(default=0, choices=((0, 0), (1, 1), (2, 2)))
elo_change = models.IntegerField(default=0)
state = models.IntegerField(choices=game_state_choices, default=PENDING,
help_text="0: pending - the game has been proposed but is still missing players. "
"1: ready - the game can be played now. Setting this state creates teams. "
Expand Down Expand Up @@ -230,6 +231,11 @@ def team2(self):
def save(self, *args, **kwargs):
if not self.season:
self.season = Season.current()
# Auto approve games
# if self.id:
# old_game = Game.objects.get(pk=self.id)
# if old_game.state == Game.READY and self.state == Game.PLAYED:
# self.state = Game.APPROVED
super().save(*args, **kwargs)

def __str__(self):
Expand Down
87 changes: 69 additions & 18 deletions djangofiles/frisbeer/signals.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,18 @@ def update_statistics(sender, instance, **kwargs):
logging.debug("Game was saved, but hasn't been played yet. Sender %s, instance %s", sender, instance)
return

update_elo()
update_score()
calculate_ranks()
update_team_score()
update_elo(instance) # 18 seconds
update_score() # 2 seconds
calculate_ranks() # 1 second
update_team_score(instance) # 9 seconds


def update_elo():
def update_elo(initialiser=None):
"""
Calculate new elos for all players.

Update is done for all players because matches are possibly added in non-chronological order
Update is done for all players if game that initialized update is from past season.
For current season games, player elos are reverted to the state of the initializer game and recalculated from there
"""

logging.info("Updating elos (mabby)")
Expand All @@ -39,9 +40,7 @@ def _elo_decay():
# Halves the distance from median elo for all players
Player.objects.all().update(elo=(F('elo') - 1500) / 2 + 1500, season_best=0)

games = Game.objects.filter(state=Game.APPROVED).order_by("date")

Player.objects.all().update(elo=1500, season_best=0)
games = rollback_player_elo(initialiser)

season = None

Expand All @@ -63,9 +62,10 @@ def _elo_decay():

# We only need to calculate elo change for one team, since elo change is the same for all players
# and symmetrical between losing and winning sides
team1_elo_change = (game.team1_score * calculate_elo_change(team1_pregame_elo, team2_pregame_elo, True)
+ game.team2_score * calculate_elo_change(team1_pregame_elo, team2_pregame_elo, False))
team1_elo_change = round((game.team1_score * calculate_elo_change(team1_pregame_elo, team2_pregame_elo, True)
+ game.team2_score * calculate_elo_change(team1_pregame_elo, team2_pregame_elo, False)))

game.elo_change = team1_elo_change
for player in team1:
player.elo += team1_elo_change
# logging.debug("{0} elo changed {1:0.2f}".format(player.name, team1_elo_change))
Expand All @@ -78,11 +78,64 @@ def _elo_decay():
if player.elo > player.season_best:
player.season_best = player.elo
player.save()

Game.objects.bulk_update(games, ["elo_change"])
# New season has begun, but no games yet played -> decay
if season != Season.current():
_elo_decay()

def rollback_player_elo(game):
"""
Runs games back in time and reverts elo changes caused by them to players
"""
if game != None and game.season == Season.current():
games = Game.objects.filter(state=Game.APPROVED).filter(date__gte=game.date).order_by("date")
if len(games) > 1:
games = Game.objects.filter(season_id=Season.current().id, state=Game.APPROVED).order_by("date")
Player.objects.all().update(season_best=0)
for game in reversed(games):
if not game.can_score():
continue
team1 = [r.player for r in list(game.gameplayerrelation_set.filter(team=1))]
team2 = [r.player for r in list(game.gameplayerrelation_set.filter(team=2))]

team1_elo_change = game.elo_change * -1
for player in team1:
old_elo = player.elo
player.elo += team1_elo_change
# logging.debug("{0} elo rollback {1:0.2f}. New: {2}".format(player.name, team1_elo_change, player.elo))
if old_elo == player.season_best and player.elo < old_elo:
# Only really applicable if only one game is being reversed. Would need individual player history to do proberly
player.season_best = player.elo
player.save()
for player in team2:
player.elo -= team1_elo_change
if old_elo == player.season_best and player.elo < old_elo:
player.season_best = player.elo
# logging.debug("{0} elo rollback {1:0.2f}. New: {2}".format(player.name, -team1_elo_change, player.elo))
player.save()
return games
else:
Player.objects.all().update(elo=1500, season_best=0)
return Game.objects.filter(state=Game.APPROVED).order_by("date")

def get_team_games(game):
"""
Gets games for team elo update
If game in question is the newest, just use that, otherwise count whole season elos
"""
season = Season.current()
if game != None:
games = Game.objects.filter(season=season, state=Game.APPROVED).filter(date__gte=game.date).order_by("date")
if len(games) == 1:
teams = GameTeamRelation.objects.filter(game_id=games[0].id)
if len(teams) == 0:
# new game, just do that
return games
games = Game.objects.filter(season=season, state=Game.APPROVED).order_by("date")
Team.objects.filter(virtual=True).delete()
Team.objects.all().update(elo=1500, season_best=0)
return games


def update_score():
logging.info("Updating scores (mabby)")
Expand Down Expand Up @@ -119,11 +172,10 @@ def update_score():
BACKUP_PENALTY_PERCENT = 22.45


def update_team_score():
Team.objects.filter(virtual=True).delete()
Team.objects.all().update(elo=1500, season_best=0)
def update_team_score(initialiser=None):
season = Season.current()
games = Game.objects.filter(season=season, state=Game.APPROVED).order_by("date")

games = get_team_games(initialiser)

for game in games:
if not game.can_score():
Expand All @@ -134,8 +186,7 @@ def update_team_score():
GameTeamRelation.objects.update_or_create(side=1, game=game, defaults={'team': team1})
GameTeamRelation.objects.update_or_create(side=2, game=game, defaults={'team': team2})

team1_elo_change = (game.team1_score * calculate_elo_change(team1.elo, team2.elo, True) +
game.team2_score * calculate_elo_change(team1.elo, team2.elo, False))
team1_elo_change = game.elo_change

team2_elo_change = -team1_elo_change

Expand Down
7 changes: 3 additions & 4 deletions setup_test_database.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,13 @@
player = Player(name=name)
player.save()
players.add(player)

Game.objects.all().delete()

playerList = list(players)
for i in range(10):
g = Game(name="Testipeli {}".format(i), season=Season.current())
g.save()
t1 = set(random.sample(players, 3))
t2 = random.sample(players - t1, 3)
t1 = set(random.sample(playerList, 3))
t2 = random.sample(list(players - t1), 3)
for player in t1:
GamePlayerRelation(player=player, game=g, team=1).save()
for player in t2:
Expand Down
84 changes: 84 additions & 0 deletions sync_database.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import os
import sys
import random
import requests
import datetime

from django.db import IntegrityError

'''
Srcipt to copy games, locations and player stats from production environment using the open api's
'''

sys.path.append(os.path.join(os.path.dirname(__file__), 'djangofiles'))
os.environ.setdefault('DJANGO_SETTINGS_MODULE', "server.settings")
r = requests.get('http://api.frisbeer.win/API/players')
ExternalPlayers = r.json()
r = requests.get('http://api.frisbeer.win/API/locations')
ExternalLocations = r.json()
r = requests.get('http://api.frisbeer.win/API/games')
ExternalGames = r.json()

import django

django.setup()

from frisbeer.models import *
from django.contrib.auth.models import User
from djangofiles.frisbeer.signals import update_elo, update_score, calculate_ranks, update_team_score

Player.objects.all().delete()
fields = ('id', 'name', 'score', 'rank', 'season_best')
for player in ExternalPlayers:
temp = Player(name=player["name"], score=1500)
temp.save()

Game.objects.all().delete()
Season.objects.all().delete()
Location.objects.all().delete()
GamePlayerRelation.objects.all().delete()
Team.objects.all().delete()
year = 2017
rules = GameRules.objects.get(id=1)
Srules = SeasonRules.objects.get(id=4)
print("starting season loop")
for i in range(datetime.datetime.now().year - year + 1):
start = datetime.datetime(year, 1, 1)
end = datetime.datetime(year, 12, 31)
s = Season(id=i+2, name=year, start_date=start, end_date=end, game_rules=rules, rules=Srules)
s.save()
year = year + 1
print("starting location loop")
for location in ExternalLocations:
l = Location(id=location["id"], name=location["name"], longitude=location["longitude"], latitude=location["latitude"])
l.save()


print("starting game loop")
for game in ExternalGames:
if game["location"] == None:
loc = None
else:
loc = Location.objects.get(id=game["location"])
g = Game(name=game["name"], season=Season.objects.get(id=game["season"]), location=loc, date=game["date"],
team1_score=game["team1_score"], team2_score=game["team2_score"], state=game["state"])
g.save()
t1 = [d for d in game["players"] if d['team'] in [1]]
t2 = [d for d in game["players"] if d['team'] in [2]]
t0 = [d for d in game["players"] if d['team'] in [0]]
for player in t1:
temp = Player.objects.get(name=player["name"])
GamePlayerRelation(player=temp, game=g, team=1).save()
for player in t2:
temp = Player.objects.get(name=player["name"])
GamePlayerRelation(player=temp, game=g, team=2).save()
for player in t0:
temp = Player.objects.get(name=player["name"])
GamePlayerRelation(player=temp, game=g, team=0).save()

print("updating values")
update_elo()
update_score()
calculate_ranks()
update_team_score()