.then(response => response.json())
.then(response => { global_json = response; process_matches(global_json); });
-function attribute_player_time(player, to, from) {
+function attribute_player_time(player, to, from, offense) {
+ let delta_time;
if (player.on_field_since > from) {
// Player came in while play happened (without a stoppage!?).
- player.playing_time_ms += to - player.on_field_since;
+ delta_time = to - player.on_field_since;
} else {
- player.playing_time_ms += to - from;
+ delta_time = to - from;
+ }
+ player.playing_time_ms += delta_time;
+ if (offense === true) {
+ player.offensive_playing_time_ms += delta_time;
+ } else if (offense === false) {
+ player.defensive_playing_time_ms += delta_time;
}
}
-function take_off_field(player, t, live_since) {
+function take_off_field(player, t, live_since, offense) {
if (live_since === null) {
// Play isn't live, so nothing to do.
} else {
- attribute_player_time(player, t, live_since);
+ attribute_player_time(player, t, live_since, offense);
}
if (player.on_field_since !== null) { // Just a safeguard; out without in should never happen.
player.field_time_ms += t - player.on_field_since;
'interceptions': 0,
'points_played': 0,
'playing_time_ms': 0,
+ 'offensive_playing_time_ms': 0,
+ 'defensive_playing_time_ms': 0,
'field_time_ms': 0,
// For efficiency.
players['globals'] = {
'points_played': 0,
'playing_time_ms': 0,
+ 'offensive_playing_time_ms': 0,
+ 'defensive_playing_time_ms': 0,
'field_time_ms': 0,
'offensive_points_completed': 0,
let handler = null;
let prev_handler = null;
let live_since = null;
- let offense = null;
+ let offense = null; // True/false/null (unknown).
let puller = null;
let pull_started = null;
let last_pull_was_ours = null; // Effectively whether we're playing an O or D point (not affected by turnovers).
if (type === 'in' && p.on_field_since === null) {
p.on_field_since = t;
} else if (type === 'out') {
- take_off_field(p, t, live_since);
+ take_off_field(p, t, live_since, offense);
}
// Liveness management
p.last_point_seen = point_num;
++p.points_played;
}
- attribute_player_time(p, t, live_since);
+ attribute_player_time(p, t, live_since, offense);
if (type !== 'stoppage') {
if (last_pull_was_ours === true) { // D point.
}
if (live_since !== null) {
globals.playing_time_ms += t - live_since;
+ if (offense === true) {
+ globals.offensive_playing_time_ms += t - live_since;
+ } else if (offense === false) {
+ globals.defensive_playing_time_ms += t - live_since;
+ }
}
live_since = null;
puller = pull_started = null;
}
- // Offense/defense management (TODO: use it for actual counting)
+ // Offense/defense management
+ let last_offense = offense;
if (type === 'set_defense' || type === 'goal' || type === 'throwaway' || type === 'drop') {
offense = false;
} else if (type === 'set_offense' || type === 'their_goal' || type === 'their_throwaway' || type === 'defense' || type === 'interception') {
offense = true;
}
+ if (last_offense !== offense && live_since !== null) {
+ // Switched offense/defense status, so attribute this drive as needed,
+ // and update live_since to take that into account.
+ for (const [q,p] of Object.entries(players)) {
+ if (p.on_field_since === null) {
+ continue;
+ }
+ attribute_player_time(p, t, live_since, last_offense);
+ }
+ globals.playing_time_ms += t - live_since;
+ if (offense === true) {
+ globals.offensive_playing_time_ms += t - live_since;
+ } else if (offense === false) {
+ globals.defensive_playing_time_ms += t - live_since;
+ }
+ live_since = t;
+ }
+
if (type === 'pull') {
last_pull_was_ours = true;
} else if (type === 'their_pull') {
}
if (live_since !== null && last_goal !== null) {
globals.playing_time_ms += last_goal - live_since;
+ if (offense === true) {
+ globals.offensive_playing_time_ms += last_goal - live_since;
+ } else if (offense === false) {
+ globals.defensive_playing_time_ms += last_goal - live_since;
+ }
}
}
add_th(header, 'Ds');
add_th(header, 'Pulls');
add_th(header, 'OOB pulls');
+ add_th(header, 'OOB%');
add_th(header, 'Avg. hang time (IB)');
add_th(header, 'Soft +/-', 6);
rows.push(header);
let avg_time = 1e-3 * sum_time / p.pulls;
let oob_pct = 100 * p.oob_pulls / p.pulls;
+ let ci_oob = make_binomial_ci(p.oob_pulls, p.pulls, z);
+ ci_oob.format = 'percentage';
+ ci_oob.desired = 0.2; // Arbitrary.
+ ci_oob.inverted = true;
+
let row = document.createElement('tr');
add_3cell(row, p.name, 'name'); // TODO: number?
add_3cell(row, p.defenses);
add_3cell(row, p.pulls);
- if (p.pulls === 0) {
- add_3cell(row, 'N/A');
- } else {
- add_3cell(row, p.oob_pulls + ' (' + oob_pct.toFixed(0) + '%)');
- }
+ add_3cell(row, p.oob_pulls);
+ add_3cell_ci(row, ci_oob);
if (p.pulls > p.oob_pulls) {
add_3cell(row, avg_time.toFixed(1) + ' sec');
} else {
add_th(header, 'Player');
add_th(header, 'Points played');
add_th(header, 'Time played');
+ add_th(header, 'O time');
+ add_th(header, 'D time');
add_th(header, 'Time on field');
add_th(header, 'O points');
add_th(header, 'D points');
add_3cell(row, p.name, 'name'); // TODO: number?
add_3cell(row, p.points_played);
add_3cell(row, Math.floor(p.playing_time_ms / 60000) + ' min');
+ add_3cell(row, Math.floor(p.offensive_playing_time_ms / 60000) + ' min');
+ add_3cell(row, Math.floor(p.defensive_playing_time_ms / 60000) + ' min');
add_3cell(row, Math.floor(p.field_time_ms / 60000) + ' min');
add_3cell(row, p.offensive_points_completed);
add_3cell(row, p.defensive_points_completed);
add_3cell(row, '');
add_3cell(row, globals.points_played);
add_3cell(row, Math.floor(globals.playing_time_ms / 60000) + ' min');
+ add_3cell(row, Math.floor(globals.offensive_playing_time_ms / 60000) + ' min');
+ add_3cell(row, Math.floor(globals.defensive_playing_time_ms / 60000) + ' min');
add_3cell(row, Math.floor(globals.field_time_ms / 60000) + ' min');
add_3cell(row, globals.offensive_points_completed);
add_3cell(row, globals.defensive_points_completed);