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