Skip to content

Commit 8570dc9

Browse files
committed
Added synchronous implementation of Matches & Tournaments
Created a ViewSet for both Matches and Tournaments, added them to the url configuration, and created serializers to validate POST data. These ViewSets inherit a base class that handles the HTTP requests and pass parameters to their respective axelrod classes (Match/Tournament). Created a ResultsSerializer class to serialize axelrod results into a json response.
1 parent 5f03bab commit 8570dc9

File tree

4 files changed

+258
-3
lines changed

4 files changed

+258
-3
lines changed

api/config/urls.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
from django.conf import settings
22
from django.conf.urls import url, include
33
from rest_framework.routers import DefaultRouter
4-
from api.core.views import StrategyViewSet
4+
from api.core.views import (
5+
MatchViewSet,
6+
StrategyViewSet,
7+
TournamentViewSet,
8+
)
59

610

711
urlpatterns = [
@@ -17,7 +21,9 @@
1721
router = DefaultRouter()
1822

1923
routes = {
20-
'strategies': StrategyViewSet
24+
'matches': MatchViewSet,
25+
'strategies': StrategyViewSet,
26+
'tournaments': TournamentViewSet,
2127
}
2228

2329
for route, viewset in routes.items():

api/core/models.py

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
from rest_framework import serializers
2+
from django.contrib.postgres.fields import JSONField
3+
from django.db.models import (
4+
AutoField,
5+
BooleanField,
6+
DateTimeField,
7+
CharField,
8+
ForeignKey,
9+
IntegerField,
10+
TextField,
11+
FloatField,
12+
Model,
13+
)
14+
import axelrod
15+
16+
17+
CHEATING_NAMES = [strategy.__name__ for strategy in axelrod.cheating_strategies]
18+
19+
# class Tournament(Model):
20+
21+
# PENDING = 0
22+
# RUNNING = 1
23+
# SUCCESS = 2
24+
# FAILED = 3
25+
26+
# STATUS_CHOICES = (
27+
# (PENDING, 'PENDING'),
28+
# (RUNNING, 'RUNNING'),
29+
# (SUCCESS, 'SUCCESS'),
30+
# (FAILED, 'FAILED'),
31+
# )
32+
33+
# # Fields
34+
# created = DateTimeField(auto_now_add=True, editable=False)
35+
# last_updated = DateTimeField(auto_now=True, editable=False)
36+
# status = IntegerField(choices=STATUS_CHOICES, default=PENDING)
37+
# results = JSONField()
38+
39+
40+
# class Meta:
41+
# ordering = ('-created',)
42+
43+
# def __unicode__(self):
44+
# return u'%s' % self.id
45+
46+
# def get_absolute_url(self):
47+
# return reverse('core_tournament_detail', args=(self.id,))
48+
49+
# def get_update_url(self):
50+
# return reverse('core_tournament_update', args=(self.id,))
51+
52+
# def get_results_url(self):
53+
# return reverse('core_tournament_results', args=(self.id,))
54+
55+
# def to_json(self):
56+
# json_results = []
57+
# if self.results:
58+
# for (player, scores) in self.results:
59+
# json_results.append({"player": player, "scores": scores})
60+
61+
# json_results = {
62+
# "results": json_results,
63+
# "meta": {
64+
# "definition": self.tournament_definition.to_json(),
65+
# "cheating_strategies": CHEATING_NAMES
66+
# }
67+
# }
68+
69+
# return json_results
70+
71+
# def run(self):
72+
73+
# if self.status != Tournament.PENDING:
74+
# raise Exception(
75+
# u'[tournament %d, current status: %s] SKIPPED !' % (
76+
# self.id, self.get_status_display()))
77+
78+
# try:
79+
# self.status = Tournament.RUNNING
80+
# self.save(update_fields=['status', ])
81+
82+
# start = datetime.now()
83+
84+
# players = json.loads(self.tournament_definition.players)
85+
86+
# strategies = []
87+
# for strategy_str, number_of_players in players.items():
88+
# for i in range(0, int(number_of_players or 0)):
89+
# strategies.append(getattr(axelrod, strategy_str)())
90+
91+
# tournament_runner = axelrod.Tournament(
92+
# players=strategies,
93+
# turns=self.tournament_definition.turns,
94+
# repetitions=self.tournament_definition.repetitions,
95+
# noise=self.tournament_definition.noise)
96+
# result_set = tournament_runner.play()
97+
98+
# self.results = []
99+
# for rank in result_set.ranking:
100+
# player = tournament_runner.players[rank].name
101+
# scores = result_set.normalised_scores[rank]
102+
# self.results.append((player, scores))
103+
104+
# end = datetime.now()
105+
# duration = (end - start).seconds
106+
107+
# # TODO: save duration
108+
# # self.duration = duration
109+
# self.status = Tournament.SUCCESS
110+
# self.save(update_fields=['status', 'results'])
111+
112+
# except Exception as e:
113+
# # log errors and set tournament status to aborted
114+
# self.status = Tournament.FAILED
115+
# self.save(update_fields=['status', ])
116+
# # TODO: eventually save error message in model
117+
118+
119+
class TournamentDefinition(Model):
120+
# Fields
121+
name = CharField(max_length=255)
122+
created = DateTimeField(auto_now_add=True, editable=False)
123+
last_updated = DateTimeField(auto_now=True, editable=False)
124+
turns = IntegerField()
125+
repetitions = IntegerField()
126+
noise = FloatField()
127+
with_morality = BooleanField()
128+
player_list = serializers.ListField(child=serializers.CharField())
129+
players = []
130+
131+
# Relationship Fields
132+
# tournament = ForeignKey('Tournament',)
133+
134+
135+
class MatchDefinition(Model):
136+
turns = IntegerField()
137+
noise = FloatField()
138+
player_list = serializers.ListField(child=serializers.CharField())
139+
players = []

api/core/serializers.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
1+
import inspect
2+
import json
13
from rest_framework import serializers
24
from rest_framework.reverse import reverse
5+
from rest_framework.response import Response
6+
7+
from . import models
38

49

510
def strategy_id(strategy):
@@ -38,3 +43,52 @@ def get_params(self, strategy):
3843
if 'memory_depth' in params and params['memory_depth'] == float('inf'):
3944
params['memory_depth'] = -1
4045
return params
46+
47+
48+
class TournamentSerializer(serializers.ModelSerializer):
49+
50+
class Meta:
51+
model = models.TournamentDefinition
52+
fields = ('name', 'created', 'last_updated', 'turns',
53+
'repetitions', 'noise', 'with_morality', 'players')
54+
55+
56+
class MatchSerializer(serializers.ModelSerializer):
57+
58+
class Meta:
59+
model = models.MatchDefinition
60+
fields = ('turns', 'noise', 'players')
61+
62+
63+
class ResultsSerializer:
64+
65+
data = None
66+
67+
def __init__(self, results):
68+
results_object = self.serialize_state_distributions(results)
69+
for key, value in results.__dict__.items():
70+
try:
71+
json.dumps(key)
72+
json.dumps(value)
73+
results_object[key] = value
74+
except TypeError:
75+
pass
76+
self.data = results_object
77+
78+
def serialize_state_distributions(self, results):
79+
keys = [
80+
'state_distribution',
81+
'normalised_state_distribution',
82+
'state_to_action_distribution',
83+
'normalised_state_to_action_distribution'
84+
]
85+
return {
86+
key: self.serialize_state_distribution(getattr(results, key))
87+
for key in keys
88+
}
89+
90+
def serialize_state_distribution(self, state_distribution):
91+
distribution = []
92+
for row in state_distribution:
93+
distribution.append([c.most_common() for c in row])
94+
return distribution

api/core/views.py

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,13 @@
33
from rest_framework import viewsets
44
from rest_framework.response import Response
55
import axelrod as axl
6-
from api.core.serializers import StrategySerializer, strategy_id
6+
from api.core.serializers import (
7+
strategy_id,
8+
MatchSerializer,
9+
StrategySerializer,
10+
TournamentSerializer,
11+
)
12+
from api.core.serializers import ResultsSerializer
713

814

915
def filter_strategies(request):
@@ -58,3 +64,53 @@ def retrieve(self, request, pk=None):
5864
raise Http404
5965
serializer = StrategySerializer(strategy, context={'request': request})
6066
return Response(serializer.data)
67+
68+
69+
class BaseTournamentView:
70+
"""
71+
72+
"""
73+
74+
strategies_index = {strategy_id(s): s for s in axl.strategies}
75+
_not_found_error = 'Strategy {} was not found'
76+
77+
def parse_players(self, player_list):
78+
players = []
79+
for player in player_list:
80+
strategy = self.strategies_index[player]
81+
players.append(strategy())
82+
return players
83+
84+
def create(self, request):
85+
data = request.data
86+
serializer = self.serializer(data=data)
87+
if serializer.is_valid():
88+
tournament_definition = serializer.data
89+
try:
90+
players = self.parse_players(data['player_list'])
91+
except KeyError as e:
92+
return Response({'player_list': self._not_found_error.format(e.args[0])})
93+
94+
results = self.run(players, tournament_definition)
95+
return Response(ResultsSerializer(results).data, 200)
96+
return Response(serializer.errors, 400)
97+
98+
99+
class TournamentViewSet(viewsets.ViewSet, BaseTournamentView):
100+
101+
serializer = TournamentSerializer
102+
103+
@staticmethod
104+
def run(players, definition):
105+
tournament = axl.Tournament(players=players, **definition)
106+
return tournament.play()
107+
108+
109+
class MatchViewSet(viewsets.ViewSet, BaseTournamentView):
110+
111+
serializer = MatchSerializer
112+
113+
@staticmethod
114+
def run(players, definition):
115+
match = axl.Match(players=players, **definition)
116+
return match.play()

0 commit comments

Comments
 (0)