+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.
<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,
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>
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
Disconnects the client from the server.
- 1.2.5. Version
+ 1.2.6. Version
VERSION <client-version-number>
INVITE
- Possible errors: NOUSER, BUSY, NONAMESET
+ Possible errors: NO_USER, BUSY, NO_NAME_SET
1.3.2. Accepting an invitation
ACCEPT
- Possible errors: NONAMESET
+ Possible errors: NO_NAME_SET
1.4. In-game commands
_|X|_
X|O|O"
- Possible errors: NOTINGAME, NONAMESET
+ Possible errors: NOT_IN_GAME, NO_NAME_SET
1.4.2. Part
Departs the current game
- Possible errors: NOTINGAME, NONAMESET
+ Possible errors: NOT_IN_GAME, NO_NAME_SET
1.4.3. Making a move
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.
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
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
3.1.1. No name set
- ERROR NONAMESET
+ ERROR NO_NAME_SET
'helo' must be sent before any command other than 'help',
'version', 'quit'.
3.1.2. Invalid name
- ERROR INVALIDNAME
+ ERROR INVALID_NAME
All names must be of non-zero length and must be unique.
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
3.4.1. No such game
- ERROR NOGAME
+ ERROR NO_GAME
A game name was provided that does not exist.
3.5.1. No such user
- ERROR NOUSER
+ ERROR NO_USER
A user name was provided that does not exist.
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.
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
3.6.2.1. Not your turn
- ERROR NOTYOURTURN
+ ERROR NOT_YOUR_TURN
A move was submitted during the other player's turn
/ /---- 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
char **request_strings;
int num_request_strings;
- char *name;
+ char *username;
ttt_bool_t registered;
int num_wins;
};
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,
{"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."},
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)
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 (¬ice, "NOTICE USER %s\r\n",
- client->name);
+ client->username);
ttt_server_broadcast (client->server, notice);
free (notice);
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,
ttt_client_send (client, response);
xasprintf (¬ice, "NOTICE MESSAGE %s \"%s\"\r\n",
- client->name,
+ client->username,
args[0]);
ttt_server_broadcast (client->server, notice);
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);
return TTT_ERROR_QUIT_REQUESTED;
xasprintf (¬ice, "NOTICE QUIT %s\r\n",
- client->name);
+ client->username);
ttt_server_broadcast (client->server, notice);
free (notice);
client->request_strings = NULL;
client->num_request_strings = 0;
- client->name = NULL;
+ client->username = NULL;
client->registered = FALSE;
client->num_wins = 0;
}
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);
/* 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. */
/* 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
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;
{
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++;
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 *));
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);
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)
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.
*
*/
#define FALSE 0
#define TRUE 1
+#define TTT_SERVER_PROTOCOL_VERSION 1
+
typedef enum {
TTT_STATUS_SUCCESS = 0,
TTT_STATUS_FAILURE,