Last Project - Go Fish

Overview, Class Instructions, Rubric, Pre-Defined Files

Overview

Updates: 

The official rules of the game are here.

This is a partnered project of no more than 2 members.  The due date will be announced soon, but expect 2 weeks to work on this project, and expect it to be out of 40 points.  At Peninsula you must have the project complete and printed out BEFORE class on Friday, June 4.  At West High you must have the project complete and printed out BEFORE class on Monday, June 14. You will NOT be able to print out or work on the project at all during the class. 

You need to write a networked, multithreaded, graphical go fish game that can support two players across a network.  You will need to write a game server, and a game client. 

The clients must start out by asking for the internet address of the server, and then attempts to initiate a line of communication to the server on the agreed port.  If a connection is made, you create a new thread to wait for information from the server, and then allow the first thread to create the GUI.  Your client is responsible for updating the GUI.  To keep the lists of cards up to date, and when it is the player's turn to go to allow them to click on the button selecting a rank to ask for. 

You will need to be able to understand the following kinds of messages between clients and servers (I will leave it to you to decide the exact format) 
In fact, MUCH of the work you have is to write code to wait for a message, and act appropriatly to the message (pass it on, or do something in reaction)  The clients and servers need to be able to process these messages, and send them when appropriate.

To store the cards you need to create a class called CardList.  CardList needs to be implemented as a Linked List internally (sections 15.3-15.5).

These instructions will be updated throughout the project to help explain and clarify details.   (as well as add extra hints)

Remember, Linked Lists are covered in Sections 15.3-15.5, Network Programming is covered in section 12.12, and Multithreaded Programming is in Section 11.10.

Note:  when you are sending messages across the network through a PrintWriter object, you need to signal the end of each line by sending a flush command in addition to the fact you're already sending a line feed character.  So in this way, writing to another computer through the network is a bit different than writing to the screen. 

Your final project should have the following files/classes

CardList
This class will be a linked list that stores Cards.  For notes on linked lists, read 15.3-15.5 and you can look at this incomplete code for tips on how to do this.  You should include the following methods. 
public void add(Card a) // adds a card to the end of a CardList
public void add(int pos, Card a) // adds a card to a position in the CardList
public Card remove() // removes the last item of the linked list, and returns the item being deleted
public Card remove(int pos) // removes a card from the middle of the CardList and returns the item
public int size() // returns the number of elements in the CardList
public Card get(int pos) // returns the Card stored at node pos
public void set(int pos, Card a) // sets the node at position pos to Card a
public void clear() // resets the CardList to empty

Note: if you follow the example of the code from here, you will find a problem... namely it is impossible to remove the first item.  This is a big problem for any shuffling algorithm as it means you will get an error if you ever try to pick the 1st card to place in the new deck.  There are a few ways to fix this, though I will talk about the easiest hacks to fix this.  The ideal way is that the linked list ignores the first node altogether, and starts counting with the 2nd node (making that node 0), but that requires much more irritating code.  There are easier solutions.  One solution is to hack remove so when a user asks to remove node 0, it will act like an array and copy the data from the next node into this node, and then recursively do the same for all nodes until the 2nd to last node, where it copies the data from the last node, and then removes the last node, in the end it needs to return the value of the first node it overrides.  This solution will work fine for this project.  Alternately you can make sure the shuffling code NEVER tries to pick the first card from the ordered deck, and instead runs for the other 51 cards only.  (add 1 to the value of nextInt to skip the 1st element).  At the end, insert the value of card 0 into a the middle of the shuffled deck.  Also make sure that remove at returns the current data value (but does not bother to actually remove) the first card when the index is 0.  I will try to go over this in detail in class.

Card
This immutable class is provided for you below.  This class stores a single card, its rank and its suit.  It can tell you if two cards are the same. 

Screen
This is a GUI class used by the client that is defined below.  It will not run on its own.  It requires you to create an instance of the class in the Client class in order to display the window.  Of importance here are the 3 JList objects and the JComboBox object which you will have to update, and the single JButton you need to create an ActionListener for in order to ask for a card.  It might be wise to create a class variable of type Screen in the Client class and assign the instance of Screen you create to display the screen to that variable so that Client can access the JLists, the JComboBox and assign an ActionListener to the JButton.  In theory you should not need to modify this class if you do not intend to implement any extensions beyond the basic assignment.

Server
You will need to have these class variables (and quite likely more than this):
private static CardList deck; // the deck of cards
private static CardList player1Sets; // the list of all complete ranks for player 1
private static CardList player2Sets; // the list of all complete ranks for player 2
private static boolean isPlayer1Turn; // basically a boolean to figure out if player 1 or player 2 should play
private static boolean gameplaying; // a boolean to tell you if the game is actually going or not (it should start off as false, and only turn to
true when 2 clients have connected and cards have been dealt)
private static int players = 0; // counts the number of players in the system
private static Random r = new Random(); // a single random object to handle all random number generation
private static PrintWriter player1Writer; // a reference to the printWriter that sends messages to Player 1
private static PrintWriter player2Writer; // a reference to the printWriter that sends messages to Player 2

You will also need a nested class inside of this class to handle creating threads for the clients, the nested class needs to look something like this:

public class clientThread implements Runnable {
private int player = 0;
private BufferedReader fromClient;
private PrintWriter toClient;
public clientThread(int p, BufferedReader from, PrintWriter to) {
fromClient = from;
toClient = to;
player = p;
}
// you need to write this method and anything else!
public void run () {
/* a possible strategy for this is to have an infinite while loop that constantly reads data from the client via readLine and then runs code appropriate for each of the 5 messages depending on what is read in.  This is where you need to do some work figuring out how to define the messages, and then what to do for the five messages in the server */
}
}

Remember, when you create the threads you need to pass the player # they are, the BufferedReader and the PrintWriter for the given socket.  (that way they know what thread they are, and how to communicate)

This is the main class for the Server.  There does not need to be any GUI. 

The server is responsible for
The client will need to be able to ask for a rank of the other client through the server.  The server will need to pass the message to the other client, and the client is responsible for figuring out if they have those cards, and if so, they must send a message to the first client of which cards they have, and remove those cards from the 2nd client's hand.  The server passed that message to the client that asked.  If the 2nd client does not have any cards, it sends a message to the server that it has none, and then the server passes the next card on its deck to the 1st client (Go Fish!).  Every time a client has a new complete set of one of the ranks, it informs the server, and the server updates its CardList of ranks that that client has, and then sends a message to the other client so it can update the list of the cards that the other player has complete.

Some tips:  in order to shuffle the deck, first create a CardList with all 52 cards in order, then create a new empty CardList.  Randomly remove a card from the first deck and place it onto the new CardList.  Do this 52 times.  Each time you pick a card make sure you pick a random card based on the CURRENT SIZE of the first deck.  (every time you remove one the size decreases by 1)

In addition you might want to have this code in your public static void main(String[] args) to make the game easier to play:
System.out.println(InetAddress.getLocalHost().getHostAddress());
This will print out the ip address of the server (hopefully) when you start up the game.

The actual methods for the Server class can be defined in any way, but realistically I'd suggest have one for creating the CardList (defining all 52 cards), one to shuffle the same CardList, and then a public static void main(String[] args) that does the following:
Client
This class should probably have at least the following class variables (and probably more)
private static Screen myScreen; // a reference to the Screen object created
private static CardList myHand; // a CardList of your hand
private static CardList mySets; // your completed sets
private static CardList theirSets; // their completed sets
private static boolean ismyturn = false; // when this is true, you can accept clicks on the button asking for a suit
private static PrintWriter toServer; // to send messages to the server (created after the socket object is created)
private static BufferedReader fromServer; // to recieve messages from the server (created after the socket object is created)

You will also need at least two nested classes, a class to be an ActionListener for the JButton, and a Runnable class to handle the network thread.  The network thread class will look very similiar to the one defined for the server, except that you do not care what player # you are here and since this is a nested class, you can use the toServer and fromServer references from the class you are nested within so you do not need to bother with the variables inside of the thread, nor do you have to bother with a non-standard constructor.  (so those references should be excised here).  The actionListener will be like any normal one, except that you will need to say myScreen.askForButton.addActionListener(new {thenameofyouractionlistenerclasshere}()); to actually assign it.  Also make sure to check if it is your turn or not in the actionPerformed BEFORE you go around and request the card. 

Basically all the network thread will have to do is wait for messages from the server, and react to them, usually by updating the display, but also to signal that is your turn and such. 

Before you even define the connection to the server or create the Screen object, you need to know how to reach the server, so you will need to ask the user via a JOptionPane for the name of the server.  Use that in creating your Socket object to help you connect.


Rough Rubric

Big Picture points
Notation of which person coded which methods - 2 points
Modules well commented - 4 points
Entire program compiles successfully - 4 points
Program executes as expected - 6 points
Total - 16 points

Module points
CardList  Total - 8 points
Implemented as a linked list - 4 points
All operations properly defined and execute correctly - 4 points
Server Total - 14 points
Properly defines the deck and shuffles it - 4 points
Properly accepts connections from 2 players and launched a thread to handle messages from each - 4 points
Properly processes messages between clients - 4 points
Client Total - 8 points
Opens up a socket to the server and launches a thread to process messages - 4 points
Properly populates the Screen with the up to date information and handles the button click - 4 points
Total - 30 points

Basic program grand total: 46 points (out of 40)

If you include ANY code you or your partner did not write, I can and will give 0 points for this project for all members.

Extra credit extensions (ONLY implement these when and if the main program is complete, and with permission from the teacher)

Files Pre-Defined

A basic workup of code for the client's GUI is as follows (note another class needs to actually create the screen by saying new Screen()):

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

public class Screen extends JFrame{
  public JPanel mainPanel = new JPanel();
  public BorderLayout borderLayout1 = new BorderLayout();
  public JPanel selectRankPanel = new JPanel();
  public JButton askForButton = new JButton();
  public JComboBox rankSelectList = new JComboBox();
  public GridLayout gridLayout1 = new GridLayout();
  public JPanel cardDisplayPanel = new JPanel();
  public BorderLayout borderLayout2 = new BorderLayout();
  public JPanel activeCardsPanel = new JPanel();
  public JList activeCardsList = new JList();
  public BorderLayout borderLayout3 = new BorderLayout();
  public JLabel activeCardsLabel = new JLabel();
  public JPanel otherCardsPanel = new JPanel();
  public JPanel yourSetsPanel = new JPanel();
  public JPanel theirSetsPanel = new JPanel();
  public GridLayout gridLayout2 = new GridLayout();
  public BorderLayout borderLayout4 = new BorderLayout();
  public BorderLayout borderLayout5 = new BorderLayout();
  public JLabel yourSetsLabel = new JLabel();
  public JLabel theirSetsLabel = new JLabel();
  public JList yourSetsList = new JList();
  public JList theirSetsList = new JList();
  public Screen() {
    this.setSize(400, 400);
    this.setState(Frame.NORMAL);
    this.setTitle("Go Fish Client");
    mainPanel.setPreferredSize(new Dimension(400, 400));
    mainPanel.setLayout(borderLayout1);
    selectRankPanel.setPreferredSize(new Dimension(100, 30));
    selectRankPanel.setLayout(gridLayout1);
    askForButton.setPreferredSize(new Dimension(95, 25));
    askForButton.setText("Ask for a");
    rankSelectList.setPreferredSize(new Dimension(95, 21));
    cardDisplayPanel.setLayout(borderLayout2);
    activeCardsPanel.setPreferredSize(new Dimension(10, 150));
    activeCardsPanel.setLayout(borderLayout3);
    activeCardsList.setBorder(BorderFactory.createLoweredBevelBorder());
    activeCardsList.setPreferredSize(new Dimension(0, 0));
    activeCardsLabel.setText("Active Cards");
    otherCardsPanel.setLayout(gridLayout2);
    yourSetsPanel.setLayout(borderLayout4);
    theirSetsPanel.setLayout(borderLayout5);
    yourSetsLabel.setRequestFocusEnabled(true);
    yourSetsLabel.setText("Your sets");
    theirSetsLabel.setText("Other Player\'s Sets");
    yourSetsList.setBorder(BorderFactory.createLoweredBevelBorder());
    theirSetsList.setBorder(BorderFactory.createLoweredBevelBorder());
    this.getContentPane().add(mainPanel, BorderLayout.CENTER);
    mainPanel.add(selectRankPanel, BorderLayout.SOUTH);
    selectRankPanel.add(askForButton, null);
    selectRankPanel.add(rankSelectList, null);
    mainPanel.add(cardDisplayPanel, BorderLayout.CENTER);
    cardDisplayPanel.add(activeCardsPanel, BorderLayout.NORTH);
    activeCardsPanel.add(activeCardsList, BorderLayout.CENTER);
    activeCardsPanel.add(activeCardsLabel, BorderLayout.NORTH);
    cardDisplayPanel.add(otherCardsPanel, BorderLayout.CENTER);
    otherCardsPanel.add(yourSetsPanel, null);
    yourSetsPanel.add(yourSetsLabel, BorderLayout.NORTH);
    yourSetsPanel.add(yourSetsList, BorderLayout.CENTER);
    otherCardsPanel.add(theirSetsPanel, null);
    theirSetsPanel.add(theirSetsLabel, BorderLayout.NORTH);
    theirSetsPanel.add(theirSetsList, BorderLayout.CENTER);
    this.setDefaultCloseOperation(this.EXIT_ON_CLOSE);
    this.setVisible(true);
  }
}

The Card class is as follows:

public class Card {
  private char rank = 'A';
  private char suit = 'S';
  public static final char RANK_ACE = 'A';
  public static final char RANK_2 = '2';
  public static final char RANK_3 = '3';
  public static final char RANK_4 = '4';
  public static final char RANK_5 = '5';
  public static final char RANK_6 = '6';
  public static final char RANK_7 = '7';
  public static final char RANK_8 = '8';
  public static final char RANK_9 = '9';
  public static final char RANK_10 = '0';
  public static final char RANK_JACK = 'J';
  public static final char RANK_QUEEN = 'Q';
  public static final char RANK_KING = 'K';
  public static final char SUIT_DIAMONDS = 'D';
  public static final char SUIT_SPADE = 'S';
  public static final char SUIT_HEART = 'H';
  public static final char SUIT_CLUBS = 'C';
  public char getRank() {
    return rank;
  }
  public char getSuit() {
    return suit;
  }
  public Card(char arank, char asuit) {
    rank = arank;
    suit = asuit;
  }
  public boolean equals(Card a) {
    return this.rank == a.rank && this.suit == a.suit;
  }
}