"""
 marlboro_tourney.py
 
 A simple tournament engine for the google ants competition
 as done by the Marlboro College programming workshop.

 Use:
   1st edit the list of bots and maps in main(), at the end of this file.
   Then run this from the command line to get the tourney results, e.g.

     $ python marlboro_tourney.py
     playing tournament  .  .  .  .  .  . 
     -- bots --
        python ../python_starter_package/MyBot.py
        python sample_bots/python/HunterBot.py
        python sample_bots/python/GreedyBot.py
     -- maps --
        maps/example/tutorial1.map
        maps/maze/maze_02p_02.map
     -- games --
           0     HunterBot.py    3     MyBot.py        0        tutorial1.map
           1     HunterBot.py    1     MyBot.py        1      maze_02p_02.map
           2     MyBot.py        0     GreedyBot.py    3        tutorial1.map
           3     MyBot.py        0     GreedyBot.py    3      maze_02p_02.map
           4     HunterBot.py    3     GreedyBot.py    0        tutorial1.map
           5     HunterBot.py    3     GreedyBot.py    0      maze_02p_02.map
     -- ranks --
     HunterBot.py    3
     GreedyBot.py    0
     MyBot.py       -3

 The games line shows (bot, score) as given by the google AI engine.

 The games (n.replay, replay.n.html) are stored as JSON and
 in a browser-viewable form in the game folder, currently ./log_marlboro/ .
 
 Jim M | Feb 2012 | MIT License
"""

# I'm keeping things simple at this point:
#   Two player maps only
#   Each bot plays all other bots, on every map.
#   Score +1 for win, -1 for loss.

#import subprocess
import commands
import json
import sys
import os
import re

engine = "./playgame.py"
log_folder = "log_marlboro"

def shortname(path):
    """ convert file path (perhaps with leading command
        to just the file, e.g. "python a/b/c/ddd.py" to "ddd.py" """
    return re.compile("\s|/").split(path)[-1]

def update_winloss(winloss, winner, loser):
    """ modify the winloss dict
        by incrementing winner and decrementing loser """
    winloss[winner] = winloss.get(winner, 0) + 1
    winloss[loser] = winloss.get(loser, 0) - 1
    return winloss

def get_ranks(games, print_rankings=False):
    """ return wins-losses for each bot given the list of games played
        as returned by play_tourney. 
        If print_rankings=True, also print winloss, high to low. """
    winloss = {}
    for (bot1, score1, bot2, score2, m) in games:
        if score1 > score2:
            winloss = update_winloss(winloss, bot1, bot2)
        elif score1 < score2:
            winloss = update_winloss(winloss, bot2, bot1)
    if print_rankings:
        print "-- ranks --"
        for bot in sorted(winloss, key=winloss.get, reverse=True):
            print "   %-12s %4i" % (bot, winloss[bot])
    return winloss

def print_games(games):
    """ print 'em, one per line; see play_tourney for their format. """
    print "-- games --"
    # print "games = " + str(games)
    index = 0
    for (bot1, score1, bot2, score2, mapp) in games:
        print "   %4i     %-12s %4i     %-12s %4i     %16s" % \
            (index, bot1, score1, bot2, score2, shortname(mapp))
        index += 1

def check_log_folder():
    """ Create log folder if it doesn't exist. """
    if not os.path.exists(log_folder):
        os.mkdir(log_folder)

def play_tourney(bots, maps, print_progress=False):
    """ Call play_game for all maps & bots combos.
        Return list of games played,
        each as tuple (bot1, score1, bot2, score2, map)
        If print_progress=True, print a period for each game played. """
    games = []
    index = 0
    check_log_folder()
    if print_progress:
        print "playing tournament ",
        sys.stdout.flush()
    for bot1 in bots:
        for bot2 in bots:
            if bot1 < bot2:
                for mapp in maps:
                    result = play_game(bot1, bot2, mapp, index)
                    ((bot1name, score1), (bot2name, score2)) = result.items()
                    games.append((bot1name, score1, bot2name, score2,
                                  shortname(mapp)))
                    index += 1
                    if print_progress:
                        print ". ",
                        sys.stdout.flush()
    if print_progress:
        print
    return games

def play_game(bot1, bot2, the_map, index=0, turns=1000):
    """ Play game index=n; put log in ./log_marlboro/n.replay,
        and return result as {bot1:score1, bot2, score2} """
    
    
    # command to run a game is similar to that in play_marlboro_game, i.e.
    #   ./playgame.py \
    #   --game=1 --log_dir=game_logs \
    #   -R --nolaunch --verbose --player_seed=42 --end_wait=0.25  --turns=1000 \
    #   --map_file=maps/example/tutorial1.map "$@" \
    #   "ruby ../sam_isaac/explore.rb" \
    #   "python sample_bots/python/HunterBot.py"

    ### take 1
    ### this version failed in spite of numerous attempts :
    #### python command line stuff sucks.
    # options = '-R --nolaunch --player_seed=42 --end_wait=0.24'
    # dir = os.getcwd()
    # command = 'cd ' + dir + ' ; ' + engine + ' ' + 
    #    ' --game=' + str(index) + ' --turns=' + str(turns) + \
    #    ' --log_dir=' + log_folder + ' --map_file=' + map + \
    #    ' "' + bot1 + '" ' + ' "' + bot2 + '" '
    # print "command = '%s'" % command
    # status = subprocess.call(command)

    ### take 2
    ### this one also failed; cwd=dir is apparently not setting working folder
    # options = ['-R', '--nolaunch', '--player_seed=42', '--end_wait=0.24']
    # args = options + ['--game=' + str(index), '--turns=' + str(turns),
    #    '--log_dir=' + log_folder,
    #    '--map_file=' + map,
    #    '"' + bot1 + '" ', ' "' + bot2 + '"']
    # dir = os.getcwd()
    # print "args = " + str(args)
    # print "engine = '" + engine + "'"
    # print "dir = '" + dir + "'"
    # status = subprocess.Popen([engine] + args, shell=True, cwd=dir).wait()
    # print "status = " + str(status)

    ### take 3
    ### using the deprecated (but much simpler) commands.getoutput("cmd -arg1")
    ### Finally! This one works (with python 2.6.1 on Mac OS anyway)
    options = '-R --nolaunch --player_seed=42 --end_wait=0.24'
    dir = os.getcwd()
    command = 'cd ' + dir + ' ; ' + engine + ' ' + options + \
       ' --game=' + str(index) + ' --turns=' + str(turns) + \
       ' --log_dir=' + log_folder + ' --map_file=' + the_map + \
       ' "' + bot1 + '" ' + ' "' + bot2 + '" '
    # print "command = '%s'" % command
    output = commands.getoutput(command)
    # print "output = '%s'" % output

    logfile = log_folder + "/" + str(index) + ".replay"
    json_result = json.load(open(logfile))
    # One issue is that json returns unicode (part of JSON spec);
    # its strings render as e.g. u"MyBot.py" rather than "MyBot.py".
    # Ah yes - more pythonic "goodness".
    # manual conversion: u"foo".encode('utf-8') gives plain old "foo"
    result = {}
    for (who, what) in zip(json_result['playernames'], json_result['score']):
        if who != None:                             # None => bot crashed
            result[who.encode('utf-8')] = what
    for bot in (shortname(bot1), shortname(bot2)):  # Fill in score=0 if crashed.
        if not result.has_key(bot):
            result[bot] = 0
    return result

def test_play_game():
    print "Playing game 0 ..."
    result = play_game(bots[0], bots[1], maps[0], 0)
    print "Done.  Scores are " + str(result)
# test_play_game()

def main():
    """ play the tourney; print results """

    ###### Modify these two lists #######
    # Paths are relative to this tools/ folder.
    # Two player maps only
    
    maps = ["maps/example/tutorial1.map",
            "maps/maze/maze_02p_02.map",
            ]
    
    bots = ["python ../alex/DumbAnts.py",
            "ruby ../sam_isaac/aggress-v1.rb",
            "java -jar ../ryan/TheBot.jar",
            "python ../jay/TestBotRandom.py",
            "python ../chad/ChadBot.py",
            "python ../jacob/antbot.py",
            ]

    #####################################

    games = play_tourney(bots, maps, print_progress=True)
    print "-- bots --"
    for bot in bots:
        print "   " + bot
    print "-- maps --"
    for mapp in maps:
        print "   " + mapp
    print_games(games)
    get_ranks(games, print_rankings=True)

main()

syntax highlighted by Code2HTML, v. 0.93pm6