Task 3: Socket Communication

_images/3-cards.png

Your task is to implement a multiplayer high-low card game, which consists of a server hosting the game with an arbitrary number of clients as players. The server and the clients communicate via sockets with the defined JSON protocol. At each round, the server randomly generates a new card and asks the players (clients) to guess if the new card is higher than, lower than, or equal to the current card. After every player has responded, the server announces the results and start a new round.

In this task, you will also have to modify your Gradle settings for local usage, and do extensive manual testing for the network logic.

As minimum requirement for passing the functional grading of this task, the following sequence must be possible: 1. Connect a single client to a server 2. Make a guess. 3. Receive and display the updated GameState.

Game Rules

We use a standard set of 52 playing cards, 13 values (1~13) for each of the 4 suits (Clubs, Diamonds, Hearts, and Spades). The cards are compared by rank with suit being the tie-breaker (Clubs < Diamonds < Hearts < Spades). If the player correctly guesses that the next card is higher or lower than the current card, s/he gets 1 additional point; if the player correctly guess that the next card is equal to the current card, s/he gets 25 additional points; otherwise, the player does not get any points in this round.

Client Side

1. Start-up: The default settings of a client should contain an address and a port number to connect the server with, and a username to register the game with. These values can also be configured via command-line arguments with corresponding flags. (A valid username cannot be null or blank.)

2. Joining game: A client sends a request (JoinGameRequest) to the server to join the game after its start-up. The request should contain the name of the client to register the game with.

3. Receiving messages: A client should be able to receive messages from the server and display them on the user’s screen (standard output).

4. Making guesses: If a new round of the game has started, a client listens to the user’s guess via standard input, and sends the user’s guess to the server. The user’s input should be one of “H”, “L”, and “E”, which corresponds to HighLowCardGame.Guess.HIGH, HighLowCardGame.Guess.LOW, and HighLowCardGame.Guess.EQUAL, respectively. Otherwise, the client should ask the user to enter her/his guess again.

5. Connection lost: A client terminates when the connection to the server is lost (IOException is thrown). No extra handling of such situation is required.

The game state is updated on the client side with GameStateNotification messages.

Server Side

1. Start-up: The default settings of the server should contain a port number that the clients can connect with, which can also be configured via command-line arguments with corresponding flags.

2. Hosting game: The server hosts a single high-low card game and manages the game state, including the list of active clients (players) and their connections, guesses and scores.

3. Handling new connections: The server listens to the socket for new incoming connections. When a connection is established, the server reads the request from the client to join the game and checks if the client’s name has already been used by others. If so, the client’s request is rejected and the connection is closed. Otherwise, the server registers the client in the game and notifies the other clients (via PlayerJoinedNotification).

4. Starting a new round: At the beginning of each round, the server notifies all the clients that a new round has started (via GameStateNotification). This notification should contain the results of the previous round (e.g. a client’s score and the correctness of its guess) and the new card on which a client’s guess is based.

5. Processing clients’ guesses: When receiving a client’s guess, the server evaluates the correctness of the guess, update the score of the client correspondingly, and also notifies the other clients (via PlayerGuessedNotification). After every client’s guess has been processed, the server initiates a new round of the game.

6. Client left: When a client’s connection is lost (i.e. encountering an unsuccessful attempt to receive/send messages from/to a client), the server deregisters the client from the game and notifies the other remaining clients (via PlayerLeftNotification).

JSON Communication Protocol

We use JSON as the communication protocol, every message should be encoded in JSON format. Use moshi for that, in version 1.12.0

Client to Server

  1. JoinGameRequest: the message that asks for joining the game.

    {"messageType":"JoinGameRequest","playerName":<NAME>}

    • String <NAME>: the client’s (player’s) name

  2. GuessRequest: the message containing the client’s (player’s) guess.

    {"messageType":"GuessRequest","guess":<GUESS>,"playerName":<NAME>}

    • String <GUESS>: one of "HIGH", "LOW" or "EQUAL"

    • String <NAME>: the client’s (player’s) name

Sever to Client

  1. GameStateNotification: the message announcing a new game state.

    {"messageType":"GameStateNotification","currentCard":<CURRENT_CARD>,"numRounds":<NUM_ROUNDS>,"playerName":<NAME>,"score":<SCORE>}

    • CardJson <CURRENT_CARD>: the JSON representation of the current card as described below

    • int <NUM_ROUNDS>: the number of rounds

    • String <NAME>: the client’s (player’s) name

    • int <SCORE>: the player’s accumulated score

    To represent a card (CardJson) in JSON:

    {"suit":<SUIT>,"value":<VALUE>}

    • String <SUIT>: the suit of the card, should be one of "CLUBS", "DIAMONDS", "HEARTS" or "SPADES"

    • int <VALUE>: the value of the card, should be an integer in the range of 1~13

  2. PlayerJoinedNotification: the message notifying all the clients that a new player just joined.

    {"messageType":"PlayerJoinedNotification","newPlayerName":<NAME>,"numPlayers":<NUM_PLAYERS>}

    • String <NAME>: the name of the player who just joined the game

    • int <NUM_PLAYERS>: the number of active players in the game

  3. PlayerGuessedNotification: the message notifying all the clients that a player just made her/his guess.

    {"messageType":"PlayerGuessedNotification","numNotGuessedPlayers":<NUM_PLAYERS>,"playerGuessed":<NAME>}

    • int <NUM_PLAYERS>: the number of players who have not made a guess

    • String <NAME>: the name of the player who just made her/his guess

  4. PlayerLeftNotification: the message notifying all the clients that a player just left the game.

    {"messageType":"PlayerLeftNotification","numPlayers":<NUM_PLAYERS>,"playerName":<NAME>}

    • int <NUM_PLAYERS>: the number of active players remaining in the game

    • String <NAME>: the name of the player who just left the game

General Requirements

  • The server should be executable as an independent program and manage the complete state of the game.

  • Use sockets for communication.

  • Use Moshi v1.12.0 with JSON as the format for data exchange.

  • Use methods Server#start and Client#start as entry points to your two programs.

  • Do not modify any public method signatures or interfaces. You are not allowed to add method parameters to existing public methods.

  • Documentation of the source code is part of the exercise and will be graded. The Google Java Style Guide must be fulfilled.

  • Your solution must be anonymous. Your name may not appear in the solution.

  • Your solution may not contain compiled data (no build directory or .class files). Please check the content of your ZIP file before uploading.

Helpful Resources

  • JSON (JavaScript Object Notation): a lightweight data-interchange format

  • Moshi (Javadoc): a modern JSON library for Java

  • ServerSocket and Socket: the server and client sockets, which serve as endpoints for communication between machines

  • InputStream and OutputStream: the I/O stream to read/write the messages on