medium sized dollop
[carveJwlIkooP6JGAAIwe30JlM.git] / highscores.h
1 #ifndef HIGHSCORES_H
2 #define HIGHSCORES_H
3
4 #include "vg/vg_store.h"
5 #include "vg/vg_stdint.h"
6 #include "world_info.h"
7
8 /*
9 * Designed to be used across client and server,
10 * for the client its only storing the local users records, for server its
11 * storing many.
12 */
13
14 typedef struct highscore highscore;
15 typedef struct highscore_record highscore_record;
16 typedef struct highscore_track_table highscore_track_table;
17 typedef struct highscore_database highscore_database;
18 typedef struct highscore_playerinfo highscore_playerinfo;
19
20 #pragma pack(push,1)
21
22 struct highscore_playerinfo
23 {
24 char nickname[10];
25 u64 playerid;
26
27 union
28 {
29 aatree_pool_node aapn;
30 aatree_node aa_playerid;
31 };
32 };
33
34 struct highscore_record
35 {
36 u16 trackid, points, time, reserved0;
37 u64 playerid;
38 u32 datetime;
39 u32 reserved1;
40
41 union
42 {
43 struct
44 {
45 aatree_node points,
46 time,
47 playerid,
48 datetime;
49 }
50 aa;
51
52 aatree_pool_node pool;
53 };
54 };
55
56 struct highscore_track_table
57 {
58 aatree_ptr root_points,
59 root_time,
60 root_playerid,
61 root_datetime;
62
63 u32 reserved[12];
64 };
65
66 struct highscore_database
67 {
68 highscore_track_table tracks[ 128 ];
69
70 aatree_ptr pool_head, playerinfo_head;
71 u32 entry_capacity,
72 playerinfo_capacity, playerinfo_root;
73
74 u32 reserved[59];
75 };
76
77 #pragma pack(pop)
78
79 static struct highscore_system
80 {
81 highscore_database dbheader;
82 aatree aainfo,
83 aainfo_points,
84 aainfo_time,
85 aainfo_playerid,
86 aainfo_datetime,
87 aainfo_playerinfo_playerid,
88 aainfo_playerinfo;
89
90 void *data, *playerinfo_data;
91 }
92 highscore_system;
93
94 static int highscore_cmp_points( void *a, void *b )
95 {
96 highscore_record *pa = a, *pb = b;
97 return (int)pa->points - (int)pb->points;
98 }
99
100 static int highscore_cmp_datetime( void *a, void *b )
101 {
102 highscore_record *pa = a, *pb = b;
103
104 if( pa->datetime == pb->datetime ) return 0;
105 return pa->datetime < pb->datetime? 1: -1;
106 }
107
108 static int highscore_cmp_time( void *a, void *b )
109 {
110 highscore_record *pa = a, *pb = b;
111 return (int)pb->time - (int)pa->time;
112 }
113
114 static int highscore_cmp_playerid( void *a, void *b )
115 {
116 highscore_record *pa = a, *pb = b;
117 if( pa->playerid == pb->playerid ) return 0;
118 return pa->playerid < pb->playerid? -1: 1;
119 }
120
121 static int highscore_cmp_playerinfo_playerid( void *a, void *b )
122 {
123 highscore_playerinfo *pa = a, *pb = b;
124 if( pa->playerid == pb->playerid ) return 0;
125 return pa->playerid < pb->playerid? -1: 1;
126 }
127
128 static void *highscore_malloc( u32 count, u32 size )
129 {
130 size_t requested_mem = size * count;
131 void *data = malloc( requested_mem );
132
133 requested_mem /= 1024;
134 requested_mem /= 1024;
135
136 if( !data )
137 {
138 vg_error( "Could not allocated %dmb of memory\n", requested_mem );
139 return NULL;
140 }
141 else
142 vg_success( "Allocated %dmb for %u records\n", requested_mem, count );
143
144 return data;
145 }
146
147 static void highscores_free(void)
148 {
149 free( highscore_system.data );
150 free( highscore_system.playerinfo_data );
151 }
152
153 static int highscores_init( u32 pool_size, u32 playerinfo_pool_size )
154 {
155 struct highscore_system *sys = &highscore_system;
156
157 sys->data = highscore_malloc( pool_size, sizeof(highscore_record) );
158 if( !sys->data ) return 0;
159
160 sys->playerinfo_data =
161 highscore_malloc( playerinfo_pool_size, sizeof(highscore_playerinfo));
162 if( !sys->playerinfo_data )
163 {
164 free( sys->data );
165 return 0;
166 }
167
168 /* This is ugly.. too bad! */
169 sys->aainfo.base = highscore_system.data;
170 sys->aainfo.stride = sizeof(highscore_record);
171 sys->aainfo.offset = offsetof(highscore_record,pool);
172 sys->aainfo.p_cmp = NULL;
173
174 sys->aainfo_datetime.base = highscore_system.data;
175 sys->aainfo_datetime.stride = sizeof(highscore_record);
176 sys->aainfo_datetime.offset = offsetof(highscore_record,aa.datetime);
177 sys->aainfo_datetime.p_cmp = highscore_cmp_datetime;
178
179 sys->aainfo_points.base = highscore_system.data;
180 sys->aainfo_points.stride = sizeof(highscore_record);
181 sys->aainfo_points.offset = offsetof(highscore_record,aa.points);
182 sys->aainfo_points.p_cmp = highscore_cmp_points;
183
184 sys->aainfo_time.base = highscore_system.data;
185 sys->aainfo_time.stride = sizeof(highscore_record);
186 sys->aainfo_time.offset = offsetof(highscore_record,aa.time);
187 sys->aainfo_time.p_cmp = highscore_cmp_time;
188
189 sys->aainfo_playerid.base = highscore_system.data;
190 sys->aainfo_playerid.stride = sizeof(highscore_record);
191 sys->aainfo_playerid.offset = offsetof(highscore_record,aa.playerid);
192 sys->aainfo_playerid.p_cmp = highscore_cmp_playerid;
193
194 sys->aainfo_playerinfo_playerid.base = highscore_system.playerinfo_data;
195 sys->aainfo_playerinfo_playerid.stride = sizeof(highscore_playerinfo);
196 sys->aainfo_playerinfo_playerid.offset =
197 offsetof(highscore_playerinfo,aa_playerid);
198 sys->aainfo_playerinfo_playerid.p_cmp = highscore_cmp_playerinfo_playerid;
199
200 sys->aainfo_playerinfo.base = highscore_system.playerinfo_data;
201 sys->aainfo_playerinfo.stride = sizeof(highscore_playerinfo);
202 sys->aainfo_playerinfo.offset = offsetof(highscore_playerinfo,aapn);
203 sys->aainfo_playerinfo.p_cmp = NULL;
204
205 FILE *fp = fopen( ".aadb", "rb" );
206 if( fp )
207 {
208 vg_info( "Loading existing database\n" );
209
210 u64 count = fread( &sys->dbheader, sizeof(highscore_database), 1, fp );
211
212 if( count != 1 )
213 {
214 vg_error( "Unexpected EOF reading database header\n" );
215
216 highscores_free();
217 return 0;
218 }
219
220 count = fread( sys->data, sizeof(highscore_record), pool_size, fp );
221 if( count != pool_size )
222 {
223 vg_error( "Unexpected EOF reading database contents;"
224 " %lu records of %u were read\n", count, pool_size );
225
226 highscores_free();
227 return 0;
228 }
229
230 count = fread( sys->playerinfo_data, sizeof(highscore_playerinfo),
231 playerinfo_pool_size, fp );
232 if( count != playerinfo_pool_size )
233 {
234 vg_error( "Unexpected EOF reading playerinfo contents;"
235 " %lu records of %u were read\n", count,
236 playerinfo_pool_size );
237
238 highscores_free();
239 return 0;
240 }
241
242 fclose( fp );
243 }
244 else
245 {
246 vg_low( "No existing database found (.aadb)\n" );
247 vg_info( "Initializing database nodes\n" );
248 memset( &sys->dbheader, 0, sizeof(highscore_database) );
249
250 sys->dbheader.pool_head = aatree_init_pool( &sys->aainfo, pool_size );
251 sys->dbheader.entry_capacity = pool_size;
252
253 for( int i=0; i<vg_list_size(sys->dbheader.tracks); i++ )
254 {
255 highscore_track_table *table = &sys->dbheader.tracks[i];
256 table->root_points = AATREE_PTR_NIL;
257 table->root_playerid = AATREE_PTR_NIL;
258 table->root_time = AATREE_PTR_NIL;
259 table->root_datetime = AATREE_PTR_NIL;
260 }
261
262 /* Initialize secondary db */
263 sys->dbheader.playerinfo_head = aatree_init_pool(
264 &sys->aainfo_playerinfo,
265 playerinfo_pool_size );
266 sys->dbheader.playerinfo_capacity = playerinfo_pool_size;
267 sys->dbheader.playerinfo_root = AATREE_PTR_NIL;
268 }
269
270 return 1;
271 }
272
273 static int highscores_serialize_all(void)
274 {
275 struct highscore_system *sys = &highscore_system;
276 vg_info( "Serializing database\n" );
277
278 FILE *fp = fopen( ".aadb", "wb" );
279
280 if( !fp )
281 {
282 vg_error( "Could not open .aadb\n" );
283 return 0;
284 }
285
286 fwrite( &sys->dbheader, sizeof(highscore_database), 1, fp );
287 fwrite( sys->data, sizeof(highscore_record),
288 sys->dbheader.entry_capacity, fp );
289 fwrite( sys->playerinfo_data, sizeof(highscore_playerinfo),
290 sys->dbheader.playerinfo_capacity, fp );
291
292 fclose( fp );
293 return 1;
294 }
295
296 static highscore_record *highscore_find_user_record( u64 playerid, u32 trackid )
297 {
298 struct highscore_system *sys = &highscore_system;
299
300 highscore_track_table *table = &sys->dbheader.tracks[trackid];
301 highscore_record temp;
302 temp.playerid = playerid;
303
304 aatree_ptr find =
305 aatree_find( &sys->aainfo_playerid, table->root_playerid, &temp );
306
307 if( find == AATREE_PTR_NIL )
308 return NULL;
309
310 return aatree_get_data( &sys->aainfo_playerid, find );
311 }
312
313 static aatree_ptr highscores_push_record( highscore_record *record )
314 {
315 struct highscore_system *sys = &highscore_system;
316
317 /* TODO: Verify steam ID */
318 vg_low( "Inserting record into database for track %hu\n",record->trackid );
319
320 if( record->trackid >= vg_list_size(sys->dbheader.tracks) )
321 {
322 vg_error( "TrackID out of range (%hu>=%d)\n", record->trackid,
323 vg_list_size(sys->dbheader.tracks) );
324
325 return AATREE_PTR_NIL;
326 }
327
328 /* Search for existing record on this track */
329 highscore_track_table *table = &sys->dbheader.tracks[record->trackid];
330 aatree_ptr existing = aatree_find( &sys->aainfo_playerid,
331 table->root_playerid,
332 record );
333
334 if( existing != AATREE_PTR_NIL )
335 {
336 highscore_record *crecord = aatree_get_data( &sys->aainfo_playerid,
337 existing );
338
339 if( crecord->time < record->time ||
340 (crecord->time == record->time && crecord->points > record->points))
341 {
342 vg_low( "Not overwriting better score\n" );
343 return existing;
344 }
345
346 vg_low( "Freeing existing record for player %lu\n", record->playerid );
347 table->root_playerid = aatree_del( &sys->aainfo_playerid, existing );
348 table->root_datetime = aatree_del( &sys->aainfo_datetime, existing );
349 table->root_points = aatree_del( &sys->aainfo_points, existing );
350 table->root_time = aatree_del( &sys->aainfo_time, existing );
351
352 aatree_pool_free( &sys->aainfo, existing, &sys->dbheader.pool_head );
353 }
354
355 aatree_ptr index =
356 aatree_pool_alloc( &sys->aainfo, &sys->dbheader.pool_head );
357
358 if( index == AATREE_PTR_NIL )
359 {
360 vg_error( "Database records are over capacity!\n" );
361 return index;
362 }
363
364 highscore_record *dst = aatree_get_data( &sys->aainfo, index );
365 memset( dst, 0, sizeof(highscore_record) );
366
367 dst->trackid = record->trackid;
368 dst->datetime = record->datetime;
369 dst->playerid = record->playerid;
370 dst->points = record->points;
371 dst->time = record->time;
372
373 table->root_time =
374 aatree_insert( &sys->aainfo_time, table->root_time, index );
375 table->root_datetime =
376 aatree_insert( &sys->aainfo_datetime, table->root_datetime, index );
377 table->root_playerid =
378 aatree_insert( &sys->aainfo_playerid, table->root_playerid, index );
379 table->root_points =
380 aatree_insert( &sys->aainfo_points, table->root_points, index );
381
382 return index;
383 }
384
385 static aatree_ptr highscore_set_user_nickname( u64 steamid, char nick[10] )
386 {
387 char name[11];
388 for( int i=0; i<10; i++ )
389 name[i] = nick[i];
390 name[10] = '\0';
391
392 vg_low( "Updating %lu's nickname -> %s\n", steamid, name );
393
394 struct highscore_system *sys = &highscore_system;
395
396 highscore_playerinfo temp;
397 temp.playerid = steamid;
398
399 aatree_ptr record = aatree_find( &sys->aainfo_playerinfo_playerid,
400 sys->dbheader.playerinfo_root,
401 &temp );
402 highscore_playerinfo *info;
403
404 if( record != AATREE_PTR_NIL )
405 {
406 info = aatree_get_data( &sys->aainfo_playerinfo, record );
407 }
408 else
409 {
410 record = aatree_pool_alloc( &sys->aainfo_playerinfo,
411 &sys->dbheader.playerinfo_head );
412
413 if( record == AATREE_PTR_NIL )
414 {
415 vg_error( "Player info database is over capacity!\n" );
416 return AATREE_PTR_NIL;
417 }
418
419 info = aatree_get_data( &sys->aainfo_playerinfo, record );
420 memset( info, 0, sizeof(highscore_playerinfo) );
421
422 info->playerid = steamid;
423 sys->dbheader.playerinfo_root = aatree_insert(
424 &sys->aainfo_playerinfo_playerid,
425 sys->dbheader.playerinfo_root,
426 record );
427 }
428
429 for( int i=0; i<10; i++ )
430 info->nickname[i] = nick[i];
431
432 return AATREE_PTR_NIL;
433 }
434
435 /* Get the length of a string, bounded by '\0' or len, whichever is first */
436 static int highscore_strlen( const char *str, int len )
437 {
438 int str_length;
439 for( str_length=0; str_length<len; str_length++ )
440 if( !str[str_length] )
441 return str_length;
442
443 return str_length;
444 }
445
446 /* Print the string(max length:len) centered into buf (has width:width) */
447 static void highscore_strc( char *buf, const char *str, int len, int width )
448 {
449 int str_length = highscore_strlen( str, len ),
450 offs = (width-str_length)/2;
451
452 for( int i=0; i<str_length; i++ )
453 {
454 int j=i+offs;
455
456 if( j >= width )
457 return;
458
459 buf[j] = str[i];
460 }
461 }
462
463 /* Print the string(max length:len) left aligned into buf */
464 static void highscore_strl( char *buf, const char *str, int len )
465 {
466 for( int i=0; i<len; i++ )
467 {
468 if( !str[i] )
469 return;
470
471 buf[i] = str[i];
472 }
473 }
474
475 /* Print the string (max length:len) right aligned into buf (has width:width) */
476 static void highscore_strr( char *buf, const char *str, int len, int width )
477 {
478 int str_length = highscore_strlen( str, len );
479
480 for( int i=0; i<len; i++ )
481 {
482 if( !str[i] )
483 return;
484
485 buf[width-str_length+i] = str[i];
486 }
487 }
488
489 /* Print integer (padded with: alt), right aligned into buf(width: len) */
490 static void highscore_intr( char *buf, int value, int len, char alt )
491 {
492 int i=0;
493 while(value)
494 {
495 if( i>=len )
496 return;
497
498 buf[ len-1 - (i ++) ] = '0' + (value % 10);
499 value /= 10;
500 }
501
502 for( ;i<len; i ++ )
503 buf[ len-1 - i ] = alt;
504 }
505
506 /* Print integer into buffer with max length len */
507 static void highscore_intl( char *buf, int value, int len )
508 {
509 char temp[32];
510
511 int i=0;
512 while(value)
513 {
514 if( i>=len )
515 break;
516
517 temp[ i ++ ] = '0' + (value % 10);
518 value /= 10;
519 }
520
521 if( i>len )
522 i = len;
523
524 for( int j=0; j<i; j ++ )
525 {
526 buf[j] = temp[ i-1-j ];
527 }
528 }
529
530 /* Clear buffer with length using clr character */
531 static void highscore_clear( char *buf, char clr, int length )
532 {
533 for( int i=0; i<length; i++ )
534 buf[i] = clr;
535 }
536
537 /*
538 Megapark Green
539 --------------------------
540 #| Player | Time | Pts
541 1|aaaabbbbcc 5:23.32 30000
542 2| jef 0:20.34 10000
543 3|aaabbbcccl 2:30.45 20000
544 4|
545 5|
546 6|
547 7|
548 8|
549 9|
550 10|
551 */
552
553 /* Generate a highscores board in text form, the width is always 27. Buffer
554 * must be (count+3)*27 in size. */
555 static void highscores_board_generate( char *buf, u32 id, u32 count )
556 {
557 int w=27;
558 highscore_clear( buf, ' ', (count+3)*w );
559
560 struct track_info *inf = &track_infos[id];
561 struct highscore_system *sys = &highscore_system;
562
563 highscore_track_table *table = &sys->dbheader.tracks[ id ];
564 aatree_ptr it = aatree_kth( &sys->aainfo_time, table->root_time, 0 );
565
566 highscore_strc ( buf+w*0, inf->name, w,w );
567 highscore_clear( buf+w*1, '-', w );
568 highscore_strl ( buf+w*2, " #| Player | Time | Pts", 27 );
569
570 for( int i=0; i<count; i++ )
571 {
572 char *line = buf+w*(3+i);
573 highscore_intr( line, i+1, 2, ' ' );
574 line[2] = '|';
575
576 if( it == AATREE_PTR_NIL )
577 continue;
578
579 highscore_record *record = aatree_get_data( &sys->aainfo_time, it );
580 highscore_playerinfo temp;
581 temp.playerid = record->playerid;
582
583 aatree_ptr info_ptr = aatree_find( &sys->aainfo_playerinfo_playerid,
584 sys->dbheader.playerinfo_root,
585 &temp );
586
587 /* Player name */
588 if( info_ptr == AATREE_PTR_NIL )
589 highscore_strl( line+3, "unknown", 10 );
590 else
591 {
592 highscore_playerinfo *inf = aatree_get_data(
593 &sys->aainfo_playerinfo_playerid, info_ptr );
594
595 highscore_strl( line+3, inf->nickname, 10 );
596 }
597
598 u16 centiseconds = record->time,
599 seconds = centiseconds / 100,
600 minutes = seconds / 60;
601
602 centiseconds %= 100;
603 seconds %= 60;
604 minutes %= 60;
605
606 if( minutes > 9 ) minutes = 9;
607
608 /* Timer */
609 highscore_intr( line+14, minutes, 1, '0' );
610 line[15] = ':';
611 highscore_intr( line+16, seconds, 2, '0' );
612 line[18] = '.';
613 highscore_intr( line+19, centiseconds, 2, '0' );
614
615 /* Score */
616 highscore_intl( line+22, record->points, 5 );
617 it = aatree_next( &sys->aainfo_time, it );
618 }
619 }
620
621 /* Print string out to file using newlines. Count is number of records
622 * ( this requires a buffer of (count+3)*27 size */
623 static void highscores_board_printf( FILE *fp, const char *buf, u32 count )
624 {
625 int w=27;
626
627 for( int i=0; i<count+3; i++ )
628 fprintf( fp, "%.27s\n", &buf[i*w] );
629 }
630
631 #endif /* HIGHSCORES_H */