From: Steinar H. Gunderson Date: Sun, 28 May 2023 20:53:22 +0000 (+0200) Subject: Implement filter-by-player-on-field. X-Git-Url: https://git.sesse.net/?a=commitdiff_plain;h=415442b56355392b207551b2cc84e663d8d7f914;p=pkanalytics Implement filter-by-player-on-field. --- diff --git a/ultimate.js b/ultimate.js index 0c44658..40461d8 100644 --- a/ultimate.js +++ b/ultimate.js @@ -27,14 +27,16 @@ function attribute_player_time(player, to, from, offense) { } } -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, 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; +function take_off_field(player, t, live_since, offense, keep) { + if (keep) { + if (live_since === null) { + // Play isn't live, so nothing to do. + } else { + 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; + } } player.on_field_since = null; } @@ -229,19 +231,43 @@ function process_matches(json, filters) { p.last_point_seen = null; } for (const e of match['events']) { - // TODO: filter events - let t = e['t']; let type = e['type']; let p = players[e['player']]; // Sub management + let keep = keep_event(players, filters); if (type === 'in' && p.on_field_since === null) { p.on_field_since = t; + if (!keep && keep_event(players, filters)) { + // A player needed for the filters went onto the field, + // so pretend people walked on right now (to start their + // counting time). + for (const [q,p2] of Object.entries(players)) { + if (p2.on_field_since !== null) { + p2.on_field_since = t; + } + } + } } else if (type === 'out') { - take_off_field(p, t, live_since, offense); + take_off_field(p, t, live_since, offense, keep); + if (keep && !keep_event(players, filters)) { + // A player needed for the filters went off the field, + // so we need to attribute time for all the others. + // Pretend they walked off and then immediately on again. + // + // TODO: We also need to take care of this to get the globals right. + for (const [q,p2] of Object.entries(players)) { + if (p2.on_field_since !== null) { + take_off_field(p2, t, live_since, offense, keep); + p2.on_field_since = t; + } + } + } } + keep = keep_event(players, filters); // Recompute after in/out. + // Liveness management if (type === 'pull' || type === 'their_pull' || type === 'restart') { live_since = t; @@ -256,51 +282,57 @@ function process_matches(json, filters) { continue; } if (type !== 'stoppage' && p.last_point_seen !== point_num) { - // In case the player did nothing this point, - // not even subbing in. - p.last_point_seen = point_num; - ++p.points_played; + if (keep) { + // In case the player did nothing this point, + // not even subbing in. + p.last_point_seen = point_num; + ++p.points_played; + } + } + if (keep) attribute_player_time(p, t, live_since, offense); + + if (type !== 'stoppage') { + if (keep) { + if (last_pull_was_ours === true) { // D point. + ++p.defensive_points_completed; + if (type === 'goal') { + ++p.defensive_points_won; + } + } else if (last_pull_was_ours === false) { // O point. + ++p.offensive_points_completed; + if (type === 'goal') { + ++p.offensive_points_won; + } + } + } } - attribute_player_time(p, t, live_since, offense); + } + if (keep) { if (type !== 'stoppage') { + // Update globals. + ++globals.points_played; if (last_pull_was_ours === true) { // D point. - ++p.defensive_points_completed; + ++globals.defensive_points_completed; if (type === 'goal') { - ++p.defensive_points_won; + ++globals.defensive_points_won; } } else if (last_pull_was_ours === false) { // O point. - ++p.offensive_points_completed; + ++globals.offensive_points_completed; if (type === 'goal') { - ++p.offensive_points_won; + ++globals.offensive_points_won; } } } - } - - if (type !== 'stoppage') { - // Update globals. - ++globals.points_played; - if (last_pull_was_ours === true) { // D point. - ++globals.defensive_points_completed; - if (type === 'goal') { - ++globals.defensive_points_won; - } - } else if (last_pull_was_ours === false) { // O point. - ++globals.offensive_points_completed; - if (type === 'goal') { - ++globals.offensive_points_won; + 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; } } } - 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; } @@ -314,8 +346,10 @@ function process_matches(json, filters) { // Point count management if (p !== undefined && type !== 'out' && p.last_point_seen !== point_num) { - p.last_point_seen = point_num; - ++p.points_played; + if (keep) { + p.last_point_seen = point_num; + ++p.points_played; + } } if (type === 'goal' || type === 'their_goal') { ++point_num; @@ -329,13 +363,13 @@ function process_matches(json, filters) { if (type === 'pull') { puller = e['player']; pull_started = t; - ++p.pulls; + if (keep) ++p.pulls; } else if (type === 'in' || type === 'out' || type === 'stoppage' || type === 'restart' || type === 'unknown' || type === 'set_defense' || type === 'set_offense') { // No effect on pull. } else if (type === 'pull_landed' && puller !== null) { - players[puller].pull_times.push(t - pull_started); + if (keep) players[puller].pull_times.push(t - pull_started); } else if (type === 'pull_oob' && puller !== null) { - ++players[puller].oob_pulls; + if (keep) ++players[puller].oob_pulls; } else { // Not pulling (if there was one, we never recorded its outcome, but still count it). puller = pull_started = null; @@ -351,17 +385,19 @@ function process_matches(json, filters) { 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; + if (keep) { + 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; } - 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; } @@ -389,19 +425,23 @@ function process_matches(json, filters) { // Event management if (type === 'catch' || type === 'goal') { if (handler !== null) { - ++players[handler].num_throws; - ++p.catches; + if (keep) { + ++players[handler].num_throws; + ++p.catches; + } } - ++p.touches; + if (keep) ++p.touches; if (type === 'goal') { - if (prev_handler !== null) { - ++players[prev_handler].hockey_assists; - } - if (handler !== null) { - ++players[handler].assists; + if (keep) { + if (prev_handler !== null) { + ++players[prev_handler].hockey_assists; + } + if (handler !== null) { + ++players[handler].assists; + } + ++p.goals; } - ++p.goals; handler = prev_handler = null; } else { // Update hold history. @@ -409,22 +449,26 @@ function process_matches(json, filters) { handler = e['player']; } } else if (type === 'throwaway') { - ++p.num_throws; - ++p.throwaways; + if (keep) { + ++p.num_throws; + ++p.throwaways; + } handler = prev_handler = null; } else if (type === 'drop') { - ++p.drops; + if (keep) ++p.drops; handler = prev_handler = null; } else if (type === 'defense') { - ++p.defenses; + if (keep) ++p.defenses; } else if (type === 'interception') { - ++p.interceptions; - ++p.defenses; - ++p.touches; + if (keep) { + ++p.interceptions; + ++p.defenses; + ++p.touches; + } prev_handler = null; handler = e['player']; } else if (type === 'offensive_soft_plus' || type === 'offensive_soft_minus' || type === 'defensive_soft_plus' || type === 'defensive_soft_minus') { - ++p[type]; + if (keep) ++p[type]; } else if (type !== 'in' && type !== 'out' && type !== 'pull' && type !== 'their_goal' && type !== 'stoppage' && type !== 'restart' && type !== 'unknown' && type !== 'set_defense' && type !== 'goal' && type !== 'throwaway' && @@ -436,20 +480,23 @@ function process_matches(json, filters) { } // Add field time for all players still left at match end. - for (const [q,p] of Object.entries(players)) { - if (p.on_field_since !== null && last_goal !== null) { - p.field_time_ms += last_goal - p.on_field_since; + const keep = keep_event(players, filters); + if (keep) { + for (const [q,p] of Object.entries(players)) { + if (p.on_field_since !== null && last_goal !== null) { + p.field_time_ms += last_goal - p.on_field_since; + } } - } - if (game_started !== null && last_goal !== null) { - globals.field_time_ms += last_goal - game_started; - } - 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; + if (game_started !== null && last_goal !== null) { + globals.field_time_ms += last_goal - game_started; + } + 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; + } } } } @@ -1192,6 +1239,27 @@ function keep_match(match_id, filters) { return true; } +function keep_event(players, filters) { + for (const filter of filters) { + if (filter.type === 'player_any') { + for (const p of Array.from(filter.elements)) { + if (players[p].on_field_since !== null) { + return true; + } + } + return false; + } else if (filter.type === 'player_all') { + for (const p of Array.from(filter.elements)) { + if (players[p].on_field_since === null) { + return false; + } + } + return true; + } + } + return true; +} + function possibly_close_menu(e) { if (e.target.closest('#filter-click-to-add') === null && e.target.closest('#filter-add-menu') === null &&