Diagnosing Crashes in Ranked Matches

My algo, Atlas, just crashed in the first round of the BYOB competition, and I can’t seem to reproduce it, it literally has never crashed before this. I’ve found myself in similar situations before, and I assume others have had trouble diagnosing crashes as well. I think it might be really useful to be able to see the debug information from your algo in matches played on the server, so that you could debug rare crashes, like happens in the playground. Are there reasons this hasn’t been done, besides not wanting users to be able to see the opponents’ output?

EDIT: The ‘clone round in new tab’ feature would be useful for this if you were still playing against your algo, and not against yourself, but that’s probably impossible, as algos can store arbitrary state.

1 Like

I created for this an algo that take actions according to a replay file : it plays the … replay

So you can make your algo play on local against this replayFollower to see where the crash occured. (obviously for this to works, your algos needs to be exactly the same as the version that played the official match)

Here is the code:

the following part read the replay file and convert into a list of actions. The replay file has to be aside the algo_strategy.py and under the name <match_id>.txt

import json

def getReplay(match_id):
    f = open('algos/replayCopy/' + str(match_id) + '.txt','r')    
    content = f.read()
    return content

def searchDico(s):
    index = []
    start_dico = 0
    n_open = 0
    n_close = 0
    l = len(s)
    i = 0
        if (s[i] == '{'):
            if(n_open == 0):
                start_dico = i
            n_open += 1
        elif (s[i] == '}'):
            n_close += 1
        if(n_open == n_close and n_open != 0):
            index.append([start_dico,i + 1])
            n_open = 0
            n_close = 0
        i += 1
    if (n_open != n_close):
        print('error: a dico has not been closed')
    return [s[o:c] for o,c in index]

def getRoundFrames(frames):
    round_frames = []
    round_num = -10
    for f in frames:
        if('turnInfo' in f and ((f['turnInfo'][1] > round_num and f['turnInfo'][2] == 0) or 'endStats' in f)):
            round_num = f['turnInfo'][1]
    return round_frames
def getActions(match_id,player_num):
    replay = getReplay(match_id)
    content = searchDico(replay)
    frames = [json.loads(c) for c in content]
    frames = getRoundFrames(frames)
    actions = []
    for f in frames:
        a = []
        spawn = f['events']['spawn']
        for s in spawn:
                location = s[0]
                if player_num == 2:
                    location = [27 - location[0],27 - location[1]]
    return actions

You just have to call self.actions = getActions(<match_id>,<board_side>) in the on_game_start method and then call the following function every turn:

def copy_strategy(self,game_state):
        unit_type_dict = {0:FILTER,1:ENCRYPTOR,2:DESTRUCTOR,3:PING,4:EMP,5:SCRAMBLER}
        turn_actions = self.actions[self.num_turn]
        for a in turn_actions:

Edit: you can see that I use a member called self.num_turn but you can guess what it is and how it works :wink:

Edit 2: I just realized that the removal action is not handled


Oops, I must have missed that; that makes sense, as the other algo spawned a lot of destructors, which take my simulator a long time to find each one’s target. Ignore my example, then :smirk:
@arnby Thank you, this looks really helpful! A lot faster than doing every move in Play By Hand.

The reason we currently do not include logs in replays is because replays are not being played when you are watching them, you are watching a visual representation of game states stored in a replay file. We could include the logs in a replay file, but there are a number of factors that make this difficult. For now, you will have to write your own tests or test on the playground.

I know, my algos defense sometimes looks a bit weird, but I totally didn’t plan on confusing my opponents so much they crash! …promise! ; )


This is absolutely awesome with maybe one trivial issue:
if a[1] == 6, I guess we are supposed to use game_state.attempt_remove to remove the stationary unit.

Another way to diagnose some of these matches in all its glory can be found in a response I wrote here.

Specifically the part about running gdb. I wrote it with c++ in mind, but it will work just fine for regular python as well. Just download the matches replay and then use that to copy paste into the commands part.

Well, my current algo now crashes on turn 0 in all ranked matches, but not in playground matches or locally, so a way to actually see the debug output from these matches would still be nice.

1 Like

What fixed it for me was to rename the compiled module:
_brain.cpython-36m-x86_64-linux-gnu.so became _brain.so (very strange but it works so…)

I’m not even using C, I’m just trying to access a file I have in my algo zip. I think the working directory might be different between playground and ranked matches…?

No but your rights of access might be different.

To be able to execute a subprogram in both the playground and ranked matches, I needed to add this line in the run.sh file:
chmod u+x $DIR/Aelgoo_cpp_linux

You may need different permissions than u+x depending on how you want to access your file.

I’m not executing anything, the file is just a pickled Python object. I think it might be running out of memory - I’m using something like 200 MB, which is probably too much for servers that should be running a bunch of matches at once.

Does it also crash against the bosses? You should be able to see the cause of the error there, even if it is something unspecific like segmentation fault. Also, have you thought about time? I have no personal experience with this but I just read this about pickling (you could try cpickle) taking 15 seconds on a 300mb file. Obviously this depends on a million things, but the idea is that it’s slow.