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']];
89 // Point count management
90 if (p !== undefined && type !== 'out' && p.last_point_seen !== point_num) {
91 p.last_point_seen = point_num;
94 if (type === 'goal' || type === 'their_goal') {
99 if (type === 'in' && p.on_field_since === null) {
100 p.on_field_since = t;
101 } else if (type === 'out') {
102 take_off_field(p, t, live_since);
105 // Liveness management
106 if (type === 'pull' || type === 'their_pull' || type === 'restart') {
108 } else if (type === 'goal' || type === 'their_goal' || type === 'stoppage') {
109 for (const [q,p] of Object.entries(players)) {
110 if (p.on_field_since !== null) {
111 attribute_player_time(p, t, live_since);
118 if (type === 'pull') {
119 puller = e['player'];
122 } else if (type === 'in' || type === 'out' || type === 'stoppage' || type === 'restart' || type === 'unknown' || type === 'set_defense' || type === 'set_offense') {
123 // No effect on pull.
124 } else if (type === 'pull_landed' && puller !== null) {
125 players[puller].pull_times.push(t - pull_started);
126 } else if (type === 'pull_oob' && puller !== null) {
127 ++players[puller].oob_pulls;
129 // Not pulling (if there was one, we never recorded its outcome, but still count it).
130 puller = pull_started = null;
133 // Offense/defense management (TODO: use it for actual counting)
134 if (type === 'set_defense' || type === 'goal' || type === 'throwaway' || type === 'drop') {
136 } else if (type === 'set_offense' || type === 'their_goal' || type === 'their_throwaway' || type === 'defense' || type === 'interception') {
141 if (type === 'catch' || type === 'goal') {
142 if (handler !== null) {
143 ++players[handler].num_throws;
148 if (type === 'goal') {
149 if (prev_handler !== null) {
150 ++players[prev_handler].hockey_assists;
152 if (handler !== null) {
153 ++players[handler].assists;
156 handler = prev_handler = null;
158 // Update hold history.
159 prev_handler = handler;
160 handler = e['player'];
162 } else if (type === 'throwaway') {
164 handler = prev_handler = null;
165 } else if (type === 'drop') {
167 handler = prev_handler = null;
168 } else if (type === 'defense') {
170 } else if (type === 'interception') {
175 handler = e['player'];
176 } else if (type === 'offensive_soft_plus' || type === 'offensive_soft_minus' || type === 'defensive_soft_plus' || type === 'defensive_soft_minus') {
178 } else if (type !== 'in' && type !== 'out' && type !== 'pull' &&
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);
189 let chosen_category = get_chosen_category();
190 write_main_menu(chosen_category);
193 if (chosen_category === 'general') {
194 rows = make_table_general(players);
195 } else if (chosen_category === 'pulls') {
196 console.log("PULL STATS");
197 for (const [q,p] of Object.entries(players)) {
202 for (const t of p.pull_times) {
205 let avg_time = 1e-3 * sum_time / p.pulls;
206 let msg = p.name + ' did ' + p.pulls + ' pull(s), ' + p.oob_pulls + ' OOB';
207 if (p.oob_pulls < p.pulls) {
208 msg += ', avg. hangtime ' + avg_time.toFixed(1) + ' sec for others';
210 console.log(msg, p.pull_times);
213 document.getElementById('stats').replaceChildren(...rows);
216 function get_chosen_category() {
217 if (window.location.hash === '#pulls') {
224 function write_main_menu(chosen_category) {
226 if (chosen_category === 'general') {
227 elems.push(document.createTextNode('General'));
229 let a = document.createElement('a');
230 a.appendChild(document.createTextNode('General'));
231 a.setAttribute('href', '#general');
234 elems.push(document.createTextNode(' | '));
235 if (chosen_category === 'pulls') {
236 elems.push(document.createTextNode('Pulls'));
238 let a = document.createElement('a');
239 a.appendChild(document.createTextNode('Pulls'));
240 a.setAttribute('href', '#pulls');
243 document.getElementById('mainmenu').replaceChildren(...elems);
246 function make_table_general(players) {
249 let header = document.createElement('tr');
250 add_cell(header, 'th', 'Player');
251 add_cell(header, 'th', '+/-');
252 add_cell(header, 'th', 'Soft +/-');
253 add_cell(header, 'th', 'Points played');
254 add_cell(header, 'th', 'Time played');
258 for (const [q,p] of Object.entries(players)) {
259 let row = document.createElement('tr');
260 let pm = p.goals + p.assists + p.hockey_assists + p.defenses - p.throwaways - p.drops;
261 let soft_pm = p.offensive_soft_plus + p.defensive_soft_plus - p.offensive_soft_minus - p.defensive_soft_minus;
262 add_cell(row, 'td', p.name); // TODO: number?
263 add_cell(row, 'td', pm > 0 ? ('+' + pm) : pm);
264 add_cell(row, 'td', soft_pm > 0 ? ('+' + soft_pm) : soft_pm);
265 add_cell(row, 'td', p.points_played);
266 add_cell(row, 'td', Math.floor(p.playing_time_ms / 60000) + ' min');
268 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);