Complete.Org: Mailing Lists: Archives: freeciv-dev: December 1999:
[Freeciv-Dev] patch: allow player name prefixes in server commands (PR#2
Home

[Freeciv-Dev] patch: allow player name prefixes in server commands (PR#2

[Top] [All Lists]

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index] [Thread Index]
To: freeciv-dev@xxxxxxxxxxx
Cc: bugs@xxxxxxxxxxxxxxxxxxx
Subject: [Freeciv-Dev] patch: allow player name prefixes in server commands (PR#210)
From: David Pfitzner <dwp@xxxxxxxxxxxxxx>
Date: Sun, 26 Dec 1999 20:35:04 -0800 (PST)

One hassle of allowing long player names (and having long defaults)
is having to type the full name for server commands.  This patch
fixes this by allowing unambiguous abbreviations (cf existing
chatline behaviour).

I ended up writing a general system for matching prefixes
from a list of names; this could probably be used elsewhere
(eg, matching server commands, options) but so far isn't.  
Also added mystrncasecmp() (ie, with n).

-- David
diff -u -r --exclude-from exclude freeciv-cvs/common/player.c 
fc-adv/common/player.c
--- freeciv-cvs/common/player.c Thu Dec 23 16:50:17 1999
+++ fc-adv/common/player.c      Mon Dec 27 13:06:21 1999
@@ -104,6 +104,30 @@
 }
 
 /***************************************************************
+  Find player by name, allowing unambigous prefix (ie abbreviation).
+  Returns NULL if could not match, or if ambiguous or other
+  problem, and fills *result with characterisation of match/non-match
+  (see shared.[ch])
+***************************************************************/
+static const char *pname_accessor(int i) {
+  return game.players[i].name;
+}
+struct player *find_player_by_name_prefix(const char *name,
+                                         enum m_pre_result *result)
+{
+  int ind;
+
+  *result = match_prefix(pname_accessor, game.nplayers, MAX_LEN_NAME-1,
+                        mystrncasecmp, name, &ind);
+
+  if (*result < M_PRE_AMBIGUOUS) {
+    return get_player(ind);
+  } else {
+    return NULL;
+  }
+}
+
+/***************************************************************
 Find player by its user name (not player/leader name)
 ***************************************************************/
 struct player *find_player_by_user(char *name)
diff -u -r --exclude-from exclude freeciv-cvs/common/player.h 
fc-adv/common/player.h
--- freeciv-cvs/common/player.h Thu Dec 23 16:50:17 1999
+++ fc-adv/common/player.h      Mon Dec 27 12:37:02 1999
@@ -15,9 +15,10 @@
 
 #include "city.h"
 #include "nation.h"
+#include "shared.h"
+#include "spaceship.h"
 #include "tech.h"
 #include "unit.h"
-#include "spaceship.h"
 
 struct tile;
 
@@ -148,6 +149,8 @@
 
 void player_init(struct player *plr);
 struct player *find_player_by_name(char *name);
+struct player *find_player_by_name_prefix(const char *name,
+                                         enum m_pre_result *result);
 struct player *find_player_by_user(char *name);
 void player_set_unit_focus_status(struct player *pplayer);
 int player_has_embassy(struct player *pplayer, struct player *pplayer2);
diff -u -r --exclude-from exclude freeciv-cvs/common/shared.c 
fc-adv/common/shared.c
--- freeciv-cvs/common/shared.c Tue Oct  5 00:03:08 1999
+++ fc-adv/common/shared.c      Mon Dec 27 13:54:52 1999
@@ -252,7 +252,7 @@
 
 
 /***************************************************************
-...
+  Compare strings like strcmp(), but ignoring case.
 ***************************************************************/
 int mystrcasecmp(const char *str0, const char *str1)
 {
@@ -263,6 +263,21 @@
   return tolower(*str0)-tolower(*str1);
 }
 
+/***************************************************************
+  Compare strings like strncmp(), but ignoring case.
+  ie, only compares first n chars.
+***************************************************************/
+int mystrncasecmp(const char *str0, const char *str1, size_t n)
+{
+  size_t i;
+  
+  for(i=0; i<n-1 && tolower(*str0)==tolower(*str1); i++, str0++, str1++)
+    if(*str0=='\0')
+      return 0;
+
+  return tolower(*str0)-tolower(*str1);
+}
+
 /**************************************************************************
 string_ptr_compare() - fiddles with pointers to do a simple string compare
                        when passed pointers to two strings -- called from
@@ -721,3 +736,73 @@
   }
 #endif
 }
+
+
+/***************************************************************************
+  Return a description string of the result.
+  In English, form of description is suitable to substitute in, eg:
+     prefix is <description>
+***************************************************************************/
+const char *m_pre_description(enum m_pre_result result)
+{
+  static const char * const descriptions[] = {
+    N_("exact match"),
+    N_("only match"),
+    N_("ambiguous"),
+    N_("empty"),
+    N_("too long"),
+    N_("non-match")
+  };
+  assert(result > 0 && result < sizeof(descriptions)/sizeof(descriptions[0]));
+  return _(descriptions[result]);
+}
+
+/***************************************************************************
+  Given n names, with maximum length max_len_name, accessed by
+  accessor_fn(0) to accessor_fn(n-1), look for matching prefix
+  according to given comparison function.
+  Returns type of match or fail, and for return <= M_PRE_AMBIGUOUS
+  sets *ind_result with matching index (or for ambiguous, first match).
+***************************************************************************/
+enum m_pre_result match_prefix(m_pre_accessor_fn_t accessor_fn,
+                              size_t n_names,
+                              size_t max_len_name,
+                              m_pre_strncmp_fn_t cmp_fn,
+                              const char *prefix,
+                              int *ind_result)
+{
+  int i, len, nmatches;
+
+  len = strlen(prefix);
+  if (len == 0) {
+    return M_PRE_EMPTY;
+  }
+  if (len > max_len_name) {
+    return M_PRE_LONG;
+  }
+
+  nmatches = 0;
+  for(i=0; i<n_names; i++) {
+    const char *name = accessor_fn(i);
+    if (cmp_fn(name, prefix, len)==0) {
+      if (strlen(name) == len) {
+       *ind_result = i;
+       return M_PRE_EXACT;
+      }
+      if (nmatches==0) {
+       *ind_result = i;        /* first match */
+      }
+      nmatches++;
+    }
+  }
+
+  if (nmatches == 1) {
+    return M_PRE_ONLY;
+  } else if (nmatches > 1) {
+    return M_PRE_AMBIGUOUS;
+  } else {
+    return M_PRE_FAIL;
+  }
+}
+
+
diff -u -r --exclude-from exclude freeciv-cvs/common/shared.h 
fc-adv/common/shared.h
--- freeciv-cvs/common/shared.h Sat Sep 18 10:05:07 1999
+++ fc-adv/common/shared.h      Mon Dec 27 12:24:02 1999
@@ -13,6 +13,8 @@
 #ifndef FC__SHARED_H
 #define FC__SHARED_H
 
+#include <stdlib.h>            /* size_t */
+
 /* Note: the capability string is now in capstr.c --dwp */
 /* Version stuff is now in version.h --dwp */
 
@@ -56,6 +58,7 @@
 char *get_sane_name(char *name);
 char *textyear(int year);
 int mystrcasecmp(const char *str0, const char *str1);
+int mystrncasecmp(const char *str0, const char *str1, size_t n);
 int string_ptr_compare(const void *first, const void *second);
 
 char *mystrerror(int errnum);
@@ -77,6 +80,33 @@
 
 void init_nls(void);
 void dont_run_as_root(const char *argv0, const char *fallback);
+
+/*** matching prefixes: ***/
+
+enum m_pre_result {
+  M_PRE_EXACT,         /* matches with exact length */
+  M_PRE_ONLY,          /* only matching prefix */
+  M_PRE_AMBIGUOUS,     /* first of multiple matching prefixes */
+  M_PRE_EMPTY,         /* prefix is empty string (no match) */
+  M_PRE_LONG,          /* prefix is too long (no match) */
+  M_PRE_FAIL,          /* no match at all */
+  M_PRE_LAST           /* flag value */
+};
+
+const char *m_pre_description(enum m_pre_result result);
+
+/* function type to access a name from an index: */
+typedef const char *(*m_pre_accessor_fn_t)(int);
+
+/* function type to compare prefix: */
+typedef int (*m_pre_strncmp_fn_t)(const char *, const char *, size_t n);
+
+enum m_pre_result match_prefix(m_pre_accessor_fn_t accessor_fn,
+                              size_t n_names,
+                              size_t max_len_name,
+                              m_pre_strncmp_fn_t cmp_fn,
+                              const char *prefix,
+                              int *ind_result);
 
 /*Mac constants-resource IDs*/
 enum DITL_ids{
diff -u -r --exclude-from exclude freeciv-cvs/server/handchat.c 
fc-adv/server/handchat.c
--- freeciv-cvs/server/handchat.c       Tue Oct  5 00:03:08 1999
+++ fc-adv/server/handchat.c    Mon Dec 27 12:40:26 1999
@@ -81,30 +81,19 @@
   cp=strchr(packet->message, ':');
 
   if (cp != NULL && (cp != &packet->message[0])) {
-    struct player *pdest=0;
-    int nlen, nmatches=0;
+    enum m_pre_result match_result;
+    struct player *pdest = NULL;
+    int nlen;
     char name[MAX_LEN_NAME];
     char *cpblank;
 
     nlen=MIN(MAX_LEN_NAME-1, cp-packet->message);
     strncpy(name, packet->message, nlen);
     name[nlen]='\0';
-     
-    if(!(pdest=find_player_by_name(name))) {
-      /* no full match,  now look for pre-match */
-      for(i=0; i<game.nplayers; ++i) {
-       int j;
-       for(j=0; j<nlen; ++j)
-         if(tolower(packet->message[j])!=tolower(game.players[i].name[j]))
-           break;
-       if(j==nlen) {
-         ++nmatches;
-         pdest=&game.players[i];
-       }
-      }
-    }
-                          
-    if(pdest && nmatches<=1) {
+
+    pdest = find_player_by_name_prefix(name, &match_result);
+    
+    if(pdest && match_result < M_PRE_AMBIGUOUS) {
       sprintf(genmsg.message, "->*%s* %s", pdest->name, cp+1+(*(cp+1)==' '));
       send_packet_generic_message(pplayer->conn, PACKET_CHAT_MSG, &genmsg);
       sprintf(genmsg.message, "*%s* %s",
@@ -112,7 +101,7 @@
       send_packet_generic_message(pdest->conn, PACKET_CHAT_MSG, &genmsg);
       return;
     }
-    if(nmatches>=2) {
+    if(match_result == M_PRE_AMBIGUOUS) {
       sprintf(genmsg.message, _("Game: %s is an ambiguous name-prefix."),
              name);
       send_packet_generic_message(pplayer->conn, PACKET_CHAT_MSG, &genmsg);
diff -u -r --exclude-from exclude freeciv-cvs/server/stdinhand.c 
fc-adv/server/stdinhand.c
--- freeciv-cvs/server/stdinhand.c      Thu Dec 23 16:51:32 1999
+++ fc-adv/server/stdinhand.c   Mon Dec 27 13:37:45 1999
@@ -821,6 +821,41 @@
 /**************************************************************************
 ...
 **************************************************************************/
+static void cmd_reply_no_such_player(enum command_id cmd,
+                                    struct player *caller,
+                                    char *name,
+                                    enum m_pre_result match_result)
+{
+  switch(match_result) {
+  case M_PRE_EMPTY:
+    cmd_reply(cmd, caller, C_SYNTAX,
+             _("Name is empty, so cannot be a player."));
+    break;
+  case M_PRE_LONG:
+    cmd_reply(cmd, caller, C_SYNTAX,
+             _("Name is too long, so cannot be a player."));
+    break;
+  case M_PRE_AMBIGUOUS:
+    cmd_reply(cmd, caller, C_FAIL,
+             _("Player name prefix '%s' is ambiguous."), name);
+    break;
+  case M_PRE_FAIL:
+    cmd_reply(cmd, caller, C_FAIL,
+             _("No player by the name of '%s'."), name);
+    break;
+  default:
+    cmd_reply(cmd, caller, C_FAIL,
+             "Unexpected match_result %d (%s) for '%s'.",
+             match_result, m_pre_description(match_result), name);
+    freelog(LOG_NORMAL,
+           "Unexpected match_result %d (%s) for '%s'.",
+           match_result, m_pre_description(match_result), name);
+  }
+}
+
+/**************************************************************************
+...
+**************************************************************************/
 static void open_metaserver_connection(struct player *caller)
 {
   server_open_udp();
@@ -997,19 +1032,13 @@
 **************************************************************************/
 static void toggle_ai_player(struct player *caller, char *arg)
 {
+  enum m_pre_result match_result;
   struct player *pplayer;
 
-  if (test_player_name(arg) != PNameOk) {
-      cmd_reply(CMD_AITOGGLE, caller, C_SYNTAX,
-            _("Name '%s' is either empty or too long, so it cannot be an AI."),
-               arg);
-      return;
-  }
+  pplayer = find_player_by_name_prefix(arg, &match_result);
 
-  pplayer=find_player_by_name(arg);
   if (!pplayer) {
-    cmd_reply(CMD_AITOGGLE, caller, C_FAIL,
-             _("No player by the name of '%s'."), arg);
+    cmd_reply_no_such_player(CMD_AITOGGLE, caller, arg, match_result);
     return;
   }
 
@@ -1030,7 +1059,7 @@
     /* Set the skill level explicitly, because eg: the player skill
        level could have been set as AI, then toggled, then saved,
        then reloaded. */ 
-    set_ai_level(caller, arg, pplayer->ai.skill_level);
+    set_ai_level(caller, pplayer->name, pplayer->ai.skill_level);
   } else {
     notify_player(0, _("Game: %s is now human."), pplayer->name);
     cmd_reply(CMD_AITOGGLE, caller, C_OK,
@@ -1105,22 +1134,21 @@
 **************************************************************************/
 static void remove_player(struct player *caller, char *arg)
 {
+  enum m_pre_result match_result;
   struct player *pplayer;
+  char name[MAX_LEN_NAME];
 
-  if (test_player_name(arg) != PNameOk) {
-      cmd_reply(CMD_REMOVE, caller, C_SYNTAX,
-           _("Name is either empty or too long, so it cannot be a player."));
-      return;
-  }
-
-  pplayer=find_player_by_name(arg);
+  pplayer=find_player_by_name_prefix(arg, &match_result);
   
   if(!pplayer) {
-    cmd_reply(CMD_REMOVE, caller, C_FAIL, _("No player by that name."));
+    cmd_reply_no_such_player(CMD_REMOVE, caller, arg, match_result);
     return;
   }
 
+  strcpy(name, pplayer->name);
   server_remove_player(pplayer);
+  cmd_reply(CMD_REMOVE, caller, C_OK,
+           _("Removed player %s from the game."), name);
 }
 
 /**************************************************************************
@@ -1278,6 +1306,7 @@
   char arg_name[MAX_LEN_CMD+1];         /* a player name, or "new" */
   char *cptr_s, *cptr_d;        /* used for string ops */
 
+  enum m_pre_result match_result;
   enum cmdlevel_id level;
   struct player *pplayer;
 
@@ -1376,12 +1405,11 @@
     notify_player(0, _("Game: New connections will have access level '%s'."),
                  cmdlevel_name(level));
   }
-  else if (test_player_name(arg_name) == PNameOk &&
-                (pplayer=find_player_by_name(arg_name))) {
+  else if ((pplayer=find_player_by_name_prefix(arg_name,&match_result))) {
     if (!pplayer->conn) {
       cmd_reply(CMD_CMDLEVEL, caller, C_FAIL,
                _("Cannot change command access for unconnected player '%s'."),
-               arg_name);
+               pplayer->name);
       return;
     }
     if (set_cmdlevel(caller,pplayer,level)) {
@@ -1397,10 +1425,7 @@
                pplayer->name);
     }
   } else {
-    cmd_reply(CMD_CMDLEVEL, caller, C_FAIL,
-             _("Cannot change command access for unknown/invalid"
-               " player name '%s'."),
-             arg_name);
+    cmd_reply_no_such_player(CMD_CMDLEVEL, caller, arg_name, match_result);
   }
 }
 
@@ -1576,6 +1601,7 @@
 ******************************************************************/
 void set_ai_level(struct player *caller, char *name, int level)
 {
+  enum m_pre_result match_result;
   struct player *pplayer;
   int i;
   enum command_id cmd = (level <= 3) ? CMD_EASY :
@@ -1583,15 +1609,9 @@
                                        CMD_NORMAL;
     /* kludge - these commands ought to be 'set' options really - rp */
 
-  if (test_player_name(name) == PNameTooLong) {
-    cmd_reply(cmd, caller, C_SYNTAX,
-             _("Name is too long, so it cannot be a player."));
-    return;
-  }
-
   assert(level > 0 && level < 11);
 
-  pplayer=find_player_by_name(name);
+  pplayer=find_player_by_name_prefix(name, &match_result);
 
   if (pplayer) {
     if (pplayer->ai.control) {
@@ -1602,7 +1622,7 @@
       cmd_reply(cmd, caller, C_FAIL,
                _("%s is not controlled by the AI."), pplayer->name);
     }
-  } else if(test_player_name(name) == PNameEmpty) {
+  } else if(match_result == M_PRE_EMPTY) {
     for (i = 0; i < game.nplayers; i++) {
       pplayer = get_player(i);
       if (pplayer->ai.control) {
@@ -1615,8 +1635,7 @@
              _("Setting game.skill_level to %d."), level);
     game.skill_level = level;
   } else {
-    cmd_reply(cmd, caller, C_FAIL,
-             _("%s is not the name of any player."), name);
+    cmd_reply_no_such_player(cmd, caller, name, match_result);
   }
 }
 
@@ -1971,25 +1990,25 @@
 **************************************************************************/
 void cut_player_connection(struct player *caller, char *playername)
 {
+  enum m_pre_result match_result;
   struct player *pplayer;
 
-  if (test_player_name(playername) != PNameOk) {
-    cmd_reply(CMD_CUT, caller, C_SYNTAX,
-             _("Name is either empty or too long,"
-               " so it cannot be a player."));
+  pplayer=find_player_by_name_prefix(playername, &match_result);
+
+  if (!pplayer) {
+    cmd_reply_no_such_player(CMD_CUT, caller, playername, match_result);
     return;
   }
 
-  pplayer=find_player_by_name(playername);
-
-  if(pplayer && pplayer->conn) {
+  if(pplayer->conn) {
     cmd_reply(CMD_CUT, caller, C_DISCONNECTED,
-              _("Cutting connection to %s."), playername);
+              _("Cutting connection to %s."), pplayer->name);
     close_connection(pplayer->conn);
     pplayer->conn=NULL;
   }
   else {
-    cmd_reply(CMD_CUT, caller, C_FAIL, _("Sorry, no such player connected."));
+    cmd_reply(CMD_CUT, caller, C_FAIL, _("Sorry, %s is not connected."),
+             pplayer->name);
   }
 }
 

[Prev in Thread] Current Thread [Next in Thread]
  • [Freeciv-Dev] patch: allow player name prefixes in server commands (PR#210), David Pfitzner <=