Friday 31 March 2017

Scrabblet: The Server Code - Java Tutorials

These last two classes are not part of this applet. Rather, they must be installed and run separately on the web server that the applet classes are to be loaded from. This will require the security rights to install and run so-called daemon processes on the web site, which not many people have. Fortunately, most users of this game will not be setting up their own servers; more likely, they will just play games connected to existing ones.

Server.java
Server is the main class for the server side of Scrabblet. Once this is installed on the web server, you have to run it using the command-line Java interpreter for that system, as shown here:

  C:\java\Scrabblet> java Server

Once running, Server will respond with the following message:

Server listening on port 6564

The Server class starts out by declaring a few variables. The port has to be the same number, 6564, as we saw in ServerConnection. The idcon Hashtable is used to store all of the connections to all of the clients. We use a hash table rather than an array to manage frequent insertion and deletion, which require lots of array copying. The id is incremented for each new connection. This corresponds to the id instance variable we saw earlier in the client.

  import java.net.*;
  import java.io.*;
  import java.util.*;

  public class Server implements Runnable {
    private int port = 6564;
    private Hashtable idcon = new Hashtable();
    private int id = 0;
    static final String CRLF = "\r\n";

addConnection( )
The addConnection( ) method is called every time a new client connects to our applet. This method creates a new instance of ClientConnection, described next, to manage the client. It passes in a reference to this Server, the socket the client connected with, and the current value of id. Finally, it increments the id to have it ready for the next connection.

    synchronized void addConnection(Socket s) {
      ClientConnection con = new ClientConnection(this, s, id);
      // we will wait for the ClientConnection to do a clean
      // handshake setting up its "name" before calling
      // set() below, which makes this connection "live."
      id++;
    }

set( )
The set( ) method is called from ClientConnection in response to the client telling us its “name.” set( ) tracks all of the connections in the idcon hash table, and first it removes this id from the table so that it won’t get duplicates if the client sends its name twice. The method calls setBusy(false) to signify that this connection is available to play a game. Then it walks through all of the other connections by enumerating the keys of the idcon hash table. For all nonbusy connections (those players waiting for an opponent), set( ) sends an “add” protocol message so they will all know about this connection.

    synchronized void set(String the_id, ClientConnection con) {
      idcon.remove(the_id) ; // make sure we're not in there twice.
      con.setBusy(false);
      // tell this one about the other clients.
      Enumeration e = idcon.keys();
      while (e.hasMoreElements()) {
        String id = (String)e.nextElement();
        ClientConnection other = (ClientConnection) idcon.get(id);
        if (!other.isBusy())
          con.write("add " + other + CRLF);
      }
      idcon.put(the_id, con);
      broadcast(the_id, "add " + con);
    }

sendto( )
sendto( ) is called in response to a “to” protocol message. It writes whatever is in the body string directly to the connection identified by dest.

    synchronized void sendto(String dest, String body) {
      ClientConnection con = (ClientConnection)idcon.get(dest);
      if (con != null) {
        con.write(body + CRLF);
      }
    }

broadcast( )
The broadcast( ) method is used to send a single message, in body, to every single connection except the one identified in exclude (typically, the sender).

    synchronized void broadcast(String exclude, String body) {
      Enumeration e = idcon.keys();
      while (e.hasMoreElements()) {
        String id = (String)e.nextElement();
        if (!exclude.equals(id)) {
          ClientConnection con = (ClientConnection) idcon.get(id);
          con.write(body + CRLF);
        }
      }
    }

delete( )
The delete( ) method is used to tell all of the connected clients to forget they ever heard of the_id. This is used by clients that are engaged in a game to remove themselves from other players’ eligibility lists.

    synchronized void delete(String the_id) {
      broadcast(the_id, "delete " + the_id);
    }

kill( )
The kill( ) method is called whenever a client explicitly quits, sending the “quit” message, or when a client simply dies if the browser quits.

    synchronized void kill(ClientConnection c) {
      if (idcon.remove(c.getId()) == c) {
        delete(c.getId());
      }
    }

run( )
The run( ) method is the main loop of the server. It creates a new socket on port 6564
and goes into an infinite loop accepting socket connections from clients. It calls
addConnection( ) with each socket that it accepts.

    public void run() {
      try {
        ServerSocket acceptSocket = new ServerSocket(port);
        System.out.println("Server listening on port " + port);
        while (true) {
          Socket s = acceptSocket.accept();
          addConnection(s);
        }
      } catch (IOException e) {
        System.out.println("accept loop IOException: " + e);
      }
    }

main( )
main( ) is, of course, the method run by the Java command-line interpreter. It creates a new instance of Server and launches a new Thread to run it.

    public static void main(String args[]) {
      new Thread(new Server()).start();
      try {
        Thread.currentThread().join();
      } catch (InterruptedException e) { }
    }
  }


ClientConnection.java

This class is the mirror image of ServerConnection in the applet. One of these is created for each client. Its job is to manage all of the I/O to and from a client. The private instance variables hold all of the states about this client. The Socket is stored in sock. The buffered reader and output streams are stored in in and out. The host name of the client machine is kept in host. A reference to the Server instance that created this client is held in server. The name of the player on this client is stored in name, while the player’s automatically assigned ID number is held in id. The busy Boolean variable stores whether or not this client is actively engaged in a game.

  import java.net.*;
  import java.io.*;
  import java.util.*;

  class ClientConnection implements Runnable {
    private Socket sock;
    private BufferedReader in;
    private OutputStream out;
    private String host;
    private Server server;
    private static final String CRLF = "\r\n";
    private String name = null; // for humans
    private String id;
    private boolean busy = false;

ClientConnection( )
The constructor saves the reference to the server and socket and remembers the unique ID. We wrap an InputStreamReader and a BufferedReader around the input so that it can call readLine( ) on it. Then it writes the id back to the client to let it know what number it is. Finally, it creates and starts a new Thread to handle this connection.

    public ClientConnection(Server srv, Socket s, int i) {
      try {
        server = srv;
        sock = s;
        in = new BufferedReader(new
                            InputStreamReader(s.getInputStream()));
        out = s.getOutputStream();
        host = s.getInetAddress().getHostName();
        id = "" + i;

        // tell the new one who it is...
        write("id " + id + CRLF);

        new Thread(this).start();
      } catch (IOException e) {
        System.out.println("failed ClientConnection " + e);
      }
    }

toString( )
We override toString( ) so that we can have a clean representation of this connection for logging.

    public String toString() {
      return id + " " + host + " " + name;
    }

getHost( ), getId( ), isBusy( ), and setBusy( )
We wrap host, id, and busy in public methods to allow read-only access.

    public String getHost() {
      return host;
    }

    public String getId() {
      return id;
    }

    public boolean isBusy() {
      return busy;
    }

    public void setBusy(boolean b) {
      busy = b;
    }

close( )
The close( ) method is called if the client explicitly quits or if we get an exception reading from the socket. We call kill( ) in the server, which removes us from any lists. Then we close the socket, which also closes both the input and output streams.

    public void close() {
      server.kill(this);
      try {
        sock.close(); // closes in and out too.
      } catch (IOException e) { }
    }

write( )
To write a string to a stream, we have to convert it to an array of bytes, using getBytes( ).

    public void write(String s) {
      byte buf[];
      buf = s.getBytes();
      try {
        out.write(buf, 0, buf.length);
      } catch (IOException e) {
        close();
      }
    }

readline( )
The readline( ) method merely converts the IOException from readLine( ) into a null return value.

    private String readline() {
      try {
        return in.readLine();
      } catch (IOException e) {
        return null;
      }
    }

Keywords
This section is very similar to the same part of the ServerConnection class, which represents the other end of the wire. The static variables and static block shown here are used to initialize the keys Hashtable with a mapping between the strings in keystrings and their position in the array—for example, keys.get(“quit”) == QUIT The lookup( ) method takes care of unpacking the Integer objects into the right int, with –1 meaning the keyword was not found.

    static private final int NAME = 1;
    static private final int QUIT = 2;
    static private final int TO = 3;
    static private final int DELETE = 4;

    static private Hashtable keys = new Hashtable();
    static private String keystrings[] = {
      "", "name", "quit", "to", "delete"
    };
    static {
      for (int i = 0; i < keystrings.length; i++)
        keys.put(keystrings[i], new Integer(i));
    }

    private int lookup(String s) {
      Integer i = (Integer) keys.get(s);
      return i == null ? -1 : i.intValue();
    }

run( )
run( ) has the loop that manages all of the communication with this client. It uses a StringTokenizer to parse the input lines, keying off of the first word in each line. The lookup( ) method just shown is used to look up these first words in the keys hash table. We then switch, based on the integer value of the keyword. The NAME message comes from clients when they first gain a human identity. We call set( ) in the server to get this connection set up. The QUIT message is sent when the client wants to end its server session. The TO message contains a destination ID and a message body to be sent to that client. We call sendto( ) in the server to pass the message along. The last message is DELETE, which is sent by clients that want to continue being connected but no longer want to have their names listed as available to play. run( ) sets the busy flag and calls delete( ) in the server, which notifies the clients that we don’t want to be called.

    public void run() {
      String s;
      StringTokenizer st;
      while ((s = readline()) != null) {
        st = new StringTokenizer(s);
        String keyword = st.nextToken();
        switch (lookup(keyword)) {
        default:
          System.out.println("bogus keyword: " + keyword + "\r");
          break;
        case NAME:
          name = st.nextToken() +
            (st.hasMoreTokens() ? " " + st.nextToken(CRLF) : "");
          System.out.println("[" + new Date() + "] " + this + "\r");
          server.set(id, this);
          break;
        case QUIT:
          close();
          return;
        case TO:
          String dest = st.nextToken();
          String body = st.nextToken(CRLF);
          server.sendto(dest, body);
          break;
        case DELETE:
          busy = true;
          server.delete(id);
          break;
        }
      }
      close();
    }
  }


Enhancing Scrabblet

This applet represents a complete client/server, multiplayer board game. In the future, the code in Server and ServerConnection could be extended in many ways. It could be used to support other turn-based games. It could track and maintain a high-score list for each game. It could be dynamically extensible to understand new protocol verbs. One such example for the game described in this chapter would be to have a lookup function that checked a series of submitted words against a dictionary stored on the server. The server could then be the arbiter for such disputes as whether xyzy is a valid word. You could also construct a word robot, which would reside on the server but act like another player and use the dictionary to generate the best word placement from its current set of seven letters. It could even use a list of pithy quotes to throw into the chat window after each move. You might want to try making some of these enhancements yourself. This applet is intended for entertainment and educational purposes. Any similarity to any and all commercial products is merely coincidental.

Scrabblet: The Source Code - Java Tutorials ( Page 6 of 6 )

hit( )
The hit( ) method returns true if the xp,yp pair passed in falls inside the bounds of this Letter.

    boolean hit(int xp, int yp) {
      return (xp >= x && xp < x + w && yp >= y && yp < y + h);
    }

validate( )
The validate( ) method is used to load the fonts to find out how big the letters are, to decide where to paint them. This information is cached in the private variables discussed earlier. The results of these calculations are used next in paint( ).

    private int font_ascent;
    void validate(Graphics g) {
      FontMetrics fm;
      if (h != lasth) {
        font = new Font("SansSerif", Font.BOLD, (int)(h * .6));
        g.setFont(font);
        fm = g.getFontMetrics();
        font_ascent = fm.getAscent();

        y0 = (h - font_ascent) * 4 / 10 + font_ascent;

        smfont = new Font("SansSerif", Font.BOLD, (int)(h * .3));
        g.setFont(smfont);
        fm = g.getFontMetrics();
        ys0 = y0 + fm.getAscent() / 2;
        lasth = h;
      }
      if (!valid) {
        valid = true;
        g.setFont(font);
        fm = g.getFontMetrics();
        w0 = fm.stringWidth(symbol);
        g.setFont(smfont);
        fm = g.getFontMetrics();
        ws0 = fm.stringWidth("" + points);
        int slop = w - (w0 + gap + ws0);
        x0 = slop / 2;
        if (x0 < 1)
          x0 = 1;
        xs0 = x0 + w0 + gap;
        if (points > 9)
          xs0--;
      }
    }

paint( )
The paint( ) method is called by the board. It passes in an integer, i, which is one of NORMAL, BRIGHT, or DIM from this class. That is used as an index into the colors array to select the base color. A sequence of rectangles is filled to create the appearance of a 3-D highlighted and shadowed button. If points is greater than zero, indicating a nonblank letter, then the main letter is drawn, and its point value is drawn next to it.

    void paint(Graphics g, int i) {
      Color c[] = colors[i];
      validate(g);
      g.setColor(c[NORMAL]);
      g.fillRect(x, y, w, h);
      g.setColor(c[BRIGHT]);
      g.fillRect(x, y, w - 1, 1);
      g.fillRect(x, y + 1, 1, h - 2);
      g.setColor(Color.black);
      g.fillRect(x, y + h - 1, w, 1);
      g.fillRect(x + w - 1, y, 1, h - 1);
      g.setColor(c[DIM]);
      g.fillRect(x + 1, y + h - 2, w - 2, 1);
      g.fillRect(x + w - 2, y + 1, 1, h - 3);
      g.setColor(Color.black);
      if (points > 0) {
        g.setFont(font);
        g.drawString(symbol, x + x0, y + y0);
        g.setFont(smfont);
        g.drawString("" + points, x + xs0, y + ys0);
      }
    }
  }


ServerConnection.java

The last class in the client side of this applet is ServerConnection, which encapsulates the communication with the server and our opponent. There are several variables declared at the beginning of the class. The socket port number to attach to on the server is 6564. CRLF is the Internet constant string representing end-of-line. The I/O streams from and to the server are in and out, respectively. The unique ID by which this connection is known on the server is stored in id. The ID that we are connected to as an opponent is stored in toid. The Scrabblet applet we are connecting for is scrabblet.

  import java.io.*;
  import java.net.*;
  import java.util.*;

  class ServerConnection implements Runnable {
    private static final int port = 6564;
    private static final String CRLF = "\r\n";
    private BufferedReader in;
    private PrintWriter out;
    private String id, toid = null;
    private Scrabblet scrabblet;

ServerConnection( )
The ServerConnection constructor takes the name of an Internet site to attach to and attempts to open a socket to the right port on that host. If that succeeds, it wraps an InputStreamReader and a BufferedReader around the input and a PrintWriter around the output. If the connection fails, an exception is thrown to the caller.

    public ServerConnection(Scrabblet sc, String site) throws
      IOException {
      scrabblet = sc;
      Socket server = new Socket(site, port);
      in = new BufferedReader(new
               InputStreamReader(server.getInputStream()));
      out = new PrintWriter(server.getOutputStream(), true);
    }

readline( )
The readline( ) method is merely a convenience function that converts the IOException from a readLine( ) into a simple null return.

    private String readline() {
      try {
        return in.readLine();
      } catch (IOException e) {
        return null;
      }
    }

setName( ) and delete( )
The setName( ) method tells the server to associate this name with us, and the delete( ) method is used to remove us from any lists the server is keeping.

    void setName(String s) {
      out.println("name " + s);
    }

    void delete() {
      out.println("delete " + id);
    }

setTo( ) and send( )
The setTo( ) method binds the ID of the opponent. Future send( ) calls will go to this player.

    void setTo(String to) {
      toid = to;
    }

    void send(String s) {
      if (toid != null)
        out.println("to " + toid + " " + s);
    }

challenge( ), accept( ), chat( ), move( ), turn( ), and quit( )
The following short methods send one-line messages from this client to the server, which will in turn send those messages on to our opponent. The challenge message is used to initiate starting a game, and accept is sent in response to a challenge. For each letter that moves, the move message is sent, and then the turn message is sent at the end of each turn. If the client quits or leaves the page with the applet on it, it sends the quit message.

    void challenge(String destid) {
      setTo(destid);
      send("challenge " + id);
    }

    void accept(String destid, int seed) {
      setTo(destid);
      send("accept " + id + " " + seed);
    }

    void chat(String s) {
      send("chat " + id + " " + s);
    }

    void move(String letter, int x, int y) {
      send("move " + letter + " " + x + " " + y);
    }

    void turn(String words, int score) {
      send("turn " + score + " " + words);
    }

    void quit() {
      send("quit " + id); // tell other player
      out.println("quit"); // unhook
    }

start( )
The next method simply starts the thread that manages the client side of the network.

    // reading from server...

    private Thread t;

    void start() {
      t = new Thread(this);
      t.start();
    }

Keywords
The static variables and static block shown here are used to initialize the keys Hashtable with a mapping between the strings in keystrings and their position in the array—for example, keys.get(“move”) == MOVE. The lookup( ) method takes care of unpacking the Integer objects into the right int, with –1 meaning the keyword was not found.

    private static final int ID = 1;
    private static final int ADD = 2;
    private static final int DELETE = 3;
    private static final int MOVE = 4;
    private static final int CHAT = 5;
    private static final int QUIT = 6;
    private static final int TURN = 7;
    private static final int ACCEPT = 8;
    private static final int CHALLENGE = 9;
    private static Hashtable keys = new Hashtable();
    private static String keystrings[] = {
      "", "id", "add", "delete", "move", "chat",
      "quit", "turn", "accept", "challenge"
    };
    static {
      for (int i = 0; i < keystrings.length; i++)
        keys.put(keystrings[i], new Integer(i));
    }

    private int lookup(String s) {
      Integer i = (Integer) keys.get(s);
      return i == null ? -1 : i.intValue();
    }

run( )
run( ) is the main loop of the game’s connection to the server. It goes into a blocking call to readline( ) that will return with a String whenever a line of text comes from the server. It uses a StringTokenizer to break the line into words. The switch statement dispatches us to the right code, based on the first word in the input line. Each of the keywords in the protocol parses the input line differently, and most of them make method calls back into the Scrabblet class to do their work.

    public void run() {
      String s;
      StringTokenizer st;
      while ((s = readline()) != null) {
        st = new StringTokenizer(s);
        String keyword = st.nextToken();
        switch (lookup(keyword)) {
        default:
          System.out.println("bogus keyword: " + keyword + "\r");
          break;
        case ID:
          id = st.nextToken();
          break;
        case ADD: {
            String id = st.nextToken();
            String hostname = st.nextToken();
            String name = st.nextToken(CRLF);
            scrabblet.add(id, hostname, name);
          }
          break;
        case DELETE:
          scrabblet.delete(st.nextToken());
          break;
        case MOVE: {
            String ch = st.nextToken();
            int x = Integer.parseInt(st.nextToken());
            int y = Integer.parseInt(st.nextToken());
            scrabblet.move(ch, x, y);
          }
          break;
        case CHAT: {
            String from = st.nextToken();
            scrabblet.chat(from, st.nextToken(CRLF));
          }
          break;
        case QUIT: {
            String from = st.nextToken();
            scrabblet.quit(from);
          }
          break;
        case TURN: {
            int score = Integer.parseInt(st.nextToken());
            scrabblet.turn(score, st.nextToken(CRLF));
          }
          break;
        case ACCEPT: {
            String from = st.nextToken();
            int seed = Integer.parseInt(st.nextToken());
            scrabblet.accept(from, seed);
          }
          break;
        case CHALLENGE: {
            String from = st.nextToken();
            scrabblet.challenge(from);
          }
          break;
        }
      }
    }
  }

Scrabblet: The Source Code - Java Tutorials ( Page 5 of 6 )

dragLetter( )
The dragLetter( ) method is handled differently than the other mouse-related events. This is mainly due to performance considerations. The goal is to have as smooth an interaction with the user as possible. dragLetter( ) goes to some length to compute the bounding box of where the tile was before this drag plus where it is now. It then directly calls paint(getGraphics( )). This is nonstandard Java applet programming, but it performs much more reliably.

    private void dragLetter(int x, int y) {
      if (pick != null) {
        int ox = pick.x;
        int oy = pick.y;
        pick.move(x + dx, y + dy);
        x0 = Math.min(ox, pick.x);
        y0 = Math.min(oy, pick.y);
        w0 = pick.w + Math.abs(ox - pick.x);
        h0 = pick.h + Math.abs(oy - pick.y);
        paint(getGraphics());
      }
    }

mousePressed( )
In the following code fragment, notice that MyMouseAdapter is an inner class that extends MouseAdapter. It overrides the mousePressed( ) and mouseReleased( ) methods.

The mousePressed( ) method invokes the selectLetter( ) method to do the necessary processing. The x and y coordinates of the current mouse position are obtained from the argument supplied to the mousePressed( ) method.

    class MyMouseAdapter extends MouseAdapter {
      public void mousePressed(MouseEvent me) {
        selectLetter(me.getX(), me.getY());
      }

mouseReleased( )
The mouseReleased( ) method invokes the dropLetter( ) method to do the necessary
processing. The x and y coordinates of the current mouse position are obtained from
the argument supplied to the mouseReleased( ) method.

      public void mouseReleased(MouseEvent me) {
        dropLetter(me.getX(), me.getY());
      }
    }

mouseDragged( )
In the following code fragment, notice that MyMouseMotionAdapter is an inner class that extends MouseMotionAdapter. It overrides the mouseDragged( ) method. The mouseDragged( ) method invokes the dragLetter( ) method to do the necessary processing. The x and y coordinates of the current mouse position are obtained from the argument supplied to the mouseDragged( ) method.

    class MyMouseMotionAdapter extends MouseMotionAdapter {
      public synchronized void mouseDragged(MouseEvent me) {
        dragLetter(me.getX(), me.getY());
      }
    }
  }


Bag.java

The Bag class is very clean compared with Board. It is a simple abstraction for the bag of letters. When you create a Bag, you pass in a random seed, which allows you to create two bags that are random but the same by passing in the same random seed. The random number generator is stored in rand. There are two somewhat strange arrays of integers, named letter_counts and letter_points. Both arrays are 27 slots long. They represent the blank tile in slot 0, and A through Z in 1 through 26. The letter_counts array says how many of each letter are in a full bag. For example, letter_counts[1] is 9, which says there are nine A tiles in the bag. Similarly, the letter_points array maps each letter to its point value. The A tiles are worth only 1 point, and the lone Z is worth 10. There are 100 letters stored in the array called letters. The number of letters actually left in the bag during game play is stored in n.

  import java.util.Random;

  class Bag {
    private Random rand;
    private int letter_counts[] = {
      2, 9, 2, 2, 4, 12, 2, 3, 2, 9, 1, 1, 4, 2,
      6, 8, 2, 1, 6, 4, 6, 4, 2, 2, 1, 2, 1
    };
    private int letter_points[] = {
      0, 1, 3, 3, 2, 1, 4, 2, 4, 1, 8, 5, 1, 3,
      1, 1, 3, 10, 1, 1, 1, 1, 4, 4, 8, 4, 10
    };
    private Letter letters[] = new Letter[100];
    private int n = 0;

Bag( )
The Bag constructor takes the seed and makes a Random object out of it. It then scans through the letter_counts array, making the right number of new Letter objects, being careful to replace the blank tile with an asterisk. It then calls putBack( ) for each letter, to put them in the bag.

    Bag(int seed) {
      rand = new Random(seed);
      for (int i = 0; i < letter_counts.length; i++) {
        for (int j = 0; j < letter_counts[i]; j++) {
          Letter l = new Letter(i == 0 ? '*' : (char)('A' + i - 1),
                                letter_points[i]);
          putBack(l);
        }
      }
    }

takeOut( )
This next method is slightly clever and a little inefficient, but in a noncritical way. takeOut( ) picks a random number between 0 and n –1. It then extracts the letter at that offset from the letters array. It closes the hole over that slot in letters using System.arraycopy( ). Then it decrements n and returns the letter.

    synchronized Letter takeOut() {
      if (n == 0)
        return null;
      int i = (int)(rand.nextDouble() * n);
      Letter l = letters[i];
      if (i != n - 1)
        System.arraycopy(letters, i + 1, letters, i, n - i - 1);
      n--;
      return l;
    }

putBack( )
The putBack( ) method is used by the constructor to put the tiles in the bag originally. It could also be used by a future game enhancement that would let players trade in tiles they were unhappy with in exchange for losing a turn. It simply puts the letter back at the end of the array.

    synchronized void putBack(Letter l) {
      letters[n++] = l;
    }
  }


Letter.java

The Letter class is fairly clean in that it doesn’t know anything about the game or the board. It merely encapsulates the position and visual rendering of a single letter. It uses several static variables to hold information about fonts and sizes. This is done so that the applet doesn’t end up with 100 fonts in memory at once. This has the side effect that a browser page cannot contain two instances of the Scrabblet applet if they each have different sizes. The second one to initialize will overwrite the values in these static variables.

The w and h variables hold the constant width and height of every letter. The font and smfont variables are the AWT font objects for the big letter and the smaller point value. The ints y0 and ys0 store the offset of the baseline of the letter and the points, respectively. A few constants are provided to be passed back into paint( ) to describe which color state to paint in: NORMAL, DIM, and BRIGHT mode.

  import java.awt.*;

  class Letter {
    static int w, h;
    private static Font font, smfont;
    private static int y0, ys0;
    private static int lasth = -1;
    static final int NORMAL = 0;
    static final int DIM = 1;
    static final int BRIGHT = 2;

colors[ ], mix( ), gain( ), and clamp( )
The colors array is initialized statically with nine color objects—three sets of three colors. The mix( ) method is used to take a set of RGB values like 250, 220, 100 and turn them into three colors, which can be used to provide 3-D–like highlights and lowlights. The mix( ) method calls on gain( ) to boost or decimate the brightness of a given color and calls on clamp( ) to make sure it remains in the legal range.

    private static Color colors[][] = {
      mix(250, 220, 100), // normal
      mix(200, 150, 80), // dim
      mix(255, 230, 150) // bright
    };

    private static Color mix(int r, int g, int b)[] {
      Color arr[] = new Color[3];

      arr[NORMAL] = new Color(r, g, b);
      arr[DIM] = gain(arr[0], .71);
      arr[BRIGHT] = gain(arr[0], 1.31);
      return arr;
    }
    private static int clamp(double d) {
      return (d < 0) ? 0 : ((d > 255) ? 255 : (int) d);
    }
    private static Color gain(Color c, double f) {
      return new Color(
        clamp(c.getRed() * f),
        clamp(c.getGreen() * f),
        clamp(c.getBlue() * f));
    }

Instance Variables
The valid flag is used to make sure that all of the sizing variables are set up exactly once, the first time this Letter is painted. There are several variables cached here to keep from having to do lots of computation each time the applet paints—such as, x0, w0, xs0, ws0, and gap—which are all explained in the following comments. The tile Point object is used to remember which square on the 15×15 board this Letter is on. If this variable is null, then the Letter is not on the board. The x,y pair is used to exactly locate the Letter.

    private boolean valid = false;

    // quantized tile position of Letter. (just stored here).
    private Point tile = null;
    int x, y; // position of Letter.
    private int x0;        // offset of symbol on tile.
    private int w0;        // width in pixels of symbol.
    private int xs0;       // offset of points on tile.
    private int ws0;       // width in pixels of points.
    private int gap = 1;   // pixels between symbol and points.

Letter( ), getSymbol( ), and getPoints( )
The symbol is a string that holds the letter displayed, and points is the point value of this letter. These are both initialized by the only constructor and returned by the wrapper methods getSymbol( ) and getPoints( ), respectively.

    private String symbol;
    private int points;
      
    Letter(char s, int p) {
      symbol = "" + s;
      points = p;
    }
    String getSymbol() {
      return symbol;
    }

    int getPoints() {
      return points;
    }

move( ), remember( ), and recall( )
The move( ) method is used to tell this tile where to draw. The remember( ) method, however, is more complicated. It can be called with a null, which means that this tile should “forget” where it was. This indicates that the letter is not in play. Otherwise, it tells which coordinate on the board this letter is occupying. This state is inspected by a call to recall( ).

    void move(int x, int y) {
      this.x = x;
      this.y = y;
    }

    void remember(Point t) {
      if (t == null) {
        tile = t;
      } else {
        tile = new Point(t.x, t.y);
      }
    }

    Point recall() {
      return tile;
    }

resize( )
The resize( ) method is called once by the board in order to tell every letter how big to be. Remember, w and h are static, so this affects all Letter instances at once.

    static void resize(int w0, int h0) {
      w = w0;
      h = h0;
    }