3 // No frameworks, no compilers, no npm, just JavaScript. :-)
8 .then(response => response.json())
9 .then(response => { process_matches(response); });
11 function attribute_player_time(player, to, from) {
12 if (player.on_field_since > from) {
13 // Player came in while play happened (without a stoppage!?).
14 player.playing_time_ms += to - player.on_field_since;
16 player.playing_time_ms += to - from;
20 function take_off_field(player, t, live_since) {
21 if (live_since === null) {
22 // Play isn't live, so nothing to do.
24 attribute_player_time(player, t, live_since);
26 player.on_field_since = null;
29 function add_cell(tr, element_type, text) {
30 let element = document.createElement(element_type);
31 element.textContent = text;
32 tr.appendChild(element);
35 function process_matches(json) {
37 for (const player of json['players']) {
38 players[player['player_id']] = {
39 'name': player['name'],
40 'number': player['number'],
56 'offensive_soft_plus': 0,
57 'offensive_soft_minus': 0,
58 'defensive_soft_plus': 0,
59 'defensive_soft_minus': 0,
66 'last_point_seen': null,
67 'on_field_since': null,
71 for (const match of json['matches']) {
73 let prev_handler = null;
74 let live_since = null;
77 let pull_started = null;
79 for (const [q,p] of Object.entries(players)) {
80 p.on_field_since = null;
81 p.last_point_seen = null;
83 for (const e of match['events']) {
86 let p = players[e['player']];
88 // Point count management
89 if (p !== undefined && type !== 'out' && p.last_point_seen !== point_num) {
90 p.last_point_seen = point_num;
93 if (type === 'goal' || type === 'their_goal') {
98 if (type === 'in' && p.on_field_since === null) {
100 } else if (type === 'out') {
101 take_off_field(p, t, live_since);
104 // Liveness management
105 if (type === 'pull' || type === 'their_pull' || type === 'restart') {
107 } else if (type === 'goal' || type === 'their_goal' || type === 'stoppage') {
108 for (const [q,p] of Object.entries(players)) {
109 if (p.on_field_since !== null) {
110 attribute_player_time(p, t, live_since);
117 if (type === 'pull') {
118 puller = e['player'];
121 } else if (type === 'in' || type === 'out' || type === 'stoppage' || type === 'restart' || type === 'unknown' || type === 'set_defense' || type === 'set_offense') {
122 // No effect on pull.
123 } else if (type === 'pull_landed' && puller !== null) {
124 players[puller].pull_times.push(t - pull_started);
125 } else if (type === 'pull_oob' && puller !== null) {
126 ++players[puller].oob_pulls;
128 // Not pulling (if there was one, we never recorded its outcome, but still count it).
129 puller = pull_started = null;
132 // Offense/defense management (TODO: use it for actual counting)
133 if (type === 'set_defense' || type === 'goal' || type === 'throwaway' || type === 'drop') {
135 } else if (type === 'set_offense' || type === 'their_goal' || type === 'their_throwaway' || type === 'defense' || type === 'interception') {
140 if (type === 'catch' || type === 'goal') {
141 if (handler !== null) {
142 ++players[handler].num_throws;
147 if (type === 'goal') {
148 if (prev_handler !== null) {
149 ++players[prev_handler].hockey_assists;
151 if (handler !== null) {
152 ++players[handler].assists;
155 handler = prev_handler = null;
157 // Update hold history.
158 prev_handler = handler;
159 handler = e['player'];
161 } else if (type === 'throwaway') {
163 handler = prev_handler = null;
164 } else if (type === 'drop') {
166 handler = prev_handler = null;
167 } else if (type === 'defense') {
169 } else if (type === 'interception') {
174 handler = e['player'];
175 } else if (type === 'offensive_soft_plus' || type === 'offensive_soft_minus' || type === 'defensive_soft_plus' || type === 'defensive_soft_minus') {
177 } else if (type !== 'in' && type !== 'out' && type !== 'pull' &&
178 type !== 'their_pull' && type !== 'restart' && type !== 'goal' &&
179 type !== 'their_goal' && type !== 'stoppage' && type !== 'unknown' &&
180 type !== 'set_defense' && type !== 'goal' && type !== 'throwaway' &&
181 type !== 'drop' && type !== 'set_offense' && type !== 'their_goal' &&
182 type !== 'pull' && type !== 'pull_landed' && type !== 'pull_oob' &&
183 type !== 'their_throwaway' && type !== 'defense' && type !== 'interception') {
184 console.log("Unknown event:", e);
192 let header = document.createElement('tr');
193 add_cell(header, 'th', 'Player');
194 add_cell(header, 'th', '+/-');
195 add_cell(header, 'th', 'Soft +/-');
196 add_cell(header, 'th', 'Points played');
197 add_cell(header, 'th', 'Time played');
201 for (const [q,p] of Object.entries(players)) {
202 let row = document.createElement('tr');
203 let pm = p.goals + p.assists + p.hockey_assists + p.defenses - p.throwaways - p.drops;
204 let soft_pm = p.offensive_soft_plus + p.defensive_soft_plus - p.offensive_soft_minus - p.defensive_soft_minus;
205 add_cell(row, 'td', p.name); // TODO: number?
206 add_cell(row, 'td', pm > 0 ? ('+' + pm) : pm);
207 add_cell(row, 'td', soft_pm > 0 ? ('+' + soft_pm) : soft_pm);
208 add_cell(row, 'td', p.points_played);
209 add_cell(row, 'td', Math.floor(p.playing_time_ms / 60000) + ' min');
211 console.log(p.name + " played " + p.points_played + " points (" + Math.floor(p.playing_time_ms / 60000) + " min), " + p.goals + " goals, " + p.assists + " assists, plus/minus: " + pm);
213 document.getElementById('stats').replaceChildren(...rows);
215 console.log("PULL STATS");
216 for (const [q,p] of Object.entries(players)) {
221 for (const t of p.pull_times) {
224 let avg_time = 1e-3 * sum_time / p.pulls;
225 let msg = p.name + ' did ' + p.pulls + ' pull(s), ' + p.oob_pulls + ' OOB';
226 if (p.oob_pulls < p.pulls) {
227 msg += ', avg. hangtime ' + avg_time.toFixed(1) + ' sec for others';
229 console.log(msg, p.pull_times);