Jump to content

Numerus - a Statistics Bot


Recommended Posts

Dear agent,

  • Updated your charts mod to SVN head revision.
  • Added the highlighting of the current tab. (The active chart category now is shown in blue text color. For this purpose I have rewritten the public/gui/summary/summary.js for highest flexibility, we should be able to add background image, borderss and whatever other style we wish without conflicting the main tabs of the summary page.)

I've set up a repository though I guess you have your own. If now then we can transfer ownership of the repository to you.

Thank you for the incredible development of your bots + now the charts. Your speed + nevertheless quality standards are great. I will never reach that.

Btw. could you submit the patch to trac? You have done the hard part, thus it's your credit. (Or do you want to wait for the real data instead of the currently randomly generated data?)

add_highlight_to_agentx_charts_and_update_for_svn.diff

Link to comment
Share on other sites

Edit: It's not the complete diff, it's only the diff for summary.js of public.

Add this to your charts.js:

   function showMetric(metric){                                              var m = Object.keys(metrics)[metric];                                   // Mark heading as active, deactivate others (TODO Use childnodes access instead for a much more elegant solution):     selectPanel(metric, Object.keys(metrics)                                   , {/*'sprite_background': 'bkBorderBlack',*/ 'textcolor': '200 200 255' }/*if active*/             , {/*'sprite': '',*/ 'textcolor': '255 255 255'/*TODO Better inherit => leave blank.*/ }/*if inactive*/             , 'chartMenuLabel'/*if sth. to prepend to bgToBeChangedObject*/             , ''/*if sth. to append to object where style to be changed*/                    , false /* Is adjusting tab spacer desired? (Attention: Conflicts with main summary panel. Don't readjust!) */             );                                                                      // print(fmt("showMetric: %s, %s\n", metric, m));                               curMetric = metric;    // ...

Warning:

The approach I used for the highlighting is not my preference. I'd prefer to wrap the 'panelNames'Elements within a parent element and execute a function after Charts.action(xy, yz).

e.g.

<action on="press">Charts.action(panelIndex); unhighlightAllButThis(this.parent.children, this);</action>

Where the function looked like follows:

function unhighlightAllButThis(children, exception) {     for (var childNode in children) {        if (childNode != exception) {            childNode.textcolor = 'r g b';        }        else {            childNode.textcolor = '255 255 255';    //<--reset        }    }   }
Here we had not to hardcode the panelNames lists/arrays.

Edit: Forgot the line: childNode.textcolor = '255 255 255'; /* default */

Edited by Hephaestion
  • Like 1
Link to comment
Share on other sites

> Or do you want to wait for the real data instead of the currently randomly generated data?

Wow, what's going on here? Thx, Hephaestion. Will look into the repo asap. And yes, want to make it complete before commit. I've still that machine setup in the pipeline and I'm currently fighting with the planner. Expect feedback end of WE.

PS: Using 'this' in the event handler is nice, didn't know that....

Link to comment
Share on other sites

I believe I have something which may work as a ChartTracker component. Only missing part is kind of a timer calling the OnUpdate function every minute with stamp info. Sander, could you provide the final bit?

function ChartTracker() {}ChartTracker.prototype.Schema =  "<a:component type='system'/><empty/>";ChartTracker.prototype.Init = function(){  this.chartData = {};};ChartTracker.prototype.GetData = function(){  return this.chartData;};  ChartTracker.prototype.OnUpdate = function(msg){  // Get player and range manager  var cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);  var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);  var numPlayers = cmpPlayerManager.GetNumPlayers();  var tickData = [];  var stamp = 0; // that should be 60, 120, 180, etc..  // push the player data  for (var i = 0; i < numPlayers; ++i)  {    var cmpPlayer = Engine.QueryInterface(cmpPlayerManager.GetPlayerByID(i), IID_Player);    // Skip gaia    if (i > 0)    {      tickData.push({        popCount: cmpPlayer.GetPopulationCount(),        resCount: cmpPlayer.GetResourceCounts(),        mapExplored: cmpRangeManager.GetPercentMapExplored(cmpPlayer.GetPlayerID())      });    }  }  this.chartData[stamp] = tickData;};Engine.RegisterComponentType(IID_ChartTracker, "ChartTracker", ChartTracker);
Link to comment
Share on other sites

This will be part of the simulation, right?

You can register a timer in the Timer component. A repeating timer is perfectly possible. But if you use OnUpdate, it will be called every simulation turn. The message passed also tells you how long the turn took (which is different between multi and single player, and might change in the future).

And you do need to add it to an entity too. To add it to the system component, you can use the RegisterSystemComponentType (only in svn). Or else you need to add it to the player template.

Link to comment
Share on other sites

Ok, found the timer. If I understand you correctly, all the components belong to a single player, so each player has a full set of components, hence his own StatisticTracker? If yes, I'll do same with ChartTracker.

One more question: At game end, the collect data should be available for the chart panel. I see in summary.js the init function got called with a 'data' object containing playerstate + statistics. Is GuiInterface.prototype.GetExtendedSimulationState the right place to attach the collect charts data to playerstate? So, basically I duplicate and adapt these lines:

var cmpPlayerStatisticsTracker = Engine.QueryInterface(playerEnt, IID_StatisticsTracker);ret.players[i].statistics = cmpPlayerStatisticsTracker.GetStatistics();

Better?

// Updates every minuteconst UPDATE_TIMER_INTERVAL = 60000;function ChartTracker() {}ChartTracker.prototype.Schema =  "<a:component type='system'/><empty/>";ChartTracker.prototype.GetChartData = function(){  var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);  cmpTimer.CancelTimer(this.updateTimer);  this.updateTimer = undefined;  return this.chartData;};  ChartTracker.prototype.Init = function(){  this.timeStamp = 0;  this.chartData = {};  var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);  this.updateTimer = cmpTimer.SetInterval(this.entity, IID_ChartTracker, "updateData", UPDATE_TIMER_INTERVAL, UPDATE_TIMER_INTERVAL, {});};ChartTracker.prototype.updateData = function(msg){  // Get player + range + stats manager  var cmpPlayer = Engine.QueryInterface(this.entity, IID_Player);  var cmpRangeManager = Engine.QueryInterface(this.entity, IID_RangeManager);  var cmpStatisticsManager = Engine.QueryInterface(this.entity, IID_StatisticsTracker);  var resourceCount = cmpPlayer.GetResourceCounts();  var statistics = cmpStatisticsManager.GetStatistics();  this.chartData[this.timeStamp++] = {    'map': cmpRangeManager.GetPercentMapExplored(cmpPlayer.GetPlayerID()),    'food': resourceCount.food,    'wood': resourceCount.wood,    'stone': resourceCount.stone,    'metal': resourceCount.metal,    'population': cmpPlayer.GetPopulationCount(),    'buildings': statistics.buildingsConstructed - statistics.buildingsLost  };};Engine.RegisterComponentType(IID_ChartTracker, "ChartTracker", ChartTracker);
Link to comment
Share on other sites

Is OnUpdate too much data then? Should we cache the time from the last data snapshot and then skip until the OnUpdate has been called often enough (summing up the msg.turnLength) that the set time interval (e.g. every minute, every second, ...) has passed?

function ChartTracker() {    this.settings = { dataGatherInterval_ms: DEFAULT_DATA_GATHER_INTERVAL; };    this.timeElapsedSinceLastDataAcquisition_ms = 0; }  ChartTracker.prototype.OnUpdate = function(msg){     // Get player and range manager    var cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);    var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);    var numPlayers = cmpPlayerManager.GetNumPlayers();        var tickData = [];    //fetch current time:    var snapshotTime_ms = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer).GetTime(); // that should be 60, 120, 180, etc..    this.timeElapsedSinceLastDataAcquisition_ms += msg.turnLength;    if (this.timeElapsedSinceLastDataAcquisition_ms < this.settings.dataGatherInterval_ms) {        return;    }        // push the player data    for (var i = 0; i < numPlayers; ++i)    {        var cmpPlayer = Engine.QueryInterface(cmpPlayerManager.GetPlayerByID(i), IID_Player);            // Skip gaia        if (i > 0)        {            tickData.push({                popCount: cmpPlayer.GetPopulationCount(),                resCount: cmpPlayer.GetResourceCounts(),                mapExplored: cmpRangeManager.GetPercentMapExplored(cmpPlayer.GetPlayerID())            });        }        }        this.chartData[snapshotTime_ms] = tickData;    this.timeElapsedSinceLastDataAcquisition_ms = 0; // or use absolut timestamp in milliseconds instead if no variable overflow must be expected.    };   

Or should we register a Timer instead which calls agentx' function (which then had to be renamed from OnUpdate to sth. different)?

(see agentx' answer)

And you do need to add it [the chart/data collector component] to an entity too.

Oh, what does that mean? Will RegisterSystemComponentType handle this or must it be attached to an ingame entity or do you mean another entity, perhaps programming entity?

Edit: to --> too.

Edited by Hephaestion
Link to comment
Share on other sites

Ok, found the timer. If I understand you correctly, all the components belong to a single player, so each player has a full set of components, hence his own StatisticTracker? If yes, I'll do same with ChartTracker.

The game has entities, and entities consist out of components that hold a state and do actions. There's one very special entity, the SYSTEM_ENTITY. That holds data for the general game (like the terrain profile, the available templates ...). In A16 and before, the components on the SYSTEM_ENTITY used to be hard-coded. Now, you can register new components on that SYSTEM_ENTITY by using the RegisterSystemComponentType. I made that available so mods can also register system components.

Apart from that system entity, every player in the game is also an entity. And every unit, building, tree or other object in the game is also an entity. All those have templates. In those templates, it's defined what components they have, and certain stats of those components. Here's the player template: http://trac.wildfiregames.com/browser/ps/trunk/binaries/data/mods/public/simulation/templates/special/player.xml

So to add your component to all players, just put it in the player template. It will be initialised once per player, and have one state per player (excluding gaia). You see the statisticstracker is also registered there.

One more question: At game end, the collect data should be available for the chart panel. I see in summary.js the init function got called with a 'data' object containing playerstate + statistics. Is GuiInterface.prototype.GetExtendedSimulationState the right place to attach the collect charts data to playerstate? So, basically I duplicate and adapt these lines:

var cmpPlayerStatisticsTracker = Engine.QueryInterface(playerEnt, IID_StatisticsTracker);ret.players[i].statistics = cmpPlayerStatisticsTracker.GetStatistics();
Exchanging data from the simulation to the GUI should indeed happen via the GuiInterface. Either expand an existing function where it fits, or make a new one. In case you make a new one, don't forget to register it publicly. Else the GUI won't be able to use the function.

One remark though. The GUI isn't synced between different players on a network. So functions called by the GuiInterface shouldn't modify the state of the components at all. Else you get Out-Of-Sync problems. I see in your code below that the GetChartData will stop the timer, and thus modify the state. This shouldn't happen. You just return the information, and let the timer run.

Better?

// Updates every minuteconst UPDATE_TIMER_INTERVAL = 60000;function ChartTracker() {}ChartTracker.prototype.Schema =  "<a:component type='system'/><empty/>";ChartTracker.prototype.GetChartData = function(){  var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);  cmpTimer.CancelTimer(this.updateTimer);  this.updateTimer = undefined;  return this.chartData;};  ChartTracker.prototype.Init = function(){  this.timeStamp = 0;  this.chartData = {};  var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);  this.updateTimer = cmpTimer.SetInterval(this.entity, IID_ChartTracker, "updateData", UPDATE_TIMER_INTERVAL, UPDATE_TIMER_INTERVAL, {});};ChartTracker.prototype.updateData = function(msg){  // Get player + range + stats manager  var cmpPlayer = Engine.QueryInterface(this.entity, IID_Player);  var cmpRangeManager = Engine.QueryInterface(this.entity, IID_RangeManager);  var cmpStatisticsManager = Engine.QueryInterface(this.entity, IID_StatisticsTracker);  var resourceCount = cmpPlayer.GetResourceCounts();  var statistics = cmpStatisticsManager.GetStatistics();  this.chartData[this.timeStamp++] = {    'map': cmpRangeManager.GetPercentMapExplored(cmpPlayer.GetPlayerID()),    'food': resourceCount.food,    'wood': resourceCount.wood,    'stone': resourceCount.stone,    'metal': resourceCount.metal,    'population': cmpPlayer.GetPopulationCount(),    'buildings': statistics.buildingsConstructed - statistics.buildingsLost  };};Engine.RegisterComponentType(IID_ChartTracker, "ChartTracker", ChartTracker);

The code looks good, apart from that state issue I mentioned above.

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

 Share

×
×
  • Create New...