Friday 31 March 2017

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

Now that you know how to play the game, it is time to examine the source code for the game. Since several of the classes are quite long, we will sprinkle comments throughout the code rather than leaving the code till the end.


The APPLET Tag

The APPLET tag for this game is simple. Just name the main class and set the size. That’s it. There aren’t any <param> tags for Scrabblet. Remember, the bigger you make the applet, the nicer the board looks. The aspect ratio should be a little taller than it is wide.

  <applet code=Scrabblet.class width=400 height=450>
  </applet>


Scrabblet.java

The main applet class is found in Scrabblet.java. At almost 300 lines, this is a fairly complicated applet class, even though most of the game logic is left to the Board class, found later in this chapter.

We start with the usual collection of import statements, loading almost every standard Java package. Then we declare Scrabblet to be a subclass of Applet that implements ActionListener.

  import java.io.*;
  import java.net.*;
  import java.awt.*;
  import java.awt.event.*;
  import java.applet.*;

  public class Scrabblet extends Applet implements ActionListener {

Next comes the declaration of a large collection of instance variables. The server is our connection to the web server running our game server. This machine’s name is stored in serverName. The bag represents the shared bag of letters for our game. Our opponent has his or her own copy of the bag, which is initialized with the same random sequence of tiles so the two bags stay in synch. The board is our copy of the playing board. Our opponent also has a copy of the board, and the game keeps them in synch after each turn.

If the network server can’t be accessed, the single flag is set, and the applet plays the game in single-player mode. The boolean ourturn is true whenever it is our turn to play. If a player can’t find a valid word, the player can pass by clicking on Done twice in a row without having any tiles on the board. The seen_pass variable is used to mark if the first Done has been clicked on.

To manage the synchronization of the remote player’s board, we keep a copy of the tiles selected in theirs. Seeing what the other person has in his or her tray is cheating, so no hacking this applet to display the contents of theirs! The two strings, name and others_name, hold our name and our opponent’s name, respectively.

  private ServerConnection server;
  private String serverName;
  private Bag bag;
  private Board board;
  private boolean single = false;
  private boolean ourturn;
  private boolean seen_pass = false;
  private Letter theirs[] = new Letter[7];
  private String name;
  private String others_name;

Next, we declare eight variables used to manage the user interface. These are all AWT components that must be manipulated by the applet in some way. topPanel holds the prompt and the namefield for getting the user’s name at start-up. The done button is used to signify that you are done with your turn. The chat TextField is used to enter chat messages. idList is used to display available opponents. The challenge button is used to attach us to our opponent. The ican Canvas holds the name and copyright notice at start-up.

  private Panel topPanel;
  private Label prompt;
  private TextField namefield;
  private Button done;
  private TextField chat;

  private List idList;
  private Button challenge;
  private Canvas ican;

init( )
The init( ) method is called once and simply sets up the BorderLayout, figures out what Internet host the applet came from, and creates the splash screen canvas.

  public void init() {
    setLayout(new BorderLayout());
    serverName = getCodeBase().getHost();
    if (serverName.equals(""))
      serverName = "localhost";
    ican = new IntroCanvas();
  }

start( )
The start( ) method is called whenever the browser redisplays the page in which the applet is found. The large try block at the beginning is used to catch the case where the network connection fails. If we succeed in making a new ServerConnection and we’ve never run start( ) before, we then set up the screen to prompt for the user’s name. While we are there, we put the splash screen, ican, in the center of the window. In the case where name is not null, that means the user left the page and has now returned. We presume we’ve already got the user’s name and jump right to nameEntered( ), the method that is called when the user types return in the name entry field. The validate( ) at the end makes sure all of the AWT components are updated properly. If an exception was thrown, we presume that the net connection failed and go into single-player mode. The call to start_game( ) gets things rolling.

  public void start() {
    try {
      showStatus("Connecting to " + serverName);
      server = new ServerConnection(this, serverName);
      server.start();
      showStatus("Connected: " + serverName);

      if (name == null) {
        prompt = new Label("Enter your name here:");
        namefield = new TextField(20);
        namefield.addActionListener(this);
        topPanel = new Panel();
        topPanel.setBackground(new Color(255, 255, 200));
        topPanel.add(prompt);
        topPanel.add(namefield);
        add("North", topPanel);
        add("Center", ican);
      } else {
        if (chat != null) {
          remove(chat);
          remove(board);
          remove(done);
        }
        nameEntered(name);
      }
      validate();
    } catch (Exception e) {
      single = true;
      start_Game((int)(0x7fffffff * Math.random()));
    }
  }

stop( )
The stop( ) method is called whenever the user leaves the page with the applet. Here, we just tell the server that we’ve left. We re-create the network connection in start( ) if the user returns to the page later.

  public void stop() {
    if (!single)
      server.quit();
    }
  
add( )
The add( ) method is called by the ServerConnection whenever a new player enters the game. We add the player’s name to our List object. Pay special attention to the formatting of the string in add( ). We use that later to extract certain IDs from the list.

  void add(String id, String hostname, String name) {
    delete(id); // in case it is already there.
    idList.add("(" + id + ") " + name + "@" + hostname);
    showStatus("Choose a player from the list");
  }

delete( )
The delete( ) method is called when a player no longer wants to be identified as available for play. This happens when a player quits or decides to play with someone else. Here, we hunt down the id string in our list by extracting the values inside parentheses. If there are no more names on the list (and we aren’t playing the game already: bag == null), then we display a special message telling the user to hang out until someone comes to make a challenge.

  void delete(String id) {
    for (int i = 0; i < idList.getItemCount(); i++) {
      String s = idList.getItem(i);
      s = s.substring(s.indexOf("(") + 1, s.indexOf(")"));
      if (s.equals(id)) {
        idList.remove(i);
        break;
      }
    }
    if (idList.getItemCount() == 0 && bag == null)
      showStatus("Wait for other players to arrive.");
  }

getName( )
The getName( ) method is very similar to delete( ), except it simply extracts the name part of the item and returns it. If the id is not found, then null is returned.

  private String getName(String id) {
    for (int i = 0; i < idList.getItemCount(); i++) {
      String s = idList.getItem(i);
      String id1 = s.substring(s.indexOf("(") + 1, s.indexOf(")"));
      if (id1.equals(id)) {
        return s.substring(s.indexOf(" ") + 3, s.indexOf("@"));
      }
    }
    return null;
  }

challenge( )
The challenge( ) method is called by the ServerConnection whenever another player challenges us to a game. We could have made this method more complicated, so that it would prompt the user to accept or refuse the challenge, but instead the challenge is automatically accepted. Notice that the random seed we use to start the game is passed back to the other player in the accept( ) method. This is used by both sides to initialize the random state of the tile bag to ensure a synchronous game. We call server.delete( ) to ensure that we are no longer solicited by other players wanting to play against us. Notice also that we cede the starting turn to the challenger by setting ourturn to false.

  // we've been challenged to a game by "id".
  void challenge(String id) {
    ourturn = false;
    int seed = (int)(0x7fffffff * Math.random());
    others_name = getName(id); // who was it?
    showStatus("challenged by " + others_name);

    // put some confirmation here...

    server.accept(id, seed);
    server.delete();
    start_Game(seed);
  }

accept( )
accept( ) is the method called on the remote side in response to the server.accept( ) call just mentioned. Just as the other player deleted himself or herself from the list of available players, so must we call server.delete( ). We take the first turn by setting ourturn to true.

  // our challenge was accepted.
  void accept(String id, int seed) {
    ourturn = true;
    others_name = getName(id);
    server.delete();
    start_Game(seed);
  }

chat( )
The chat( ) method is called by the server whenever the opponent types in his or her chat window. In this implementation, the method simply shows the chat message in the browser’s status message. In the future, it might be nice to log these into a TextArea.

  void chat(String id, String s) {
    showStatus(others_name + ": " + s);
  }

move( )
The move( ) method is called once for each tile your opponent plays. It looks through the letters saved in theirs to find the one used. If the square is already occupied, the tile is returned to the player’s tray. Otherwise, the opponent’s letter is moved onto the board permanently. Next, the tile is replaced in theirs by bag.takeOut( ). If the bag is empty, a status message appears. The board is repainted to show the new tiles on it. Note that no scoring is done based on the placement of these tiles. The applet waits until turn( ) is called to give the score.

  // the other guy moved, and placed 'letter' at (x, y).
  void move(String letter, int x, int y) {
    for (int i = 0; i < 7; i++) {
      if (theirs[i] != null && theirs[i].getSymbol().equals
                                                     (letter)) {
        Letter already = board.getLetter(x, y);
        if (already != null) {
          board.moveLetter(already, 15, 15); // on the tray.
        }
        board.moveLetter(theirs[i], x, y);
        board.commitLetter(theirs[i]);
        theirs[i] = bag.takeOut();
        if (theirs[i] == null)
          showStatus("No more letters");
        break;
      }
    }
    board.repaint();
  }

turn( )
The turn( ) method is called after all of the opponent’s tiles are moved. The remote instance of Scrabblet computes the score and sends it to us, so our copy doesn’t have to redo it. Then the score is reported in the status line, and the setEnabled method allows us to take a turn. othersTurn( ) tells the board about the score. The board will reflect the new score at this point.

  void turn(int score, String words) {
    showStatus(others_name + " played: " + words + " worth " +
               score);
    done.setEnabled(true);
    board.othersTurn(score);
  }

quit( )
When the other side quits cleanly, quit( ) is called. It removes the AWT components of the game and jumps right back into nameEntered( ), described next, to get connected back into the player list.

  void quit(String id) {
    showStatus(others_name + " just quit.");
    remove(chat);
    remove(board);
    remove(done);
    nameEntered(name);
  }

No comments:

Post a Comment