]> git.sesse.net Git - pkanalytics/blob - events.cpp
Fix various issues with setting the special “none/unknown” formation.
[pkanalytics] / events.cpp
1 #include <algorithm>
2 #include <string>
3 #include <map>
4 #include <vector>
5 #include <optional>
6 #include <sqlite3.h>
7 #include "events.h"
8
9 using namespace std;
10
11 string format_timestamp(uint64_t pos);
12
13 EventsModel::EventsModel(sqlite3 *db, int match_id) : db(db), match_id(match_id)
14 {
15         load_data();
16 }
17
18 QVariant EventsModel::headerData(int section, Qt::Orientation orientation, int role) const
19 {
20         if (role != Qt::DisplayRole) {
21                 return QVariant();
22         }
23         if (orientation == Qt::Horizontal) {
24                 if (section == 0) {
25                         return "Time";
26                 } else if (section == 1) {
27                         return "Who/what";
28                 } else {
29                         return "Type";
30                 }
31         } else {
32                 return "";
33         }
34 }
35
36 QVariant EventsModel::data(const QModelIndex &index, int role) const
37 {
38         if (role != Qt::DisplayRole) {
39                 return QVariant();
40         }
41         const Event &e = events[index.row()];
42         if (index.column() == 0) {
43                 return QString::fromUtf8(format_timestamp(e.t));
44         } else if (index.column() == 1) {
45                 optional<int> player_id = e.player_id;
46                 optional<int> formation_id = e.formation_id;
47                 if (player_id) {
48                         auto p_it = players.find(*player_id);
49                         const Player &p = p_it->second;
50                         return QString::fromUtf8(p.name + " (" + p.number + ")");
51                 } else if (formation_id) {
52                         auto f_it = formations.find(*formation_id);
53                         const Formation &f = f_it->second;
54                         return QString::fromUtf8(f.name);
55                 } else if (e.type == "formation_offense" || e.type == "formation_defense") {
56                         return "(None/unknown)";
57                 } else {
58                         return QVariant();
59                 }
60         } else if (index.column() == 2) {
61                 return QString::fromUtf8(events[index.row()].type);
62         }
63         return QVariant();
64 }
65
66 void EventsModel::load_data()
67 {
68         players.clear();
69         events.clear();
70
71         // Read the players. (The ordering is used to build the order map.)
72         sqlite3_stmt *stmt;
73         int ret = sqlite3_prepare_v2(db, "SELECT player, number, name FROM player ORDER BY gender, (number+0), number", -1, &stmt, 0);
74         if (ret != SQLITE_OK) {
75                 fprintf(stderr, "SELECT prepare: %s\n", sqlite3_errmsg(db));
76                 abort();
77         }
78         int order = 0;
79         for ( ;; ) {
80                 ret = sqlite3_step(stmt);
81                 if (ret == SQLITE_ROW) {
82                         Player p;
83                         p.player_id = sqlite3_column_int(stmt, 0);
84                         p.number = (const char *)sqlite3_column_text(stmt, 1);
85                         p.name = (const char *) sqlite3_column_text(stmt, 2);
86                         players[p.player_id] = std::move(p);
87                         player_ordering[p.player_id] = order++;
88                 } else if (ret == SQLITE_DONE) {
89                         break;
90                 } else {
91                         fprintf(stderr, "SELECT step: %s\n", sqlite3_errmsg(db));
92                         abort();
93                 }
94         }
95         ret = sqlite3_finalize(stmt);
96         if (ret != SQLITE_OK) {
97                 fprintf(stderr, "SELECT finalize: %s\n", sqlite3_errmsg(db));
98                 abort();
99         }
100
101         // Read the formations.
102         ret = sqlite3_prepare_v2(db, "SELECT formation, name FROM formation", -1, &stmt, 0);
103         if (ret != SQLITE_OK) {
104                 fprintf(stderr, "SELECT prepare: %s\n", sqlite3_errmsg(db));
105                 abort();
106         }
107         for ( ;; ) {
108                 ret = sqlite3_step(stmt);
109                 if (ret == SQLITE_ROW) {
110                         Formation f;
111                         f.formation_id = sqlite3_column_int(stmt, 0);
112                         f.name = (const char *) sqlite3_column_text(stmt, 1);
113                         formations[f.formation_id] = std::move(f);
114                 } else if (ret == SQLITE_DONE) {
115                         break;
116                 } else {
117                         fprintf(stderr, "SELECT step: %s\n", sqlite3_errmsg(db));
118                         abort();
119                 }
120         }
121         ret = sqlite3_finalize(stmt);
122         if (ret != SQLITE_OK) {
123                 fprintf(stderr, "SELECT finalize: %s\n", sqlite3_errmsg(db));
124                 abort();
125         }
126
127         // Read the events.
128         ret = sqlite3_prepare_v2(db, "SELECT event, t, player, formation, type FROM event WHERE match=? ORDER BY t", -1, &stmt, 0);
129         if (ret != SQLITE_OK) {
130                 fprintf(stderr, "SELECT prepare: %s\n", sqlite3_errmsg(db));
131                 abort();
132         }
133         sqlite3_bind_int64(stmt, 1, match_id);
134         for ( ;; ) {
135                 ret = sqlite3_step(stmt);
136                 if (ret == SQLITE_ROW) {
137                         Event e;
138                         e.event_id = sqlite3_column_int(stmt, 0);
139                         e.t = sqlite3_column_int(stmt, 1);
140                         if (sqlite3_column_type(stmt, 2) == SQLITE_INTEGER) {  // Non-NULL.
141                                 e.player_id = sqlite3_column_int(stmt, 2);
142                         }
143                         if (sqlite3_column_type(stmt, 3) == SQLITE_INTEGER) {  // Non-NULL.
144                                 e.formation_id = sqlite3_column_int(stmt, 3);
145                         }
146                         e.type = (const char *)sqlite3_column_text(stmt, 4);
147                         events.push_back(std::move(e));
148                 } else if (ret == SQLITE_DONE) {
149                         break;
150                 } else {
151                         fprintf(stderr, "SELECT step: %s\n", sqlite3_errmsg(db));
152                         abort();
153                 }
154         }
155         ret = sqlite3_finalize(stmt);
156         if (ret != SQLITE_OK) {
157                 fprintf(stderr, "SELECT finalize: %s\n", sqlite3_errmsg(db));
158                 abort();
159         }
160 }
161
162 unsigned EventsModel::insert_event(uint64_t t, optional<int> player_id, optional<int> formation_id, const string &type)
163 {
164         auto it = lower_bound(events.begin(), events.end(), t,
165                 [](const Event &e, uint64_t t) { return e.t < t; });
166         unsigned pos = distance(events.begin(), it);
167         beginInsertRows(QModelIndex(), pos, pos);
168
169         Event e;
170         e.t = t;
171         e.player_id = player_id;
172         e.formation_id = formation_id;
173         e.type = type;
174         events.insert(events.begin() + pos, e);
175
176         endInsertRows();
177
178         // Insert the new row into the database.
179         sqlite3_stmt *stmt;
180         int ret = sqlite3_prepare_v2(db, "INSERT INTO event (match, t, player, formation, type) VALUES (?, ?, ?, ?, ?)", -1, &stmt, 0);
181         if (ret != SQLITE_OK) {
182                 fprintf(stderr, "INSERT prepare: %s\n", sqlite3_errmsg(db));
183                 abort();
184         }
185
186         sqlite3_bind_int64(stmt, 1, match_id);
187         sqlite3_bind_int64(stmt, 2, t);
188         if (player_id) {
189                 sqlite3_bind_int64(stmt, 3, *player_id);
190         } else {
191                 sqlite3_bind_null(stmt, 3);
192         }
193         if (formation_id) {
194                 sqlite3_bind_int64(stmt, 4, *formation_id);
195         } else {
196                 sqlite3_bind_null(stmt, 4);
197         }
198         sqlite3_bind_text(stmt, 5, type.data(), type.size(), SQLITE_STATIC);
199
200         ret = sqlite3_step(stmt);
201         if (ret == SQLITE_ROW) {
202                 fprintf(stderr, "INSERT step: %s\n", sqlite3_errmsg(db));
203                 abort();
204         }
205
206         ret = sqlite3_finalize(stmt);
207         if (ret != SQLITE_OK) {
208                 fprintf(stderr, "INSERT finalize: %s\n", sqlite3_errmsg(db));
209                 abort();
210         }
211
212         events[pos].event_id = sqlite3_last_insert_rowid(db);
213         return pos;
214 }
215
216 void EventsModel::delete_event(unsigned pos)
217 {
218         int event_id = events[pos].event_id;
219
220         beginRemoveRows(QModelIndex(), pos, pos);
221         events.erase(events.begin() + pos);
222         endRemoveRows();
223
224         // Delete the row from the database.
225         sqlite3_stmt *stmt;
226         int ret = sqlite3_prepare_v2(db, "DELETE FROM event WHERE event=?", -1, &stmt, 0);
227         if (ret != SQLITE_OK) {
228                 fprintf(stderr, "DELETE prepare: %s\n", sqlite3_errmsg(db));
229                 abort();
230         }
231
232         sqlite3_bind_int64(stmt, 1, event_id);
233
234         ret = sqlite3_step(stmt);
235         if (ret == SQLITE_ROW) {
236                 fprintf(stderr, "DELETE step: %s\n", sqlite3_errmsg(db));
237                 abort();
238         }
239
240         ret = sqlite3_finalize(stmt);
241         if (ret != SQLITE_OK) {
242                 fprintf(stderr, "DELETE finalize: %s\n", sqlite3_errmsg(db));
243                 abort();
244         }
245 }
246
247 void EventsModel::set_event_type(unsigned pos, const string &type)
248 {
249         events[pos].type = type;
250         emit dataChanged(createIndex(pos, 0), createIndex(pos, 2));
251
252         sqlite3_stmt *stmt;
253         int ret = sqlite3_prepare_v2(db, "UPDATE event SET type=? WHERE event=?", -1, &stmt, 0);
254         if (ret != SQLITE_OK) {
255                 fprintf(stderr, "INSERT prepare: %s\n", sqlite3_errmsg(db));
256                 abort();
257         }
258
259         sqlite3_bind_text(stmt, 1, type.data(), type.size(), SQLITE_STATIC);
260         sqlite3_bind_int64(stmt, 2, events[pos].event_id);
261
262         ret = sqlite3_step(stmt);
263         if (ret == SQLITE_ROW) {
264                 fprintf(stderr, "UPDATE step: %s\n", sqlite3_errmsg(db));
265                 abort();
266         }
267
268         ret = sqlite3_finalize(stmt);
269         if (ret != SQLITE_OK) {
270                 fprintf(stderr, "UPDATE finalize: %s\n", sqlite3_errmsg(db));
271                 abort();
272         }
273 }
274
275 unsigned EventsModel::get_last_event_pos(uint64_t t) const
276 {
277         // upper_bound() gives first where e.t > t,
278         // and the one before that is the one we want.
279         auto it = upper_bound(events.begin(), events.end(), t,
280                 [](uint64_t t, const Event &e) { return t < e.t; });
281         if (it == events.begin()) {
282                 return 0;
283         } else {
284                 return distance(events.begin(), it - 1);
285         }
286 }
287
288 EventsModel::Status EventsModel::get_status_at(uint64_t t)
289 {
290         Status s;
291         s.our_score = 0;
292         s.their_score = 0;
293         s.attack_state = Status::NOT_STARTED;
294         s.stoppage = false;
295         s.pull_state = Status::SHOULD_PULL;
296         uint64_t last_gained_possession = 0;
297         uint64_t last_stoppage = 0;
298         uint64_t time_spent_in_stoppage = 0;
299         unsigned num_touches = 0;
300
301         auto set_offense = [&s] { s.attack_state = Status::OFFENSE; };
302         auto set_defense = [&s] { s.attack_state = Status::DEFENSE; };
303
304         for (const Event &e : events) {
305                 if (e.t > t) {
306                         break;
307                 }
308
309                 if (e.type == "goal" || e.type == "their_goal") {
310                         s.pull_state = Status::SHOULD_PULL;
311                 } else if (e.type == "in" || e.type == "out" || e.type == "stoppage" || e.type == "restart" || e.type == "unknown" || e.type == "set_defense" || e.type == "set_offense") {
312                         // No effect on pull status.
313                 } else if (e.type == "pull") {
314                         s.pull_state = Status::PULL_IN_AIR;
315                 } else {
316                         s.pull_state = Status::NOT_PULLING;  // Includes pull_landed and pull_oob.
317                 }
318
319                 if (e.type == "set_offense") {
320                         set_offense();
321                 } else if (e.type == "set_defense") {
322                         set_defense();
323                 }
324
325                 if (e.type == "goal") {
326                         ++s.our_score;
327                         set_defense();
328                         num_touches = 0;
329                 }
330                 if (e.type == "their_goal") {
331                         ++s.their_score;
332                         set_offense();
333                         num_touches = 0;
334                 }
335                 if (e.type == "catch") {
336                         if (num_touches == 0) {  // Pick up.
337                                 last_gained_possession = e.t;
338                                 time_spent_in_stoppage = 0;
339                         }
340                         ++num_touches;
341                 }
342                 if (e.type == "interception") {
343                         num_touches = 1;
344                         set_offense();
345                         last_gained_possession = e.t;
346                         time_spent_in_stoppage = 0;
347                 }
348                 if (e.type == "defense" || e.type == "their_throwaway") {
349                         set_offense();
350                         num_touches = 0;
351                         time_spent_in_stoppage = 0;
352                 }
353                 if (e.type == "drop" || e.type == "throwaway") {
354                         set_defense();
355                         num_touches = 0;
356                 }
357                 if (e.type == "stoppage") {
358                         s.stoppage = true;
359                         last_stoppage = e.t;
360                 }
361                 if (e.type == "restart") {
362                         s.stoppage = false;
363                         if (last_stoppage != 0) {
364                                 time_spent_in_stoppage += (e.t - last_stoppage);
365                                 last_stoppage = 0;
366                         }
367                 }
368         }
369         if (s.stoppage && last_stoppage != 0) {
370                 time_spent_in_stoppage += (t - last_stoppage);
371         }
372
373         s.num_passes = (num_touches == 0) ? 0 : num_touches - 1;
374         s.possession_sec = (s.attack_state == Status::OFFENSE && last_gained_possession != 0 && num_touches != 0) ? (t - last_gained_possession - time_spent_in_stoppage) / 1000 : 0;
375         s.stoppage_sec = (s.attack_state == Status::OFFENSE && last_gained_possession != 0 && num_touches != 0) ? time_spent_in_stoppage / 1000 : 0;
376         return s;
377 }
378
379 set<int> EventsModel::get_team_at(uint64_t t)
380 {
381         set<int> team;
382         for (const Event &e : events) {
383                 if (e.t > t) {
384                         break;
385                 }
386                 if (e.type == "in") {
387                         team.insert(*e.player_id);
388                 }
389                 if (e.type == "out") {
390                         team.erase(*e.player_id);
391                 }
392         }
393         return team;
394 }
395
396 void EventsModel::set_team_at(uint64_t t, const set<int> &new_team)
397 {
398         // Backdate to the last goal or stoppage, _or_ the last time someone
399         // going out is mentioned. (We don't really track injuries yet;
400         // do we want an explicit injury type? If we had one, it would probably
401         // be the simplest.)
402         uint64_t backdate_point = 0;
403         for (const Event &e : events) {
404                 if (e.t > t) {
405                         break;
406                 }
407                 if (e.type == "goal" || e.type == "their_goal" || e.type == "stoppage" || e.type == "reset" || e.type == "set_offense" || e.type == "set_defense") {
408                         backdate_point = e.t + 1;
409                 }
410                 if (e.player_id.has_value() && !new_team.count(*e.player_id)) {
411                         backdate_point = e.t + 1;
412                 }
413         }
414
415         // Delete all in/outs already at the backdate point.
416         for (unsigned i = 0; i < events.size(); ) {
417                 if (events[i].t > backdate_point) {
418                         break;
419                 }
420                 if (events[i].t == backdate_point && (events[i].type == "in" || events[i].type == "out")) {
421                         delete_event(i);
422                 } else {
423                         ++i;
424                 }
425         }
426
427         // Finally make the subs we need.
428         set<int> old_team = get_team_at(backdate_point);
429         for (int player_id : old_team) {
430                 if (!new_team.count(player_id)) {
431                         insert_event(backdate_point, player_id, nullopt, "out");
432                 }
433         }
434         for (int player_id : new_team) {
435                 if (!old_team.count(player_id)) {
436                         insert_event(backdate_point, player_id, nullopt, "in");
437                 }
438         }
439 }
440
441 void EventsModel::set_formation_at(uint64_t t, bool offense, unsigned formation)
442 {
443         // If there's another goal/stoppage no more than 20 seconds ago,
444         // we assume that the formation started at that point (it just took
445         // the operator a bit of time to see it). If not, we assume we
446         // changed in the middle of a point.
447         uint64_t backdate_point = 0;
448         for (const Event &e : events) {
449                 if (e.t > t) {
450                         break;
451                 }
452                 if (e.type == "goal" || e.type == "their_goal" || e.type == "stoppage" || e.type == "reset" || e.type == "set_offense" || e.type == "set_defense" || e.type == "in" || e.type == "out" || e.type == "pull" || e.type == "their_pull") {
453                         backdate_point = e.t + 1;
454                 }
455                 if (e.type == "formation_offense" || e.type == "formation_defense") {
456                         backdate_point = 0;
457                 }
458         }
459         if (backdate_point != 0 && t - backdate_point < 20000) {
460                 t = backdate_point;
461         }
462         if (offense) {
463                 insert_event(t, nullopt, formation == 0 ? nullopt : optional{formation}, "formation_offense");
464         } else {
465                 insert_event(t, nullopt, formation == 0 ? nullopt : optional{formation}, "formation_defense");
466         }
467 }
468
469 vector<int> EventsModel::sort_team(const set<int> &team) const
470 {
471         vector<int> ret(team.begin(), team.end());
472         std::sort(ret.begin(), ret.end(), [this](int a, int b) {
473                 return player_ordering.find(a)->second < player_ordering.find(b)->second;
474         });
475         return ret;
476 }