Skip to content

Latest commit

 

History

History
377 lines (285 loc) · 11.4 KB

File metadata and controls

377 lines (285 loc) · 11.4 KB

CS50 Nuggets

Design Spec

jjen, Spring 2025

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.

Player

The client acts in one of two modes:

  1. spectator, the passive spectator mode described in the requirements spec
  2. player, the interactive game-playing mode described in the requirements spec

User interface

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 and outputs

Inputs:

  • The valid player keystrokes are Q, h, l, j, k, y, u, b, n
  • Server messages consist of OK, GRID, GOLD, DISPLAY, QUIT, and ERROR

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

Functional decomposition into modules

We anticipate the following modules or functions:

  1. main, which parses the command-line arguments, handles joining the game as a player or spectator, and manages the message loop
  2. grid, which is a wrapper module for the game's display (renders grid)
  3. input, which handles keyboard inputs and sends messages to the server
  4. message, which handles incoming messages
  5. log, which records useful error messages
  6. state, which maintains the game state (known/visible grids, player purse, remaining gold, etc.)

Pseudo code for logic/algorithmic flow

client/main module

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
parseArgs - parse command-line arguments
if command-line arguments are invalid
    log error message to stderr
    return false
else
    return true

Major data structures

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).

Testing plan

Unit Testing

  • 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

Integration Testing

  • use miniclient to 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

System Testing

  • 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

Server

User interface

See the requirements spec for the command-line interface. There is no interaction with the user.

./server map.txt

Inputs and outputs

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:

solid rock - interstitial space outside rooms
- a horizontal boundary
| a vertical boundary
+ a corner boundary
. an empty room spot
# an empty passage spot

The server also receives an optional seed to place the nugget within a specific space and have consistent output.

Message inputs/ouputs

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

Functional decomposition into modules

  1. 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
  2. initialize game creates the initial data structures and information required for the player/game
  3. message looping goes through the inputs by the clients and updates the server/grid based on the information given

Pseudo code for logic/algorithmic flow

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

initializeGame()

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

message_loop()

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 

Major data structures

grid_t

Described later in the grid module

player

This will store each player's score, location, and ID

typedef struct {
    int x
    int y
    counters_t playerID
}

players

This will keep track of all players in the game, just needs to be an array of ID's.

Testing plan

Unit Testing

  • grid module: rendering and visibility checking
  • validation of the messages
  • verify handling of adding/removing players

Integration Testing

  • server with mock messages using miniclient
  • server with the client giving hardcoded malformed information
  • server with the client
  • gold depletion, verify QUIT summary

System Testing

  • Full game with clients and spectators
  • Trying various endgames and stress testing for edge cases Finally, test with valgrind to ensure no memory leaks

Extra Credit

Player who quits before the end of the game gives up their gold, leaving a new pile at their last location.

Grid Module

Functional decomposition

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);

Pseudo code for logic/algorithmic flow

grid_new

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
}

grid_get_visible

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          
            
}

grid_get_point

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]
}

grid_get_rows

Returns grid height

int grid_getRows(grid_t* grid)
{
    return grid->rows
}

grid_get_cols

Returns grid width

int grid_getCols(grid_t* grid)
{
    return grid->cols
}

grid_delete

Deletes grid

void grid_delete(grid_t* grid)
{
    for each row
        for each col
            free grid->points[row][col]
    free grid->points
    free grid            
}

Major data structures

typedef struct {
    int rows
    int cols
    char** points    
} grid_t;

input module

Functional decomposition

handleInput()

Pseudo code for logic/algorithmic flow

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

message module

Functional decomposition

handleMessage()

Pseudo code for logic/algorithmic flow

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