Skip to content

Commit 2403b2a

Browse files
committed
scout players page, WIP
1 parent 370d04b commit 2403b2a

File tree

5 files changed

+319
-32
lines changed

5 files changed

+319
-32
lines changed

app/assets/javascripts/angular/controllers/scout.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,15 @@ gg.controller('ScoutController', ['$scope', '$element', '$urlFilter',
2525
} else {
2626
raceDim.filterAll();
2727
}
28+
}
29+
if (typeof oppRaceDim !== 'undefined') {
2830
if (_.isString($scope.vs_race) && ($scope.vs_race.length > 0)) {
2931
oppRaceDim.filter($scope.vs_race[0].toUpperCase());
3032
} else {
3133
oppRaceDim.filterAll();
3234
}
33-
renderAll();
3435
}
36+
renderAll();
3537
});
3638
}
3739
]);

app/assets/javascripts/scout.js

Lines changed: 274 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -78,12 +78,17 @@ function render(method) {
7878

7979
function renderAll() {
8080
$('.filterchart').each(function() { this.render() });
81+
82+
// TODO make original scout page work again
8183
list.each(render);
82-
d3.select("#active").text(formatNumber(gr_all.value()));
83-
numWins = _.find(winGrp.all(), function(grp) {return grp.key == "true"}).value.value
84-
pctWins = Math.round(1000.0 * numWins / gr_all.value()) / 10.0;
84+
85+
d3.select("#active").text(formatNumber(cf_all.value()));
86+
87+
// TODO make original scout page work again
88+
// numWins = _.find(winGrp.all(), function(grp) {return grp.key == "true"}).value.value
89+
// pctWins = Math.round(1000.0 * numWins / cf_all.value()) / 10.0;
8590
// console.log("wins:", numWins, pctWins);
86-
d3.select("#winrate").text(pctWins);
91+
// d3.select("#winrate").text(pctWins);
8792
}
8893

8994
function matchList(elem) {
@@ -127,7 +132,21 @@ function entities_url() {
127132
return data_host() + "/ents" + debug_suffix() + ".csv";
128133
}
129134

130-
function filter_chart(element, dimension, group, colorgroup, domain) {
135+
function cumulativize(group) {
136+
// TODO can be more efficient, but does it matter.
137+
return _.map(group, function(element, index, list) {
138+
var allFollowingGroups = _.rest(list, index);
139+
var allFollowingSummary = _.reduce(allFollowingGroups,
140+
function(memo, group){
141+
return {wins: memo.wins + group.value.wins,
142+
games: memo.games + group.value.games};
143+
}, {wins:0, games:0});
144+
allFollowingSummary.value = Math.round(1000.0 * allFollowingSummary.wins / allFollowingSummary.games) / 10.0;
145+
return {key: element.key, value: allFollowingSummary};
146+
});
147+
}
148+
149+
function filter_chart(element, dimension, group, colorgroup, domain, tooltip) {
131150
element.group = group;
132151
element.colorgroup = colorgroup;
133152
element.dimension = dimension;
@@ -136,15 +155,23 @@ function filter_chart(element, dimension, group, colorgroup, domain) {
136155

137156
element.render = function () {
138157
var grp = this.group.all();
139-
var colorGrp = this.colorgroup.all();
140-
141-
var xycolor = _.map(_.zip(grp, colorGrp), function (gs) {
158+
// var cumColorGrp = cumulativize(colorGrp);
159+
var data;
160+
161+
if (this.colorgroup) {
162+
var colorGrp = this.colorgroup.all();
163+
data = _.map(_.zip(grp, colorGrp), function (gs) {
142164
var winpct = gs[1].value.value;
143165
var color = winInterp(logistic((winpct - 50) / 10.0));
144166
return {x:gs[0].key, y:gs[0].value.value, color:color, winpct:winpct};
145-
});
167+
});
168+
} else {
169+
data = _.map(grp, function (g) {
170+
return {x:g.key, y:g.value.value, color: 'steelblue'};
171+
});
172+
}
146173

147-
this.chart.series[0].setData(xycolor);
174+
this.chart.series[0].setData(data);
148175
}
149176

150177
element.reset = function () {
@@ -157,7 +184,7 @@ function filter_chart(element, dimension, group, colorgroup, domain) {
157184
}
158185

159186
element.range_select = function (event) {
160-
console.log("Selected", event.xAxis[0].min, event.xAxis[0].max, event);
187+
// console.log("Selected", event.xAxis[0].min, event.xAxis[0].max, event);
161188

162189
event.xAxis[0].axis.removePlotBand('plot-band-1');
163190

@@ -182,9 +209,7 @@ function filter_chart(element, dimension, group, colorgroup, domain) {
182209
event.preventDefault();
183210
};
184211

185-
element.tooltip = function () {
186-
return this.x + ': ' + this.y + ' games, ' + this.point.winpct + '% win.';
187-
};
212+
element.tooltip = tooltip;
188213

189214
var options = {
190215
chart: {
@@ -254,7 +279,9 @@ function reset_click(e) {
254279
filterchart.reset();
255280
}
256281

257-
function add_filterchart(element, dimension, group, title, domain) {
282+
function add_filterchart(element, dimension, group, title, domain, addWinPct, tooltip) {
283+
284+
addWinPct = typeof addWinPct !== 'undefined' ? addWinPct : true;
258285

259286
var chartContainer = $(document.createElement('div')).addClass('djchart');
260287

@@ -271,9 +298,12 @@ function add_filterchart(element, dimension, group, title, domain) {
271298
var thechart = $(document.createElement('div')).addClass('filterchart');
272299
chartContainer.append(thechart);
273300

274-
var colorgroup = groupWinPct(group.clone());
301+
var colorgroup = null;
302+
if (addWinPct) {
303+
colorgroup = groupWinPct(group.clone());
304+
}
275305

276-
filter_chart(thechart[0], dimension, group, colorgroup, domain);
306+
filter_chart(thechart[0], dimension, group, colorgroup, domain, tooltip);
277307

278308
element.append(chartContainer);
279309
}
@@ -325,7 +355,7 @@ function scout_init() {
325355
});
326356
var rec_built = Date.now();
327357
gr_cf = crossfilter(gamerecords);
328-
gr_all = gr_cf.groupAll();
358+
cf_all = gr_cf.groupAll();
329359

330360
raceDim = gr_cf.dimension(function(gr) { return gr.player.race });
331361
raceGrp = groupDJ(raceDim.group());
@@ -388,15 +418,232 @@ function scout_init() {
388418
})
389419
});
390420

391-
add_filterchart($('#filtercharts'), lgDim, lgGrp, 'Game league', [0, 6]);
392-
add_filterchart($('#filtercharts'), asDim, asGrp, 'Player\'s Army Strength @ X minutes', [0, 2500]);
393-
add_filterchart($('#filtercharts'), oasDim, oasGrp, 'Opponent\'s Army Strength @ X minutes', [0, 2500]);
394-
add_filterchart($('#filtercharts'), wsDim, wsGrp, 'Player\'s Workers @ X minutes', [0, 60]);
395-
add_filterchart($('#filtercharts'), owsDim, owsGrp, 'Opponent\'s Workers @ X minutes', [0, 60]);
396-
add_filterchart($('#filtercharts'), mb2Dim, mb2Grp, 'Player\'s 2nd Mining Base Timing', [0, 15]);
397-
add_filterchart($('#filtercharts'), omb2Dim, omb2Grp, 'Opponent\'s 2nd Mining Base Timing', [0, 15]);
398-
add_filterchart($('#filtercharts'), durDim, durGrp, 'Game Length, minutes', [0, 40]);
399-
add_filterchart($('#filtercharts'), dateDim, dateGrp, 'Game Date', [Date.UTC(2014, 6, 25), Date.UTC(2014, 8, 15)]);
421+
422+
tooltip = function () {
423+
return this.x + ': ' + this.y + ' games, ' + this.point.winpct + '% win.';
424+
};
425+
426+
add_filterchart($('#filtercharts'), lgDim, lgGrp, 'Game league', [0, 6], tooltip);
427+
add_filterchart($('#filtercharts'), asDim, asGrp, 'Player\'s Army Strength @ X minutes', [0, 2500], tooltip);
428+
add_filterchart($('#filtercharts'), oasDim, oasGrp, 'Opponent\'s Army Strength @ X minutes', [0, 2500], tooltip);
429+
add_filterchart($('#filtercharts'), wsDim, wsGrp, 'Player\'s Workers @ X minutes', [0, 60], tooltip);
430+
add_filterchart($('#filtercharts'), owsDim, owsGrp, 'Opponent\'s Workers @ X minutes', [0, 60], tooltip);
431+
add_filterchart($('#filtercharts'), mb2Dim, mb2Grp, 'Player\'s 2nd Mining Base Timing', [0, 15], tooltip);
432+
add_filterchart($('#filtercharts'), omb2Dim, omb2Grp, 'Opponent\'s 2nd Mining Base Timing', [0, 15], tooltip);
433+
add_filterchart($('#filtercharts'), durDim, durGrp, 'Game Length, minutes', [0, 40], tooltip);
434+
add_filterchart($('#filtercharts'), dateDim, dateGrp, 'Game Date', [Date.UTC(2014, 6, 25), Date.UTC(2014, 8, 15)], tooltip);
435+
436+
renderAll();
437+
438+
var end = Date.now();
439+
var total_time = end - start;
440+
console.log("init took " + (total_time / 1000) + " seconds");
441+
442+
var build_rec = (rec_built - start) / 1000;
443+
var build_dims = (dims_built - rec_built) / 1000;
444+
var build_chart = (end - dims_built) / 1000;
445+
console.log(build_rec, build_dims, build_chart);
446+
});
447+
});
448+
449+
450+
}
451+
452+
// TODO do this with angular rather than d3, so we can ditch d3 someday
453+
function playerList(elem) {
454+
var playersByNumGames = numGamesDim.top(80);
455+
456+
elem.each(function() {
457+
var player = d3.select(this).selectAll(".player")
458+
.data(playersByNumGames);
459+
460+
var playerEnter = player.enter().append("tr").attr("class", "player");
461+
playerEnter.append("td").attr("class", "num").append("a");
462+
playerEnter.append("td").attr("class", "race");
463+
playerEnter.append("td").attr("class", "league");
464+
playerEnter.append("td").attr("class", "apm");
465+
playerEnter.append("td").attr("class", "num_games");
466+
playerEnter.append("td").attr("class", "winpct");
467+
468+
player.exit().remove();
469+
470+
player.select('.num a').text(function(p) { return p.identity_id });
471+
player.select('.num a').attr("href", function(p) { return "http://ggtracker.com/players/" + p.identity_id });
472+
player.select('.race').text(function(p) { return p.race(); });
473+
player.select('.league').text(function(p) { return p.league(); });
474+
player.select('.apm').text(function(p) { return p.apm(); });
475+
player.select('.num_games').text(function(p) { return p.num_games.total; });
476+
player.select('.winpct').text(function(p) { return p.win_pct('total'); });
477+
});
478+
}
479+
480+
function scout_players_init() {
481+
var start = Date.now();
482+
483+
/**
484+
* Each player has the following defined:
485+
*
486+
* identity_id
487+
* num_games[matchup] <-- where matchup is each of: PvT, PvZ, PvP...TvT and total
488+
* num_wins[matchup]
489+
* sum_apm <-- sum of apm over all games
490+
* sum_league <-- sum of league over all games
491+
* num_games_by_race[chosen_race] <-- where chosen_race is P, T, Z, R
492+
*
493+
* apm()
494+
* league()
495+
* win_pct(matchup)
496+
* race()
497+
*/
498+
499+
matches = {};
500+
entities = [];
501+
players = {};
502+
match_winner = {};
503+
match_loser = {};
504+
gamerecords = [];
505+
506+
507+
var matchup_zero = {'PvT': 0,
508+
'PvZ': 0,
509+
'PvP': 0,
510+
'TvT': 0,
511+
'TvP': 0,
512+
'TvZ': 0,
513+
'ZvZ': 0,
514+
'ZvT': 0,
515+
'ZvP': 0,
516+
'total': 0};
517+
var race_zero = {'P':0 , 'T':0, 'Z':0, 'R':0};
518+
519+
var apm = function() {
520+
if (this.num_games.total == 0) return 0;
521+
return Math.floor(this.sum_apm / this.num_games.total);
522+
}
523+
var league = function() {
524+
if (this.num_games.total == 0) return 0;
525+
return Math.floor(10 * this.sum_league / this.num_games.total) / 10.0;
526+
}
527+
var win_pct = function(matchup) {
528+
if (this.num_games[matchup] == 0) return 0;
529+
return Math.floor(1000.0 * this.num_wins[matchup] / this.num_games[matchup]) / 10.0;
530+
}
531+
var race = function() {
532+
var num_most_played = _.max(_.values(this.num_games_by_race));
533+
var race_most_played = _.find(_.pairs(this.num_games_by_race), function(kv) { return kv[1] == num_most_played; })[0];
534+
return race_most_played;
535+
}
536+
537+
// record
538+
record_player_in_match = function(match, player_entity, opponent_entity) {
539+
var matchup = player_entity.race + 'v' + opponent_entity.race;
540+
541+
var player;
542+
if (!(player_entity.identity_id in players)) {
543+
player = { identity_id: player_entity.identity_id,
544+
num_games: _.clone(matchup_zero),
545+
num_wins: _.clone(matchup_zero),
546+
num_games_by_race: _.clone(race_zero),
547+
sum_apm: 0,
548+
sum_league: 0,
549+
apm: apm,
550+
league: league,
551+
win_pct: win_pct,
552+
race: race
553+
};
554+
players[player_entity.identity_id] = player;
555+
} else {
556+
player = players[player_entity.identity_id];
557+
}
558+
player.num_games[matchup]++;
559+
player.num_games['total']++;
560+
player.num_games_by_race[player_entity.chosen_race]++;
561+
if (player_entity.win == 'true') {
562+
player.num_wins[matchup]++;
563+
player.num_wins['total']++;
564+
}
565+
player.sum_apm += player_entity.apm;
566+
player.sum_league += match.average_league;
567+
}
568+
569+
entity_non_numerics = ["race", "chosen_race", "win"]
570+
571+
d3.csv(matches_url(), function(error, csv_matches) {
572+
csv_matches.forEach( function(match, index) {
573+
match.play_date = new Date(match.play_date);
574+
match.id = +match.id
575+
match.average_league = +match.average_league
576+
match.duration_minutes = +match.duration_minutes
577+
matches[match.id] = match
578+
});
579+
d3.csv(entities_url(), function(error, csv_ents) {
580+
csv_ents.forEach( function(entity, index) {
581+
582+
// convert numeric fields to be actual numbers
583+
for (var key in entity) {
584+
if (!(_.contains(entity_non_numerics, key))) {
585+
entity[key] = +entity[key];
586+
}
587+
}
588+
589+
if (entity.match_id in matches) {
590+
match = matches[entity.match_id];
591+
if (entity.win == "true") {
592+
match_winner[entity.match_id] = entity;
593+
} else if (entity.win == "false") {
594+
match_loser[entity.match_id] = entity;
595+
}
596+
597+
if (entity.match_id in match_winner && entity.match_id in match_loser) {
598+
record_player_in_match(match, match_winner[entity.match_id], match_loser[entity.match_id]);
599+
record_player_in_match(match, match_loser[entity.match_id], match_winner[entity.match_id]);
600+
}
601+
}
602+
});
603+
604+
// get rid of players with less than 10 games, not useful here
605+
players = _.values(players);
606+
players = _.filter(players, function(player) { return player.num_games.total >= 10 });
607+
608+
var rec_built = Date.now();
609+
p_cf = crossfilter(players);
610+
cf_all = p_cf.groupAll();
611+
612+
raceDim = p_cf.dimension(function(p) { return p.race(); });
613+
raceGrp = groupDJ(raceDim.group());
614+
615+
numGamesDim = p_cf.dimension(function(p) { return p.num_games.total; });
616+
numGamesGrp = groupDJ(numGamesDim.group(function(d) { return 5 * Math.floor(d / 5); }));
617+
618+
leagueDim = p_cf.dimension(function(p) { return p.league(); });
619+
leagueGrp = groupDJ(leagueDim.group(function(d) { return Math.floor(d * 2) / 2; }));
620+
621+
winPctDim = p_cf.dimension(function(p) { return p.win_pct('total'); });
622+
winPctGrp = groupDJ(winPctDim.group(function(d) { return 5 * Math.floor(d / 5); }));
623+
624+
apmDim = p_cf.dimension(function(p) { return p.apm(); });
625+
apmGrp = groupDJ(apmDim.group(function(d) { return 5 * Math.floor(d / 5); }));
626+
627+
var dims_built = Date.now();
628+
629+
// Render the initial lists.
630+
list = d3.selectAll(".list tbody")
631+
.data([playerList]);
632+
633+
if (false) {
634+
635+
d3.selectAll("#total")
636+
.text(formatNumber(p_cf.size()));
637+
}
638+
639+
tooltip = function () {
640+
return this.x + ': ' + this.y + ' players.';
641+
};
642+
643+
add_filterchart($('#filtercharts'), numGamesDim, numGamesGrp, 'Number of Games Played', [0, 150], false, tooltip);
644+
add_filterchart($('#filtercharts'), leagueDim, leagueGrp, 'League', [0, 6], false, tooltip);
645+
add_filterchart($('#filtercharts'), winPctDim, winPctGrp, 'Win Percentage', [0, 100], false, tooltip);
646+
add_filterchart($('#filtercharts'), apmDim, apmGrp, 'APM', [0, 300], false, tooltip);
400647

401648
renderAll();
402649

0 commit comments

Comments
 (0)