Getting Data From Running a Replay

After coming to the disappointing conclusion that statistics are not uploaded with BAR replays, I realized that I needed to come at this from a different direction in order to get everything I needed. I knew that the Spring engine calculates statistics during the running game (you can see this when running a replay and clicking the “statistics” button), so if I could figure a way to pull that out then we could have our solution. But doing this was not as easy as I had hoped (there are a lot of moving pieces).

In order to get these statistics out I know that we have to generate statistics into a file from headless mode (basically run only the simulation aspect of the game, not the graphics or anything extra). In the BAR engine folder lives an executable called spring-headless.txt, so my hunch is that this is how you run a headless game (crazy I know!). To get this executable to actually run a headless game from a specific replay file I edited the <BAR install directory>/data/_script.txt to point to the replay file located in <BAR install directory>/data/demos like this:

1
2
3
4
[game]  
{  
demofile=replay_file_name.sdfz;  
}

Then we can run the game using the following command:

1
spring-headless.exe --write-dir <full path to the BAR/data directory> _script.txt

This successfully runs the replay in headless mode! The only problem is it runs at normal game speed (or at least not super-duper fast game speed) and doesn’t actually generate any output.

Looking at the BAR engine source again I found an example Lua widget that edits the game speed during a headless game. As I looked around I found in a few places (like here) that widgets do not have to be consistent between users, therefore adding widgets to this rerun of the replay should not cause any desync issues, so I figured that all my future endeavors of headless and information gathering would be best served by creating widgets to hook into the game. By copying the following stripped-down and sped-up version of the BAR headless script into the data/engine/<BAR engine>/LuaUI/Widgets directory and re-running the earlier command I was able to run the replay at blistering speeds, which will be much better for generating data from lots of replays!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function widget:GetInfo()  
    return {  
       name = "BuildETA",  
       desc = "Displays estimated time of arrival for builds",  
       author = "trepan (modified by jK)",  
       date = "2007",  
       license = "GNU GPL, v2 or later",  
       layer = -9,  
       enabled = true  
    }  
end

local headless
local startingSpeed = 9999

function widget:Initialize()  
    headless = (Spring.GetConfigInt('Headless', 0) ~= 0)  
    if (headless) then  
        Spring.Echo('Prepping for headless...')  
        Spring.SendCommands(  
        string.format('setmaxspeed %i', startingSpeed),  
        string.format('setminspeed %i', startingSpeed),  
        'hideinterface'  
        )  
    end
end

Now something about the GetInfo data is very particular. I copy/pasted this from gui_build_eta.lua so that it is basically spoofing an already existing widget, but even that is acting quite flaky. It will intermittently not be run in the replay, so then the replay takes a very long time and doesn’t give you any statistics. I’m guessing there is some sort of a whitelist somewhere in the engine just like with the AI’s, but I’m not sure. Either way, this makes it run well enough that I can ignore the replays that don’t cooperate.

Side note: Trying to find the right combination of directory, command, and Widget setup to get this to work properly was atrocious! I was throwing Widget directories all up and down the directory tree, renaming stuff left and right, and editing the crap out of the _script.txt file. Fun times! Do not recommend! I’m sure there were others who are much smarter than me and had already figured all of this out, but I’m notorious for taking the long way around….

Other side note: In all of this searching I also found this forum post that notes you can edit your _script.txt to also speed up a game or replay to be ridiculously fast using the following. I don’t know who might need this, but it is really fun trying to play the game at x2000 speed (in case you were wondering, I lost in about 5 real-time seconds).

1
2
3
4
5
[modoptions]
{
	MinSpeed = 2000;
	MaxSpeed = 2000;
}

With this running it was time to move on to actually getting statistics out of the replay run. Now I was starting to get my head wrapped around how BAR works internally, I knew I needed to find where these statistics come from:

BAR Statistics Widget

My hunch was this is a widget, along with most everything else that is displayed on the screen during a BAR match. If I could find this file and the stats it generates, then I could pop those out into a file. Sure enough, I found the BAR statistics overlay widget source, and within it a series of commands that continuously pull frame, player, and statistics data to display them on the screen. I don’t really care much about the displaying, but by adding a few lines to our headless script I can generate a basic CSV file of the statistics data in the BAR data directory:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
...
function widget:Initialize()
	...
	
	file = io.open("stats.csv", "w")  
	io.output(file)  
	  
	for team,stats in pairs(Spring.GetTeamStatsHistory(0,0)) do  
	    for name,value in pairs(stats) do  
	       io.write(name)  
	       io.write(",")  
	    end  
	    io.write("team")  
	    break  
	end  
	  
	io.write("\n")  
	io.close(file)
end

function widget:GameFrame(n)  
    frame = n   
    io.output(file)  
  
    num_teams = #Spring.GetAllyTeamList() - 1  
    for i=0,num_teams do  
       hist_len = Spring.GetTeamStatsHistory(i)  
       for key,stats in pairs(Spring.GetTeamStatsHistory(i, hist_len)) do  
          for name,value in pairs(stats) do  
             io.write(value)  
             io.write(",")  
          end  
          io.write(i)  
          io.write("\n")  
       end  
    end  
end

BAR Statistics Widget While doing this I figured it would probably also be useful to get the unit defs for the replay and unit counts for each player. I found some basic information in this widget and retrofitted it to also generate CSV’s for unitdefs and unit creation/destruction like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
...
function widget:Initialize()
	...
	file = io.open("defs.csv", "w")  
	io.output(file)  
	io.write("id,name,translatedTooltip,translatedHumanName\n")  
	for k,v in pairs(UnitDefs) do  
	    id = k  
	    name = ""  
	    tooltip = ""  
	    human_name = ""  
	    for k1, v1 in v:pairs() do  
	        if k1 == "name" then  
	            name = v1  
	            def_names[id] = name  
	        elseif k1 == "translatedTooltip" then  
	            tooltip = v1:gsub(",", "")  
	        elseif k1 == "translatedHumanName" then  
	            human_name = v1  
	        end  
	    end    io.write(id)  
	    io.write(",")  
	    io.write(name)  
	    io.write(",")  
	    io.write(tooltip)  
	    io.write(",")  
	    io.write(human_name)  
	    io.write("\n")  
	end  
	io.close(file)
	
	file = io.open("units.csv", "w")  
	io.output(file)  
	io.write("frame,unit_def_name,unit_id,team_id,action\n")  
	io.close(file)

	...
end

...

function widget:UnitCreated(unitID, unitDefID, teamID)  
    file = io.open("units.csv", "a")  
    io.output(file)  
    io.write(frame)  
    io.write(",")  
    io.write(def_names[unitDefID])  
    io.write(",")  
    io.write(unitID)  
    io.write(",")  
    io.write(teamID)  
    io.write(",add\n")  
    io.close(file)  
end  
  
function widget:UnitDestroyed(unitID, unitDefID, teamID)  
    file = io.open("units.csv", "a")  
    io.output(file)  
    io.write(frame)  
    io.write(",")  
    io.write(def_names[unitDefID])  
    io.write(",")  
    io.write(unitID)  
    io.write(",")  
    io.write(teamID)  
    io.write(",remove\n")  
    io.close(file)  
end  
  
function widget:UnitGiven(unitID, unitDefID, newTeamID, teamID)  
    file = io.open("units.csv", "a")  
    io.output(file)  
    io.write(frame)  
    io.write(",")  
    io.write(def_names[unitDefID])  
    io.write(",")  
    io.write(unitID)  
    io.write(",")  
    io.write(teamID)  
    io.write(",remove\n")  
    io.close(file)  
end  
  
function widget:UnitTaken(unitID, unitDefID, oldTeamID, teamID)  
    file = io.open("units.csv", "a")  
    io.output(file)  
    io.write(frame)  
    io.write(",")  
    io.write(def_names[unitDefID])  
    io.write(",")  
    io.write(unitID)  
    io.write(",")  
    io.write(teamID)  
    io.write(",add\n")  
    io.close(file)  
end

Unit Defs CSV

Unit Defs CSV

Now that this data is successfully generating, I hope to be able to actually train an AI, and to get that I’ll have to randomly download replays with all their dependencies. I’m still in the process of doing that, but once it’s together I’ll make a post all about it.

0%