3 // No frameworks, no compilers, no npm, just JavaScript. :-)
7 addEventListener('hashchange', () => { console.log('heei'); process_matches(global_json); });
9 .then(response => response.json())
10 .then(response => { global_json = response; process_matches(global_json); });
12 function attribute_player_time(player, to, from) {
13 if (player.on_field_since > from) {
14 // Player came in while play happened (without a stoppage!?).
15 player.playing_time_ms += to - player.on_field_since;
17 player.playing_time_ms += to - from;
21 function take_off_field(player, t, live_since) {
22 if (live_since === null) {
23 // Play isn't live, so nothing to do.
25 attribute_player_time(player, t, live_since);
27 player.on_field_since = null;
30 function add_cell(tr, element_type, text) {
31 let element = document.createElement(element_type);
32 element.textContent = text;
33 tr.appendChild(element);
36 function process_matches(json) {
38 for (const player of json['players']) {
39 players[player['player_id']] = {
40 'name': player['name'],
41 'number': player['number'],
57 'offensive_soft_plus': 0,
58 'offensive_soft_minus': 0,
59 'defensive_soft_plus': 0,
60 'defensive_soft_minus': 0,
67 'last_point_seen': null,
68 'on_field_since': null,
72 for (const match of json['matches']) {
74 let prev_handler = null;
75 let live_since = null;
78 let pull_started = null;
80 for (const [q,p] of Object.entries(players)) {
81 p.on_field_since = null;
82 p.last_point_seen = null;
84 for (const e of match['events']) {
87 let p = players[e['player']];
90 if (type === 'in' && p.on_field_since === null) {
92 } else if (type === 'out') {
93 take_off_field(p, t, live_since);
96 // Liveness management
97 if (type === 'pull' || type === 'their_pull' || type === 'restart') {
99 } else if (type === 'goal' || type === 'their_goal' || type === 'stoppage') {
100 for (const [q,p] of Object.entries(players)) {
101 if (p.on_field_since !== null) {
102 if (p.last_point_seen !== point_num) {
103 // In case the player did nothing this point,
104 // not even subbing in.
105 p.last_point_seen = point_num;
108 attribute_player_time(p, t, live_since);
114 // Point count management
115 if (p !== undefined && type !== 'out' && p.last_point_seen !== point_num) {
116 p.last_point_seen = point_num;
119 if (type === 'goal' || type === 'their_goal') {
124 if (type === 'pull') {
125 puller = e['player'];
128 } else if (type === 'in' || type === 'out' || type === 'stoppage' || type === 'restart' || type === 'unknown' || type === 'set_defense' || type === 'set_offense') {
129 // No effect on pull.
130 } else if (type === 'pull_landed' && puller !== null) {
131 players[puller].pull_times.push(t - pull_started);
132 } else if (type === 'pull_oob' && puller !== null) {
133 ++players[puller].oob_pulls;
135 // Not pulling (if there was one, we never recorded its outcome, but still count it).
136 puller = pull_started = null;
139 // Offense/defense management (TODO: use it for actual counting)
140 if (type === 'set_defense' || type === 'goal' || type === 'throwaway' || type === 'drop') {
142 } else if (type === 'set_offense' || type === 'their_goal' || type === 'their_throwaway' || type === 'defense' || type === 'interception') {
147 if (type === 'catch' || type === 'goal') {
148 if (handler !== null) {
149 ++players[handler].num_throws;
154 if (type === 'goal') {
155 if (prev_handler !== null) {
156 ++players[prev_handler].hockey_assists;
158 if (handler !== null) {
159 ++players[handler].assists;
162 handler = prev_handler = null;
164 // Update hold history.
165 prev_handler = handler;
166 handler = e['player'];
168 } else if (type === 'throwaway') {
171 handler = prev_handler = null;
172 } else if (type === 'drop') {
174 handler = prev_handler = null;
175 } else if (type === 'defense') {
177 } else if (type === 'interception') {
182 handler = e['player'];
183 } else if (type === 'offensive_soft_plus' || type === 'offensive_soft_minus' || type === 'defensive_soft_plus' || type === 'defensive_soft_minus') {
185 } else if (type !== 'in' && type !== 'out' && type !== 'pull' &&
186 type !== 'their_goal' && type !== 'stoppage' && type !== 'unknown' &&
187 type !== 'set_defense' && type !== 'goal' && type !== 'throwaway' &&
188 type !== 'drop' && type !== 'set_offense' && type !== 'their_goal' &&
189 type !== 'pull' && type !== 'pull_landed' && type !== 'pull_oob' &&
190 type !== 'their_throwaway' && type !== 'defense' && type !== 'interception') {
191 console.log("Unknown event:", e);
196 let chosen_category = get_chosen_category();
197 write_main_menu(chosen_category);
200 if (chosen_category === 'general') {
201 rows = make_table_general(players);
202 } else if (chosen_category === 'offense') {
203 rows = make_table_offense(players);
204 } else if (chosen_category === 'defense') {
205 rows = make_table_defense(players);
207 document.getElementById('stats').replaceChildren(...rows);
210 function get_chosen_category() {
211 if (window.location.hash === '#offense') {
213 } else if (window.location.hash === '#defense') {
220 function write_main_menu(chosen_category) {
222 if (chosen_category === 'general') {
223 elems.push(document.createTextNode('General'));
225 let a = document.createElement('a');
226 a.appendChild(document.createTextNode('General'));
227 a.setAttribute('href', '#general');
231 elems.push(document.createTextNode(' | '));
232 if (chosen_category === 'offense') {
233 elems.push(document.createTextNode('Offense'));
235 let a = document.createElement('a');
236 a.appendChild(document.createTextNode('Offense'));
237 a.setAttribute('href', '#offense');
241 elems.push(document.createTextNode(' | '));
242 if (chosen_category === 'defense') {
243 elems.push(document.createTextNode('Defense'));
245 let a = document.createElement('a');
246 a.appendChild(document.createTextNode('Defense'));
247 a.setAttribute('href', '#defense');
251 document.getElementById('mainmenu').replaceChildren(...elems);
254 function make_table_general(players) {
257 let header = document.createElement('tr');
258 add_cell(header, 'th', 'Player');
259 add_cell(header, 'th', '+/-');
260 add_cell(header, 'th', 'Soft +/-');
261 add_cell(header, 'th', 'Points played');
262 add_cell(header, 'th', 'Time played');
266 for (const [q,p] of Object.entries(players)) {
267 let row = document.createElement('tr');
268 let pm = p.goals + p.assists + p.hockey_assists + p.defenses - p.throwaways - p.drops;
269 let soft_pm = p.offensive_soft_plus + p.defensive_soft_plus - p.offensive_soft_minus - p.defensive_soft_minus;
270 add_cell(row, 'td', p.name); // TODO: number?
271 add_cell(row, 'td', pm > 0 ? ('+' + pm) : pm);
272 add_cell(row, 'td', soft_pm > 0 ? ('+' + soft_pm) : soft_pm);
273 add_cell(row, 'td', p.points_played);
274 add_cell(row, 'td', Math.floor(p.playing_time_ms / 60000) + ' min');
276 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);
281 function make_table_offense(players) {
284 let header = document.createElement('tr');
285 add_cell(header, 'th', 'Player');
286 add_cell(header, 'th', 'Goals');
287 add_cell(header, 'th', 'Assists');
288 add_cell(header, 'th', 'Hockey assists');
289 add_cell(header, 'th', 'Throws');
290 add_cell(header, 'th', 'Throwaways');
291 add_cell(header, 'th', '%OK');
292 add_cell(header, 'th', 'Catches');
293 add_cell(header, 'th', 'Drops');
294 add_cell(header, 'th', '%OK');
295 add_cell(header, 'th', 'Soft +/-');
299 for (const [q,p] of Object.entries(players)) {
300 let throw_ok = 100 * (1 - p.throwaways / p.num_throws);
301 let catch_ok = 100 * (p.catches / (p.catches + p.drops));
303 let row = document.createElement('tr');
304 add_cell(row, 'td', p.name); // TODO: number?
305 add_cell(row, 'td', p.goals);
306 add_cell(row, 'td', p.assists);
307 add_cell(row, 'td', p.hockey_assists);
308 add_cell(row, 'td', p.num_throws);
309 add_cell(row, 'td', p.throwaways);
310 add_cell(row, 'td', throw_ok.toFixed(0) + '%');
311 add_cell(row, 'td', p.catches);
312 add_cell(row, 'td', p.drops);
313 add_cell(row, 'td', catch_ok.toFixed(0) + '%');
314 add_cell(row, 'td', '+' + p.offensive_soft_plus);
315 add_cell(row, 'td', '-' + p.offensive_soft_minus);
321 function make_table_defense(players) {
324 let header = document.createElement('tr');
325 add_cell(header, 'th', 'Player');
326 add_cell(header, 'th', 'Ds');
327 add_cell(header, 'th', 'Pulls');
328 add_cell(header, 'th', 'OOB pulls');
329 add_cell(header, 'th', 'Avg. hang time (IB)');
330 add_cell(header, 'th', 'Soft +/-');
333 for (const [q,p] of Object.entries(players)) {
335 for (const t of p.pull_times) {
338 let avg_time = 1e-3 * sum_time / p.pulls;
339 let oob_pct = 100 * p.oob_pulls / p.pulls;
341 let row = document.createElement('tr');
342 add_cell(row, 'td', p.name); // TODO: number?
343 add_cell(row, 'td', p.defenses);
344 add_cell(row, 'td', p.pulls);
346 add_cell(row, 'td', 'N/A');
348 add_cell(row, 'td', p.oob_pulls + ' (' + oob_pct.toFixed(0) + '%)');
350 if (p.pulls > p.oob_pulls) {
351 add_cell(row, 'td', avg_time.toFixed(1) + ' sec');
353 add_cell(row, 'td', 'N/A');
355 add_cell(row, 'td', '+' + p.defensive_soft_plus);
356 add_cell(row, 'td', '-' + p.defensive_soft_minus);