diff --git a/README.md b/README.md index 519db3d..090ac34 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,6 @@ The backend can be installed with ``` pip install -r requirements.txt cd server -python manage.py makemigrations python manage.py migrate cd .. ``` diff --git a/docs/api.md b/docs/api.md index d08d0f3..1f0f43c 100644 --- a/docs/api.md +++ b/docs/api.md @@ -16,30 +16,32 @@ The definitions of the fields are as follows: * `payoff`: Float. The payoff of the agent in the first seat. * `index`: Integer. The index of the game of the same environent and same agent. It is in the range \[0, eval_num-1\] -| type | Resource | Parameters | Description | -|------|---------------------------|------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------| -| GET | tournament/launch | `eval_num`, `name` | Launch tournment on the game. Each pair of models will play `eval_num` times. Results will be saved in database. | -| GET | tournament/query\_game | `name`, `index`, `agent0`, `agent1`, `win`, `payoff`, `elements_every_page`, `page_index` | Query the games with the given parameters | -| GET | tournament/query\_payoff | `name`, `agent0`, `agent1`, `payoff` | Query the payoffs with the given parameters | -| GET | tournament/query\_agent\_payoff | `name`, `elements_every_page`, `page_index`, | Query the payoffs of all the agents | -| GET | tournament/replay | `name`, `agent0`, `agent1`, `index` | Return the replay data | -| POST | tournament/upload\_agent | `model`(Python file), `name`, `game`, `entry` | Upload a model file. `name` is model ID, `entry` is the class name of the model | -| GET | tournament/delete\_agent | `name` | Delete the agent of the given name | -| GET | tournament/list\_uploaded\_agents | `game` | list all the uploaded agents | -| GET | tournament/list\_baseline\_agents | `game` | list all the baseline agents | +| type | Resource | Parameters | Description | +|------|-------------------------------------|-------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------| +| GET | tournament/launch | `eval_num`, `name` | Launch tournment on the game. Each pair of models will play `eval_num` times. Results will be saved in database. | +| GET | tournament/query\_game | `name`, `index`, `agent0`, `agent1`, `win`, `payoff`, `elements_every_page`, `page_index` | Query the games with the given parameters | +| GET | tournament/query\_payoff | `name`, `agent0`, `agent1`, `payoff` | Query the payoffs with the given parameters | +| GET | tournament/query\_agent\_payoff | `name`, `elements_every_page`, `page_index`, | Query the payoffs of all the agents | +| GET | tournament/replay | `name`, `agent0`, `agent1`, `index` | Return the replay data | +| POST | tournament/upload\_agent | `model`(Python file), `name`, `game`, `entry` | Upload a model file. `name` is model ID, `entry` is the class name of the model | +| GET | tournament/delete\_agent | `name` | Delete the agent of the given name | +| GET | tournament/list\_uploaded\_agents | `game` | list all the uploaded agents | +| GET | tournament/list\_baseline\_agents | `game` | list all the baseline agents | +| GET | download\_examples | `name` | download the example agents | ### Example API -| API | Description | -|-----------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------| -| http://127.0.0.1:8000/tournament/launch?eval_num=200&name=leduc-holdem | Evaluate on Leduc Holdem with 200 games for each pair of models | +| API | Description | +|------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------| +| http://127.0.0.1:8000/tournament/launch?eval\_num=200&name=leduc-holdem | Evaluate on Leduc Holdem with 200 games for each pair of models | | http://127.0.0.1:8000/tournament/replay?name=leduc-holdem&agent0=leduc-holdem-rule-v1&agent1=leduc-holdem-cfr&index=3 | Obtain the replay data between rule model and CFR model. Obtain the data of the 3rd game | -| http://127.0.0.1:8000/tournament/query_game&elements_every_page=10&page_index=0 | Get all the game data | -| http://127.0.0.1:8000/tournament/query_game?name=leduc-holdem&elements_every_page=10&page_index=0 | Get all the game data of Leduc Holdem | -| http://127.0.0.1:8000/tournament/query_payoff | Get all the payoffs | -| http://127.0.0.1:8000/tournament/query_payoff?agent0=leduc-holdem-cfr&agent1=leduc-holdem-rule-v1 | Get all the payoffs between rule and CFR models | -| http://127.0.0.1:8000/tournament/query_agent_payoff?name=leduc-holdem&elements_every_page=1&page_index=1 | Get the payoffs of all the agents of leduc-holdem | -| http://127.0.0.1:8000/tournament/list_uploaded_agents?game=leduc-holdem | List the uploaded agents of leduc-holdem | -| http://127.0.0.1:8000/tournament/list_baseline_agents?game=leduc-holdem | List the baseline agents of leduc-holdem | +| http://127.0.0.1:8000/tournament/query\_game&elements\_every\_page=10&page\_index=0 | Get all the game data | +| http://127.0.0.1:8000/tournament/query\_game?name=leduc-holdem&elements\_every\_page=10&page\_index=0 | Get all the game data of Leduc Holdem | +| http://127.0.0.1:8000/tournament/query\_payoff | Get all the payoffs | +| http://127.0.0.1:8000/tournament/query\_payoff?agent0=leduc-holdem-cfr&agent1=leduc-holdem-rule-v1 | Get all the payoffs between rule and CFR models | +| http://127.0.0.1:8000/tournament/query\_agent\_payoff?name=leduc-holdem&elements\_every\_page=1&page\_index=1 | Get the payoffs of all the agents of leduc-holdem | +| http://127.0.0.1:8000/tournament/list\_uploaded\_agents?game=leduc-holdem | List the uploaded agents of leduc-holdem | +| http://127.0.0.1:8000/tournament/list\_baseline\_agents?game=leduc-holdem | List the baseline agents of leduc-holdem | +| http://127.0.0.1:8000/tournament/download\_examples?name=example\_luduc\_nfsp\_model | Download the NFSP example model for Leduc Hold'em | ## Registered Models Some models have been pre-registered as baselines diff --git a/server/media/example_agents/example_luduc_nfsp_model.zip b/server/media/example_agents/example_luduc_nfsp_model.zip new file mode 100644 index 0000000..83aafac Binary files /dev/null and b/server/media/example_agents/example_luduc_nfsp_model.zip differ diff --git a/server/media/example_agents/example_luduc_rule_model.zip b/server/media/example_agents/example_luduc_rule_model.zip new file mode 100644 index 0000000..bfad00f Binary files /dev/null and b/server/media/example_agents/example_luduc_rule_model.zip differ diff --git a/server/tournament/migrations/0001_initial.py b/server/tournament/migrations/0001_initial.py index 66523fb..ced81c0 100644 --- a/server/tournament/migrations/0001_initial.py +++ b/server/tournament/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 2.2.12 on 2020-05-13 02:29 +# Generated by Django 3.1.4 on 2020-12-11 17:43 from django.db import migrations, models @@ -40,7 +40,6 @@ class Migration(migrations.Migration): ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('name', models.CharField(max_length=100)), ('game', models.CharField(max_length=100)), - ('entry', models.CharField(max_length=100)), ('f', models.FileField(upload_to='uploaded_agents')), ], ), diff --git a/server/tournament/models.py b/server/tournament/models.py index a2f6eac..4f71ef3 100644 --- a/server/tournament/models.py +++ b/server/tournament/models.py @@ -43,9 +43,6 @@ class UploadedAgent(models.Model): # The game of the agent game = models.CharField(max_length=100) - # The class name of the Model - entry = models.CharField(max_length=100) - # File f = models.FileField(upload_to='uploaded_agents') diff --git a/server/tournament/urls.py b/server/tournament/urls.py index c52387b..5c8bd6f 100644 --- a/server/tournament/urls.py +++ b/server/tournament/urls.py @@ -12,4 +12,5 @@ urlpatterns = [ path('delete_agent', views.delete_agent, name='delete_agent'), path('list_uploaded_agents', views.list_uploaded_agents, name='list_uploaded_agents'), path('list_baseline_agents', views.list_baseline_agents, name='list_baseline_agents'), + path('download_examples', views.download_examples, name='download_examples'), ] diff --git a/server/tournament/views.py b/server/tournament/views.py index b156c1e..3677391 100644 --- a/server/tournament/views.py +++ b/server/tournament/views.py @@ -3,9 +3,11 @@ import os import importlib.util import math import copy +import zipfile +import shutil from django.shortcuts import render -from django.http import HttpResponse +from django.http import HttpResponse, Http404 from django.db import transaction from django.core import serializers from django.views.decorators.csrf import csrf_exempt @@ -27,9 +29,10 @@ def _get_model_ids_all(): path = os.path.join(settings.MEDIA_ROOT, agent.f.name) name = agent.name game = agent.game - entry = agent.entry - module_name = path.split('/')[-1].split('.')[0] - spec = importlib.util.spec_from_file_location(module_name, path) + target_path = os.path.join(os.path.abspath(os.path.join(path, os.pardir)), name) + module_name = 'model' + entry = 'Model' + spec = importlib.util.spec_from_file_location(module_name, os.path.join(target_path, module_name+'.py')) module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) M = getattr(module, entry) @@ -40,7 +43,7 @@ def _get_model_ids_all(): self._entry_point = M def load(self): - model = self._entry_point() + model = self._entry_point(target_path) return model rlcard.models.registration.model_registry.model_specs[name] = ModelSpec() MODEL_IDS_ALL[game].append(name) @@ -92,7 +95,6 @@ def query_agent_payoff(request): if not 'name' in request.GET: return HttpResponse(json.dumps({'value': -2, 'info': 'name should be given'})) result = list(Payoff.objects.filter(name=request.GET['name']).values('agent0').annotate(payoff = Avg('payoff')).order_by('-payoff')) - print(result) result, total_page, total_row = _get_page(result, request.GET['elements_every_page'], request.GET['page_index']) return HttpResponse(json.dumps({'value': 0, 'data': result, 'total_page': total_page, 'total_row': total_row})) @@ -132,12 +134,15 @@ def upload_agent(request): f = request.FILES['model'] name = request.POST['name'] game = request.POST['game'] - entry = request.POST['entry'] if UploadedAgent.objects.filter(name=name).exists(): return HttpResponse(json.dumps({'value': -1, 'info': 'name exists'})) - a = UploadedAgent(name=name, game=game, f=f, entry=entry) + a = UploadedAgent(name=name, game=game, f=f) a.save() + path = os.path.join(settings.MEDIA_ROOT, a.f.name) + target_path = os.path.join(os.path.abspath(os.path.join(path, os.pardir)), name) + with zipfile.ZipFile(path, 'r') as zip_ref: + zip_ref.extractall(target_path) return HttpResponse(json.dumps({'value': 0, 'info': 'success'})) def delete_agent(request): @@ -146,9 +151,16 @@ def delete_agent(request): if not UploadedAgent.objects.filter(name=name).exists(): return HttpResponse(json.dumps({'value': -1, 'info': 'name not exists'})) - UploadedAgent.objects.filter(name=name).delete() + agents = UploadedAgent.objects.filter(name=name) + path = os.path.join(settings.MEDIA_ROOT, agents[0].f.name) + target_path = os.path.join(os.path.abspath(os.path.join(path, os.pardir)), name) + shutil.rmtree(target_path) + + agents.delete() Game.objects.filter(agent0=name).delete() Game.objects.filter(agent1=name).delete() + Payoff.objects.filter(agent0=name).delete() + Payoff.objects.filter(agent1=name).delete() return HttpResponse(json.dumps({'value': 0, 'info': 'success'})) def list_uploaded_agents(request): @@ -170,3 +182,14 @@ def auto_delete_file_on_delete(sender, instance, **kwargs): if instance.f: if os.path.isfile(instance.f.path): os.remove(instance.f.path) + +def download_examples(request): + if request.method == 'GET': + name = request.GET['name'] + file_path = os.path.join(settings.MEDIA_ROOT, 'example_agents', name+'.zip') + if os.path.exists(file_path): + with open(file_path, 'rb') as fh: + response = HttpResponse(fh.read(), content_type="application/vnd.ms-excel") + response['Content-Disposition'] = 'inline; filename=' + os.path.basename(file_path) + return response + raise Http404 diff --git a/server/upload_test/example_model.py b/server/upload_test/example_model.py deleted file mode 100644 index e5010a4..0000000 --- a/server/upload_test/example_model.py +++ /dev/null @@ -1,84 +0,0 @@ -''' Leduc Hold 'em rule model -''' -import rlcard -from rlcard.models.model import Model - -class LeducHoldemRuleAgentV2(object): - ''' Leduc Hold 'em Rule agent version 2 - ''' - def __init__(self): - self.use_raw = True - - def step(self, state): - ''' Predict the action when given raw state. A simple rule-based AI. - Args: - state (dict): Raw state from the game - - Returns: - action (str): Predicted action - ''' - legal_actions = state['raw_legal_actions'] - state = state['raw_obs'] - hand = state['hand'] - public_card = state['public_card'] - action = 'fold' - ''' - When having only 2 hand cards at the game start, choose fold to drop terrible cards: - Acceptable hand cards: - Pairs - AK, AQ, AJ, AT - A9s, A8s, ... A2s(s means flush) - KQ, KJ, QJ, JT - Fold all hand types except those mentioned above to save money - ''' - if public_card: - if public_card[1] == hand[1]: - action = 'raise' - else: - action = 'fold' - else: - if hand[0] == 'K': - action = 'raise' - elif hand[0] == 'Q': - action = 'check' - else: - action = 'fold' - - #return action - if action in legal_actions: - return action - else: - if action == 'raise': - return 'call' - if action == 'check': - return 'fold' - if action == 'call': - return 'raise' - else: - return action - - def eval_step(self, state): - return self.step(state), [] - -class LeducHoldemRuleModelV2(Model): - ''' Leduc holdem Rule Model version 2 - ''' - - def __init__(self): - ''' Load pretrained model - ''' - env = rlcard.make('leduc-holdem') - rule_agent = LeducHoldemRuleAgentV2() - self.rule_agents = [rule_agent for _ in range(env.player_num)] - - @property - def agents(self): - ''' Get a list of agents for each position in a the game - - Returns: - agents (list): A list of agents - - Note: Each agent should be just like RL agent with step and eval_step - functioning well. - ''' - return self.rule_agents