well yeah i guess
[carveJwlIkooP6JGAAIwe30JlM.git] / highscores.h
index 2ffaf6acdd19f90bea341182801835b31c181900..f2c5eabc855b0654a1d3c3d5b6eca3dcdedf8b56 100644 (file)
@@ -1,8 +1,13 @@
+/*
+ * Copyright (C) 2021-2022 Mt.ZERO Software, Harry Godden - All Rights Reserved
+ */
+
 #ifndef HIGHSCORES_H
 #define HIGHSCORES_H
 
 #include "vg/vg_store.h"
 #include "vg/vg_stdint.h"
+#include "world_info.h"
 
 /* 
  * Designed to be used across client and server,
@@ -14,15 +19,28 @@ typedef struct highscore highscore;
 typedef struct highscore_record highscore_record;
 typedef struct highscore_track_table highscore_track_table;
 typedef struct highscore_database highscore_database;
+typedef struct highscore_playerinfo highscore_playerinfo;
 
 #pragma pack(push,1)
+
+struct highscore_playerinfo
+{
+   char nickname[16];
+   u64 playerid;
+
+   union
+   {
+      aatree_pool_node aapn;
+      aatree_node aa_playerid;
+   };
+};
+
 struct highscore_record
 {
    u16 trackid, points, time, reserved0;
    u64 playerid;
    u32 datetime;
-
-   u32 reserved[7];
+   u32 reserved1;
    
    union
    {
@@ -35,12 +53,7 @@ struct highscore_record
       }
       aa;
 
-      struct
-      {
-         /* TODO pool allocator */
-         u32 next, prev;
-      }
-      pool;
+      aatree_pool_node pool;
    };
 };
 
@@ -58,32 +71,40 @@ struct highscore_database
 {
    highscore_track_table tracks[ 128 ];
 
-   aatree_ptr pool_head;
-   u32 reserved[63];
+   aatree_ptr pool_head, playerinfo_head;
+   u32 entry_capacity, 
+       playerinfo_capacity, playerinfo_root;
+
+   u32 reserved[59];
 };
 
 #pragma pack(pop)
 
-static struct highscore_system
+VG_STATIC struct highscore_system
 {
    highscore_database dbheader;
    aatree aainfo,
           aainfo_points,
           aainfo_time,
           aainfo_playerid,
-          aainfo_datetime;
+          aainfo_datetime,
+          aainfo_playerinfo_playerid,
+          aainfo_playerinfo;
+
+   void *data, 
+        *playerinfo_data;
 
-   void *data;
+   u32 pool_size, playerinfo_pool_size;
 }
 highscore_system;
 
-static int highscore_cmp_points( void *a, void *b )
+VG_STATIC int highscore_cmp_points( void *a, void *b )
 {
    highscore_record *pa = a, *pb = b;
    return (int)pa->points - (int)pb->points;
 }
 
-static int highscore_cmp_datetime( void *a, void *b )
+VG_STATIC int highscore_cmp_datetime( void *a, void *b )
 {
    highscore_record *pa = a, *pb = b;
    
@@ -91,37 +112,112 @@ static int highscore_cmp_datetime( void *a, void *b )
    return pa->datetime < pb->datetime? 1: -1;
 }
 
-static int highscore_cmp_time( void *a, void *b )
+VG_STATIC int highscore_cmp_time( void *a, void *b )
 {
    highscore_record *pa = a, *pb = b;
    return (int)pb->time - (int)pa->time;
 }
 
-static int highscore_cmp_playerid( void *a, void *b )
+VG_STATIC int highscore_cmp_playerid( void *a, void *b )
 {
    highscore_record *pa = a, *pb = b;
    if( pa->playerid == pb->playerid ) return 0;
    return pa->playerid < pb->playerid? -1: 1;
 }
 
-static int highscores_init( u32 pool_size )
+VG_STATIC int highscore_cmp_playerinfo_playerid( void *a, void *b )
+{
+   highscore_playerinfo *pa = a, *pb = b;
+   if( pa->playerid == pb->playerid ) return 0;
+   return pa->playerid < pb->playerid? -1: 1;
+}
+
+VG_STATIC void highscores_create_db(void)
 {
    struct highscore_system *sys = &highscore_system;
 
-   size_t requested_mem = pool_size * sizeof(highscore_record);
-   sys->data = malloc( requested_mem );
+   vg_info( "Initializing database nodes\n" );
+   memset( &sys->dbheader, 0, sizeof(highscore_database) );
 
-   requested_mem /= 1024;
-   requested_mem /= 1024;
+   sys->dbheader.pool_head = aatree_init_pool( &sys->aainfo, sys->pool_size );
+   sys->dbheader.entry_capacity = sys->pool_size;
 
-   if( !highscore_system.data )
+   for( int i=0; i<vg_list_size(sys->dbheader.tracks); i++ )
    {
-      vg_error( "Could not allocated %dmb of memory for database\n",
-                  requested_mem );
-      return 0;
+      highscore_track_table *table = &sys->dbheader.tracks[i];
+      table->root_points = AATREE_PTR_NIL;
+      table->root_playerid = AATREE_PTR_NIL;
+      table->root_time = AATREE_PTR_NIL;
+      table->root_datetime = AATREE_PTR_NIL;
+   }
+
+   /* Initialize secondary db */
+   sys->dbheader.playerinfo_head = aatree_init_pool( 
+         &sys->aainfo_playerinfo,
+         sys->playerinfo_pool_size );
+   sys->dbheader.playerinfo_capacity = sys->playerinfo_pool_size;
+   sys->dbheader.playerinfo_root = AATREE_PTR_NIL;
+}
+
+VG_STATIC int highscores_read(void)
+{
+   struct highscore_system *sys = &highscore_system;
+
+   FILE *fp = fopen( ".aadb", "rb" );
+   if( fp )
+   {
+      vg_info( "Loading existing database\n" );
+      
+      u64 count = fread( &sys->dbheader, sizeof(highscore_database), 1, fp );
+
+      if( count != 1 )
+      {
+         vg_error( "Unexpected EOF reading database header\n" );
+         return 0;
+      }
+
+      count = fread( sys->data, sizeof(highscore_record), 
+                     sys->pool_size, fp );
+
+      if( count != sys->pool_size )
+      {
+         vg_error( "Unexpected EOF reading database contents;"
+                   " %lu records of %u were read\n", count, sys->pool_size );
+         return 0;
+      }
+
+      count = fread( sys->playerinfo_data, sizeof(highscore_playerinfo),
+                     sys->playerinfo_pool_size, fp );
+
+      if( count != sys->playerinfo_pool_size )
+      {
+         vg_error( "Unexpected EOF reading playerinfo contents;"
+                   " %lu records of %u were read\n", count, 
+                   sys->playerinfo_pool_size );
+         return 0;
+      }
+
+      fclose( fp );
+      return 1;
    }
    else
-      vg_success( "Allocated %dmb for database\n", requested_mem );
+   {
+      vg_low( "No existing database found (.aadb)\n" );
+      return 0;
+   }
+}
+
+VG_STATIC void highscores_init( u32 pool_size, u32 playerinfo_pool_size )
+{
+   struct highscore_system *sys = &highscore_system;
+
+   sys->data = vg_linear_alloc( vg_mem.rtmemory, 
+                                pool_size*sizeof(highscore_record) );
+
+   sys->playerinfo_data = 
+      vg_linear_alloc( vg_mem.rtmemory,
+                       playerinfo_pool_size * sizeof(highscore_playerinfo) );
+
 
    /* This is ugly.. too bad! */
    sys->aainfo.base = highscore_system.data;
@@ -149,41 +245,66 @@ static int highscores_init( u32 pool_size )
    sys->aainfo_playerid.offset = offsetof(highscore_record,aa.playerid);
    sys->aainfo_playerid.p_cmp = highscore_cmp_playerid;
 
+   sys->aainfo_playerinfo_playerid.base = highscore_system.playerinfo_data;
+   sys->aainfo_playerinfo_playerid.stride = sizeof(highscore_playerinfo);
+   sys->aainfo_playerinfo_playerid.offset = 
+      offsetof(highscore_playerinfo,aa_playerid);
+   sys->aainfo_playerinfo_playerid.p_cmp = highscore_cmp_playerinfo_playerid;
 
-   /* TODO: Load from disk if avalible */
-   if( 0 )
-   {
+   sys->aainfo_playerinfo.base = highscore_system.playerinfo_data;
+   sys->aainfo_playerinfo.stride = sizeof(highscore_playerinfo);
+   sys->aainfo_playerinfo.offset = offsetof(highscore_playerinfo,aapn);
+   sys->aainfo_playerinfo.p_cmp = NULL;
 
-   }
-   else
-   {
-      vg_info( "Initializing database nodes\n" );
-      sys->dbheader.pool_head = aatree_init_pool( &sys->aainfo, pool_size );
+   sys->playerinfo_pool_size = playerinfo_pool_size;
+   sys->pool_size = pool_size;
+}
 
-      for( int i=0; i<vg_list_size(sys->dbheader.tracks); i++ )
-      {
-         highscore_track_table *table = &sys->dbheader.tracks[i];
-         table->root_points = AATREE_PTR_NIL;
-         table->root_playerid = AATREE_PTR_NIL;
-         table->root_time = AATREE_PTR_NIL;
-         table->root_datetime = AATREE_PTR_NIL;
-      }
+VG_STATIC int highscores_serialize_all(void)
+{
+   struct highscore_system *sys = &highscore_system;
+   vg_info( "Serializing database\n" );
+
+   FILE *fp = fopen( ".aadb", "wb" );
+
+   if( !fp )
+   {
+      vg_error( "Could not open .aadb\n" );
+      return 0;
    }
 
+   fwrite( &sys->dbheader, sizeof(highscore_database), 1, fp );
+   fwrite( sys->data, sizeof(highscore_record), 
+           sys->dbheader.entry_capacity, fp );
+   fwrite( sys->playerinfo_data, sizeof(highscore_playerinfo),
+            sys->dbheader.playerinfo_capacity, fp );
+
+   fclose( fp );
    return 1;
 }
 
-static void highscores_free(void)
+VG_STATIC highscore_record *highscore_find_user_record( u64 playerid, u32 trackid )
 {
-   free( highscore_system.data );
+   struct highscore_system *sys = &highscore_system;
+
+   highscore_track_table *table = &sys->dbheader.tracks[trackid];
+   highscore_record temp;
+   temp.playerid = playerid;
+
+   aatree_ptr find = 
+      aatree_find( &sys->aainfo_playerid, table->root_playerid, &temp );
+
+   if( find == AATREE_PTR_NIL )
+      return NULL;
+
+   return aatree_get_data( &sys->aainfo_playerid, find );
 }
 
-static aatree_ptr highscores_push_record( highscore_record *record )
+VG_STATIC aatree_ptr highscores_push_record( highscore_record *record )
 {
    struct highscore_system *sys = &highscore_system;
 
-   /* TODO: Verify steam ID */
-   vg_log( "Inserting record into database for track %hu\n",record->trackid );
+   vg_low( "Inserting record into database for track %hu\n",record->trackid );
 
    if( record->trackid >= vg_list_size(sys->dbheader.tracks) )
    {
@@ -201,7 +322,17 @@ static aatree_ptr highscores_push_record( highscore_record *record )
 
    if( existing != AATREE_PTR_NIL )
    {
-      vg_log( "Freeing existing record for player %lu\n", record->playerid );
+      highscore_record *crecord = aatree_get_data( &sys->aainfo_playerid, 
+                                                   existing );
+
+      if( crecord->time < record->time || 
+            (crecord->time == record->time && crecord->points > record->points))
+      {
+         vg_low( "Not overwriting better score\n" );
+         return existing;
+      }
+
+      vg_low( "Freeing existing record for player %lu\n", record->playerid );
       table->root_playerid = aatree_del( &sys->aainfo_playerid, existing );
       table->root_datetime = aatree_del( &sys->aainfo_datetime, existing );
       table->root_points = aatree_del( &sys->aainfo_points, existing );
@@ -214,7 +345,10 @@ static aatree_ptr highscores_push_record( highscore_record *record )
       aatree_pool_alloc( &sys->aainfo, &sys->dbheader.pool_head );
 
    if( index == AATREE_PTR_NIL )
+   {
+      vg_error( "Database records are over capacity!\n" );
       return index;
+   }
 
    highscore_record *dst = aatree_get_data( &sys->aainfo, index );
    memset( dst, 0, sizeof(highscore_record) );
@@ -237,35 +371,251 @@ static aatree_ptr highscores_push_record( highscore_record *record )
    return index;
 }
 
-static void _highscore_showtime( void *data )
+VG_STATIC aatree_ptr highscore_set_user_nickname( u64 steamid, char nick[16] )
 {
-   highscore_record *record = data;
-   printf( "%hu", record->time );
+   char name[17];
+   for( int i=0; i<16; i++ )
+      name[i] = nick[i];
+   name[16] = '\0';
+
+   vg_low( "Updating %lu's nickname -> %s\n", steamid, name );
+
+   struct highscore_system *sys = &highscore_system;
+   
+   highscore_playerinfo temp;
+   temp.playerid = steamid;
+
+   aatree_ptr record = aatree_find( &sys->aainfo_playerinfo_playerid, 
+                                     sys->dbheader.playerinfo_root,
+                                     &temp );
+   highscore_playerinfo *info;
+
+   if( record != AATREE_PTR_NIL )
+   {
+      info = aatree_get_data( &sys->aainfo_playerinfo, record );
+   }
+   else
+   {
+      record = aatree_pool_alloc( &sys->aainfo_playerinfo, 
+                                  &sys->dbheader.playerinfo_head );
+
+      if( record == AATREE_PTR_NIL )
+      {
+         vg_error( "Player info database is over capacity!\n" );
+         return AATREE_PTR_NIL;
+      }
+
+      info = aatree_get_data( &sys->aainfo_playerinfo, record );
+      memset( info, 0, sizeof(highscore_playerinfo) );
+
+      info->playerid = steamid;
+      sys->dbheader.playerinfo_root = aatree_insert( 
+            &sys->aainfo_playerinfo_playerid,
+            sys->dbheader.playerinfo_root, record );
+   }
+
+   for( int i=0; i<16; i++ )
+      info->nickname[i] = nick[i];
+
+   return AATREE_PTR_NIL;
 }
 
-static void highscores_print_track( u32 trackid, u32 count )
+/* Get the length of a string, bounded by '\0' or len, whichever is first */
+VG_STATIC int highscore_strlen( const char *str, int len )
 {
+   int str_length;
+   for( str_length=0; str_length<len; str_length++ )
+      if( !str[str_length] )
+         return str_length;
+
+   return str_length;
+}
+
+/* Print the string(max length:len) centered into buf (has width:width) */
+VG_STATIC void highscore_strc( char *buf, const char *str, int len, int width )
+{
+   int str_length = highscore_strlen( str, len ),
+       offs = (width-str_length)/2;
+
+   for( int i=0; i<str_length; i++ )
+   {
+      int j=i+offs;
+
+      if( j >= width )
+         return;
+
+      buf[j] = str[i];
+   }
+}
+
+/* Print the string(max length:len) left aligned into buf */
+VG_STATIC void highscore_strl( char *buf, const char *str, int len )
+{
+   for( int i=0; i<len; i++ )
+   {
+      if( !str[i] )
+         return;
+
+      buf[i] = str[i];
+   }
+}
+
+/* Print the string (max length:len) right aligned into buf (has width:width) */
+VG_STATIC void highscore_strr( char *buf, const char *str, int len, int width )
+{
+   int str_length = highscore_strlen( str, len );
+   
+   for( int i=0; i<len; i++ )
+   {
+      if( !str[i] )
+         return;
+
+      buf[width-str_length+i] = str[i];
+   }
+}
+
+/* Print integer (padded with: alt), right aligned into buf(width: len) */
+VG_STATIC void highscore_intr( char *buf, int value, int len, char alt )
+{
+   int i=0;
+   while(value)
+   {
+      if( i>=len ) 
+         return;
+
+      buf[ len-1 - (i ++) ] = '0' + (value % 10);
+      value /= 10;
+   }
+
+   for( ;i<len; i ++ )
+      buf[ len-1 - i ] = alt;
+}
+
+/* Print integer into buffer with max length len */
+VG_STATIC void highscore_intl( char *buf, int value, int len )
+{
+   char temp[32];
+
+   int i=0;
+   while(value)
+   {
+      if( i>=len ) 
+         break;
+
+      temp[ i ++ ] = '0' + (value % 10);
+      value /= 10;
+   }
+   
+   if( i>len )
+      i = len;
+
+   for( int j=0; j<i; j ++ )
+   {
+      buf[j] = temp[ i-1-j ];
+   }
+}
+
+/* Clear buffer with length using clr character */
+VG_STATIC void highscore_clear( char *buf, char clr, int length )
+{
+   for( int i=0; i<length; i++ )
+      buf[i] = clr;
+}
+
+/*
+       Megapark Green      
+--------------------------
+ #|   Player |  Time | Pts 
+ 1|aaaabbbbcc 5:23.32 30000
+ 2|       jef 0:20.34 10000
+ 3|aaabbbcccl 2:30.45 20000
+ 4|
+ 5|
+ 6|
+ 7|
+ 8|
+ 9|
+10|
+*/
+
+/* Generate a highscores board in text form, the width is always 27. Buffer 
+ * must be (count+3)*27 in size. */
+VG_STATIC void highscores_board_generate( char *buf, u32 id, u32 count )
+{
+   int w=27;
+   highscore_clear( buf, ' ', (count+3)*w );
+
+   struct track_info *inf = &track_infos[id];
    struct highscore_system *sys = &highscore_system;
 
-   highscore_track_table *table = &sys->dbheader.tracks[ trackid ];
+   highscore_track_table *table = &sys->dbheader.tracks[ id ];
    aatree_ptr it = aatree_kth( &sys->aainfo_time, table->root_time, 0 );
 
-   vg_info( "Highscores, top %u records for track %u\n", count, trackid );
-   vg_info( "==============================================\n" );
+   highscore_strc ( buf+w*0, inf->name, w,w );
+   highscore_clear( buf+w*1, '-', w );
+   highscore_strl ( buf+w*2, " #|     Player     | Time  ", 27 );
 
-   int i=0;
-   while( it != AATREE_PTR_NIL && i < 10 )
+   for( int i=0; i<count; i++ )
    {
+      char *line = buf+w*(3+i);
+      highscore_intr( line, i+1, 2, ' ' );
+      line[2] = '|';
+
+      if( it == AATREE_PTR_NIL )
+         continue;
+
       highscore_record *record = aatree_get_data( &sys->aainfo_time, it );
-      vg_info( "  [%d]: player(%lu), time: %hu, score: %hu, track:%hu\n",
-                  i+1, record->playerid, record->time, record->points,
-                  record->trackid );
+      highscore_playerinfo temp;
+      temp.playerid = record->playerid;
+
+      aatree_ptr info_ptr = aatree_find( &sys->aainfo_playerinfo_playerid,
+                                          sys->dbheader.playerinfo_root,
+                                          &temp );
+      
+      /* Player name */
+      if( info_ptr == AATREE_PTR_NIL )
+         highscore_strl( line+3, "unknown", 16 );
+      else
+      {
+         highscore_playerinfo *inf = aatree_get_data( 
+               &sys->aainfo_playerinfo_playerid, info_ptr );
 
-      i++;
+         highscore_strl( line+3, inf->nickname, 16 );
+      }
+
+      u16 centiseconds = record->time,
+          seconds      = centiseconds / 100,
+          minutes      = seconds / 60;
+
+      centiseconds %= 100;
+      seconds     %= 60;
+      minutes     %= 60;
+
+      if( minutes > 9 ) minutes = 9;
+      
+      /* Timer */
+      highscore_intr( line+20, minutes, 1, '0' );
+      line[21] = ':';
+      highscore_intr( line+22, seconds, 2, '0' );
+      line[24] = '.';
+      highscore_intr( line+25, centiseconds, 2, '0' );
+
+#if 0
+      /* Score */
+      highscore_intl( line+22, record->points, 5 );
+#endif
       it = aatree_next( &sys->aainfo_time, it );
    }
+}
+
+/* Print string out to file using newlines. Count is number of records 
+ * ( this requires a buffer of (count+3)*27 size */
+VG_STATIC void highscores_board_printf( FILE *fp, const char *buf, u32 count )
+{
+   int w=27;
 
-   vg_info( "==============================================\n" );
+   for( int i=0; i<count+3; i++ )
+      fprintf( fp, "%.27s\n", &buf[i*w] );
 }
 
 #endif /* HIGHSCORES_H */