Reading

Required: These notes!
Recommended: Java in a Nutshell, TO APPEAR.

Before we jump into Object Oriented Programming ...

Before we jump into Object Oriented Programming, we will round out our introduction to the structure of Java programs by consdering how we would write the kind of procedural programs we're used to in the Java lanaguage / run-time environment. In theory this is something you should not do! You should use OOP with Java. However, seeing how the pieces fit together in a familiar kind of program will be valuable, and put you in the right place from which to begin our investigation of OOP.
  1. classes as collections of functions (collections of methods in Java parlance)
  2. calling methods/functions of one class from within another
  3. classes as collections of data, i.e. classes as structs
  4. understanding how Java programs involving multiple class files run

A problem to motivate us

Suppose we wanted to write a program that allowed the user to play the card game Blindman's bluff poker against a single AI opponent. The idea is that each player gets a card, but only gets to see their opponent's card, not their own. A bet is placed by one player, then the next player raises, calls or folds. In case of a raise, the first player can either call or fold. "Call" means match the bet, "fold" means give up (other player gets money), and "raise" means match the bet and then add some more. Let's say there's a 10cent ante (initial amount put in before the game starts) and a 50cent maximum bet. In our game, the AI and the user both start with $5. The game ends when one player is out of money, or at any point the user wants to stop. We might not finish the game in class, but it'll give us a motivational problem. We should certainly get through the gameplay minus the betting.

Representing a cards: We're going to use the same representation of a card as an int that we did in IC210, summarized below:

  1. represent suits by number according to these rules: 0 = ♣, 1 = ♦, 2 = ♥, 3 = ♠
  2. represent face values according to these rules: 2 = 2, 3 = 3, ..., 10 = 10, 11 = J(jack), 12 = Q(queen), 13 = K(king), 14 = A(ace)
  3. represent a card as a number by the rule: cardvalue = 20 × suit + facevalue and (suit = cardvalue/20, facevalue=cardvalue%20)
    So, for example, the card Q♥ → (suit=2,facevalue=12) → 20×2 + 12 = 52
    In the other direction, cardnumber 74 = 20×3 + 14 (i.e. 74/20 = 3, 74%20 = 14) → (suit=3,facevalue=14) → A♠
Our Unix terminal program understands "UTF-8" character encodings, which means we can print card suits to the screen. Here are the suits and the corresponding string for printing them in unicode. ♣ ← "\u2663", ♦ ← "\u2666", ♥ ← "\u2665", ♠ ← "\u2660".

A little bottom-up design - Cards: classes as collections of functions ("methods" in java parlance)

Let's do a bit of bottom-up design. We know we're going to want to have functions to take care of basic card manipulation functionality: building a card, returning pretty string form of a card, making and shuffling decks, etc. Bottom-up design says let's go ahead and write those functions and test them before we go on to actually write our game. The structure of Java programs makes this really nice and easy to do! Why? Well you can use a class as a simple way of collecting functions that do similar things into a nice tidy package. The main function in that class can be used simply as a way to test those functions, not as an interesting program in and of itself. Later on, when we're ready to use these functions in a real program, they just stay in their class ... no copying and pasting, no #includes, no separating .h from .cpp files. Very convenient!
public class Card
{
  public static int makeCard(int face, int suit) { return 20*suit + face; }
  public static int cardToSuit(int card) { return card/20; }
  public static int cardToFace(int card) { return card%20; }
  public static String cardToString(int card)
  {
    String[] suits = {"\u2663","\u2666","\u2665","\u2660"};
    String[] faces = {"","","2","3","4","5","6","7","8","9","10","J","Q","K","A"};
    return faces[cardToFace(card)] + suits[cardToSuit(card)];
  }
  public static void main(String[] args)
  {
    int c1 = makeCard(10,0);
    System.out.println(cardToString(c1));
  }
}
Hopefully this code is mostly self-explanatory. Note how the nice Java array-initializer syntax makes it easy to implement cardToString! The main is there to test our functions, and if you compile and run everything looks good:
~/$ javac Card1.java
~/$ java Card1
10♣
The next step is to add functions for making and shuffling decks. That's pretty straightforward. Take a look at the complete version of Card.java.

Calling the Card functions from another file

Now our actual game program is going to want to call these functions. But how? The answer is that outside of the class Card we name these functions by their full names: Card.name. For example, we'd call makeCard like this:
Card.makeCard(3,1)
It's a little wordy, of course, but it allows to easily reuse these functions in any Java program we like! So, let's start off our game by creating, shuffling and printing a deck. This is going to be a new class called Game, defined of course in a new file Game.java.
Game.java Card.java
import java.util.*;

public class Game
{
  public static void main(String[] args)
  {
    Random rand = new Random(System.currentTimeMillis());
    int[] D = Card.makeDeck();
    Card.shuffle(D,rand);
    for(int top = 0; top < D.length; top++)
      System.out.println(Card.cardToString(D[top]));
  }
}
import java.util.*;

public class Card
{
  public static int makeCard(int face, int suit) { return 20*suit + face; }
  public static int cardToSuit(int card) { return card/20; }
  public static int cardToFace(int card) { return card % 20; }
  public static String cardToString(int card) { ... }
  public static int[] makeDeck() { ... }
  public static void shuffle(int[] D, Random rand) { ... }
  public static void main(String[] args)
  {
    Random rand = new Random(System.currentTimeMillis());
    int[] D = makeDeck();
    shuffle(D,rand);
    for(int i = 0; i < 52; i++)
      System.out.println(cardToString(D[i]));
  }
}
So, at this point you have a program that consists of two files Game.java and Car.java. You compile them both, either individually, or together like this:
javac Game.java Card.java
The order in which you compile them is irrelevent. To run the program, you only specify Game, like this:
java Game
What about Card? Well, here's where Java's organization works nicely. When the JVM exectues the call to function Card.makeDeck(), it knows it needs to load the classfile Card.class, because that's where the class Card is defined. As long as both classfiles are in the current directory, Java finds them and loads them as needed.

Players: classes as collections of data (like structs)

We're going to want to represent two players (and maybe more later). For each player we need to represent the amount of money it has and its current card. Hopefully you'd find it natural to use a struct for this in C/C++. In Java, we have classes not structs. We can use classes just like structs, though, i.e. simply as a collection of data. The class for a player will look like this:
public class Player
{
  int card;
  double money;
}
Where we put this definition is a bit up to us. We could put it inside the definition of the class Game, where it would actually be a part of Game. In fact, its full name would be Game.Player. [Note: if we do this, the definition will have to be static class Player { ... }. Why this would be needed in this case will be discussed in a few lessons.] Or we could put it in its own file, Player.java, in which case it would be an independent type, not part of or dependent on anything. We'll chose the latter, as it's the more common and straightforward way of doing things. Now we can go ahead and create an object of type player to represent the user or the AI. Remember that Player pu; only declares the reference pu, it doesn't actually create a player object. That's done with a call to new, like this:
Player pu = new Player();
This is called instantiating and object of type Player. The object itself, i.e. the thing pu refers-to/points-to is said to be an instance of class Player. The class itself is a type, not an object. It is a blueprint for creating Player objects. Only the call to new Player() creates new Player objects. We refer to data members of a Player object with a . like this: pu.money = 5.00;. Here's a program:
Game.java Player.java Card.java
import java.util.*;

public class Game
{
  public static void main(String[] args)
  {
    Random rand = new Random(System.currentTimeMillis());
    int[] D = Card.makeDeck();
    Card.shuffle(D,rand);
    int top = 0;
    Player pu = new Player();
    pu.money = 5.00;
    pu.card = D[top++];
    System.out.println("Player has $" + pu.money + " and card " 
		       + Card.cardToString(pu.card));
  }
}
public class Player
{
  int card;
  double money;
}
import java.util.*;

public class Card
{
  public static int makeCard(int face, int suit) { return 20*suit + face; }
  public static int cardToSuit(int card) { return card/20; }
  public static int cardToFace(int card) { return card % 20; }
  public static String cardToString(int card) { ... }
  public static int[] makeDeck() { ... }
  public static void shuffle(int[] D, Random rand) { ... }
  public static void main(String[] args)
  {
    Random rand = new Random(System.currentTimeMillis());
    int[] D = makeDeck();
    shuffle(D,rand);
    for(int i = 0; i < 52; i++)
      System.out.println(cardToString(D[i]));
  }
}
Once again, we need to compile each .java file, individually or together makes no difference. In fact, Card.java hasn't changed, so it doesn't need to be recompiled. Then we run the program by calling the JVM with Game. The other .class files will be loaded by the JVM as they are needed.
~/$ javac Game.java Player.java
~/$ java Game
Player has $5.0 and card K♥

From here on out it's just programming

From here on out it's just programming. Not much Java-specific. Here's an implementation of the game up to but not including dealing with betting. Note: In one card poker, the high face value wins, ties are broken by comparing suit, for which spades beats hearts which beats diamonds which beats clubs. Check out Game.java which implements all but the betting.

Problems

TO APPEAR