Skip to content

Commit

Permalink
Merge pull request #344 from aiarena/staging
Browse files Browse the repository at this point in the history
Release v1.6.4
  • Loading branch information
lladdy authored Feb 15, 2022
2 parents ebd1f90 + e0dd791 commit 3afe005
Show file tree
Hide file tree
Showing 7 changed files with 112 additions and 31 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Generated by Django 3.2.9 on 2022-02-15 06:10

import aiarena.core.models.competition_participation
import aiarena.core.storage
from django.db import migrations
import private_storage.fields


class Migration(migrations.Migration):

dependencies = [
('core', '0040_alter_arenaclientstatus_logged_at'),
]

operations = [
migrations.AddField(
model_name='competitionparticipation',
name='elo_graph_update_plot',
field=private_storage.fields.PrivateFileField(blank=True, null=True, storage=aiarena.core.storage.OverwritePrivateStorage(base_url='/'), upload_to=aiarena.core.models.competition_participation.elo_graph_update_plot_upload_to),
),
]
7 changes: 6 additions & 1 deletion aiarena/core/models/competition_participation.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@
from django.db import models
from django.utils.text import slugify
from django.core.validators import MinValueValidator
from private_storage.fields import PrivateFileField

from aiarena.settings import ELO_START_VALUE
from .bot import Bot
from .mixins import LockableModelMixin
from .competition import Competition
from ..storage import OverwriteStorage
from ..storage import OverwriteStorage, OverwritePrivateStorage
from ..validators import validate_not_nan, validate_not_inf

logger = logging.getLogger(__name__)
Expand All @@ -18,6 +19,9 @@
def elo_graph_upload_to(instance, filename):
return '/'.join(['graphs', f'{instance.competition_id}_{instance.bot.id}_{instance.bot.name}.png'])

def elo_graph_update_plot_upload_to(instance, filename):
return '/'.join(['competitions', 'stats', f'{instance.id}_elo_graph_update_plot.png'])


class CompetitionParticipation(models.Model, LockableModelMixin):
competition = models.ForeignKey(Competition, on_delete=models.CASCADE, related_name='participations')
Expand All @@ -33,6 +37,7 @@ class CompetitionParticipation(models.Model, LockableModelMixin):
crash_perc = models.FloatField(blank=True, null=True, validators=[validate_not_nan, validate_not_inf])
crash_count = models.IntegerField(default=0)
elo_graph = models.FileField(upload_to=elo_graph_upload_to, storage=OverwriteStorage(), blank=True, null=True)
elo_graph_update_plot = PrivateFileField(upload_to=elo_graph_update_plot_upload_to, storage=OverwritePrivateStorage(base_url='/'), blank=True, null=True)
highest_elo = models.IntegerField(blank=True, null=True)
slug = models.SlugField(max_length=255, blank=True)
active = models.BooleanField(default=True)
Expand Down
80 changes: 52 additions & 28 deletions aiarena/core/stats/stats_generator.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import io
from datetime import datetime

import matplotlib.dates as mdates
import matplotlib.pyplot as plt
import pandas as pd
from django.db import connection, transaction
from django.db import connection
from django.db.models import Max

from aiarena.core.models import MatchParticipation, CompetitionParticipation
from aiarena.core.models import MatchParticipation, CompetitionParticipation, Bot
from aiarena.core.models.competition_bot_matchup_stats import CompetitionBotMatchupStats


Expand Down Expand Up @@ -44,9 +45,12 @@ def update_stats(sp: CompetitionParticipation):
match__round__competition=sp.competition) \
.aggregate(Max('resultant_elo'))['resultant_elo__max']

graph = StatsGenerator._generate_elo_graph(sp.bot.id, sp.competition_id)
if graph is not None:
sp.elo_graph.save('elo.png', graph)
graph1, graph2 = StatsGenerator._generate_elo_graph(sp.bot.id, sp.competition_id)
if graph1 is not None:
sp.elo_graph.save('elo.png', graph1)

if graph2 is not None:
sp.elo_graph_update_plot.save('elo_update_plot.png', graph2)
else:
sp.win_count = 0
sp.loss_count = 0
Expand All @@ -58,24 +62,28 @@ def update_stats(sp: CompetitionParticipation):

@staticmethod
def _update_matchup_stats(sp: CompetitionParticipation):
for competition_participation in CompetitionParticipation.objects.filter(competition=sp.competition).exclude(bot=sp.bot):
for competition_participation in CompetitionParticipation.objects.filter(competition=sp.competition).exclude(
bot=sp.bot):
with connection.cursor() as cursor:
matchup_stats = CompetitionBotMatchupStats.objects.select_for_update() \
.get_or_create(bot=sp, opponent=competition_participation)[0]

matchup_stats.match_count = StatsGenerator._calculate_matchup_count(cursor, competition_participation, sp)
matchup_stats.match_count = StatsGenerator._calculate_matchup_count(cursor, competition_participation,
sp)

if matchup_stats.match_count != 0:
matchup_stats.win_count = StatsGenerator._calculate_win_count(cursor, competition_participation, sp)
matchup_stats.win_perc = matchup_stats.win_count / matchup_stats.match_count * 100

matchup_stats.loss_count = StatsGenerator._calculate_loss_count(cursor, competition_participation, sp)
matchup_stats.loss_count = StatsGenerator._calculate_loss_count(cursor, competition_participation,
sp)
matchup_stats.loss_perc = matchup_stats.loss_count / matchup_stats.match_count * 100

matchup_stats.tie_count = StatsGenerator._calculate_tie_count(cursor, competition_participation, sp)
matchup_stats.tie_perc = matchup_stats.tie_count / matchup_stats.match_count * 100

matchup_stats.crash_count = StatsGenerator._calculate_crash_count(cursor, competition_participation, sp)
matchup_stats.crash_count = StatsGenerator._calculate_crash_count(cursor, competition_participation,
sp)
matchup_stats.crash_perc = matchup_stats.crash_count / matchup_stats.match_count * 100
else:
matchup_stats.win_count = 0
Expand Down Expand Up @@ -196,27 +204,43 @@ def _get_data(bot_id, competition_id):
return elo_over_time

@staticmethod
def _generate_plot_image(df):
plot = io.BytesIO()

fig, ax = plt.subplots(figsize=(12, 9))
ax.plot(df["Date"], df['ELO'], color='#86c232')
ax.spines["top"].set_visible(False)
ax.spines["right"].set_visible(False)
ax.spines["left"].set_color('#86c232')
ax.spines["bottom"].set_color('#86c232')
ax.autoscale(enable=True, axis='x')
ax.get_xaxis().tick_bottom()
ax.get_yaxis().tick_left()
ax.xaxis.set_major_formatter(mdates.DateFormatter('%b-%d'))
ax.tick_params(axis='x', colors='#86c232', labelsize=16)
ax.tick_params(axis='y', colors='#86c232', labelsize=16)
plt.title('ELO over time', fontsize=20, color=('#86c232'))
def _generate_plot_images(df, update_date: datetime):
plot1 = io.BytesIO()
plot2 = io.BytesIO()

legend = []

fig, ax1 = plt.subplots(1, 1, figsize=(12, 9), sharex='all', sharey='all')
ax1.plot(df["Date"], df['ELO'], color='#86c232')
# ax.plot(df["Date"], df['ELO'], color='#86c232')
ax1.spines["top"].set_visible(False)
ax1.spines["right"].set_visible(False)
ax1.spines["left"].set_color('#86c232')
ax1.spines["bottom"].set_color('#86c232')
ax1.autoscale(enable=True, axis='x')
ax1.get_xaxis().tick_bottom()
ax1.get_yaxis().tick_left()
ax1.xaxis.set_major_formatter(mdates.DateFormatter('%b-%d'))
ax1.tick_params(axis='x', colors='#86c232', labelsize=16)
ax1.tick_params(axis='y', colors='#86c232', labelsize=16)
# if update_date:

legend.append('ELO')
ax1.legend(legend, loc='lower center', fontsize='xx-large')

plt.title('ELO over time', fontsize=20, color=('#86c232'))
plt.tight_layout() # Avoids savefig cutting off x-label
plt.savefig(plot, format="png", transparent=True)
plt.savefig(plot1, format="png", transparent=True)

legend.append('Last update')
ax1.legend(legend, loc='lower center', fontsize='xx-large')
ax1.vlines([update_date],
min(df['ELO']), max(df['ELO']), colors='r', linestyles='--')
legend.append('Last update')
ax1.legend(legend, loc='lower center', fontsize='xx-large')
plt.savefig(plot2, format="png", transparent=True)
plt.cla() # Clears axis in preparation for new graph
return plot
return plot1, plot2

@staticmethod
def _generate_elo_graph(bot_id: int, competition_id: int):
Expand All @@ -226,6 +250,6 @@ def _generate_elo_graph(bot_id: int, competition_id: int):
df['2'] = pd.to_datetime(df['2'])
df.columns = ['Name', 'ELO', 'Date']

return StatsGenerator._generate_plot_image(df)
return StatsGenerator._generate_plot_images(df, Bot.objects.get(id=bot_id).bot_zip_updated)
else:
return None
2 changes: 1 addition & 1 deletion aiarena/frontend/templates/403.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
{% if user.is_authenticated %}
You dont have permissions to do that!
{% else %}
You need to be <a href="{% url 'login' %}">{% trans "logged in" %}</a> to do that!
You need to be <a href="{% url 'login' %}">"logged in"</a> to do that!
{% endif %}
</center></div>
{% endblock %}
7 changes: 6 additions & 1 deletion aiarena/frontend/templates/bot_competition_stats.html
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,12 @@
<br/>
</div>

{% if competitionparticipation.elo_graph %}
{# If this user owns the bot, and is a supporter, display the private ELO graph #}
{% if competitionparticipation.bot.user == user and user.patreon_level != 'none' and competitionparticipation.elo_graph_update_plot %}
<div id="elo_graph">
<img src="{{ competitionparticipation.elo_graph_update_plot.url }}" alt="ELOGraph"/>
</div>
{% elif competitionparticipation.elo_graph %}
<div id="elo_graph">
<img src="{{ competitionparticipation.elo_graph.url }}" alt="ELOGraph"/>
</div>
Expand Down
25 changes: 25 additions & 0 deletions aiarena/frontend/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,31 @@ def get_context_data(self, **kwargs):
return context


class BotCompetitionStatsEloUpdatePlot(DetailView):
model = CompetitionParticipation
template_name = 'bot_competition_stats.html'

def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['competition_bot_matchups'] = self.object.competition_matchup_stats.filter(
opponent__competition=context['competitionparticipation'].competition).order_by('-win_perc').distinct()
context['updated'] = context['competition_bot_matchups'][0].updated if context['competition_bot_matchups'] else "Never"
return context


class BotCompetitionStatsEloGraphUpdatePlot(PrivateStorageDetailView):
model = CompetitionParticipation
model_file_field = 'elo_graph_update_plot'

content_disposition = 'inline'

def get_content_disposition_filename(self, private_file):
return 'elo_graph_update_plot.png'

def can_access_file(self, private_file):
# Allow if the owner of the bot and a patreon supporter
return private_file.parent_object.bot.user == private_file.request.user and private_file.request.user.patreon_level != 'None'


class BotUpdateForm(forms.ModelForm):
"""
Expand Down
1 change: 1 addition & 0 deletions aiarena/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@

path('competitions/stats/<int:pk>/', core_views.BotCompetitionStatsDetail.as_view()),
path('competitions/stats/<int:pk>/<slug:slug>', core_views.BotCompetitionStatsDetail.as_view(), name='bot_competition_stats'),
path('competitions/stats/<int:pk>_elo_graph_update_plot.png', core_views.BotCompetitionStatsEloGraphUpdatePlot.as_view()),

path('botupload/', core_views.BotUpload.as_view(), name='botupload'),
path('requestmatch/', core_views.RequestMatch.as_view(), name='requestmatch'),
Expand Down

0 comments on commit 3afe005

Please sign in to comment.