diff --git a/.gitignore b/.gitignore index ce153cd..a232253 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ assets/ node_modules package.lock *.pyc +config.js diff --git a/pytron b/pytron index b2504f4..8d27a92 160000 --- a/pytron +++ b/pytron @@ -1 +1 @@ -Subproject commit b2504f49f9e30e3ffa07ec3ab11d6b465829b593 +Subproject commit 8d27a9293e2ffbb1119b9c3c453aec76be612a50 diff --git a/pytron_run/ai_manager/__init__.py b/pytron_run/ai_manager/__init__.py index d2dd5e3..5739040 100644 --- a/pytron_run/ai_manager/__init__.py +++ b/pytron_run/ai_manager/__init__.py @@ -1,4 +1,34 @@ -from os.path import dirname, basename, isfile -import glob -modules = glob.glob(dirname(__file__)+"/*/ai.py") -__all__ = ['.'.join(f.split('.')[:-1]) for f in modules if isfile(f)] +from os.path import dirname, basename, isfile, getmtime +from importlib import import_module +from glob import glob + +modules = glob(dirname(__file__)+"/*/ai.py") +__all__ = [] + +class UploadedAi: + def __init__(self, name, module, constructor, date): + self.name = name + self.module = module + self.constructor = constructor + self.date = date + +for path in [f for f in modules if isfile(f)]: + # Remove the .py + toto = '.'.join(path.split('.')[:-1]) + + # Compute the name + name = toto.split('/')[-2] + + # Find the module + module = import_module('.'.join(toto.split('/')[-3:])) + + # Find the constructor + constructor = getattr(module, 'Ai') + + # Moment when the file was last modified + date = getmtime(path) + + # Append to the list of uploaded ais + __all__.append(UploadedAi(name, module, constructor, date)) + +__all__ = sorted(__all__, key = lambda ai: ai.name) diff --git a/pytron_run/refresh.py b/pytron_run/refresh.py new file mode 100755 index 0000000..621d6c1 --- /dev/null +++ b/pytron_run/refresh.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python3 + +import os +import pathlib +import sys +import json +sys.path.append('../pytron') + +from tron.map import Map +from tron.game import Game, PositionPlayer +from tron.player import Direction, ConstantPlayer + +import ai_manager +from utils import run_battle + +ASSETS_PATH = "assets/data.json" +LAST_REFRESH_PATH = "assets/refresh.dat" + +width = 10 +height = 10 + +def main(): + + print('Welcome to pytron refresh!') + sys.stdout.flush() + + # Read the last moment when this script was run + last_refresh = os.path.getmtime(LAST_REFRESH_PATH) + + # Set last update time + pathlib.Path(LAST_REFRESH_PATH).touch() + + # Find the ais that need to be refreshed + ais_to_refresh = [ai for ai in ai_manager.__all__ if ai.date > last_refresh] + print('New ais: ', list(map(lambda x: x.name, ais_to_refresh))) + print('Ais: ', list(map(lambda x: x.name, ai_manager.__all__))) + + # Read current state + with open(ASSETS_PATH, 'r') as file: + dictionnary = json.loads(file.read()) + + # Dictionnary to record battles that already occured + battles_done = {} + + for ai1 in ais_to_refresh: + for ai2 in ai_manager.__all__: + if ai1.name == ai2.name: + continue + + # Sort ais by name just to be sure + if ai1.name > ai2.name: + (sai2, sai1) = (ai1, ai2) + else: + (sai1, sai2) = (ai1, ai2) + + slash_name = sai1.name + "/" + sai2.name + + # Record battle to be done, skip if already done + if battles_done.get(slash_name, None) is None: + battles_done[slash_name] = True + else: + continue + + print("Battling {} vs {}".format(sai1.name, sai2.name)) + sys.stdout.flush() + + (score1, score2, nulls) = run_battle(sai1, sai2, width, height) + dictionnary[slash_name] = [score1, score2, nulls] + + with open(ASSETS_PATH, "w") as f: + f.write(json.dumps(dictionnary)) + + print('Pytron run has finished') + +if __name__ == '__main__': + main() + diff --git a/pytron_run/run.py b/pytron_run/run.py index 5275b74..3b6c5fa 100755 --- a/pytron_run/run.py +++ b/pytron_run/run.py @@ -2,82 +2,43 @@ import sys import json +import pathlib sys.path.append('../pytron') from tron.map import Map from tron.game import Game, PositionPlayer from tron.player import Direction, ConstantPlayer -from importlib import import_module - -import positions import ai_manager +from utils import run_battle -# Find all the AIs -class AiClass: - def __init__(self, name, builder): - self.name = name - self.builder = builder - -ais = [] -for real_ai in ai_manager.__all__: - ai_name = real_ai.split('/')[-2] - ai_module = import_module('.'.join(real_ai.split('/')[-3:])) - ai_class = getattr(ai_module, "Ai") - ais.append(AiClass(ai_name, ai_class)) - - -# This script shows how to create a game with AI, that will run automatically. -# It is made to be fast and not to be used by humans. It especially doesn't -# display and window and doesn't listen to any keystrokes. +ASSETS_PATH = "assets/data.json" +LAST_REFRESH_PATH = "assets/refresh.dat" width = 10 height = 10 -def run_battle(ai1, ai2): - games = 50 - ai1_victories = 0 - ai2_victories = 0 - - for (initial_position_one, initial_position_two) in positions.positions(): - game = Game(width, height, [ - PositionPlayer(1, ai1.builder(), initial_position_one), - PositionPlayer(2, ai2.builder(), initial_position_two), - ]) - game.main_loop() - - if game.winner == 1: - ai1_victories += 1 - elif game.winner == 2: - ai2_victories += 1 - - # Inverse positions and replay to be symmetrical - game = Game(width, height, [ - PositionPlayer(1, ai2.builder(), initial_position_one), - PositionPlayer(2, ai1.builder(), initial_position_two), - ]) - game.main_loop() - - if game.winner == 1: - ai2_victories += 1 - elif game.winner == 2: - ai1_victories += 1 - - return (ai1_victories, ai2_victories, 2 * games - ai1_victories - ai2_victories) - def main(): - print('Welcome to pytron run!') - dictionnary = {"ais": list(map(lambda x: x.name, ais)), "battles": {}} + dictionnary = {} - for (id1, ai1) in enumerate(ais): - for (id2, ai2) in enumerate(ais): + # Set last update time + pathlib.Path(LAST_REFRESH_PATH).touch() + + for (id1, ai1) in enumerate(ai_manager.__all__): + for (id2, ai2) in enumerate(ai_manager.__all__): if id1 >= id2: continue - print("Battling {} vs {}".format(ai1.name, ai2.name)) - (score1, score2, nulls) = run_battle(ai1, ai2) - dictionnary["battles"][ai1.name + "/" + ai2.name] = [score1, score2, nulls] + # Sort ais by name just to be sure + if ai1.name > ai2.name: + (sai2, sai1) = (ai1, ai2) + else: + (sai1, sai2) = (ai1, ai2) + + print("Battling {} vs {}".format(sai1.name, sai2.name)) + (score1, score2, nulls) = run_battle(sai1, sai2, width, height) + dictionnary[sai1.name + "/" + sai2.name] = [score1, score2, nulls] with open("assets/data.json", "w") as f: f.write(json.dumps(dictionnary)) diff --git a/pytron_run/utils.py b/pytron_run/utils.py new file mode 100644 index 0000000..c8ae7af --- /dev/null +++ b/pytron_run/utils.py @@ -0,0 +1,34 @@ +from positions import positions +from tron.game import Game, PositionPlayer + +def run_battle(ai1, ai2, width, height): + games = 50 + ai1_victories = 0 + ai2_victories = 0 + + for (initial_position_one, initial_position_two) in positions(): + game = Game(width, height, [ + PositionPlayer(1, ai1.constructor(), initial_position_one), + PositionPlayer(2, ai2.constructor(), initial_position_two), + ]) + game.main_loop() + + if game.winner == 1: + ai1_victories += 1 + elif game.winner == 2: + ai2_victories += 1 + + # Inverse positions and replay to be symmetrical + game = Game(width, height, [ + PositionPlayer(1, ai2.constructor(), initial_position_one), + PositionPlayer(2, ai1.constructor(), initial_position_two), + ]) + game.main_loop() + + if game.winner == 1: + ai2_victories += 1 + elif game.winner == 2: + ai1_victories += 1 + + return (ai1_victories, ai2_victories, 2 * games - ai1_victories - ai2_victories) + diff --git a/server.js b/server.js index 2c0d186..091a0eb 100644 --- a/server.js +++ b/server.js @@ -9,13 +9,7 @@ const pug = require('pug'); const unzip = require('unzip'); const touch = require('touch'); const bcrypt = require('bcryptjs'); - -// Consts -const port = 8000; -const pythonPath = '/home/pytron/miniconda3/envs/pytron/bin/python' -const aisPath = 'pytron_run/ai_manager/' -const aisPathOld = 'pytron_run/ai_manager_old' -const hashPath = "__bcrypt__hash.txt" +const { port, pythonPath, aisPath, aisPathOld, hashPath } = require('./config'); // Create the directories to store the files try { fs.mkdirSync(aisPath); } catch { } @@ -34,7 +28,7 @@ function runPython() { } process.stdout.write('\n'); - let p = spawn(pythonPath, ['run.py'], {cwd: 'pytron_run'}); + let p = spawn(pythonPath, ['refresh.py'], {cwd: 'pytron_run'}); p.stdout.on('data', (data) => { let content = data.toString().split('\n'); for (let line of content) { @@ -62,35 +56,52 @@ function runPython() { } }); } + // parsed.ais.push({ + // }); function parse(data) { let content = JSON.parse(data); let parsed = {ais: [], battles: {}}; - for (let ai of content.ais) { - parsed.ais.push({ - name: ai, - victories: 0, - defeats: 0, - nulls: 0, - score: 0, - }); - } - for (let key in content.battles) { + for (let key in content) { let battlers = key.split('/'); let ai1 = battlers[0]; let ai2 = battlers[1]; - parsed.battles[ai1 + '/' + ai2] = content.battles[key][0]; - parsed.battles[ai2 + '/' + ai1] = content.battles[key][1]; + parsed.battles[ai1 + '/' + ai2] = content[key][0]; + parsed.battles[ai2 + '/' + ai1] = content[key][1]; let realAi1 = parsed.ais.find((x) => x.name == ai1); + + if (realAi1 === undefined) { + realAi1 = { + name: ai1, + victories: 0, + defeats: 0, + nulls: 0, + score: 0, + }; + parsed.ais.push(realAi1); + } + let realAi2 = parsed.ais.find((x) => x.name == ai2); - realAi1.victories += content.battles[key][0]; - realAi1.defeats += content.battles[key][1]; - realAi2.victories += content.battles[key][1]; - realAi2.defeats += content.battles[key][0]; - realAi1.nulls += content.battles[key][2]; - realAi2.nulls += content.battles[key][2]; + + if (realAi2 === undefined) { + realAi2 = { + name: ai2, + victories: 0, + defeats: 0, + nulls: 0, + score: 0, + }; + parsed.ais.push(realAi2); + } + + realAi1.victories += content[key][0]; + realAi1.defeats += content[key][1]; + realAi2.victories += content[key][1]; + realAi2.defeats += content[key][0]; + realAi1.nulls += content[key][2]; + realAi2.nulls += content[key][2]; } parsed.sortedAis = parsed.ais.slice(0);