Team balancing

Developer discussion of experimental fixes, changes, and improvements.

Moderators: Nexuiz Moderators, Moderators

Postby esteel » Mon Aug 14, 2006 10:58 am

Does one have access to the player (map) time in QC? Maybe something like points/time can be used to get a score for each player. That alone will not be sufficent but it might help.
esteel
Site admin and forum addon
 
Posts: 3924
Joined: Wed Mar 01, 2006 8:27 am

Postby Cinquero » Mon Aug 14, 2006 11:00 am

Points per death? Amount of points relatiive to best player in a single game?

(or train a neural net? Maybe that would even be patentable *g*)

A combination including the winning-team fraction (ie. how many times was the player in a winning team?). See, for example, http://stier.dynu.com/~nexuiz-server/.

To get correct scores, we should also have a unique player ID -- the probably most simple solution would be to create a random number in a large range that makes collissions nearly impossible. Server-specific player IDs (determined non-randomly by the server) would of course also be a reasonable idea.
Cinquero
Advanced member
 
Posts: 91
Joined: Wed Jul 19, 2006 11:13 pm

Postby Cinquero » Mon Aug 21, 2006 11:57 pm

I have got a small problem here:

I use the same filenames in g_world.qc to save and load the player stats, but when I try to read them, fopen always returns -1. What is the problem here? I have extracted nexuiz-2.0 into /scratch/Nexuiz and I start the dedicated server from there, so I guess it writes to ~/.nexuiz/data and reads from /scratch/Nexuiz/data.... is that the reason?

Code: Select all
diff -ru orig/Nexuiz/sources/qcsrc/server/g_world.qc Nexuiz/sources/qcsrc/server/g_world.qc
--- orig/Nexuiz/sources/qcsrc/server/g_world.qc   2006-06-13 14:59:26.000000000 +0200
+++ Nexuiz/sources/qcsrc/server/g_world.qc   2006-08-22 01:45:58.000000000 +0200
@@ -1,3 +1,5 @@
+float def_scorehist_len = 3;
+
string GetMapname();
void GotoNextMap();

@@ -681,6 +683,10 @@
{
   local float file;
   local string s;
+   local string player_map_id;
+   local string tmp;
+   local string tmp2;
+   local float i;

   if(cvar("_printstats"))
      cvar_set("_printstats", "0");
@@ -714,13 +720,52 @@
         s = strcat(s, ftos(rint(time - other.jointime)), ":");
         s = strcat(s, ftos(other.team), ":");

-         if(cvar("sv_logscores_file"))
-            fputs(file, strcat(s, other.netname, "\n"));
         if(cvar("sv_eventlog") && gameover)
            GameLogEcho(strcat(s, ftos(other.playerid), ":", other.netname), TRUE);
         else if(cvar("sv_logscores_console"))
            ServerConsoleEcho(strcat(s, other.netname), TRUE);
+                       
+      if (clienttype(other) == CLIENTTYPE_REAL)
+        s = strcat(s, "human:");
+      else
+        s = strcat(s, "bot:");
+         if(cvar("sv_logscores_file"))
+            fputs(file, strcat(s, other.netname, "\n"));
+   
+         // update player score history used   for advanced team balancing      
+         if (clienttype(other) == CLIENTTYPE_REAL) {
+            local string fn;
+            local float fh;
+            local string newline;
+            local string oldlines;
+            fn = strcat("data/scores.db/", other.netname, ".dat");
+            ServerConsoleEcho(strcat("  fn = ",fn),TRUE);
+            fh = fopen ( fn, FILE_READ );
+            ServerConsoleEcho(strcat("  fh = ",ftos(fh)),TRUE);
+            newline = strcat(ftos(other.frags), "\n");
+            // get old player scores history data
+            if(fh!=-1) {
+               local float j;
+               local string line;
+               // read up to def_scorehist_len-1 lines from previous player scores history
+               j=0;
+               line = fgets(fh);
+               while(line && j<def_scorehist_len-1) {
+                  ServerConsoleEcho(strcat("  line = ",line),TRUE);
+                  oldlines = strcat ( oldlines, line, "\n" );
+                  j++;
+                  line = fgets(fh);
+               }
+               fclose(fh);
+            }
+            // write updated player scores history
+            fh = fopen ( fn, FILE_WRITE );
+            ServerConsoleEcho(strcat("  fh = ",ftos(fh)),TRUE);
+            fputs ( fh, strcat(newline,oldlines) );
+            fclose ( fh );
+         }
      }
+      
      other = other.chain;
   }

diff -ru orig/Nexuiz/sources/qcsrc/server/teamplay.qc Nexuiz/sources/qcsrc/server/teamplay.qc
--- orig/Nexuiz/sources/qcsrc/server/teamplay.qc   2006-06-12 19:49:22.000000000 +0200
+++ Nexuiz/sources/qcsrc/server/teamplay.qc   2006-08-22 01:45:58.000000000 +0200
@@ -583,6 +583,84 @@
   }
}

+void GetTeamHistPts(entity ignore)
+{
+   entity head;
+   // now count how many players are on each team already
+
+   // FIXME: also find and memorize the lowest-scoring bot on each team (in case players must be shuffled around)
+   // also remember the lowest-scoring player
+
+   head = find(world, classname, "player");
+   while(head)
+   {
+      if(head != ignore)// && head.netname != "")
+      {
+        // compute avg player score for this map
+         local float avg;
+         local string fn;
+         local float fh;
+         fn = strcat("scores.db/", head.netname, ".dat");
+         fh = fopen ( fn, FILE_READ );
+         //ServerConsoleEcho(strcat("  fh = ",ftos(fh)),TRUE);
+         if(fh!=-1) {
+            local string line;
+            local float n;
+            n = 0;
+            avg = 0;
+            line = fgets(fh);
+            while(line) {
+               avg = avg + stof(line);
+               n++;
+               line = fgets(fh);
+            }
+            if(n>0) {
+               avg = avg / n;
+            } else {
+               avg = 1;
+            }
+            fclose(fh);
+         } else {
+            avg = 1;
+         }
+                       
+         if(head.team == COLOR_TEAM1)
+         {
+            if(c1 >= 0)
+            {
+               c1 = c1 + avg;
+               cb1 = cb1 + avg;
+            }
+         }
+         if(head.team == COLOR_TEAM2)
+         {
+            if(c2 >= 0)
+            {
+               c2 = c2 + avg;
+               cb2 = cb2 + avg;
+            }
+         }
+         if(head.team == COLOR_TEAM3)
+         {
+            if(c3 >= 0)
+            {
+               c3 = c3 + avg;
+               cb3 = cb3 + avg;
+            }
+         }
+         if(head.team == COLOR_TEAM4)
+         {
+            if(c4 >= 0)
+            {
+               c4 = c4 + avg;
+               cb4 = cb4 + avg;
+            }
+         }
+      }
+      head = find(head, classname, "player");
+   }
+}
+
// returns # of smallest team (1, 2, 3, 4)
// NOTE: Assumes CheckAllowedTeams has already been called!
float FindSmallestTeam(entity pl, float ignore_pl)
@@ -614,12 +692,6 @@
   }


-   // count how many players are in each team
-   if(ignore_pl)
-      GetTeamCounts(pl);
-   else
-      GetTeamCounts(world);
-
   // c1...c4 now have counts of each team
   // figure out which is smallest, giving priority to the team the player is already on as a tie-breaker

@@ -628,9 +700,25 @@

   // 2 gives priority to what team you're already on, 1 goes in order
   // 2 doesn't seem to work though...
-   balance_type = 1;
+   // 3 is experimental for now and based on each player's performance during
+   // the last N games in the same map.
+   balance_type = 3;
+
+   // count how many players are in each team
+   if(balance_type < 3)
+   {
+    if(ignore_pl)
+      GetTeamCounts(pl);
+    else
+      GetTeamCounts(world);
+  } else {
+    if(ignore_pl)
+      GetTeamHistPts(pl);
+    else
+      GetTeamHistPts(world);
+  }

-   if(balance_type == 1)
+   if(balance_type == 1 || balance_type == 3)
   {
      if(c1 >= 0 && c1 < smallestteam_count)
      {
Cinquero
Advanced member
 
Posts: 91
Joined: Wed Jul 19, 2006 11:13 pm

Postby KadaverJack » Tue Aug 22, 2006 1:37 am

Cinquero wrote:
Code: Select all
+            fn = strcat("data/scores.db/", other.netname, ".dat");
+            ServerConsoleEcho(strcat("  fn = ",fn),TRUE);
+            fh = fopen ( fn, FILE_READ );
+            ServerConsoleEcho(strcat("  fh = ",ftos(fh)),TRUE);

1) All new files will be created in .nexuiz/data/data/ (for security reasons, otherwise qc code would be able to overwrite default.cfg or do other nasty things).
2) You don't need to prepend data/, all file operations are relative to Nexuiz/data/.
3) Using other.netname in the filename is a bad idea, it might contain characters that are not allowed on the server's filesystem.
4) ServerConsoleEcho(strcat(" fn = ",fn),TRUE); <-- that command will most definitely mess up all your tempstrings. Either strzone() (see http://www.quakesrc.org/tutorials/old/142 for an explanation) your filename or don't use ServerConsoleEcho().
You should use ServerConsoleEcho(s, TRUE) only for "untrusted" strings that might contain characters like \n, \r or ". For anything else it's better to use ServerConsoleEcho(s, FALSE) (it's much faster and won't mess up tempstrings) . If it's just for debugging, use dprint() and set "developer 1" on your testserver.
KadaverJack
Site admin and forum addon
 
Posts: 1102
Joined: Tue Feb 28, 2006 9:42 pm

Postby Cinquero » Tue Aug 22, 2006 1:50 am

Agreed, but nothing of that explains why previously created files fail to open... even when using some simple and fixed filename.

Code: Select all
Nur in Nexuiz/sources: progs.dat.
Nur in Nexuiz/sources: progs.lno.
diff -ru orig/Nexuiz/sources/qcsrc/server/g_world.qc Nexuiz/sources/qcsrc/server/g_world.qc
--- orig/Nexuiz/sources/qcsrc/server/g_world.qc   2006-06-13 14:59:26.000000000 +0200
+++ Nexuiz/sources/qcsrc/server/g_world.qc   2006-08-22 04:30:52.000000000 +0200
@@ -1,3 +1,5 @@
+float def_scorehist_len = 3;
+
string GetMapname();
void GotoNextMap();

@@ -714,13 +716,19 @@
         s = strcat(s, ftos(rint(time - other.jointime)), ":");
         s = strcat(s, ftos(other.team), ":");

-         if(cvar("sv_logscores_file"))
-            fputs(file, strcat(s, other.netname, "\n"));
         if(cvar("sv_eventlog") && gameover)
            GameLogEcho(strcat(s, ftos(other.playerid), ":", other.netname), TRUE);
         else if(cvar("sv_logscores_console"))
            ServerConsoleEcho(strcat(s, other.netname), TRUE);
+                       
+      if (clienttype(other) == CLIENTTYPE_REAL)
+        s = strcat(s, "human:");
+      else
+        s = strcat(s, "bot:");
+         if(cvar("sv_logscores_file"))
+            fputs(file, strcat(s, other.netname, "\n"));
      }
+      
      other = other.chain;
   }

@@ -733,6 +741,52 @@
      fputs(file, ":end\n");
      fclose(file);
   }
+
+   other = findchainflags(flags, FL_CLIENT);
+   while (other)
+   {
+      // TODO: replace player netnames by player IDs against which the client
+      // authenticates (for example by using a random token given by the server).
+      // This auth stuff should probably be done on a per-server basis.
+      // (We surely don't want unnecessary central servers on which everyone
+      // is dependent)
+      
+      // update player score history used   for advanced team balancing      
+      if (clienttype(other) == CLIENTTYPE_REAL) {
+         local string fn;
+         local float fh;
+         local string newline;
+         local string oldlines;
+         local float j;
+         local string line;
+         fn = strcat("history/", GetMapname(), "/", other.netname, ".dat");
+         //fn = "all.dat";
+         ServerConsoleEcho(strcat("  fn = ",fn),TRUE);
+         fh = fopen ( fn, FILE_READ );
+         ServerConsoleEcho(strcat("  fh(read) = ",ftos(fh)),TRUE);
+         newline = strcat(ftos(other.frags), "\n");
+         // get old player scores history data
+         if(fh!=-1) {
+            // read up to def_scorehist_len-1 lines from previous player scores history
+            j=0;
+            line = fgets(fh);
+            while(line && j<def_scorehist_len-1) {
+               ServerConsoleEcho(strcat("  line = ",line),TRUE);
+               oldlines = strcat ( oldlines, line, "\n" );
+               j++;
+               line = fgets(fh);
+            }
+            fclose(fh);
+         }
+         // write updated player scores history
+         fh = fopen ( fn, FILE_WRITE );
+         ServerConsoleEcho(strcat("  fh(write) = ",ftos(fh)),TRUE);
+         fputs ( fh, strcat(newline,oldlines) );
+         fclose ( fh );
+      }
+
+      other = other.chain;
+   }
}


diff -ru orig/Nexuiz/sources/qcsrc/server/teamplay.qc Nexuiz/sources/qcsrc/server/teamplay.qc
--- orig/Nexuiz/sources/qcsrc/server/teamplay.qc   2006-06-12 19:49:22.000000000 +0200
+++ Nexuiz/sources/qcsrc/server/teamplay.qc   2006-08-22 04:30:52.000000000 +0200
@@ -1,3 +1,5 @@
+string GetMapname();
+
float COLOR_TEAM1   = 5;  // red
float COLOR_TEAM2   = 14; // blue
float COLOR_TEAM3   = 10; // pink
@@ -583,6 +585,84 @@
   }
}

+void GetTeamHistPts(entity ignore)
+{
+   entity head;
+   // now count how many players are on each team already
+
+   // FIXME: also find and memorize the lowest-scoring bot on each team (in case players must be shuffled around)
+   // also remember the lowest-scoring player
+
+   head = find(world, classname, "player");
+   while(head)
+   {
+      if(head != ignore)// && head.netname != "")
+      {
+        // compute avg player score for this map
+         local float avg;
+         local string fn;
+         local float fh;
+         fn = strcat("history/", GetMapname(), "/", head.netname, ".dat");
+         fh = fopen ( fn, FILE_READ );
+         //ServerConsoleEcho(strcat("  fh = ",ftos(fh)),TRUE);
+         if(fh!=-1) {
+            local string line;
+            local float n;
+            n = 0;
+            avg = 0;
+            line = fgets(fh);
+            while(line) {
+               avg = avg + stof(line);
+               n++;
+               line = fgets(fh);
+            }
+            if(n>0) {
+               avg = avg / n;
+            } else {
+               avg = 1;
+            }
+            fclose(fh);
+         } else {
+            avg = 1;
+         }
+                       
+         if(head.team == COLOR_TEAM1)
+         {
+            if(c1 >= 0)
+            {
+               c1 = c1 + avg;
+               cb1 = cb1 + avg;
+            }
+         }
+         if(head.team == COLOR_TEAM2)
+         {
+            if(c2 >= 0)
+            {
+               c2 = c2 + avg;
+               cb2 = cb2 + avg;
+            }
+         }
+         if(head.team == COLOR_TEAM3)
+         {
+            if(c3 >= 0)
+            {
+               c3 = c3 + avg;
+               cb3 = cb3 + avg;
+            }
+         }
+         if(head.team == COLOR_TEAM4)
+         {
+            if(c4 >= 0)
+            {
+               c4 = c4 + avg;
+               cb4 = cb4 + avg;
+            }
+         }
+      }
+      head = find(head, classname, "player");
+   }
+}
+
// returns # of smallest team (1, 2, 3, 4)
// NOTE: Assumes CheckAllowedTeams has already been called!
float FindSmallestTeam(entity pl, float ignore_pl)
@@ -614,12 +694,6 @@
   }


-   // count how many players are in each team
-   if(ignore_pl)
-      GetTeamCounts(pl);
-   else
-      GetTeamCounts(world);
-
   // c1...c4 now have counts of each team
   // figure out which is smallest, giving priority to the team the player is already on as a tie-breaker

@@ -628,9 +702,25 @@

   // 2 gives priority to what team you're already on, 1 goes in order
   // 2 doesn't seem to work though...
-   balance_type = 1;
+   // 3 is experimental for now and based on each player's performance during
+   // the last N games in the same map.
+   balance_type = 3;
+
+   // count how many players are in each team
+   if(balance_type < 3)
+   {
+    if(ignore_pl)
+      GetTeamCounts(pl);
+    else
+      GetTeamCounts(world);
+  } else {
+    if(ignore_pl)
+      GetTeamHistPts(pl);
+    else
+      GetTeamHistPts(world);
+  }

-   if(balance_type == 1)
+   if(balance_type == 1 || balance_type == 3)
   {
      if(c1 >= 0 && c1 < smallestteam_count)
      {


That patch gives a server log like

Code: Select all
^7:ctf:capture:14:1
^7Cinquero wins.
^7:scores:ctf_facing_worlds_nex_b1k:84
^7:player:21:0:32:5:1:Cinquero
^7:end
^7  fn = history/ctf_facing_worlds_nex_b1k/Cinquero.dat
^7  fh(read) = -1
^7  fh(write) = 0
^7:gameover


indicating that we failed to open the dat file. But the file exists on the file system:

Code: Select all
mark@voyager ctf_facing_worlds_nex_b1k $ pwd
/home/mark/.nexuiz/data/data/history/ctf_facing_worlds_nex_b1k
mark@voyager ctf_facing_worlds_nex_b1k $ ls -l
insgesamt 4
-rw-r--r-- 1 mark users 3 22. Aug 04:14 Cinquero.dat
mark@voyager ctf_facing_worlds_nex_b1k $ cat Cinquero.dat
21


That also happens when using a simpler filename like "all.dat".

Would be great if someone could try my patch and check if that problem persists on his machine. I tried fteqcc-2770 bin from sourceforge and fteqc-25xx compiled from sources and then just moved progs.* to the data dir.
Cinquero
Advanced member
 
Posts: 91
Joined: Wed Jul 19, 2006 11:13 pm

Postby KadaverJack » Tue Aug 22, 2006 2:37 am

Code: Select all
float fh;
fh = fopen("foo/bar.txt", FILE_WRITE);
fputs(fh, "narf");
fclose(fh);
fh = fopen("foo/bar.txt", FILE_READ);
dprint(fgets(fh), "\n");
fclose(fh);
Works fine here...
Is the file actually created and readable?
KadaverJack
Site admin and forum addon
 
Posts: 1102
Joined: Tue Feb 28, 2006 9:42 pm

Postby Cinquero » Tue Aug 22, 2006 3:56 am

Works now. How do I transform a player's name into a filename? If we would use C/C++, I could just access the single characters as numbers and replace those being out of specific ranges... is that somehow possible with fteqcc? I perused the codebase somewhat, but did not find any obvious solution.
Cinquero
Advanced member
 
Posts: 91
Joined: Wed Jul 19, 2006 11:13 pm

Postby esteel » Tue Aug 22, 2006 9:32 am

I"m still not sure that such a database is the right way to balance the teams. I'd rather try to create some rating by points/time or points/death or maybe something combined.
esteel
Site admin and forum addon
 
Posts: 3924
Joined: Wed Mar 01, 2006 8:27 am

Postby Cinquero » Tue Aug 22, 2006 12:39 pm

esteel wrote:I"m still not sure that such a database is the right way to balance the teams. I'd rather try to create some rating by points/time or points/death or maybe something combined.


You still need some sort of database for that.
Cinquero
Advanced member
 
Posts: 91
Joined: Wed Jul 19, 2006 11:13 pm

Postby king_ofall1 » Tue Aug 22, 2006 3:17 pm

Now I remember why i never tried learning C (any version)
The herring shall pwn you all.
king_ofall1
Member
 
Posts: 26
Joined: Wed Jul 26, 2006 12:52 pm
Location: Wales, U.K.

PreviousNext

Return to Nexuiz - Development

Who is online

Users browsing this forum: No registered users and 1 guest

cron