From 47ff725ff3b109b94a23ea7c8c07c8b1417e31ef Mon Sep 17 00:00:00 2001
From: Richard Worth <richard@theworths.org>
Date: Sat, 3 Dec 2005 05:36:42 +0000
Subject: [PATCH] 2005-12-03  Richard D. Worth  <richard@theworths.org>

        * PROTOCOL: Add underscores to error codes. Add STATISTICS
        command. Remove statistics from WHO.

        * TODO: Add STATISTICS. Check off STATISTICS.

        * src/ttt.h: Define TTT_SERVER_PROTOCOL_VERSION for VERSION.

        * src/ttt-error.c: (ttt_error_string): Add underscores to error
        codes.

        * src/ttt-client.h:
        * src/ttt-client.c: (_ttt_client_execute_helo),
        (_ttt_client_execute_statistics), (_ttt_client_execute_message),
        (_ttt_client_execute_version), (_ttt_client_execute_quit),
        (_ttt_client_init), (_ttt_client_fini), (ttt_client_get_username),
        (ttt_client_set_username): Rename client->name to
        client->username. Implement STATISTICS. Complete implementation of
        VERSION.

        * src/ttt-server.h:
        * src/ttt-server.c: (ttt_server_register_client),
        (ttt_server_unregister_client), (ttt_server_who),
        (ttt_server_statistics): Track rename of client->name to
        client->username. Add server helper for STATISTICS.
---
 ChangeLog        | 27 ++++++++++++++++
 PROTOCOL         | 76 +++++++++++++++++++++++++++----------------
 TODO             | 11 ++++---
 src/ttt-client.c | 84 ++++++++++++++++++++++++++++++++++++++----------
 src/ttt-client.h |  4 +--
 src/ttt-error.c  | 16 ++++-----
 src/ttt-server.c | 50 ++++++++++++++++++++++------
 src/ttt-server.h | 16 +++++++++
 src/ttt.h        |  2 ++
 9 files changed, 217 insertions(+), 69 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index bb05052..3d2bc1d 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,30 @@
+2005-12-03  Richard D. Worth  <richard@theworths.org>
+
+	* PROTOCOL: Add underscores to error codes. Add STATISTICS
+	command. Remove statistics from WHO.
+
+	* TODO: Add STATISTICS. Check off STATISTICS.
+	
+	* src/ttt.h: Define TTT_SERVER_PROTOCOL_VERSION for VERSION.
+
+	* src/ttt-error.c: (ttt_error_string): Add underscores to error
+	codes.
+
+	* src/ttt-client.h:
+	* src/ttt-client.c: (_ttt_client_execute_helo),
+	(_ttt_client_execute_statistics), (_ttt_client_execute_message),
+	(_ttt_client_execute_version), (_ttt_client_execute_quit),
+	(_ttt_client_init), (_ttt_client_fini), (ttt_client_get_username),
+	(ttt_client_set_username): Rename client->name to
+	client->username. Implement STATISTICS. Complete implementation of
+	VERSION.
+
+	* src/ttt-server.h:
+	* src/ttt-server.c: (ttt_server_register_client),
+	(ttt_server_unregister_client), (ttt_server_who),
+	(ttt_server_statistics): Track rename of client->name to
+	client->username. Add server helper for STATISTICS.
+
 2005-12-02  Richard D. Worth  <richard@theworths.org>
 
 	* TODO: Check off Global commands, HELP, VERSION.
diff --git a/PROTOCOL b/PROTOCOL
index d855cdf..be0f04d 100644
--- a/PROTOCOL
+++ b/PROTOCOL
@@ -31,11 +31,11 @@ Document Conventions
 	<response> is one of:
 
 	<command> <args>
-	ERROR <message>
+	ERROR <error-code>
 
 1. Requests
 
-1.1 Connection setup
+1.1. Connection setup
 
     The TTTP server has no well defined port; agreement on which port to
     use must be done through some external mechanism.  Once connected,
@@ -47,23 +47,37 @@ Document Conventions
     
     HELO <username> <server-addr> <server-port>
 
-    Possible errors: INVALIDNAME
+    Possible errors: INVALID_NAME
 
 1.2. Global commands
 
-    1.2.1 Listing available users
+    1.2.1. Listing available users
 
 	WHO
 
 	->
 
-	WHO <username1> <games1> <username2> <games2> ...
+	WHO <username1> <username2> ...
 
-	Lists connected users and the number of games they've won.
+	Lists connected users.
 
-	Possible errors: NONAMESET
+	Possible errors: NO_NAME_SET
 
-    1.2.2. Message
+    1.2.2. STATISTICS
+
+	STATISTICS <username>
+
+	->
+
+	STATISTICS <username> "
+	TICTACTOE WINS <wins>
+	"
+
+	Lists the statistics for the specified user.
+
+	Possible errors: NO_NAME_SET, NO_USER
+
+    1.2.3. Message
 
 	MESSAGE <text>
 
@@ -75,17 +89,23 @@ Document Conventions
 	single token. Use a quoted-string to include spaces in the
 	message text.
 
-	Possible errors: NONAMESET
+	Possible errors: NO_NAME_SET
 
-    1.2.3. Help
+    1.2.4. Help
 
 	HELP { <command> }
 
+	->
+
+	HELP { <command> } "
+	<overview or detailed help>
+	"
+
 	Displays help.  If <command> is provided, displays more
 	detailed help on a specific command, otherwise displays an
 	overview of all commands.
 
-    1.2.4. Quit
+    1.2.5. Quit
 
 	QUIT
 
@@ -95,7 +115,7 @@ Document Conventions
 
 	Disconnects the client from the server.
 
-    1.2.5. Version
+    1.2.6. Version
 
 	VERSION <client-version-number>
 
@@ -119,7 +139,7 @@ Document Conventions
 
 	INVITE
 
-	Possible errors: NOUSER, BUSY, NONAMESET
+	Possible errors: NO_USER, BUSY, NO_NAME_SET
 
     1.3.2. Accepting an invitation
 
@@ -129,7 +149,7 @@ Document Conventions
 
 	ACCEPT
 
-	Possible errors: NONAMESET
+	Possible errors: NO_NAME_SET
 
 1.4. In-game commands
 
@@ -158,7 +178,7 @@ Document Conventions
         _|X|_
         X|O|O"
     
-	Possible errors: NOTINGAME, NONAMESET
+	Possible errors: NOT_IN_GAME, NO_NAME_SET
     
     1.4.2. Part
     
@@ -170,7 +190,7 @@ Document Conventions
     
 	Departs the current game
 
-	Possible errors: NOTINGAME, NONAMESET
+	Possible errors: NOT_IN_GAME, NO_NAME_SET
     
     1.4.3. Making a move
 
@@ -187,7 +207,7 @@ Document Conventions
 	3|4|5
 	6|7|8
     
-	Possible errors: NOTINGAME, NOTYOURMOVE, NOTGRID, NONAMESET
+	Possible errors: NOT_IN_GAME, NOT_YOUR_MOVE, NOT_GRID, NO_NAME_SET
     
 2. Asynchronous notification.  
 
@@ -226,6 +246,8 @@ Document Conventions
 
 	NOTICE MESSAGE <username> <text>
 
+	where <text> is a quoted-string. Quotes escaped with '\"'
+
 2.2. Game notices
 
     These notices are sent to all players and watchers in
@@ -243,7 +265,7 @@ Document Conventions
 
 	    NOTICE GAMEOVER <outcome> <username>
 
-	    <outcame> is either WON in which case <username> indicates
+	    <outcome> is either WON in which case <username> indicates
 	    the winner or CATSGAME in which case <username> is "".
 
     2.2.2. Move notices
@@ -262,7 +284,7 @@ Document Conventions
 
     3.1.1. No name set
     
-	ERROR NONAMESET
+	ERROR NO_NAME_SET
     
 	'helo' must be sent before any command other than 'help',
 	'version', 'quit'.
@@ -271,7 +293,7 @@ Document Conventions
     
     3.1.2. Invalid name
     
-	ERROR INVALIDNAME
+	ERROR INVALID_NAME
     
 	All names must be of non-zero length and must be unique.
 
@@ -295,13 +317,13 @@ Document Conventions
     
     3.2.3. Not number
     
-	ERROR NOTNUMBER
+	ERROR NOT_NUMBER
     
 	A non-numeric value was supplied where a number was required
 	
     3.2.4. Not a grid number
     
-	ERROR NOTGRID
+	ERROR NOT_GRID
     
 	The number specified in the command was not a valid grid number
     
@@ -315,7 +337,7 @@ Document Conventions
     
     3.4.1. No such game
 	
-	ERROR NOGAME
+	ERROR NO_GAME
 	
 	A game name was provided that does not exist.
 	
@@ -323,7 +345,7 @@ Document Conventions
 
     3.5.1. No such user
     
-	ERROR NOUSER
+	ERROR NO_USER
 
 	A user name was provided that does not exist.
 
@@ -333,7 +355,7 @@ Document Conventions
     
 	3.6.1.1. Not in game
     
-	    ERROR NOTINGAME
+	    ERROR NOT_IN_GAME
     
 	    A game playing command was made, but the user is not a
 	    particpant of any game.
@@ -342,7 +364,7 @@ Document Conventions
 
 	3.6.1.2. Not playing
 
-	    ERROR NOTPLAYING
+	    ERROR NOT_PLAYING
 
 	    A command was executed by a watching user that is
 	    permitted only to players
@@ -351,7 +373,7 @@ Document Conventions
     
 	3.6.2.1. Not your turn
 
-	    ERROR NOTYOURTURN
+	    ERROR NOT_YOUR_TURN
     
 	    A move was submitted during the other player's turn
 
diff --git a/TODO b/TODO
index f0bf20a..f7fe04b 100644
--- a/TODO
+++ b/TODO
@@ -5,13 +5,14 @@ working on something.
  / /---- Client, implemented in ttt
 S C
     1. Requests
-✓   1.1 HELO
+✓   1.1. HELO
 ✓   1.2. Global commands
 ✓   1.2.1. WHO
-✓   1.2.2. MESSAGE
-✓   1.2.3. HELP
-✓   1.2.4. QUIT
-✓   1.2.5. VERSION
+✓   1.2.2. STATISTICS
+✓   1.2.3. MESSAGE
+✓   1.2.4. HELP
+✓   1.2.5. QUIT
+✓   1.2.6. VERSION
     1.3. Game management commands
     1.3.1. INVITE
     1.3.2. ACCEPT
diff --git a/src/ttt-client.c b/src/ttt-client.c
index 1dd755f..c92c4eb 100644
--- a/src/ttt-client.c
+++ b/src/ttt-client.c
@@ -40,7 +40,7 @@ struct _ttt_client {
     char            **request_strings;
     int             num_request_strings;
     
-    char            *name;
+    char            *username;
     ttt_bool_t      registered;
     int             num_wins;
 };
@@ -59,6 +59,11 @@ _ttt_client_execute_who (ttt_client_t *client,
 			 char         **args,
 			 int          num_args);
 
+static ttt_error_t
+_ttt_client_execute_statistics (ttt_client_t *client,
+				char         **args,
+				int          num_args);
+
 static ttt_error_t
 _ttt_client_execute_message (ttt_client_t *client,
 			     char         **args,
@@ -98,6 +103,9 @@ ttt_command_description_t command_descriptions[] = {
     {"MESSAGE", 1, 1, _ttt_client_execute_message,
      "MESSAGE <message>       ", "Send a message to everyone."},
 
+    {"STATISTICS", 1, 1, _ttt_client_execute_statistics,
+     "STATISTICS <username>   ", "Lists the statistics for the specified user."},
+
     {"QUIT",    0, 0, _ttt_client_execute_quit,
      "QUIT                    ", "Quit session."},
 
@@ -121,7 +129,7 @@ _ttt_client_execute_helo (ttt_client_t *client,
 
     assert (num_args == 1);
 
-    ttt_client_set_name (client, args[0]);
+    ttt_client_set_username (client, args[0]);
 
     error = ttt_server_register_client (client->server, client);
     if (error)
@@ -129,13 +137,13 @@ _ttt_client_execute_helo (ttt_client_t *client,
     client->registered = TRUE;
 
     xasprintf (&response, "HELO %s %s %s\r\n",
-	       client->name,
+	       client->username,
 	       ttt_server_get_host (client->server),
 	       ttt_server_get_port (client->server));
     ttt_client_send (client, response);
 
     xasprintf (&notice, "NOTICE USER %s\r\n",
-	       client->name);
+	       client->username);
     ttt_server_broadcast (client->server, notice);
 
     free (notice);
@@ -164,6 +172,33 @@ _ttt_client_execute_who (ttt_client_t *client,
     return TTT_ERROR_NONE;
 }
 
+static ttt_error_t
+_ttt_client_execute_statistics (ttt_client_t *client,
+				char         **args,
+				int          num_args)
+{
+    char *response;
+    char *username;
+    ttt_error_t error;
+
+    assert (num_args == 1);
+
+    username = args[0];
+
+    if (!client->registered)
+	return TTT_ERROR_NO_NAME_SET;
+
+    error = ttt_server_statistics (client->server, username, &response);
+    if (error)
+	return error;
+
+    ttt_client_send (client, response);
+
+    free (response);
+
+    return TTT_ERROR_NONE;
+}
+
 static ttt_error_t
 _ttt_client_execute_message (ttt_client_t  *client,
 			     char	   **args,
@@ -181,7 +216,7 @@ _ttt_client_execute_message (ttt_client_t  *client,
     ttt_client_send (client, response);
 
     xasprintf (&notice, "NOTICE MESSAGE %s \"%s\"\r\n",
-	      client->name,
+	      client->username,
 	      args[0]);
     ttt_server_broadcast (client->server, notice);
 
@@ -250,13 +285,28 @@ _ttt_client_execute_version (ttt_client_t  *client,
 			     int	   num_args)
 {
     char *response;
+    char *clientversion;
+    int version;
+    int i;
 
     assert (num_args == 1);
 
-    /* XXX: Argument is being ignored.
-       This is not completely implemented. */
+    clientversion = args[0];
+
+    /* Verify that provided version arg is a positive integer */
+    for (i = 0; i < strlen(clientversion); i++)
+	if (!isdigit (clientversion[i]))
+	    return TTT_ERROR_SYNTAX;
+
+    version = atoi (clientversion);
+
+    if (version < 1)
+	return TTT_ERROR_SYNTAX;
+
+    if (version > TTT_SERVER_PROTOCOL_VERSION)
+	version = TTT_SERVER_PROTOCOL_VERSION;
 
-    xasprintf (&response, "VERSION 1\r\n");
+    xasprintf (&response, "VERSION %d\r\n", version);
     ttt_client_send (client, response);
 
     free (response);
@@ -276,7 +326,7 @@ _ttt_client_execute_quit (ttt_client_t *client,
 	return TTT_ERROR_QUIT_REQUESTED;
     
     xasprintf (&notice, "NOTICE QUIT %s\r\n",
-	       client->name);
+	       client->username);
     ttt_server_broadcast (client->server, notice);
     
     free (notice);
@@ -306,7 +356,7 @@ _ttt_client_init (ttt_client_t	*client,
     client->request_strings = NULL;
     client->num_request_strings = 0;
 
-    client->name = NULL;
+    client->username = NULL;
     client->registered = FALSE;
     client->num_wins = 0;
 }
@@ -319,8 +369,8 @@ _ttt_client_fini (ttt_client_t *client)
     if (client->registered)
 	ttt_server_unregister_client (client->server, client);
 
-    free (client->name);
-    client->name = NULL;
+    free (client->username);
+    client->username = NULL;
 
     yylex_destroy (client->scanner);
     shutdown (client->socket, SHUT_RDWR);
@@ -476,17 +526,17 @@ ttt_client_send (ttt_client_t *client, const char *message)
 
 /* Exported: See ttt-client.h for documentation. */
 const char*
-ttt_client_get_name (ttt_client_t *client)
+ttt_client_get_username (ttt_client_t *client)
 {
-    return client->name;
+    return client->username;
 }
 
 /* Exported: See ttt-client.h for documentation. */
 void
-ttt_client_set_name (ttt_client_t *client, const char *name)
+ttt_client_set_username (ttt_client_t *client, const char *username)
 {
-    free (client->name);
-    client->name = xstrdup (name);
+    free (client->username);
+    client->username = xstrdup (username);
 }
 
 /* Exported: See ttt-client.h for documentation. */
diff --git a/src/ttt-client.h b/src/ttt-client.h
index d4e3346..c759650 100644
--- a/src/ttt-client.h
+++ b/src/ttt-client.h
@@ -43,11 +43,11 @@ ttt_client_send (ttt_client_t *client, const char *message);
 
 /* Get a client's name. */
 const char*
-ttt_client_get_name (ttt_client_t *client);
+ttt_client_get_username (ttt_client_t *client);
 
 /* Set a client's name. */
 void
-ttt_client_set_name (ttt_client_t *client, const char *name);
+ttt_client_set_username (ttt_client_t *client, const char *username);
 
 /* Return the client's win count */
 int
diff --git a/src/ttt-error.c b/src/ttt-error.c
index 21d30e6..d3e57cb 100644
--- a/src/ttt-error.c
+++ b/src/ttt-error.c
@@ -28,25 +28,25 @@ ttt_error_string (ttt_error_t error)
     case TTT_ERROR_NONE:
 	return "ERROR NONEi\r\n";
     case TTT_ERROR_NO_NAME_SET:
-	return "ERROR NONAMESET\r\n";
+	return "ERROR NO_NAME_SET\r\n";
     case TTT_ERROR_INVALID_NAME:
-	return "ERROR INVALIDNAME\r\n";
+	return "ERROR INVALID_NAME\r\n";
     case TTT_ERROR_COMMAND:
 	return "ERROR COMMAND\r\n";
     case TTT_ERROR_SYNTAX:
 	return "ERROR SYNTAX\r\n";
     case TTT_ERROR_NOT_NUMBER:
-	return "ERROR NOTNUMBER\r\n";
+	return "ERROR NOT_NUMBER\r\n";
     case TTT_ERROR_NOT_GRID:
-	return "ERROR NOTGRID\r\n";
+	return "ERROR NOT_GRID\r\n";
     case TTT_ERROR_NO_USER:
-	return "ERROR NOUSER\r\n";
+	return "ERROR NO_USER\r\n";
     case TTT_ERROR_NOT_IN_GAME:
-	return "ERROR_NOTINGAME\r\n";
+	return "ERROR_NOT_IN_GAME\r\n";
     case TTT_ERROR_NOT_PLAYING:
-	return "ERROR_NOTPLAYING\r\n";
+	return "ERROR_NOT_PLAYING\r\n";
     case TTT_ERROR_NOT_YOUR_TURN:
-	return "ERROR NOTYOURTURN\r\n";
+	return "ERROR NOT_YOUR_TURN\r\n";
     /* Not an actual protocol errror, so this should never happen. */
     case TTT_ERROR_QUIT_REQUESTED:
 	ASSERT_NOT_REACHED;
diff --git a/src/ttt-server.c b/src/ttt-server.c
index 23fd138..8130420 100644
--- a/src/ttt-server.c
+++ b/src/ttt-server.c
@@ -56,25 +56,25 @@ ttt_server_register_client (ttt_server_t *server, ttt_client_t *client)
 {
     int i;
     ttt_error_t error = TTT_ERROR_NONE;
-    const char *name;
+    const char *username;
 
     pthread_mutex_lock (&server->mutex);
 
-    name = ttt_client_get_name (client);
+    username = ttt_client_get_username (client);
 
-    assert (name != NULL);
+    assert (username != NULL);
 
-    if (name[0] == '\0')
+    if (username[0] == '\0')
 	return TTT_ERROR_INVALID_NAME;
 
     for (i = 0; i < server->num_clients; i++) {
-	if (strcmp (ttt_client_get_name (server->clients[i]), name) == 0) {
+	if (strcmp (ttt_client_get_username (server->clients[i]), username) == 0) {
 	    error = TTT_ERROR_INVALID_NAME;
 	    goto CLEANUP_LOCK;
 	}
     }
 
-    printf ("Client %s has joined.\r\n", name);
+    printf ("Client %s has joined.\r\n", username);
 
     server->num_clients++;
 
@@ -110,7 +110,7 @@ ttt_server_unregister_client (ttt_server_t *server, ttt_client_t *client)
 
     assert (i < server->num_clients);
 
-    printf ("Client %s has left.\r\n", ttt_client_get_name (client));
+    printf ("Client %s has left.\r\n", ttt_client_get_username (client));
 
     memmove (&server->clients[i], &server->clients[i+1],
 	     (server->num_clients - i - 1) * sizeof (ttt_client_t *));
@@ -146,10 +146,9 @@ ttt_server_who (ttt_server_t *server)
     xasprintf (&response, "WHO");
 
     for (i = 0; i < server->num_clients; i++)
-	xasprintf (&response, "%s %s %d",
+	xasprintf (&response, "%s %s",
 		   response,
-		   ttt_client_get_name (server->clients[i]),
-		   ttt_client_get_num_wins (server->clients[i]));
+		   ttt_client_get_username (server->clients[i]));
 
     xasprintf (&response, "%s\r\n", response);
 
@@ -158,6 +157,37 @@ ttt_server_who (ttt_server_t *server)
     return response;
 }
 
+/* Exported: See ttt-server.h for documentation. */
+ttt_error_t
+ttt_server_statistics (ttt_server_t *server, const char *username, char **response)
+{
+    ttt_bool_t usernamefound = FALSE;
+    char *client_username;
+    int client_num_wins;
+    int i;
+
+    pthread_mutex_lock (&server->mutex);
+
+    for (i = 0; i < server->num_clients; i++) {
+	client_username = ttt_client_get_username (server->clients[i]);
+	if (strcasecmp (username, client_username) == 0) {
+	    usernamefound = TRUE;
+	    client_num_wins = ttt_client_get_num_wins (server->clients[i]);
+	    xasprintf (response, "STATISTICS %s \"\r\n"
+		       "TICTACTOE WINS %d\r\n\"\r\n",
+		       client_username,
+		       client_num_wins);
+	}
+    }
+
+    pthread_mutex_unlock (&server->mutex);
+
+    if (!usernamefound)
+	return TTT_ERROR_NO_USER;
+
+    return TTT_ERROR_NONE;
+}
+
 /* Exported: See ttt-server.h for documentation. */
 const char*
 ttt_server_get_host (ttt_server_t *server)
diff --git a/src/ttt-server.h b/src/ttt-server.h
index fef7030..8f2d34b 100644
--- a/src/ttt-server.h
+++ b/src/ttt-server.h
@@ -73,6 +73,22 @@ ttt_server_broadcast (ttt_server_t *server, const char *message);
 const char*
 ttt_server_who (ttt_server_t *server);
 
+/* Generates the statistics for the user. If the function does not
+ * return an error, the response will be allocated in this function
+ * and will need to be free'd by the caller.
+ *
+ * Locking: The server mutex will be acquired and held throughout the
+ * execution of this function. Each client mutex may also be acquired
+ * and held by functions called during the execution of this function.
+ *
+ * Errors: If an error such as an IO error occurs, this function will
+ * not return.
+ */
+ttt_error_t
+ttt_server_statistics (ttt_server_t *server,
+		       const char *username,
+		       char **response);
+
 /* Gets the server hostname.
  *
  */
diff --git a/src/ttt.h b/src/ttt.h
index 1b22ba0..c1662af 100644
--- a/src/ttt.h
+++ b/src/ttt.h
@@ -61,6 +61,8 @@ typedef int ttt_bool_t;
 #define FALSE 0
 #define TRUE  1
 
+#define TTT_SERVER_PROTOCOL_VERSION 1
+
 typedef enum {
     TTT_STATUS_SUCCESS = 0,
     TTT_STATUS_FAILURE,
-- 
2.45.2