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