According to the Requirements Spec, the Nuggets game requires two standalone programs: a client and a server.
Our design also includes several modules for each program. We describe the responsibilities, key functions, and data structures of each program and module separately.
We avoid repeating information provided in the requirements spec and do not describe the provided support library.
The client acts in one of two modes:
- spectator, the passive spectator mode described in the requirements spec
- player, the interactive game-playing mode described in the requirements spec
Player:
./client hostname port playername
Spectator:
./client hostname port
See the requirements spec for both the command-line and interactive UI.
- The client responds to keystrokes that are sent to the server.
- The client uses ncurses to render an ASCII interface of the grid
- The grid display consists of a NR+1 by NC grid and a status line (player/spectator)
Inputs:
- The valid player keystrokes are
Q,h,l,j,k,y,u,b,n - Server messages consist of
OK,GRID,GOLD,DISPLAY,QUIT, andERROR
Outputs:
- The client is to send logging and error messages to stderr
- For example, in response to malformatted messages, the client will log an error
- Game summary to stdout after
QUIT - ASCII display vis ncurses
We anticipate the following modules or functions:
- main, which parses the command-line arguments, handles joining the game as a player or spectator, and manages the message loop
- grid, which is a wrapper module for the game's display (renders grid)
- input, which handles keyboard inputs and sends messages to the server
- message, which handles incoming messages
- log, which records useful error messages
- state, which maintains the game state (known/visible grids, player purse, remaining gold, etc.)
if parseArgs(command-line arguments) is true
connect to server
if player
send PLAY message
else
send SPECTATE message
wait for message from server
while game runs:
if input available
read keystroke
send KEY message
if message received
handle the message
if command-line arguments are invalid
log error message to stderr
return false
else
return true
state_t will be used to represent the client's state (see state module below).
counters_t from libcs50 will be used to maintain the purse.
grid_t will be used for grid and visiblegrid (see grid module below).
- send PLAY and SPECTATE messages with various edge cases to ensure that it all works
- input module
- try various keystrokes (valid and invalid) to verify KEY messages are sent properly
- state module
- test getters/setters for grid size, purse, gold remaining, etc.
- grid module
- use test data to render known and visible maps; verify with known output
- use
miniclientto test handling of malformed messages, sequential messages, and transitions between spectator and player - stress testing various inputs to see if the client sends it properly and the server processes/rejects the correct ones
- trying various tests with the master grid and player specific grid to see how they respond to the client's movements/inputs
- Full game with clients and spectators
- Verify GOLD and DISPLAY are correctly updated, QUIT message cleanly exits client, unknown keystrokes are logged
- Try various endgames and stress testing for edge cases
- Test with valgrind to ensure no memory leaks
See the requirements spec for the command-line interface. There is no interaction with the user.
./server map.txt
Inputs
Map file is a text file with NR lines and each line has NC characters (NR x NC gridpoints), representing the rooms and passages in which the game is played. The map file is provided from the command line (map.txt).
Every gridpoint is described by one of these characters:
The server also receives an optional seed to place the nugget within a specific space and have consistent output.
Server is ready to receive message types like PLAY, SPECTATE, and KEY from the client/player. The spectators sends the message SPECTATE to join as a spectator, sending QUIT to the previous spectator and kicking them out if previous exists.
The server updates the clients whenever a player moves or gold is collected. Server sends immediate message to clients in the form of GRID nrows ncols. Server then continues to send messages in GOLD n p r. n p r are positive integers that represent the progress in collecting gold nuggets. n gold nuggets, its purse now has p gold nuggets, and there remain r nuggets left to be found. The value of n may be zero. The value of p shall be initially zero, but will increase when gold is found. The spectator shall always receive n=0, p=0. Also sends information about the display to new clients and all clients constantly: DISPLAY\nstring with the nstring representing the display. If the server quits for any reason it sends this to all clients - QUIT explanation.
The server logs useful information that can be saved into a log file by logging into stderr through this usage ./server 2>server.log map.txt
- main receives messages from the client/player or from a spectator to send them the current state of the world (client specific world for the player). Whenever a player moves, updates are sent to the grid module
- initialize game creates the initial data structures and information required for the player/game
- message looping goes through the inputs by the clients and updates the server/grid based on the information given
The server will run as follows:
execute from a command line
parse command-line arguments, validate parameters
call initializeGame() to set up data structures
initialize the 'message' module
print the port number on which we wait
call message_loop(), to await clients
if all gold is collected
call gameOver() to inform clients the game has ended
clean up
creates the grid data structure specific to the players visibility, starting at a specific grid
initializes the remaining purse data structure (counters wrapper)
update the master game with new player and new data structures
if PLAY
add player and send OK, GRID, GOLD, DISPLAY
if SPECTATE
replace the current spectator
if KEY
move the player or handle the quit message
update the rest of the players about the changes in the map
Described later in the grid module
This will store each player's score, location, and ID
typedef struct {
int x
int y
counters_t playerID
}This will keep track of all players in the game, just needs to be an array of ID's.
- grid module: rendering and visibility checking
- validation of the messages
- verify handling of adding/removing players
- server with mock messages using miniclient
- server with the client giving hardcoded malformed information
- server with the client
- gold depletion, verify QUIT summary
- Full game with clients and spectators
- Trying various endgames and stress testing for edge cases Finally, test with valgrind to ensure no memory leaks
Player who quits before the end of the game gives up their gold, leaving a new pile at their last location.
grid_t* grid_new(FILE* file);
char grid_getPoint(grid_t* grid, int row, int col);
grid_t grid_getVisible(grid_t, int row, int col, char* player ID);
int grid_getRows(grid_t* grid);
int grid_getCols(grid_t* grid);
void grid_delete(grid_t* grid);Creates a new grid using an input map file
grid_t* grid_new(FILE* file)
{
set NR = 0
set NC = 0
create char** coordinateArray
create new grid_t masterGrid
while new file line not NULL
NR++
get length of line
if length of line > NC
NC = length of line
loop through each character of each line
Set character in file to 2D array at coordinateArray[NR][i]
update masterGrid with NC, NR, and coordinateArray
return masterGrid
}
gets the visible grid based on player coordinates
grid_t grid_getVisible(grid_t master, int row, int col, char* player ID)
{
initialize grid_t playerGrid
if ID is spectator, return grid (this may just be done in server)
else if player
loop through 5 x 5 radius from row,col
grid_getPoints from master for each of those points and set playerGrid point equal to those points
use visibility algorithm to determine which other points should be erased
}
Get the char for a certain point in the grid
char grid_getPoint(grid_t* grid, int row, int col)
{
if row or col point beyond range of grid
return null character
else
if grid->points[row][col] is *
char temp = *
grid->points[row][col] = .
return temp
return grid->points[row][col]
}
Returns grid height
int grid_getRows(grid_t* grid)
{
return grid->rows
}
Returns grid width
int grid_getCols(grid_t* grid)
{
return grid->cols
}
Deletes grid
void grid_delete(grid_t* grid)
{
for each row
for each col
free grid->points[row][col]
free grid->points
free grid
}
typedef struct {
int rows
int cols
char** points
} grid_t;handleInput()
read character from stdin
if character is valid (Q, h, j, k, l, y, u, b, n)
send KEY message to server
else if EOF
send KEY Q to server
handleMessage()
if message starts with OK
store player letter in state
else if message starts with GRID
initialize grid dimensions in state and ncurses
else if message starts with GOLD
update purse/gold remaining in state
else if message starts with DISPLAY
extract display string and pass to grid module
else if message starts with QUIT
end ncurses
print summary to stdout
exit program
else if message starts with ERROR
log error message to stderr
else
log malformed message