Refactor ai-test-suite (python script).
This commit is contained in:
parent
6472560ca7
commit
516154c3d1
11 changed files with 680 additions and 787 deletions
|
@ -32,6 +32,7 @@ Version 1.11.6+dev:
|
|||
configuration work again (suspected version 1.7.4 regression).
|
||||
* Move_Leader_To_Keep CA will now move all leaders back to a keep
|
||||
(multiple leader support for recruitment).
|
||||
* Refactored the AI-Test-Suite completely (/utils/ai_test/).
|
||||
* Campaigns:
|
||||
* Legend of Wesmere:
|
||||
* Removed now redundant MP code.
|
||||
|
|
|
@ -1,17 +1,73 @@
|
|||
[default]
|
||||
path_to_wesnoth_binary=../../wesnoth-debug ../..
|
||||
arguments_to_wesnoth_binary=--log-info=ai/testing,mp/connect --nogui --multiplayer --controller1=ai --controller2=ai
|
||||
number_of_tests=10000
|
||||
ai_config1=ai/ais/formula_ai.cfg
|
||||
ai_config2=ai/ais/default_ai.cfg
|
||||
faction1=
|
||||
faction2=
|
||||
db_ip=127.0.0.1
|
||||
db_port=5432
|
||||
db_name=org.wesnoth.ai.test
|
||||
db_user=wesnoth_ai_test_user
|
||||
db_pass=PASSWORD
|
||||
map1=multiplayer_Weldyn_Channel
|
||||
map2=multiplayer_The_Freelands
|
||||
map3=multiplayer_Den_of_Onis
|
||||
map4=multiplayer_Fallenstar_Lake
|
||||
|
||||
# The title is only used when using the database.
|
||||
# Include in the title some hints of the version
|
||||
# of the AI you are using
|
||||
# (e.g. "V3 Combat Analysis weighted twice")
|
||||
title = Untitled
|
||||
|
||||
|
||||
path_to_wesnoth_binary = ../../cmake_build_dir/wesnoth
|
||||
|
||||
|
||||
# this is the first parts of arguments
|
||||
# the second part is added by the script
|
||||
# the first argument should be the
|
||||
# relative path to <data-directory>
|
||||
additional_arguments = ../../ --controller=1:ai --controller=2:ai --parm 1:gold:200 --parm 2:gold:200
|
||||
|
||||
|
||||
# This is the number of tests when
|
||||
# structured_test = no
|
||||
number_of_tests = 2
|
||||
|
||||
|
||||
# Repeats each test n times using the --repeat argument.
|
||||
# The idea behind this is that repeated games will run
|
||||
# without executing wesnoth over and over again.
|
||||
# When n is high enough it will save ~30 % execution time.
|
||||
# The number of total games is
|
||||
# number_of_tests * repeats.
|
||||
repeat = 1
|
||||
|
||||
|
||||
# If you encounter problems with macros
|
||||
# (e.g. "Macro/file 'AI_CA_GOTO' is missing")
|
||||
# add the following line at the beginning
|
||||
# of the AI's cfg-file:
|
||||
# {core/macros}
|
||||
ai_config1 = ai/ais/ai_default_rca.cfg
|
||||
ai_config2 = ai/dev/idle_ai.cfg
|
||||
|
||||
|
||||
# leave empty for random fractions
|
||||
# Possible factions are: Drakes, Rebels, Undead, Northerners, Knalgan Alliance, Loyalists
|
||||
faction1 =
|
||||
faction2 =
|
||||
|
||||
|
||||
randomize_sides = yes
|
||||
|
||||
|
||||
# When set to 'yes' the factions will be not randomized.
|
||||
# Instead each faction-combination will play <repeat> times.
|
||||
# <number_of_tests>, <faction1>, <faction2> will then be ignored.
|
||||
structured_test = no
|
||||
|
||||
|
||||
# Comment this line out when you don't want to create logfiles.
|
||||
# See here for time-format conventions: http://docs.python.org/2/library/time.html#time.strftime
|
||||
log_file = ai_test__%Y_%m_%d__%H-%M__myAI_vs_default.log
|
||||
|
||||
|
||||
# uncomment this line if you want to save
|
||||
# the results in a sqlite3 Database
|
||||
# Note that we should have write access to this file.
|
||||
# Also it is convenient to save the file outside the git repo.
|
||||
# sqlite_file = ../../../ai_test_db/ai_test.sqlite
|
||||
|
||||
|
||||
map = multiplayer_Weldyn_Channel
|
||||
#map = multiplayer_The_Freelands
|
||||
#map = multiplayer_Den_of_Onis
|
||||
#map = multiplayer_Fallenstar_Lake
|
||||
|
|
|
@ -1,183 +1,346 @@
|
|||
#!/usr/bin/env python
|
||||
from subprocess import Popen,PIPE
|
||||
from subprocess import Popen, PIPE
|
||||
from time import clock, time
|
||||
from pyPgSQL import PgSQL
|
||||
import datetime
|
||||
import sqlite3
|
||||
import ConfigParser
|
||||
import os
|
||||
import string
|
||||
import random
|
||||
import sys
|
||||
|
||||
class GameResult:
|
||||
ai_config1 = ''
|
||||
ai_config2 = ''
|
||||
ai_ident1 = ''
|
||||
ai_ident2 = ''
|
||||
duration = '0'
|
||||
faction1 = ''
|
||||
faction2 = ''
|
||||
is_success = 'false'
|
||||
local_modifications = 'false'
|
||||
map = ''
|
||||
repo_release = '0'
|
||||
test = 'default'
|
||||
end_turn = '0'
|
||||
version_string = ''
|
||||
winner_side = '0'
|
||||
# Wording conversations:
|
||||
# We have one 'Test'
|
||||
# One 'Test' includes multiple 'Executions'
|
||||
# One 'Execution' has one or more 'Games'
|
||||
|
||||
def __init__(self, _ai_config1, _ai_config2, _faction1, _faction2, _map,
|
||||
_test):
|
||||
self.ai_config1 = _ai_config1
|
||||
self.ai_config2 = _ai_config2
|
||||
self.faction1 = _faction1
|
||||
self.faction2 = _faction2
|
||||
self.map = _map
|
||||
self.test = _test
|
||||
class Test:
|
||||
ai_config1 = ''
|
||||
ai_config2 = ''
|
||||
ai_ident1 = ''
|
||||
ai_ident2 = ''
|
||||
map = ''
|
||||
version_string = ''
|
||||
faction1 = ''
|
||||
faction2 = ''
|
||||
time = ''
|
||||
test_id = 0
|
||||
title = ''
|
||||
game_results = []
|
||||
|
||||
def __init__(self, _ai_config1, _ai_config2, _faction1, _faction2, _map, _title):
|
||||
self.ai_config1 = _ai_config1
|
||||
self.ai_config2 = _ai_config2
|
||||
self.faction1 = _faction1
|
||||
self.faction2 = _faction2
|
||||
self.map = _map
|
||||
self.title = _title
|
||||
|
||||
today = datetime.datetime.now()
|
||||
self.time = today.strftime('%Y-%m-%d %H:%M')
|
||||
|
||||
def filter_non_printable(str):
|
||||
return ''.join(c for c in str if ord(c) > 31 or ord(c) == 9)
|
||||
return ''.join(c for c in str if ord(c) > 31 or ord(c) == 9)
|
||||
|
||||
def construct_command_line(cfg, ai1, ai2, f1, f2,map):
|
||||
wesnoth = cfg.get('default', 'path_to_wesnoth_binary')
|
||||
options= cfg.get('default', 'arguments_to_wesnoth_binary')
|
||||
ai_config1= '--ai_config1=' + ai1
|
||||
ai_config2= '--ai_config2=' +ai2
|
||||
if not map:
|
||||
optmap = ''
|
||||
else:
|
||||
optmap = '--scenario=' + map
|
||||
return '%s %s %s %s %s' % (wesnoth, options, optmap, ai_config1, ai_config2)
|
||||
def construct_command_line(cfg, test, switched_side):
|
||||
wesnoth = cfg.get('default', 'path_to_wesnoth_binary')
|
||||
options = cfg.get('default', 'additional_arguments')
|
||||
repeats = cfg.getint('default', 'repeat')
|
||||
repeats_param = '--multiplayer-repeat ' + str(repeats)
|
||||
if repeats > 1:
|
||||
print 'Be patient, ' + str(repeats) + ' repeats are going to take a while.'
|
||||
|
||||
side1 = test.ai_config1 if not switched_side else test.ai_config2
|
||||
side2 = test.ai_config2 if not switched_side else test.ai_config1
|
||||
faction1 = test.faction1 if not switched_side else test.faction2
|
||||
faction2 = test.faction2 if not switched_side else test.faction1
|
||||
ai_param1 = '--ai-config 1:' + side1 if side1 else ''
|
||||
ai_param2 = '--ai-config 2:' + side2 if side2 else ''
|
||||
faction_param1 = '--side 1:"' + faction1 + '"' if faction1 else ''
|
||||
faction_param2 = '--side 2:"' + faction2 + '"' if faction2 else ''
|
||||
map_param = '--scenario=' + test.map if test.map else ''
|
||||
|
||||
if len(sys.argv) > 1 and sys.argv[1] == '-p':
|
||||
gui = '--debug'
|
||||
else:
|
||||
gui = '--nogui'
|
||||
|
||||
statics = '--log-info=ai/testing,mp/connect/engine --multiplayer'
|
||||
return (wesnoth + ' ' + options + ' ' + map_param + ' ' + ai_param1 + ' ' + ai_param2 +
|
||||
' ' + faction_param1 + ' ' + faction_param2 + ' ' + gui + ' ' + repeats_param + ' ' + statics)
|
||||
|
||||
def do_filter(str, substring):
|
||||
n = str.find(substring)
|
||||
if n > -1:
|
||||
return n, str[n + len(substring):].strip()
|
||||
return n, ''
|
||||
n = str.find(substring)
|
||||
if (n > -1):
|
||||
temp = str[n + len(substring):].strip()
|
||||
c = temp.find(',')
|
||||
if (c > -1):
|
||||
return n, temp[:c].strip()
|
||||
else:
|
||||
return n, temp
|
||||
return n, ''
|
||||
|
||||
def run_game(cfg,game_result):
|
||||
command_line = construct_command_line(cfg, game_result.ai_config1, game_result.ai_config2, game_result.faction1, game_result.faction2, game_result.map)
|
||||
print 'Running: ' + command_line
|
||||
start = time()
|
||||
p = Popen(command_line, shell=True, bufsize=10000000, stdout=PIPE, stderr=PIPE)
|
||||
# outlines = p.stdout.readlines()
|
||||
outerrlines = p.stderr.readlines()
|
||||
print 'Finished'
|
||||
for line in outerrlines:
|
||||
str = filter_non_printable(line.strip())
|
||||
n,s = do_filter(str, 'info ai/testing: WINNER:')
|
||||
if n > -1:
|
||||
#print 'AND THE WINNER IS: '+s
|
||||
game_result.winner_side = s
|
||||
game_result.is_success = 'true'
|
||||
continue
|
||||
def run_game(cfg, test, switched_side):
|
||||
command_line = construct_command_line(cfg, test, switched_side)
|
||||
print 'Running: ' + command_line
|
||||
|
||||
n, s = do_filter(str, 'info ai/testing: VERSION:')
|
||||
if n > -1:
|
||||
#print 'AND THE VERSION IS: '+s
|
||||
game_result.version_string = s
|
||||
n1 = s.rfind('(')
|
||||
n2 = s.rfind(')')
|
||||
if -1 < n1 < n2:
|
||||
sz = s[n1+1:n2]
|
||||
#parse local_modifications
|
||||
n3 = sz.rfind('M')
|
||||
if n3 > -1:
|
||||
sz = sz[:n3]
|
||||
game_result.local_modifications = 1
|
||||
#parse repo_release
|
||||
game_result.repo_release = sz
|
||||
continue
|
||||
game_results = []
|
||||
game_result = None
|
||||
faction1 = ''
|
||||
faction2 = ''
|
||||
debugout = ''
|
||||
|
||||
n,s = do_filter(str ,'info ai/testing: GAME_END_TURN:')
|
||||
if n > -1:
|
||||
#print 'AND THE VICTORY_TURN IS: '+s
|
||||
game_result.end_turn = s
|
||||
continue
|
||||
p = Popen(command_line, shell=True, bufsize=10000000, stdout=PIPE, stderr=PIPE)
|
||||
|
||||
n, s = do_filter(str, 'info ai/testing: AI_IDENTIFIER1:')
|
||||
if n > -1:
|
||||
#print 'AND THE AI_IDENTIFIER1 IS: '+s
|
||||
game_result.ai_ident1 = s.strip()
|
||||
continue
|
||||
for line in p.stderr:
|
||||
l = filter_non_printable(line.strip())
|
||||
debugout += l + '\n'
|
||||
|
||||
n, s = do_filter(str, 'info ai/testing: AI_IDENTIFIER2:')
|
||||
if n > -1:
|
||||
#print 'AND THE AI_IDENTIFIER2 IS: '+s
|
||||
game_result.ai_ident2 = s.strip()
|
||||
continue
|
||||
n, s = do_filter(l , 'side 1: faction=')
|
||||
if (n > -1):
|
||||
faction1 = s
|
||||
continue
|
||||
|
||||
n, s = do_filter(str, 'info mp/connect: FACTION1:')
|
||||
if n > -1:
|
||||
#print 'AND THE FACTION1 IS: '+s
|
||||
game_result.faction1 = s
|
||||
continue
|
||||
n, s = do_filter(l , 'side 2: faction=')
|
||||
if (n > -1):
|
||||
faction2 = s
|
||||
continue
|
||||
|
||||
n, s = do_filter(str, 'info mp/connect: FACTION2:')
|
||||
if n > -1:
|
||||
#print 'AND THE FACTION2 IS: '+s
|
||||
game_result.faction2 = s
|
||||
continue
|
||||
n, s = do_filter(l , 'info ai/testing: VERSION:')
|
||||
if (n > -1):
|
||||
test.version_string = s
|
||||
continue
|
||||
|
||||
game_result.duration = time() - start
|
||||
if (game_result.is_success=='false'):
|
||||
print 'Warning: not success!'
|
||||
print '===================='
|
||||
print 'stderr:'
|
||||
for line in outerrlines:
|
||||
print filter_non_printable(line.strip())
|
||||
print '===================='
|
||||
n, s = do_filter(l , 'info ai/testing: AI_IDENTIFIER1:')
|
||||
if(n > -1):
|
||||
if switched_side:
|
||||
test.ai_ident2 = s
|
||||
else:
|
||||
test.ai_ident1 = s
|
||||
|
||||
return game_result
|
||||
# this is the first line of a game.
|
||||
# We'll do some initializations here.
|
||||
|
||||
def save_result(cfg,game_result):
|
||||
print 'Saving to DB....'
|
||||
query = 'insert into game(ai_config1,ai_config2,ai_ident1,ai_ident2,duration,faction1,faction2,is_success,local_modifications,map,repo_release,test,end_turn,version_string,winner_side) values (%s,%s,%s,%s,cast(%s as double precision),%s,%s,cast(%s as boolean),cast(%s as boolean),%s,cast(%s as int),%s,cast(%s as int),%s,cast(%s as int))'
|
||||
db_ip = cfg.get('default','db_ip')
|
||||
db_port = cfg.getint('default','db_port')
|
||||
db_name = cfg.get('default','db_name')
|
||||
db_user = cfg.get('default','db_user')
|
||||
db_pass = cfg.get('default','db_pass')
|
||||
game_result = {}
|
||||
game_result['switched_side'] = switched_side
|
||||
game_result['is_success'] = False
|
||||
continue
|
||||
|
||||
dbconnection = PgSQL.connect(database=db_name,host=db_ip,port=db_port,user=db_user,password=db_pass)
|
||||
cu = dbconnection.cursor()
|
||||
cu.execute(query, game_result.ai_config1, game_result.ai_config2, game_result.ai_ident1, game_result.ai_ident2, game_result.duration, game_result.faction1, game_result.faction2, game_result.is_success, game_result.local_modifications, game_result.map, game_result.repo_release, game_result.test, game_result.end_turn, game_result.version_string, game_result.winner_side)
|
||||
cu.execute('commit')
|
||||
dbconnection.close()
|
||||
print 'Saved to DB'
|
||||
n, s = do_filter(l , 'info ai/testing: AI_IDENTIFIER2:')
|
||||
if(n > -1):
|
||||
if switched_side:
|
||||
test.ai_ident1 = s
|
||||
else:
|
||||
test.ai_ident2 = s
|
||||
continue
|
||||
|
||||
def maps(cfg):
|
||||
mp = 1
|
||||
while 1:
|
||||
try:
|
||||
yield cfg.get('default', 'map' + repr(mp))
|
||||
mp += 1
|
||||
except:
|
||||
return
|
||||
n, s = do_filter(l , 'info ai/testing: WINNER:')
|
||||
if (n > -1):
|
||||
if (int(s) == 1) != switched_side:
|
||||
winner = 1
|
||||
else:
|
||||
winner = 2
|
||||
print 'AND THE WINNER IS: ' + str(winner)
|
||||
if game_result.has_key('winner'):
|
||||
game_result['is_success'] = False
|
||||
break
|
||||
game_result['winner'] = winner
|
||||
game_result['is_success'] = True
|
||||
continue
|
||||
|
||||
def tests(cfg):
|
||||
ai1 = cfg.get('default', 'ai_config1').strip()
|
||||
ai2 = cfg.get('default', 'ai_config2').strip()
|
||||
f1 = cfg.get('default', 'faction1').strip()
|
||||
f2 = cfg.get('default', 'faction2').strip()
|
||||
n = cfg.getint('default', 'number_of_tests')
|
||||
maplist = []
|
||||
for map in maps(cfg):
|
||||
maplist.append(map)
|
||||
random.seed()
|
||||
for i in xrange(n):
|
||||
map = random.choice(maplist)
|
||||
d = random.randint(0, 1)
|
||||
print 'TEST: map '+map+' i='+str(i)+' d='+str(d)
|
||||
if not d:
|
||||
game_result = GameResult(ai1, ai2, f1, f2, map, 'default')
|
||||
else:
|
||||
game_result = GameResult(ai2, ai1, f2, f1, map, 'default')
|
||||
yield game_result
|
||||
n, s = do_filter(l , 'info ai/testing: DRAW:')
|
||||
if(n > -1):
|
||||
print 'AND THE WINNER IS: DRAW'
|
||||
if game_result.has_key('winner'):
|
||||
game_result['is_success'] = False
|
||||
break
|
||||
game_result['winner'] = 0
|
||||
game_result['is_success'] = True
|
||||
continue
|
||||
|
||||
n, s = do_filter(l , 'info ai/testing: GAME_END_TURN:')
|
||||
if (n > -1):
|
||||
# this is the last line printed in a game
|
||||
# so we do some checking here and adding
|
||||
# game_result to game_results.
|
||||
|
||||
print 'AND THE VICTORY_TURN IS: ' + s
|
||||
|
||||
if game_result.has_key('end_turn'):
|
||||
game_result['is_success'] = False
|
||||
break
|
||||
|
||||
game_result['end_turn'] = int(s)
|
||||
game_result['faction1'] = faction1 if not switched_side else faction2
|
||||
game_result['faction2'] = faction2 if not switched_side else faction1
|
||||
|
||||
if not game_result['is_success']:
|
||||
print_error(debugout)
|
||||
|
||||
game_results.append(game_result)
|
||||
continue
|
||||
|
||||
n, s = do_filter(l , 'error')
|
||||
if(n > -1):
|
||||
# forward errors from stderr.
|
||||
print 'stderr give: error ' + s
|
||||
continue
|
||||
|
||||
|
||||
if game_result is None or not game_result['is_success']:
|
||||
print_error(debugout)
|
||||
return game_results
|
||||
|
||||
def print_error(debugout):
|
||||
print 'Warning: not success!'
|
||||
print '===================='
|
||||
print 'stderr:'
|
||||
print debugout
|
||||
print '===================='
|
||||
|
||||
def save_result_logfile(cfg, test, game_result, log_file):
|
||||
print 'Saving to log file....'
|
||||
log_file.write('"' + test.ai_config1 + '", "' +
|
||||
test.ai_config2 + '", "' +
|
||||
test.ai_ident1 + '", "' +
|
||||
test.ai_ident2 + '", "' +
|
||||
str(game_result['switched_side']) + '", "' +
|
||||
game_result['faction1'] + '", "' +
|
||||
game_result['faction2'] + '", "' +
|
||||
str(game_result['is_success']) + '", "' +
|
||||
test.map + '", "' +
|
||||
str(game_result['end_turn']) + '", "' +
|
||||
str(test.version_string) + '", "' +
|
||||
str(game_result['winner']) + '"\n');
|
||||
|
||||
log_file.flush();
|
||||
print 'Saved to log file'
|
||||
|
||||
def save_result_database(cfg, test, game_result, sqlite_file):
|
||||
print 'Saving to DB....'
|
||||
query = ('INSERT INTO games("test_id","faction1","faction2","switched_side","is_success","end_turn","winner")' +
|
||||
'VALUES (?,?,?,?,?,?,?)')
|
||||
|
||||
conn = sqlite3.connect(sqlite_file)
|
||||
cur = conn.cursor()
|
||||
cur.execute(query, (
|
||||
test.test_id,
|
||||
game_result['faction1'],
|
||||
game_result['faction2'],
|
||||
game_result['switched_side'],
|
||||
game_result['is_success'],
|
||||
game_result['end_turn'],
|
||||
game_result['winner']))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
print 'Saved to DB'
|
||||
|
||||
def executions(cfg, test):
|
||||
structured = cfg.getboolean('default', 'structured_test')
|
||||
if structured:
|
||||
factions = ['Drakes', 'Rebels', 'Undead', 'Northerners', 'Knalgan Alliance', 'Loyalists']
|
||||
i = 1
|
||||
for faction1 in factions:
|
||||
for faction2 in factions:
|
||||
print 'EXECUTION: ' + str(i) + '/36 --- ' + faction1 + ' against ' + faction2
|
||||
test.faction1 = faction1
|
||||
test.faction2 = faction2
|
||||
game_results = run_game(cfg, test, False)
|
||||
yield game_results
|
||||
i = i + 1
|
||||
test.faction1 = 'structured'
|
||||
test.faction2 = 'structured'
|
||||
else:
|
||||
n = cfg.getint('default', 'number_of_tests')
|
||||
randomize = cfg.getboolean('default', 'randomize_sides')
|
||||
random.seed()
|
||||
|
||||
for i in range(0, n):
|
||||
switched_side = (random.randint(0, 1) == 1) if randomize else False
|
||||
print 'EXECUTION ' + str(i + 1)
|
||||
game_results = run_game(cfg, test, switched_side)
|
||||
yield game_results
|
||||
|
||||
if test.faction1 == '':
|
||||
test.faction1 = 'random'
|
||||
if test.faction2 == '':
|
||||
test.faction2 = 'random'
|
||||
|
||||
# main
|
||||
|
||||
cfg = ConfigParser.ConfigParser()
|
||||
cfg.read('ai_test.cfg')
|
||||
|
||||
for test in tests(cfg):
|
||||
game_result = run_game(cfg, test)
|
||||
save_result(cfg,game_result)
|
||||
ai1 = cfg.get('default', 'ai_config1').strip()
|
||||
ai2 = cfg.get('default', 'ai_config2').strip()
|
||||
faction1 = cfg.get('default', 'faction1').strip()
|
||||
faction2 = cfg.get('default', 'faction2').strip()
|
||||
map = cfg.get('default', 'map').strip()
|
||||
if cfg.has_option('default', 'title'):
|
||||
title = cfg.get('default', 'title')
|
||||
else:
|
||||
title = ''
|
||||
|
||||
test = Test(ai1, ai2, faction1, faction2, map, title)
|
||||
|
||||
# only 'test the test' with GUI / start one game then exit
|
||||
if len(sys.argv) > 1 and sys.argv[1] == '-p':
|
||||
executions(cfg, test).next()
|
||||
sys.exit(0)
|
||||
|
||||
log_file = None
|
||||
if cfg.has_option('default', 'log_file'):
|
||||
log_file = open(datetime.datetime.now().strftime(cfg.get('default', 'log_file').strip()) , 'w')
|
||||
log_file.write('"ai_config1", "ai_config2", "ai_ident1", "ai_ident2", "switched_side", ' +
|
||||
'"faction1", "faction2", "is_success", "local_modifications", ' +
|
||||
'"map", "repo_release", "end_turn", "version_string", "winner_side"\n');
|
||||
log_file.flush();
|
||||
sqlite_file = None
|
||||
if cfg.has_option('default', 'sqlite_file'):
|
||||
sqlite_file = cfg.get('default', 'sqlite_file')
|
||||
conn = sqlite3.connect(sqlite_file)
|
||||
cur = conn.cursor()
|
||||
cur.execute('INSERT INTO tests ("title","ai_config1","ai_config2","map","time") ' +
|
||||
'VALUES ("' + test.title + '","' + test.ai_config1 + '","' + test.ai_config2 + '","' +
|
||||
test.map + '","' + test.time + '")')
|
||||
test.test_id = cur.lastrowid
|
||||
conn.commit()
|
||||
conn.close();
|
||||
|
||||
# the following variables are for generating a print output only
|
||||
total = 0
|
||||
ai1_won = 0
|
||||
ai2_won = 0
|
||||
draw = 0
|
||||
|
||||
for game_results in executions(cfg, test):
|
||||
for game_result in game_results:
|
||||
if log_file:
|
||||
save_result_logfile(cfg, test, game_result, log_file)
|
||||
if sqlite_file:
|
||||
save_result_database(cfg, test, game_result, sqlite_file)
|
||||
|
||||
total = total + 1
|
||||
if game_result['winner'] == 0:
|
||||
draw = draw + 1
|
||||
elif game_result['winner'] == 1:
|
||||
ai1_won = ai1_won + 1
|
||||
elif game_result['winner'] == 2:
|
||||
ai2_won = ai2_won + 1
|
||||
print '\n=====Status====='
|
||||
print 'Total games: ' + str(total)
|
||||
print 'AI1(' + ai1 + ') won: ' + str(ai1_won) + "/" + str(ai1_won * 100 / total) + '%'
|
||||
print 'AI2(' + ai2 + ') won: ' + str(ai2_won) + "/" + str(ai2_won * 100 / total) + '%'
|
||||
print 'Draws: ' + str(draw) + "/" + str(draw * 100 / total) + '%\n'
|
||||
if sqlite_file:
|
||||
conn = sqlite3.connect(sqlite_file)
|
||||
cur = conn.cursor()
|
||||
query = "UPDATE tests SET ai_ident1 = ?, ai_ident2 = ?, version = ? , faction1 = ?, faction2 = ? WHERE id = ?;"
|
||||
cur.execute(query, (test.ai_ident1,
|
||||
test.ai_ident2,
|
||||
test.version_string,
|
||||
test.faction1,
|
||||
test.faction2,
|
||||
test.test_id))
|
||||
conn.commit()
|
||||
conn.close();
|
||||
|
|
|
@ -1,18 +0,0 @@
|
|||
[default]
|
||||
path_to_wesnoth_binary=../../wesnoth
|
||||
arguments_to_wesnoth_binary=--log-info=ai/testing,mp/connect --nogui --multiplayer --controller1=ai --controller2=ai
|
||||
number_of_tests=400
|
||||
ai_config1=ai/ais/default_ai.cfg
|
||||
ai_config2=ai/ais/default_ai.cfg
|
||||
faction1=
|
||||
faction2=
|
||||
db_ip=127.0.0.1
|
||||
db_port=5432
|
||||
db_name=org.wesnoth.ai.test
|
||||
db_user=wesnoth_ai_test_user
|
||||
db_pass=PASSWORD
|
||||
log_file=ai_test__%A_%d_%B_%Y_%I-%M%p.log
|
||||
map1=multiplayer_Weldyn_Channel
|
||||
map2=multiplayer_The_Freelands
|
||||
map3=multiplayer_Den_of_Onis
|
||||
map4=multiplayer_Fallenstar_Lake
|
|
@ -1,178 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
from subprocess import Popen,PIPE
|
||||
from time import clock, time
|
||||
from datetime import datetime
|
||||
import ConfigParser
|
||||
import os
|
||||
import string
|
||||
import random
|
||||
|
||||
class GameResult:
|
||||
ai_config1 = ''
|
||||
ai_config2 = ''
|
||||
ai_ident1 = ''
|
||||
ai_ident2 = ''
|
||||
duration = '0'
|
||||
faction1 = ''
|
||||
faction2 = ''
|
||||
is_success = 'false'
|
||||
local_modifications = 'false'
|
||||
map = ''
|
||||
repo_release = '0'
|
||||
test = 'default'
|
||||
end_turn = '0'
|
||||
version_string = ''
|
||||
winner_side = '0'
|
||||
|
||||
def __init__(self,_ai_config1,_ai_config2,_faction1,_faction2,_map,_test):
|
||||
self.ai_config1 = _ai_config1
|
||||
self.ai_config2 = _ai_config2
|
||||
self.faction1 = _faction1
|
||||
self.faction2 = _faction2
|
||||
self.map = _map
|
||||
self.test = _test
|
||||
|
||||
def filter_non_printable(str):
|
||||
return ''.join(c for c in str if ord(c) > 31 or ord(c) == 9)
|
||||
|
||||
def construct_command_line(cfg,ai1,ai2,f1,f2,map):
|
||||
wesnoth = cfg.get('default','path_to_wesnoth_binary')
|
||||
options= cfg.get('default','arguments_to_wesnoth_binary')
|
||||
ai_config1='--ai_config1='+ai1
|
||||
ai_config2='--ai_config2='+ai2
|
||||
if (map==''):
|
||||
optmap=''
|
||||
else:
|
||||
optmap='--scenario='+map
|
||||
|
||||
return wesnoth+' '+options+' '+optmap+' '+ai_config1+' '+ai_config2
|
||||
|
||||
def do_filter(str,substring):
|
||||
n = str.find(substring)
|
||||
if (n>-1):
|
||||
return n,str[n+len(substring):].strip()
|
||||
return n,''
|
||||
|
||||
def run_game(cfg,game_result):
|
||||
|
||||
command_line = construct_command_line(cfg,game_result.ai_config1,game_result.ai_config2, game_result.faction1, game_result.faction2, game_result.map)
|
||||
print 'Running: '+command_line
|
||||
start = time()
|
||||
p = Popen(command_line, shell=True, bufsize=10000000, stdout=PIPE, stderr=PIPE)
|
||||
# outlines = p.stdout.readlines()
|
||||
outerrlines = p.stderr.readlines()
|
||||
print 'Finished'
|
||||
for line in outerrlines:
|
||||
str = filter_non_printable(line.strip())
|
||||
n,s = do_filter(str,'info ai/testing: WINNER:')
|
||||
if (n>-1):
|
||||
print 'AND THE WINNER IS: '+s
|
||||
game_result.winner_side = s
|
||||
game_result.is_success = 'true'
|
||||
continue
|
||||
|
||||
n,s = do_filter(str,'info ai/testing: VERSION:')
|
||||
if (n>-1):
|
||||
#print 'AND THE VERSION IS: '+s
|
||||
game_result.version_string = s
|
||||
n1 = s.rfind('(')
|
||||
n2 = s.rfind(')')
|
||||
if ((n1>-1) and (n2>-1) and (n2>n1)):
|
||||
sz = s[n1+1:n2]
|
||||
#parse local_modifications
|
||||
n3 = sz.rfind('M')
|
||||
if (n3>-1):
|
||||
sz = sz[:n3]
|
||||
game_result.local_modifications = 1
|
||||
#parse repo_release
|
||||
game_result.repo_release = sz
|
||||
continue
|
||||
|
||||
n,s = do_filter(str,'info ai/testing: GAME_END_TURN:')
|
||||
if (n>-1):
|
||||
print 'AND THE VICTORY_TURN IS: '+s
|
||||
game_result.end_turn = s
|
||||
continue
|
||||
|
||||
n,s = do_filter(str,'info ai/testing: AI_IDENTIFIER1:')
|
||||
if (n>-1):
|
||||
#print 'AND THE AI_IDENTIFIER1 IS: '+s
|
||||
game_result.ai_ident1 = s.strip()
|
||||
continue
|
||||
|
||||
n,s = do_filter(str,'info ai/testing: AI_IDENTIFIER2:')
|
||||
if (n>-1):
|
||||
#print 'AND THE AI_IDENTIFIER2 IS: '+s
|
||||
game_result.ai_ident2 = s.strip()
|
||||
continue
|
||||
|
||||
n,s = do_filter(str,'info mp/connect: FACTION1:')
|
||||
if (n>-1):
|
||||
#print 'AND THE FACTION1 IS: '+s
|
||||
game_result.faction1 = s
|
||||
continue
|
||||
|
||||
n,s = do_filter(str,'info mp/connect: FACTION2:')
|
||||
if (n>-1):
|
||||
#print 'AND THE FACTION2 IS: '+s
|
||||
game_result.faction2 = s
|
||||
continue
|
||||
|
||||
game_result.duration = time() - start
|
||||
if (game_result.is_success=='false'):
|
||||
print 'Warning: not success!'
|
||||
print '===================='
|
||||
print 'stderr:'
|
||||
for line in outerrlines:
|
||||
print filter_non_printable(line.strip())
|
||||
print '===================='
|
||||
|
||||
return game_result
|
||||
|
||||
def save_result(cfg,log_file,game_result):
|
||||
print 'Saving to log file....'
|
||||
print 'game duration: '+str(game_result.duration);
|
||||
log_file.write('"'+game_result.ai_config1+'", "'+game_result.ai_config2+'", "'+game_result.ai_ident1+'", "'+game_result.ai_ident2+'", "'+ str(game_result.duration)+'", "'+game_result.faction1+'", "'+game_result.faction2+'", "'+str(game_result.is_success)+'", "'+str(game_result.local_modifications)+'", "'+game_result.map+'", "'+str(game_result.repo_release)+'", "'+str(game_result.test)+'", "'+str(game_result.end_turn)+'", "'+str(game_result.version_string)+'", "'+str(game_result.winner_side)+'"\n');
|
||||
log_file.flush();
|
||||
print 'Saved to log file'
|
||||
|
||||
def maps(cfg):
|
||||
mp = 1
|
||||
while 1:
|
||||
try:
|
||||
yield cfg.get('default','map' + repr(mp));
|
||||
mp= mp+1
|
||||
except:
|
||||
return
|
||||
|
||||
def tests(cfg):
|
||||
ai1=cfg.get('default','ai_config1').strip()
|
||||
ai2=cfg.get('default','ai_config2').strip()
|
||||
f1=cfg.get('default','faction1').strip()
|
||||
f2=cfg.get('default','faction2').strip()
|
||||
n=cfg.getint('default','number_of_tests')
|
||||
maplist = []
|
||||
for map in maps(cfg):
|
||||
maplist.append(map)
|
||||
random.seed()
|
||||
for i in range(0, n):
|
||||
map = random.choice(maplist)
|
||||
d = random.randint(0,1)
|
||||
print 'TEST: map '+map+' i='+str(i)+' d='+str(d)
|
||||
if (d==0):
|
||||
game_result = GameResult(ai1,ai2,f1,f2,map,'default')
|
||||
else:
|
||||
game_result = GameResult(ai2,ai1,f2,f1,map,'default')
|
||||
yield game_result
|
||||
|
||||
# main
|
||||
|
||||
cfg = ConfigParser.ConfigParser()
|
||||
cfg.read('ai_test.cfg')
|
||||
|
||||
log_file = open(datetime.now().strftime(cfg.get('default','log_file').strip()) , 'w')
|
||||
log_file.write('"ai_config1"'+', '+'"ai_config2"'+', '+'"ai_ident1"'+', '+'"ai_ident2"'+', '+ '"duration"'+', '+'"faction1"'+', '+'"faction2"'+', '+'"is_success"'+', '+'"local_modifications"'+', '+'"map"'+', '+'"repo_release"'+', '+'"test"'+', '+'"end_turn"'+', '+'"version_string"'+', '+'"winner_side"'+'\n');
|
||||
log_file.flush();
|
||||
for test in tests(cfg):
|
||||
game_result = run_game(cfg,test)
|
||||
save_result(cfg,log_file,game_result)
|
|
@ -1,189 +0,0 @@
|
|||
--
|
||||
-- PostgreSQL database dump
|
||||
--
|
||||
|
||||
-- Started on 2009-04-28 03:24:33 EEST
|
||||
-- In March 2013, svn_release -> repo_release
|
||||
|
||||
SET client_encoding = 'UTF8';
|
||||
SET standard_conforming_strings = off;
|
||||
SET check_function_bodies = false;
|
||||
SET client_min_messages = warning;
|
||||
SET escape_string_warning = off;
|
||||
|
||||
--
|
||||
-- TOC entry 296 (class 2612 OID 45054)
|
||||
-- Name: plpgsql; Type: PROCEDURAL LANGUAGE; Schema: -; Owner: pgsql
|
||||
--
|
||||
|
||||
CREATE PROCEDURAL LANGUAGE plpgsql;
|
||||
|
||||
|
||||
ALTER PROCEDURAL LANGUAGE plpgsql OWNER TO pgsql;
|
||||
|
||||
SET search_path = public, pg_catalog;
|
||||
|
||||
SET default_tablespace = '';
|
||||
|
||||
SET default_with_oids = false;
|
||||
|
||||
--
|
||||
-- TOC entry 1469 (class 1259 OID 44782)
|
||||
-- Dependencies: 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 6
|
||||
-- Name: game; Type: TABLE; Schema: public; Owner: wesnoth_ai_test_user; Tablespace:
|
||||
--
|
||||
|
||||
CREATE TABLE game (
|
||||
id bigint NOT NULL,
|
||||
repo_release integer DEFAULT 0 NOT NULL,
|
||||
datetime timestamp with time zone DEFAULT now() NOT NULL,
|
||||
winner_side integer DEFAULT 0 NOT NULL,
|
||||
ai_config1 text DEFAULT ''::text NOT NULL,
|
||||
ai_config2 text DEFAULT ''::text NOT NULL,
|
||||
local_modifications boolean DEFAULT false NOT NULL,
|
||||
turn integer DEFAULT 1 NOT NULL,
|
||||
ai_ident1 text DEFAULT ''::text NOT NULL,
|
||||
ai_ident2 text DEFAULT ''::text NOT NULL,
|
||||
is_success boolean DEFAULT true NOT NULL,
|
||||
version_string text DEFAULT ''::text NOT NULL,
|
||||
duration double precision DEFAULT 0 NOT NULL,
|
||||
map text DEFAULT ''::text NOT NULL,
|
||||
faction1 text DEFAULT ''::text NOT NULL,
|
||||
faction2 text DEFAULT ''::text NOT NULL,
|
||||
test text DEFAULT 'default'::text NOT NULL,
|
||||
CONSTRAINT winner_side_gte_0 CHECK ((winner_side >= 0))
|
||||
);
|
||||
|
||||
|
||||
ALTER TABLE public.game OWNER TO wesnoth_ai_test_user;
|
||||
|
||||
--
|
||||
-- TOC entry 1470 (class 1259 OID 45085)
|
||||
-- Dependencies: 1546 6
|
||||
-- Name: games_side; Type: VIEW; Schema: public; Owner: wesnoth_ai_test_user
|
||||
--
|
||||
|
||||
CREATE VIEW games_side AS
|
||||
SELECT game.id, game.repo_release, game.datetime, game.duration, game.map, 1 AS my_side, CASE WHEN (game.winner_side = 1) THEN 1 WHEN (game.winner_side = 2) THEN -1 ELSE 0 END AS outcome, CASE WHEN (game.winner_side = 1) THEN 1 ELSE 0 END AS win, CASE WHEN (game.winner_side = 0) THEN 1 ELSE 0 END AS draw, CASE WHEN (game.winner_side = 2) THEN 1 ELSE 0 END AS loss, CASE WHEN (game.winner_side = 1) THEN game.turn ELSE 0 END AS win_turns, CASE WHEN (game.winner_side = 2) THEN game.turn ELSE 0 END AS loss_turns, game.is_success, game.version_string, game.ai_config1 AS ai_config_me, game.ai_config2 AS ai_config_enemy, game.ai_ident1 AS ai_ident_me, game.ai_ident2 AS ai_ident_enemy, game.local_modifications, game.turn, game.faction1 AS faction_me, game.faction2 AS faction_enemy FROM game WHERE (game.repo_release <> 0) UNION SELECT game.id, game.repo_release, game.datetime, game.duration, game.map, 2 AS my_side, CASE WHEN (game.winner_side = 1) THEN -1 WHEN (game.winner_side = 2) THEN 1 ELSE 0 END AS outcome, CASE WHEN (game.winner_side = 2) THEN 1 ELSE 0 END AS win, CASE WHEN (game.winner_side = 0) THEN 1 ELSE 0 END AS draw, CASE WHEN (game.winner_side = 1) THEN 1 ELSE 0 END AS loss, CASE WHEN (game.winner_side = 2) THEN game.turn ELSE 0 END AS win_turns, CASE WHEN (game.winner_side = 1) THEN game.turn ELSE 0 END AS loss_turns, game.is_success, game.version_string, game.ai_config2 AS ai_config_me, game.ai_config1 AS ai_config_enemy, game.ai_ident2 AS ai_ident_me, game.ai_ident1 AS ai_ident_enemy, game.local_modifications, game.turn, game.faction1 AS faction_me, game.faction2 AS faction_enemy FROM game WHERE (game.repo_release <> 0);
|
||||
|
||||
|
||||
ALTER TABLE public.games_side OWNER TO wesnoth_ai_test_user;
|
||||
|
||||
--
|
||||
-- TOC entry 20 (class 1255 OID 45064)
|
||||
-- Dependencies: 296 6
|
||||
-- Name: avg_from(bigint, bigint); Type: FUNCTION; Schema: public; Owner: pgsql
|
||||
--
|
||||
|
||||
CREATE FUNCTION avg_from(bigint, bigint) RETURNS double precision
|
||||
AS $_$
|
||||
BEGIN
|
||||
IF $2=0 THEN
|
||||
RETURN 0;
|
||||
ELSE
|
||||
RETURN cast($1 as double precision)/$2;
|
||||
END IF;
|
||||
END;
|
||||
$_$
|
||||
LANGUAGE plpgsql IMMUTABLE;
|
||||
|
||||
|
||||
ALTER FUNCTION public.avg_from(bigint, bigint) OWNER TO pgsql;
|
||||
|
||||
--
|
||||
-- TOC entry 1468 (class 1259 OID 44780)
|
||||
-- Dependencies: 1469 6
|
||||
-- Name: game_id_seq; Type: SEQUENCE; Schema: public; Owner: wesnoth_ai_test_user
|
||||
--
|
||||
|
||||
CREATE SEQUENCE game_id_seq
|
||||
INCREMENT BY 1
|
||||
NO MAXVALUE
|
||||
NO MINVALUE
|
||||
CACHE 1;
|
||||
|
||||
|
||||
ALTER TABLE public.game_id_seq OWNER TO wesnoth_ai_test_user;
|
||||
|
||||
--
|
||||
-- TOC entry 1765 (class 0 OID 0)
|
||||
-- Dependencies: 1468
|
||||
-- Name: game_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: wesnoth_ai_test_user
|
||||
--
|
||||
|
||||
ALTER SEQUENCE game_id_seq OWNED BY game.id;
|
||||
|
||||
|
||||
--
|
||||
-- TOC entry 1738 (class 2604 OID 44785)
|
||||
-- Dependencies: 1469 1468 1469
|
||||
-- Name: id; Type: DEFAULT; Schema: public; Owner: wesnoth_ai_test_user
|
||||
--
|
||||
|
||||
ALTER TABLE game ALTER COLUMN id SET DEFAULT nextval('game_id_seq'::regclass);
|
||||
|
||||
|
||||
--
|
||||
-- TOC entry 1757 (class 2606 OID 44794)
|
||||
-- Dependencies: 1469 1469
|
||||
-- Name: pk_game; Type: CONSTRAINT; Schema: public; Owner: wesnoth_ai_test_user; Tablespace:
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY game
|
||||
ADD CONSTRAINT pk_game PRIMARY KEY (id);
|
||||
|
||||
|
||||
--
|
||||
-- TOC entry 1762 (class 0 OID 0)
|
||||
-- Dependencies: 6
|
||||
-- Name: public; Type: ACL; Schema: -; Owner: pgsql
|
||||
--
|
||||
|
||||
REVOKE ALL ON SCHEMA public FROM PUBLIC;
|
||||
REVOKE ALL ON SCHEMA public FROM pgsql;
|
||||
GRANT ALL ON SCHEMA public TO pgsql;
|
||||
GRANT ALL ON SCHEMA public TO PUBLIC;
|
||||
|
||||
|
||||
--
|
||||
-- TOC entry 1763 (class 0 OID 0)
|
||||
-- Dependencies: 1469
|
||||
-- Name: game; Type: ACL; Schema: public; Owner: wesnoth_ai_test_user
|
||||
--
|
||||
|
||||
REVOKE ALL ON TABLE game FROM PUBLIC;
|
||||
REVOKE ALL ON TABLE game FROM wesnoth_ai_test_user;
|
||||
GRANT ALL ON TABLE game TO wesnoth_ai_test_user;
|
||||
GRANT SELECT ON TABLE game TO wesnoth_ai_test_viewer;
|
||||
|
||||
|
||||
--
|
||||
-- TOC entry 1764 (class 0 OID 0)
|
||||
-- Dependencies: 1470
|
||||
-- Name: games_side; Type: ACL; Schema: public; Owner: wesnoth_ai_test_user
|
||||
--
|
||||
|
||||
REVOKE ALL ON TABLE games_side FROM PUBLIC;
|
||||
REVOKE ALL ON TABLE games_side FROM wesnoth_ai_test_user;
|
||||
GRANT ALL ON TABLE games_side TO wesnoth_ai_test_user;
|
||||
GRANT SELECT ON TABLE games_side TO wesnoth_ai_test_viewer;
|
||||
|
||||
|
||||
--
|
||||
-- TOC entry 1766 (class 0 OID 0)
|
||||
-- Dependencies: 1468
|
||||
-- Name: game_id_seq; Type: ACL; Schema: public; Owner: wesnoth_ai_test_user
|
||||
--
|
||||
|
||||
REVOKE ALL ON SEQUENCE game_id_seq FROM PUBLIC;
|
||||
REVOKE ALL ON SEQUENCE game_id_seq FROM wesnoth_ai_test_user;
|
||||
GRANT ALL ON SEQUENCE game_id_seq TO wesnoth_ai_test_user;
|
||||
GRANT SELECT ON SEQUENCE game_id_seq TO wesnoth_ai_test_viewer;
|
||||
|
||||
|
||||
-- Completed on 2009-04-28 03:24:36 EEST
|
||||
|
||||
--
|
||||
-- PostgreSQL database dump complete
|
||||
--
|
||||
|
BIN
utils/ai_test/ai_test_empty.sqlite
Normal file
BIN
utils/ai_test/ai_test_empty.sqlite
Normal file
Binary file not shown.
|
@ -1,11 +1,12 @@
|
|||
/*
|
||||
AI Batch testing suite
|
||||
Copyright (C) 2009 by Yurii Chernyi <terraninfo@terraninfo.net>
|
||||
Copyright (C) 2009 - 2013 by Yurii Chernyi <terraninfo@terraninfo.net>
|
||||
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License version 2
|
||||
or at your option any later version.
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
|
@ -15,35 +16,30 @@
|
|||
|
||||
REQUIRES:
|
||||
Wesnoth
|
||||
Postgresql database for storing data
|
||||
PHP-enabled web server for web frontend
|
||||
|
||||
INSTALLATION:
|
||||
|
||||
1. Create a role, two users and a database - one for uploader script with INSERT priv (and ability to use the sequence used for generating IDs), and another for web frontend with SELECT priv.
|
||||
---------------------
|
||||
CREATE ROLE wesnoth_ai_test_viewer
|
||||
NOSUPERUSER INHERIT NOCREATEDB NOCREATEROLE;
|
||||
|
||||
CREATE ROLE wesnoth_ai_test_user LOGIN
|
||||
PASSWORD 'YOUR_PASSWORD_FOR_TEST_USER'
|
||||
NOSUPERUSER INHERIT NOCREATEDB NOCREATEROLE;
|
||||
GRANT wesnoth_ai_test_viewer TO wesnoth_ai_test_user;
|
||||
|
||||
CREATE ROLE wesnoth_ai_test_viewer_impl LOGIN
|
||||
PASSWORD 'YOUR_PASSWORD_FOR_WEB_FRONTEND_USER'
|
||||
NOSUPERUSER INHERIT NOCREATEDB NOCREATEROLE;
|
||||
GRANT wesnoth_ai_test_viewer TO wesnoth_ai_test_viewer_impl;
|
||||
|
||||
CREATE DATABASE "org.wesnoth.ai.test"
|
||||
WITH OWNER = wesnoth_ai_test_user
|
||||
ENCODING = 'UTF8';
|
||||
---------------------
|
||||
2. Restore ai_test_db.backup to that newly created DB to create the DB schema and set privilegies.
|
||||
|
||||
3. place wesnoth_ai_test.php at a php-enabled web server
|
||||
|
||||
4. Modify passwords/paths in ai_test.cfg and wesnoth_ai_test.php
|
||||
Python
|
||||
(optional) PHP-enabled web server for web frontend
|
||||
|
||||
USAGE:
|
||||
1. Edit ai_test.cfg. (Read comments there for explanations)
|
||||
2. Run "python ai_test.pyi [-p]" to run the tests.
|
||||
|
||||
USAGE WITH SQLITE DATABASE:
|
||||
1. Copy ai_test_empty.sqlite to somewhere outside the git repo.
|
||||
2. Give ai_test_empty.sqlite write access.
|
||||
3. place wesnoth.php and wesnoth_test.php at a php-enabled web server.
|
||||
4. Edit BOTH php files and change the variable $sqlitefile.
|
||||
5. Edit ai_test.cfg. (Don't forget to change 'sqlite_file' there).
|
||||
6. Run "python ai_test.py [-p]".
|
||||
|
||||
TIPPS:
|
||||
- Before you start testing use the "-p" parameter
|
||||
to play a test-game with gui. Then you may want to use
|
||||
:inspect to see if the ais are set up correctly.
|
||||
- 100 tests are nothing, 500 tests are good to see some
|
||||
trends. I recommend to run at least 1000 tests.
|
||||
- You can make use of your multicores and run multiple
|
||||
tests at the same time. In Linux the 'System Monitor'
|
||||
is your friend. You can set the process priority there
|
||||
to 'Very Low' or pause the process and continue it later.
|
||||
- In linux you can pause the tests by pressing Ctrl + z.
|
||||
You can continue the process by running 'fg'.
|
||||
|
|
80
utils/ai_test/wesnoth.php
Normal file
80
utils/ai_test/wesnoth.php
Normal file
|
@ -0,0 +1,80 @@
|
|||
<?
|
||||
$sqlitefile = 'RELATIVE PATH TO SQLITE-FILE';
|
||||
?>
|
||||
<html>
|
||||
<head>
|
||||
<title>Wesnoth AI Testing Statistics</title>
|
||||
<link rel="stylesheet" type="text/css" href="http://wiki.wesnoth.org/skins/glamdrol/main.css">
|
||||
<style>
|
||||
thead th, thead td {
|
||||
text-align: center;
|
||||
}
|
||||
tbody th, tbody td {
|
||||
text-align: center;
|
||||
}
|
||||
tfoot th, tfoot td {
|
||||
text-align: center;
|
||||
}
|
||||
table {
|
||||
margin: auto;
|
||||
}
|
||||
div {
|
||||
text-align:center;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="main">
|
||||
<?php
|
||||
$db = new SQLite3($sqlitefile);
|
||||
if (!$db) {
|
||||
print("Connection Failed.");
|
||||
exit;
|
||||
} else {
|
||||
# print("Connection Ok!");
|
||||
}
|
||||
?>
|
||||
<h2>Ai-Test Suite - Test results</h2>
|
||||
<table>
|
||||
<tr>
|
||||
<th>title</th>
|
||||
<th>ai_ident1</th>
|
||||
<th>ai_ident2</th>
|
||||
<th>games</th>
|
||||
<th>1 won</th>
|
||||
<th>2 won</th>
|
||||
<th>draws</th>
|
||||
</tr>
|
||||
<?
|
||||
$query = "SELECT test_id, title, ai_config1, ai_config2, ai_ident1, ai_ident2, map, tests.faction1, tests.faction2, time,
|
||||
count(games.id) as total_games,
|
||||
sum(case when winner = 1 then 1 else 0 end) as side1_won,
|
||||
sum(case when winner = 2 then 1 else 0 end) as side2_won,
|
||||
sum(case when winner = 0 then 1 else 0 end) as draw,
|
||||
sum(case when winner = 1 then 1 else 0 end) * 100.0 / count(games.id) as side1_won_p,
|
||||
sum(case when winner = 2 then 1 else 0 end) * 100.0 / count(games.id) as side2_won_p,
|
||||
sum(case when winner = 0 then 1 else 0 end) * 100.0 / count(games.id) as draw_p,
|
||||
avg(games.end_turn) as avg_end_turn
|
||||
FROM tests, games
|
||||
WHERE tests.id = games.test_id
|
||||
GROUP BY test_id
|
||||
ORDER BY test_id DESC";
|
||||
$result = $db->query($query) or die('Query failed');
|
||||
while ($row = $result->fetchArray())
|
||||
{
|
||||
$title = (strlen($row['title']) > 30) ? substr($row['title'], 0, 30) . "..." : $row['title'];
|
||||
echo "<tr>";
|
||||
echo "<td><a href=wesnoth_test.php?id=" . $row['test_id'] . ">{$title}</a></td>";
|
||||
echo "<td>{$row['ai_ident1']}</td>";
|
||||
echo "<td>{$row['ai_ident2']}</td>";
|
||||
echo "<td>{$row['total_games']}</td>";
|
||||
echo "<td>" . round($row['side1_won_p'], 2) . "%</td>";
|
||||
echo "<td>" . round($row['side2_won_p'], 2) . "%</td>";
|
||||
echo "<td>" . round($row['draw_p'], 2) . "%</td>";
|
||||
echo "</tr>";
|
||||
}
|
||||
?>
|
||||
</table>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -1,203 +0,0 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>Wesnoth AI Testing Statistics</title>
|
||||
</head>
|
||||
<body>
|
||||
<?php
|
||||
$database = pg_connect("host=127.0.0.1 dbname=org.wesnoth.ai.test user=wesnoth_ai_test_viewer_impl password=PASSWORD");
|
||||
if (!$database) {
|
||||
print("Connection Failed.");
|
||||
exit;
|
||||
} else {
|
||||
# print("Connection Ok!");
|
||||
}
|
||||
?>
|
||||
<h2>Latest repository AI wins % graph:</h2>
|
||||
<?php
|
||||
$query = "select ai_ident_me,avg_from(sum(win)*100,count(*))::bigint as win_percent from games_side where repo_release=(select max(repo_release) from games_side) group by ai_ident_me order by ai_ident_me;";
|
||||
$result = pg_query($query);
|
||||
if (!$result) {
|
||||
echo pg_last_error();
|
||||
exit();
|
||||
}
|
||||
$a = "";
|
||||
$b = "";
|
||||
$i = true;
|
||||
while($myrow = pg_fetch_assoc($result)) {
|
||||
if ($i) {
|
||||
$a=$a.$myrow['ai_ident_me'];
|
||||
$b=$b.$myrow['win_percent'];
|
||||
$i=false;
|
||||
} else {
|
||||
$a=$a."|".$myrow['ai_ident_me'];
|
||||
$b=$b.",".$myrow['win_percent'];
|
||||
}
|
||||
}
|
||||
|
||||
printf("<img src=\"http://chart.apis.google.com/chart?cht=p3&chd=t:$b&chs=500x200&chl=$a\" alt=\"win percentages\" title=\"win percentages\"");
|
||||
?>
|
||||
|
||||
<h2>By AI:</h2>
|
||||
<table border=1>
|
||||
<tr>
|
||||
<th>AI</th>
|
||||
<th>Repository_Revision</th>
|
||||
<th>Win %</th>
|
||||
<th>Games</th>
|
||||
<th>Wins</th>
|
||||
<th>Losses</th>
|
||||
<th>Avg. turns to win</th>
|
||||
<th>Avg. turns to lose</th>
|
||||
</tr>
|
||||
<?php
|
||||
$query = "select ai_ident_me, repo_release, avg_from(sum(win)*100,count(*)) as win_percent, count(*) as games, sum(win) as wins, sum(draw) as draws, sum(loss) as losses, avg_from(sum(win_turns),sum(win)) as avg_win_turns, avg_from(sum(loss_turns),sum(loss)) as avg_loss_turns from games_side group by ai_ident_me, repo_release order by ai_ident_me, repo_release desc;";
|
||||
$result = pg_query($query);
|
||||
if (!$result) {
|
||||
echo pg_last_error();
|
||||
exit();
|
||||
}
|
||||
|
||||
while($myrow = pg_fetch_assoc($result)) {
|
||||
printf ("<tr><td>%s</td><td>%d</td><td>%.1f</td><td>%d</td><td>%d</td><td>%d</td><td>%.1f</td><td>%.1f</td></tr>",
|
||||
$myrow['ai_ident_me'],$myrow['repo_release'],$myrow['win_percent'],$myrow['games'],$myrow['wins'],$myrow['losses'],$myrow['avg_win_turns'],$myrow['avg_loss_turns']);
|
||||
}
|
||||
?>
|
||||
</table>
|
||||
<h2>By AI and side</h2>
|
||||
<table border=1>
|
||||
<tr>
|
||||
<th>AI</th>
|
||||
<th>Side</th>
|
||||
<th>Repository_Revision</th>
|
||||
<th>Win %</th>
|
||||
<th>Games</th>
|
||||
<th>Wins</th>
|
||||
<th>Losses</th>
|
||||
<th>Avg. turns to win</th>
|
||||
<th>Avg. turns to lose</th>
|
||||
</tr>
|
||||
<?php
|
||||
$query = "select ai_ident_me, my_side, repo_release, avg_from(sum(win)*100,count(*)) as win_percent, count(*) as games, sum(win) as wins, sum(draw) as draws, sum(loss) as losses, avg_from(sum(win_turns),sum(win)) as avg_win_turns, avg_from(sum(loss_turns),sum(loss)) as avg_loss_turns from games_side group by ai_ident_me, my_side, repo_release order by ai_ident_me, my_side, repo_release desc;";
|
||||
$result = pg_query($query);
|
||||
if (!$result) {
|
||||
echo pg_last_error();
|
||||
exit();
|
||||
}
|
||||
|
||||
while($myrow = pg_fetch_assoc($result)) {
|
||||
printf ("<tr><td>%s</td><td>%s</td><td>%d</td><td>%.1f</td><td>%d</td><td>%d</td><td>%d</td><td>%.1f</td><td>%.1f</td></tr>",
|
||||
$myrow['ai_ident_me'],$myrow['my_side'],$myrow['repo_release'],$myrow['win_percent'],$myrow['games'],$myrow['wins'],$myrow['losses'],$myrow['avg_win_turns'],$myrow['avg_loss_turns']);
|
||||
}
|
||||
?>
|
||||
</table>
|
||||
<h2>By AI and map</h2>
|
||||
<table border=1>
|
||||
<tr>
|
||||
<th>AI</th>
|
||||
<th>Map</th>
|
||||
<th>Repository_Revision</th>
|
||||
<th>Win %</th>
|
||||
<th>Games</th>
|
||||
<th>Wins</th>
|
||||
<th>Losses</th>
|
||||
<th>Avg. turns to win</th>
|
||||
<th>Avg. turns to lose</th>
|
||||
</tr>
|
||||
<?php
|
||||
$query = "select ai_ident_me, map, repo_release, avg_from(sum(win)*100,count(*)) as win_percent, count(*) as games, sum(win) as wins, sum(draw) as draws, sum(loss) as losses, avg_from(sum(win_turns),sum(win)) as avg_win_turns, avg_from(sum(loss_turns),sum(loss)) as avg_loss_turns from games_side group by ai_ident_me, map, repo_release order by ai_ident_me, map, repo_release desc;";
|
||||
$result = pg_query($query);
|
||||
if (!$result) {
|
||||
echo pg_last_error();
|
||||
exit();
|
||||
}
|
||||
|
||||
while($myrow = pg_fetch_assoc($result)) {
|
||||
printf ("<tr><td>%s</td><td>%s</td><td>%d</td><td>%.1f</td><td>%d</td><td>%d</td><td>%d</td><td>%.1f</td><td>%.1f</td></tr>",
|
||||
$myrow['ai_ident_me'],$myrow['map'],$myrow['repo_release'],$myrow['win_percent'],$myrow['games'],$myrow['wins'],$myrow['losses'],$myrow['avg_win_turns'],$myrow['avg_loss_turns']);
|
||||
}
|
||||
?>
|
||||
</table>
|
||||
<h2>By AI and own faction</h2>
|
||||
<table border=1>
|
||||
<tr>
|
||||
<th>AI</th>
|
||||
<th>Own faction</th>
|
||||
<th>Repository_Revision</th>
|
||||
<th>Win %</th>
|
||||
<th>Games</th>
|
||||
<th>Wins</th>
|
||||
<th>Losses</th>
|
||||
<th>Avg. turns to win</th>
|
||||
<th>Avg. turns to lose</th>
|
||||
</tr>
|
||||
<?php
|
||||
$query = "select ai_ident_me, faction_me, repo_release, avg_from(sum(win)*100,count(*)) as win_percent, count(*) as games, sum(win) as wins, sum(draw) as draws, sum(loss) as losses, avg_from(sum(win_turns),sum(win)) as avg_win_turns, avg_from(sum(loss_turns),sum(loss)) as avg_loss_turns from games_side group by ai_ident_me, faction_me, repo_release order by ai_ident_me, faction_me, repo_release desc;";
|
||||
$result = pg_query($query);
|
||||
if (!$result) {
|
||||
echo pg_last_error();
|
||||
exit();
|
||||
}
|
||||
|
||||
while($myrow = pg_fetch_assoc($result)) {
|
||||
printf ("<tr><td>%s</td><td>%s</td><td>%d</td><td>%.1f</td><td>%d</td><td>%d</td><td>%d</td><td>%.1f</td><td>%.1f</td></tr>",
|
||||
$myrow['ai_ident_me'],$myrow['faction_me'],$myrow['repo_release'],$myrow['win_percent'],$myrow['games'],$myrow['wins'],$myrow['losses'],$myrow['avg_win_turns'],$myrow['avg_loss_turns']);
|
||||
}
|
||||
?>
|
||||
</table>
|
||||
<h2>By AI and enemy faction</h2>
|
||||
<table border=1>
|
||||
<tr>
|
||||
<th>AI</th>
|
||||
<th>Enemy faction</th>
|
||||
<th>Repository_Revision</th>
|
||||
<th>Win %</th>
|
||||
<th>Games</th>
|
||||
<th>Wins</th>
|
||||
<th>Losses</th>
|
||||
<th>Avg. turns to win</th>
|
||||
<th>Avg. turns to lose</th>
|
||||
</tr>
|
||||
<?php
|
||||
$query = "select ai_ident_me, faction_enemy, repo_release, avg_from(sum(win)*100,count(*)) as win_percent, count(*) as games, sum(win) as wins, sum(draw) as draws, sum(loss) as losses, avg_from(sum(win_turns),sum(win)) as avg_win_turns, avg_from(sum(loss_turns),sum(loss)) as avg_loss_turns from games_side group by ai_ident_me, faction_enemy, repo_release order by ai_ident_me, faction_enemy, repo_release desc;";
|
||||
$result = pg_query($query);
|
||||
if (!$result) {
|
||||
echo pg_last_error();
|
||||
exit();
|
||||
}
|
||||
|
||||
while($myrow = pg_fetch_assoc($result)) {
|
||||
printf ("<tr><td>%s</td><td>%s</td><td>%d</td><td>%.1f</td><td>%d</td><td>%d</td><td>%d</td><td>%.1f</td><td>%.1f</td></tr>",
|
||||
$myrow['ai_ident_me'],$myrow['faction_enemy'],$myrow['repo_release'],$myrow['win_percent'],$myrow['games'],$myrow['wins'],$myrow['losses'],$myrow['avg_win_turns'],$myrow['avg_loss_turns']);
|
||||
}
|
||||
?>
|
||||
</table>
|
||||
<h2>By AI and factions</h2>
|
||||
<table border=1>
|
||||
<tr>
|
||||
<th>AI</th>
|
||||
<th>Own faction</th>
|
||||
<th>Enemy faction</th>
|
||||
<th>Repository_Revision</th>
|
||||
<th>Win %</th>
|
||||
<th>Games</th>
|
||||
<th>Wins</th>
|
||||
<th>Losses</th>
|
||||
<th>Avg. turns to win</th>
|
||||
<th>Avg. turns to lose</th>
|
||||
</tr>
|
||||
<?php
|
||||
$query = "select ai_ident_me, faction_me, faction_enemy, repo_release, avg_from(sum(win)*100,count(*)) as win_percent, count(*) as games, sum(win) as wins, sum(draw) as draws, sum(loss) as losses, avg_from(sum(win_turns),sum(win)) as avg_win_turns, avg_from(sum(loss_turns),sum(loss)) as avg_loss_turns from games_side group by ai_ident_me, faction_me, faction_enemy ,repo_release order by ai_ident_me, faction_me, faction_enemy, repo_release desc;";
|
||||
$result = pg_query($query);
|
||||
if (!$result) {
|
||||
echo pg_last_error();
|
||||
exit();
|
||||
}
|
||||
|
||||
while($myrow = pg_fetch_assoc($result)) {
|
||||
printf ("<tr><td>%s</td><td>%s</td><td>%s</td><td>%d</td><td>%.1f</td><td>%d</td><td>%d</td><td>%d</td><td>%.1f</td><td>%.1f</td></tr>",
|
||||
$myrow['ai_ident_me'],$myrow['faction_me'],$myrow['faction_enemy'],$myrow['repo_release'],$myrow['win_percent'],$myrow['games'],$myrow['wins'],$myrow['losses'],$myrow['avg_win_turns'],$myrow['avg_loss_turns']);
|
||||
}
|
||||
?>
|
||||
</table>
|
||||
|
||||
</body>
|
185
utils/ai_test/wesnoth_test.php
Normal file
185
utils/ai_test/wesnoth_test.php
Normal file
|
@ -0,0 +1,185 @@
|
|||
<?
|
||||
$sqlitefile = 'RELATIVE PATH TO SQLITE-FILE';
|
||||
$db = new SQLite3($sqlitefile);
|
||||
if (!$db) {
|
||||
print("Connection Failed.");
|
||||
exit;
|
||||
} else {
|
||||
# print("Connection Ok!");
|
||||
}
|
||||
$id = $_GET["id"];
|
||||
$query = "SELECT * FROM tests WHERE id = $id";
|
||||
$result = $db->query($query) or die('Query failed');
|
||||
$row = $result->fetchArray();
|
||||
$title = $row["title"];
|
||||
$ai_config1 = $row["ai_config1"];
|
||||
$ai_config2 = $row["ai_config2"];
|
||||
$ai_ident1 = $row["ai_ident1"];
|
||||
$ai_ident2 = $row["ai_ident2"];
|
||||
$map = $row["map"];
|
||||
$version = $row["version"];
|
||||
$faction1 = $row["faction1"];
|
||||
$faction2 = $row["faction2"];
|
||||
$time = $row["time"];
|
||||
|
||||
$query = 'SELECT faction1, faction2,
|
||||
count(test_id) as total_games,
|
||||
sum(case when winner = 1 then 1 else 0 end) as side1_won,
|
||||
sum(case when winner = 2 then 1 else 0 end) as side2_won,
|
||||
sum(case when winner = 0 then 1 else 0 end) as draw,
|
||||
sum(case when winner = 1 then 1 else 0 end) * 100. / count(test_id) as side1_won_p,
|
||||
sum(case when winner = 2 then 1 else 0 end) * 100. / count(test_id) as side2_won_p,
|
||||
sum(case when winner = 0 then 1 else 0 end) * 100. / count(test_id) as draw_p,
|
||||
avg(end_turn) as avg_end_turn
|
||||
FROM "games"
|
||||
WHERE test_id = ' . $id . '
|
||||
GROUP BY faction1, faction2';
|
||||
$result = $db->query($query) or die('Query failed');
|
||||
?>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<title>Wesnoth AI Testing Statistics</title>
|
||||
<link rel="stylesheet" type="text/css" href="http://wiki.wesnoth.org/skins/glamdrol/main.css">
|
||||
<style>
|
||||
thead th, thead td {
|
||||
text-align: center;
|
||||
}
|
||||
tbody th, tbody td {
|
||||
text-align: center;
|
||||
}
|
||||
tfoot th, tfoot td {
|
||||
text-align: center;
|
||||
}
|
||||
table {
|
||||
margin: auto;
|
||||
}
|
||||
div {
|
||||
text-align:center;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="main">
|
||||
<h2><?echo $title;?></h2>
|
||||
<script type='text/javascript' src='https://www.google.com/jsapi'></script>
|
||||
<script type='text/javascript'>
|
||||
google.load('visualization', '1', {packages:['table']});
|
||||
google.setOnLoadCallback(drawTable);
|
||||
function drawTable() {
|
||||
var data = new google.visualization.DataTable();
|
||||
data.addColumn('string', 'Side1 / Side2');
|
||||
data.addColumn('number', 'Drakes');
|
||||
data.addColumn('number', 'Rebels');
|
||||
data.addColumn('number', 'Undead');
|
||||
data.addColumn('number', 'Northerners');
|
||||
data.addColumn('number', 'Knalgan Alliance');
|
||||
data.addColumn('number', 'Loyalists');
|
||||
data.addRow(['Drakes', null, null, null, null, null, null]);
|
||||
data.addRow(['Rebels', null, null, null, null, null, null]);
|
||||
data.addRow(['Undead', null, null, null, null, null, null]);
|
||||
data.addRow(['Northerners', null, null, null, null, null, null]);
|
||||
data.addRow(['Knalgan Alliance', null, null, null, null, null, null]);
|
||||
data.addRow(['Loyalists', null, null, null, null, null, null]);
|
||||
<?
|
||||
while ($row = $result->fetchArray()) {
|
||||
echo "data.setCell(toId('".$row['faction1']."')-1, toId('".$row['faction2']."'), ".$row['side1_won_p'].", '".round($row['side1_won_p'],2)."% / ".round($row['side2_won_p'],2)."% / ".$row['total_games']."');";
|
||||
}
|
||||
?>
|
||||
var table = new google.visualization.Table(document.getElementById('table_div'));
|
||||
var formatter = new google.visualization.ColorFormat();
|
||||
formatter.addGradientRange(-0.001, 100.0001, 'black', '#FF3333', '#33FF33');
|
||||
formatter.format(data, 1);
|
||||
formatter.format(data, 2);
|
||||
formatter.format(data, 3);
|
||||
formatter.format(data, 4);
|
||||
formatter.format(data, 5);
|
||||
formatter.format(data, 6);
|
||||
table.draw(data, {showRowNumber: false, allowHtml: true});
|
||||
}
|
||||
|
||||
function toId(faction) {
|
||||
switch(faction) {
|
||||
case 'Drakes': return 1;
|
||||
case 'Rebels': return 2;
|
||||
case 'Undead': return 3;
|
||||
case 'Northerners': return 4;
|
||||
case 'Knalgan Alliance': return 5;
|
||||
case 'Loyalists': return 6;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<div id="table_div"> </div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<?
|
||||
$query = "SELECT test_id,
|
||||
count(games.id) as total_games,
|
||||
sum(case when winner = 1 then 1 else 0 end) as side1_won,
|
||||
sum(case when winner = 2 then 1 else 0 end) as side2_won,
|
||||
sum(case when winner = 0 then 1 else 0 end) as draw,
|
||||
sum(case when winner = 1 then 1 else 0 end) * 100.0 / count(games.id) as side1_won_p,
|
||||
sum(case when winner = 2 then 1 else 0 end) * 100.0 / count(games.id) as side2_won_p,
|
||||
sum(case when winner = 0 then 1 else 0 end) * 100.0 / count(games.id) as draw_p,
|
||||
avg(games.end_turn) as avg_end_turn
|
||||
FROM games
|
||||
WHERE test_id = " . $id . "
|
||||
GROUP BY test_id";
|
||||
$result = $db->query($query) or die('Query failed');
|
||||
$row = $result->fetchArray()
|
||||
?>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td><b>ai_ident1</b></td>
|
||||
<td><?echo $ai_ident1;?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>ai_ident2</b></td>
|
||||
<td><?echo $ai_ident2;?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>ai_config1</b></td>
|
||||
<td><?echo $ai_config1;?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>ai_config2</b></td>
|
||||
<td><?echo $ai_config2;?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>map</b></td>
|
||||
<td><?echo $map;?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>time</b></td>
|
||||
<td><?echo $time;?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>version</b></td>
|
||||
<td><?echo $version;?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>total games</b></td>
|
||||
<td><?echo $row['total_games'];?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>side1 won</b></td>
|
||||
<td><?echo $row['side1_won'] . " / " . round($row['side1_won_p'], 2) . "%";?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>side2 won</b></td>
|
||||
<td><?echo $row['side2_won'] . " / " . round($row['side2_won_p'], 2) . "%";?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>draws</b></td>
|
||||
<td><?echo $row['draw'] . " / " . round($row['draw_p'], 2) . "%";?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>average end turn</b></td>
|
||||
<td><?echo round($row['avg_end_turn'], 1);?></td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
Loading…
Add table
Reference in a new issue