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