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