dont remember
[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 = vg_alloc( 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 vg_free( highscore_system.data );
150 vg_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 vg_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 vg_low( "Inserting record into database for track %hu\n",record->trackid );
318
319 if( record->trackid >= vg_list_size(sys->dbheader.tracks) )
320 {
321 vg_error( "TrackID out of range (%hu>=%d)\n", record->trackid,
322 vg_list_size(sys->dbheader.tracks) );
323
324 return AATREE_PTR_NIL;
325 }
326
327 /* Search for existing record on this track */
328 highscore_track_table *table = &sys->dbheader.tracks[record->trackid];
329 aatree_ptr existing = aatree_find( &sys->aainfo_playerid,
330 table->root_playerid,
331 record );
332
333 if( existing != AATREE_PTR_NIL )
334 {
335 highscore_record *crecord = aatree_get_data( &sys->aainfo_playerid,
336 existing );
337
338 if( crecord->time < record->time ||
339 (crecord->time == record->time && crecord->points > record->points))
340 {
341 vg_low( "Not overwriting better score\n" );
342 return existing;
343 }
344
345 vg_low( "Freeing existing record for player %lu\n", record->playerid );
346 table->root_playerid = aatree_del( &sys->aainfo_playerid, existing );
347 table->root_datetime = aatree_del( &sys->aainfo_datetime, existing );
348 table->root_points = aatree_del( &sys->aainfo_points, existing );
349 table->root_time = aatree_del( &sys->aainfo_time, existing );
350
351 aatree_pool_free( &sys->aainfo, existing, &sys->dbheader.pool_head );
352 }
353
354 aatree_ptr index =
355 aatree_pool_alloc( &sys->aainfo, &sys->dbheader.pool_head );
356
357 if( index == AATREE_PTR_NIL )
358 {
359 vg_error( "Database records are over capacity!\n" );
360 return index;
361 }
362
363 highscore_record *dst = aatree_get_data( &sys->aainfo, index );
364 memset( dst, 0, sizeof(highscore_record) );
365
366 dst->trackid = record->trackid;
367 dst->datetime = record->datetime;
368 dst->playerid = record->playerid;
369 dst->points = record->points;
370 dst->time = record->time;
371
372 table->root_time =
373 aatree_insert( &sys->aainfo_time, table->root_time, index );
374 table->root_datetime =
375 aatree_insert( &sys->aainfo_datetime, table->root_datetime, index );
376 table->root_playerid =
377 aatree_insert( &sys->aainfo_playerid, table->root_playerid, index );
378 table->root_points =
379 aatree_insert( &sys->aainfo_points, table->root_points, index );
380
381 return index;
382 }
383
384 static aatree_ptr highscore_set_user_nickname( u64 steamid, char nick[10] )
385 {
386 char name[11];
387 for( int i=0; i<10; i++ )
388 name[i] = nick[i];
389 name[10] = '\0';
390
391 vg_low( "Updating %lu's nickname -> %s\n", steamid, name );
392
393 struct highscore_system *sys = &highscore_system;
394
395 highscore_playerinfo temp;
396 temp.playerid = steamid;
397
398 aatree_ptr record = aatree_find( &sys->aainfo_playerinfo_playerid,
399 sys->dbheader.playerinfo_root,
400 &temp );
401 highscore_playerinfo *info;
402
403 if( record != AATREE_PTR_NIL )
404 {
405 info = aatree_get_data( &sys->aainfo_playerinfo, record );
406 }
407 else
408 {
409 record = aatree_pool_alloc( &sys->aainfo_playerinfo,
410 &sys->dbheader.playerinfo_head );
411
412 if( record == AATREE_PTR_NIL )
413 {
414 vg_error( "Player info database is over capacity!\n" );
415 return AATREE_PTR_NIL;
416 }
417
418 info = aatree_get_data( &sys->aainfo_playerinfo, record );
419 memset( info, 0, sizeof(highscore_playerinfo) );
420
421 info->playerid = steamid;
422 sys->dbheader.playerinfo_root = aatree_insert(
423 &sys->aainfo_playerinfo_playerid,
424 sys->dbheader.playerinfo_root,
425 record );
426 }
427
428 for( int i=0; i<10; i++ )
429 info->nickname[i] = nick[i];
430
431 return AATREE_PTR_NIL;
432 }
433
434 /* Get the length of a string, bounded by '\0' or len, whichever is first */
435 static int highscore_strlen( const char *str, int len )
436 {
437 int str_length;
438 for( str_length=0; str_length<len; str_length++ )
439 if( !str[str_length] )
440 return str_length;
441
442 return str_length;
443 }
444
445 /* Print the string(max length:len) centered into buf (has width:width) */
446 static void highscore_strc( char *buf, const char *str, int len, int width )
447 {
448 int str_length = highscore_strlen( str, len ),
449 offs = (width-str_length)/2;
450
451 for( int i=0; i<str_length; i++ )
452 {
453 int j=i+offs;
454
455 if( j >= width )
456 return;
457
458 buf[j] = str[i];
459 }
460 }
461
462 /* Print the string(max length:len) left aligned into buf */
463 static void highscore_strl( char *buf, const char *str, int len )
464 {
465 for( int i=0; i<len; i++ )
466 {
467 if( !str[i] )
468 return;
469
470 buf[i] = str[i];
471 }
472 }
473
474 /* Print the string (max length:len) right aligned into buf (has width:width) */
475 static void highscore_strr( char *buf, const char *str, int len, int width )
476 {
477 int str_length = highscore_strlen( str, len );
478
479 for( int i=0; i<len; i++ )
480 {
481 if( !str[i] )
482 return;
483
484 buf[width-str_length+i] = str[i];
485 }
486 }
487
488 /* Print integer (padded with: alt), right aligned into buf(width: len) */
489 static void highscore_intr( char *buf, int value, int len, char alt )
490 {
491 int i=0;
492 while(value)
493 {
494 if( i>=len )
495 return;
496
497 buf[ len-1 - (i ++) ] = '0' + (value % 10);
498 value /= 10;
499 }
500
501 for( ;i<len; i ++ )
502 buf[ len-1 - i ] = alt;
503 }
504
505 /* Print integer into buffer with max length len */
506 static void highscore_intl( char *buf, int value, int len )
507 {
508 char temp[32];
509
510 int i=0;
511 while(value)
512 {
513 if( i>=len )
514 break;
515
516 temp[ i ++ ] = '0' + (value % 10);
517 value /= 10;
518 }
519
520 if( i>len )
521 i = len;
522
523 for( int j=0; j<i; j ++ )
524 {
525 buf[j] = temp[ i-1-j ];
526 }
527 }
528
529 /* Clear buffer with length using clr character */
530 static void highscore_clear( char *buf, char clr, int length )
531 {
532 for( int i=0; i<length; i++ )
533 buf[i] = clr;
534 }
535
536 /*
537 Megapark Green
538 --------------------------
539 #| Player | Time | Pts
540 1|aaaabbbbcc 5:23.32 30000
541 2| jef 0:20.34 10000
542 3|aaabbbcccl 2:30.45 20000
543 4|
544 5|
545 6|
546 7|
547 8|
548 9|
549 10|
550 */
551
552 /* Generate a highscores board in text form, the width is always 27. Buffer
553 * must be (count+3)*27 in size. */
554 static void highscores_board_generate( char *buf, u32 id, u32 count )
555 {
556 int w=27;
557 highscore_clear( buf, ' ', (count+3)*w );
558
559 struct track_info *inf = &track_infos[id];
560 struct highscore_system *sys = &highscore_system;
561
562 highscore_track_table *table = &sys->dbheader.tracks[ id ];
563 aatree_ptr it = aatree_kth( &sys->aainfo_time, table->root_time, 0 );
564
565 highscore_strc ( buf+w*0, inf->name, w,w );
566 highscore_clear( buf+w*1, '-', w );
567 highscore_strl ( buf+w*2, " #| Player | Time | Pts", 27 );
568
569 for( int i=0; i<count; i++ )
570 {
571 char *line = buf+w*(3+i);
572 highscore_intr( line, i+1, 2, ' ' );
573 line[2] = '|';
574
575 if( it == AATREE_PTR_NIL )
576 continue;
577
578 highscore_record *record = aatree_get_data( &sys->aainfo_time, it );
579 highscore_playerinfo temp;
580 temp.playerid = record->playerid;
581
582 aatree_ptr info_ptr = aatree_find( &sys->aainfo_playerinfo_playerid,
583 sys->dbheader.playerinfo_root,
584 &temp );
585
586 /* Player name */
587 if( info_ptr == AATREE_PTR_NIL )
588 highscore_strl( line+3, "unknown", 10 );
589 else
590 {
591 highscore_playerinfo *inf = aatree_get_data(
592 &sys->aainfo_playerinfo_playerid, info_ptr );
593
594 highscore_strl( line+3, inf->nickname, 10 );
595 }
596
597 u16 centiseconds = record->time,
598 seconds = centiseconds / 100,
599 minutes = seconds / 60;
600
601 centiseconds %= 100;
602 seconds %= 60;
603 minutes %= 60;
604
605 if( minutes > 9 ) minutes = 9;
606
607 /* Timer */
608 highscore_intr( line+14, minutes, 1, '0' );
609 line[15] = ':';
610 highscore_intr( line+16, seconds, 2, '0' );
611 line[18] = '.';
612 highscore_intr( line+19, centiseconds, 2, '0' );
613
614 /* Score */
615 highscore_intl( line+22, record->points, 5 );
616 it = aatree_next( &sys->aainfo_time, it );
617 }
618 }
619
620 /* Print string out to file using newlines. Count is number of records
621 * ( this requires a buffer of (count+3)*27 size */
622 static void highscores_board_printf( FILE *fp, const char *buf, u32 count )
623 {
624 int w=27;
625
626 for( int i=0; i<count+3; i++ )
627 fprintf( fp, "%.27s\n", &buf[i*w] );
628 }
629
630 #endif /* HIGHSCORES_H */