Check out other Tutorials

Tic Tac Toe Tutorial

This tutorial shows you how to add a Tic Tac Toe game within LimeWire to play against a friend using the chat feature to send messages. This tutorial builds upon the LimeWire 5 Friends support (a way to log into an XMPP service to get a list of existing friends).

Image:TTTChallenge.png

The goal of the tutorial isn't to make LimeWire the preeminent Tic Tac Toe application in the world; it was picked for its simplicity. We want to give you an introduction to the feature and hopefully you'll find ways to add more features; do you want to interact with multiple friends in real time? or send messages when friends sign into LimeWire, etc. It's up to you ...

At the end of this tutorial you'll find patches to the complete tutorial and links to extended code examples. In the meanwhile, here are some images for this tutorial:

Signed into Friends Right click (control click) name to challenge
Image:FriendActions.png Image:FriendTicTacToeAction.png

Then Chris who was challenged receives the challenge message in his chat screen (he gets a 'Chat(1)' in the lower right corner):

Image:ChatNotification.png

Incoming chat message notification If Chris declines the challenge If he beats you
Image:ChallengeExtended.png Image:NotInterested.png Image:X_Won.png

Overview Steps

  1. General Logic
  2. Adding Feature Support in Friends
  3. UI
    1. Create panels
    2. Icons
    3. Localizing
    4. UI Actions
    5. Writer
  4. Conversation Interceptor
  5. Event bus and handling events
  6. Receive challenge
    1. Reject
    2. Accept
  7. Challenge a friend
  8. Logging and JUnit Test Cases

General Logic

Each Tic Tac Toe game needs to handshake all actions with the other player. Also, each player must judge when a game was won/lost or ended in a draw (TicTacToeBoard), and must know what buttons and labels to show (controlled in TicTacToePane) since there isn't an intermediary judge/game arbitrator. Every move from one player needs to be sent to the other player, including challenges to play, accepting the game, selecting moves, playing again requests and ending game notifications. Without the handshake, your friend might wait forever thinking you are deciding where to move.

Adding Feature Support in Friends

You need to tell your LimeWire client to support the feature, in this case, the Tic Tac Toe. For example, it would be really annoying for me to send out chat messages to friends who can't play Tic Tac Toe.

Add your support

  • TicTacToeSupport - interface to mark a client supports Tic Tac Toe.
  • TicTacToeFeature - sets the ID to show Tic Tac Toe support.
  • TicTacToeInitializer - adds the Tic Tac Toe as a supported feature for a client.

You need to register with SmackConnectionListener that you support Tic Tac Toe and your initializer includes the feature ID.

//tell that you support Tic Tac Toe
 new TicTacToeInitializer().register(discoInfoListener);

Check a friend's support

You can check which features a friend supports through the FriendPresence hasFeatures call:

for(FriendPresence friendPresence : chatFriend.getFriend().getPresences().values()) {
    if(friendPresence.hasFeatures(TicTacToeFeature.ID)) {
       //...
    } 
 }

User Interface

The UI for Tic Tac Toe uses a JPanel to show the Tic Tac Toe board, action listeners and a message writer to send moves to your friend.

JPanel

The Tic Tac Toe board, TicTacToePane is a JPanel which includes the game panel, buttons and text labels for status updates. The game panel uses MigLayout for the layout, with nine buttons representing the cells for the Tic Tac Toe board. Each cell button has a listener which responds to clicks.

For a black cross and a white background, set the game panel background black and each Tic Tac Toe button has a white background. Then the gap between the buttons creates the black crosses.

Feature creep opportunity: set the icon for the Tic Tac Toe dialog box. For example:

@Resource
 private ImageIcon limeIcon;

Then in Appframe.properties

#The icon to use for all frames or dialogues.
TicTacToePane.limeIcon = icons/limeicon.gif

Layout

This tutorial uses MigLayout for the UI. If you add 'debug' to the miglayout constructor you can see you what's going on internally:

Image:TTTLayoutDebug.png

Here's how the game panel is setup:

private void initGamePanel() {
 
     setLayout(new MigLayout(
                "insets 12 12 6 12, debug",
                "[left]",                         // col constraints
                "[top]12[top]6[top]12[bottom]")); // row constraints
 
     // Initialize game panel with dark background.
     gamePanel.setBackground(Color.BLACK);
 
     gamePanel.setLayout(new MigLayout(
            "insets 0 0 0 0,fill,debug",
            "[left]6[left]6[left]",  // col constraints
            "[top]6[top]6[top]"));   // row constraints
 
     // Add game buttons with light background.  The gap between buttons
     // show up as the grid.
     gameButtons = new TicTacToeJButton[9];
     for (int i = 0; i < gameButtons.length; i++) {
         gameButtons[i] = new TicTacToeJButton();
         gameButtons[i].setPreferredSize(new Dimension(60, 60));
 
         // Change button appearance. 
         gameButtons[i].setBackground(Color.WHITE);
         gameButtons[i].setContentAreaFilled(false);
         gameButtons[i].setFocusPainted(false);
         gameButtons[i].setOpaque(true);
 
         gamePanel.add(gameButtons[i], (((i % 3) == 2)  && i != 8)? "wrap" : null);
     }
     initialCellAppearance();
 }

Icons

If you want to add some UI style points, paint the X or O icon onto the cell buttons. For example, to paint the X icon in XIcon:

public void paintIcon(Component c, Graphics g, int x, int y) {
     // Create graphics.
     Graphics2D g2d = (Graphics2D) g.create();
 
     // Set graphics to use anti-aliasing for smoothness.
     g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
     RenderingHints.VALUE_ANTIALIAS_ON);
 
     // Set line color and thickness.
     float thickness = Math.max(this.size / SIZE_TO_THICKNESS, 1.0f);
     g2d.setColor(this.color);
     g2d.setStroke(new BasicStroke(thickness));
 
     // Create shape.
     Shape backSlash = new Line2D.Double(0, 0, this.size, this.size);
     Shape slash = new Line2D.Double(0, this.size, this.size, 0);
 
     // Draw shape at specified position.
     g2d.translate(x, y);
     g2d.draw(backSlash);
     g2d.draw(slash);
 
     // Dispose graphics.
     g2d.dispose();
 }

and for the O:

public void paintIcon(Component c, Graphics g, int x, int y) {
 ...
 
 // Create shape.
 Shape circle = new Ellipse2D.Double(0, 0, this.size, this.size);
 
 // Draw shape at specified position.
 g2d.translate(x, y);
 g2d.draw(circle);
 
 ...
 }

When you need to mark a winner on the gamePanel, set the background for each winning cell (a TicTacToeJButton) yellow.

setBackground(Color.YELLOW);

Localizing

All text which appears needs to be internationalized for various languages. For example, when you need to update the status to show that your friend is no longer available, you need to localize the text:

statusText.setText(I18n.tr("Your friend signed out of Friends"));

Feature creep opportunity: the UI needs to be properly sized for translations. Some languages use a lot more characters for words which can cause labels and buttons to be cut off. Check out MultiLineLabel.

Tic Tac Toe board UI Actions

TicTacToePane controls when buttons are clickable depending on the progress of the game. If it's your move, set each Tic Tac Toe cell button to visible and enabled.

When the game ends, you get an option to play again (if you lost or went second on the previous game). On the other hand, if your friend wants to play again, your TicTacToePane shows an AgainAction button.

  • TicTacToeCellAction gets which of the nine cells was clicked; sends the move to your friend through a writer.
  • PlayAgainAction appears if you lost a game, or if you tied the game and went second.
  • AgainAction shows if your friend wants to play again.

Here are two Tic Tac Toe boards from Abby (playing as O) and Chris (playing as X). Abby (the loser) sees the play again button. If she wants to play again, then she must do a handshaking with Chris who gets a 'Abby wants to play again' button (not visible at the moment):

Abby's dialog Chris' dialog
Image:X_Won.png Image:X_Won2.png

Writer

TicTacToePane uses a MessageWriter to send data to your Friend. The text is unencrypted chat messages (which makes it possible in theory to play the game without the UI).

Feature creep opportunity: you could add security to hide the clear text so it isn't human readable in the chat window, or don't write to the chat window. If you obscure the text, you could for example, create a 'Three strikes your out' game to guess letters in a word phrase.

/**
  * Sends a string to your friend's chat frame.
  */
 private void sendMessage(String message) {
     try {   
         messageWriter.writeMessage(message);
     } catch(FriendException x) {
         x.printStackTrace();
     }        
 }

Messages

Tic Tac Toe sends various messages, all prefaced with a 'tictactoe-'. For example, you can have the message: 'tictactoe-' + 'O_MOVE:' + '4' to show that O selected the center cell. As seen in the chat window:

me:
tictactoe-Initiate TicTacToe game  
Chris:
tictactoe-Set X:0  
me:
tictactoe-Set O:1  
Chris:
tictactoe-Set X:2  
me:
tictactoe-Set O:3  
Chris:
tictactoe-Set X:4  
me:
tictactoe-Set O:8  
Chris:
tictactoe-Set X:5  
me:
tictactoe-Set O:7  
Chris:
tictactoe-Set X:6  
me:
tictactoe-Play Again

Incoming messages are intercepted in the ConversationPane and routed via the event bus. Here are the Tic Tac Toe messages as defined in TicTacToeMessages:

Incoming Message Why sent Description
BAD_PICK You picked a spot that was already selected You need to pick a different spot
DRAW You drew the game, the other player wants another game You will now be O if you decide to play again
EXIT_GAME Your friend doesn't want to play Your friend closed out his Tic Tac Toe board frame
INITIATE_GAME You challenge a friend to play ConversationPane's Tic Tac Toe interceptor checks for this message
NO_THANKS_GAME Your friend rejects playing the game. In the chat panel, your friend rejected playing
O_MOVE The cell spot 0-8 that O picked What your friend picked as O
O_WON The game has an outcome Whoever was O won
PLAY_AGAIN Person who lost a game, or upon Draw as O wants to play again You want to play again, this time you will be X
PLAY_AGAIN_YES When you hit the 'Again?' button. You agree to play again, this time you will be O
REJECT_GAME You don't want to play, or have left the game early You need to tell the other person you don't want to play
TICTACTOE Prefixes all Tic Tac Toe messages Generic Tic Tac Toe message
X_MOVE The cell spot 0-8 that X picked What your friend picked as X
X_WON The game has an outcome Whoever was X won

Customize Jabber

Feature creep opportunity: Extend the XMPP protocol to allow sending Tic Tac Toe messages (instead of chat messages). (Then you wouldn't need to parse each message.)

Conversation Interceptor

Chat messages from a friend go to ConversationPane handleConversationMessage. Here you need to add a call to intercept all messages to see if it is a Tic Tac Toe message.

if(message.getFriendID().matches(chatFriend.getID())) {
     interceptTicTacToeMessages(message);
 }

The interceptor checks messages for 'TicTacToeMessages.TICTACTOE', and is incoming. If the message is a request to play a game, the interceptor creates a MessageExtendTicTacToeChallenge and publishes it to the event bus as a MessageReceivedEvent. Otherwise, Tic Tac Toe messages are published to the event bus by the TicTacToeSelectedFromFriendEvent.

Note: In order to extend or implement a Message, this tutorial code needs LimeWire's Message interface to be public (currently is protected). Therefore, you need to change Messages visibility.

Event bus and handling events

Using the event bus is both annoying and time saving. It's annoying cause it can be difficult to track where events are published and who catches. But if you know where, and who published and handled the event, life is easy. To use the event bus, you must:

  1. add 'EventAnnotationProcessor.subscribe(this);' in the constructor of a class where you want to handle an event bus event.
  2. add '@EventSubscriber' before the handle method, and make sure the event the method is handling is an argument. For example:
@EventSubscriber
 public void handleTicTacToeSignOffFriendsEvent(TicTacToeSignOffFriendsEvent event) {
    ...
 }
Event Where published Where handled What
TicTacToeSelectedFromFriendEvent ConversationPane's interceptTicTacToeMessages, for a non initiated game TicTacToePane#handleTicTacToeSelectedFromFriendEvent, one of the Messages Used to indicate that a general Tic Tac Toe message was sent
ChallengeToPlayTicTacToeRejectedEvent ChatHyperlinkListener#fireChallengeToPlayTicTacToeRejected ConversationPane#handleChallengeToPlayTicTacToeRejectedEvent You don't want to play
TicTacToeSignOffFriendsEvent ChatDocumentBuilder#appendIsTypingMessage TicTacToePane#handleTicTacToeSignOffFriendsEvent, ConversationPane#handleTicTacToeSignOffFriendsEvent You signed out of Friends and need to tell your friend
MessageReceivedEvent ConversationPane#interceptTicTacToeMessages for initiate game ConversationPane#handleConversationMessage Used to send a request to play Tic Tac Toe message to your friend
CreateTicTacToeFrameEvent ChatHyperlinkListener#hyperlinkUpdate, TicTacToeAction#actionPerformed, TicTacToeActionPerformed#playGame ConversationPane#handleCreatePane In the chat panel, you accepted to play a game, so this creates the Tic Tac Toe pane

Receive challenge

MessageExtendTicTacToeChallenge creates an HTML style form to show in your chat pane. The form includes the notice about the challenge, a hyperlink to to reject the game and a button to accept.

When a friend challenges you to a game, you receive a TicTacToeMessages.INITIATE_GAME message, which causes the interceptor to publish MessageReceivedEvent with a MessageExtendTicTacToeChallenge as a parameter.

Invite to play

Here is the request to play Tic Tac Toe:

Image:ChallengeExtended.png

MessageExtendTicTacToeChallenge uses StringBuilder to create the form:

private static final String LATER_TEXT = tr("Play now, or " +
            "{0}reject{1}.","<a href=\"" + TicTacToeMessages.REJECT_GAME + "\">", "</a>");
 
 private String formatButtonText(String buttonText, boolean buttonEnabled) {
     StringBuilder bldr = new StringBuilder();
     bldr.append("<br/>")
         .append("<form action=\"\"><input type=\"hidden\" name=\"fileid\" value=\"")
         .append("Play TicTacToe?")
         .append("\"/><input type=\"submit\" value=\"")
         .append(buttonText)
         .append(buttonEnabled ? "\"/>" : ":disabled\"/>")
         .append("</form><br/>");
         return bldr.toString();
 }
 ...
 private String formatIncoming() {
 
     String tictactoeOfferReceived = tr("{0} wants to play Tic Tac Toe with you.", getFriendID());
 
     String defaultMessage = tictactoeOfferReceived + formatButtonText(tr("Accept the challenge"), true)
        + LATER_TEXT;
 
     return defaultMessage;
 }

Feature creep opportunity: You could use a different way to signal a request to play Tic Tac Toe. For example, you could use the slider notification class Notification (but you need to resolve if Tools > Options > Misc > Show popup system notifications is turned off), or another way.

Reject a game

Clicking the 'reject' link sends a message to your friend through ChatHyperlinkListener. The ChatHyperlinkListener checks if the person accepts or rejects the offer to play a game and publishes a ChallengeToPlayTicTacToeRejectedEvent. If the reject hyperlinks is clicked, hyperlinkUpdate calls handleLinkClick:

private void handleLinkClick(String linkDescription, URL url) {
 
     if (TicTacToeMessages.REJECT_GAME.equals(linkDescription)) {
         fireChallengeToPlayTicTacToeRejected();            
     }
     ...
 } 
 
 /**
  * Send TicTacToeMessages.NO_THANKS_GAME.
  */
 void fireChallengeToPlayTicTacToeRejected() {
     //the request to play is rejected, publish an event so we know to create the
     //dialog box in conversationPane
     ChallengeToPlayTicTacToeRejectedEvent event = new ChallengeToPlayTicTacToeRejectedEvent();
     event.publish();
 }

ChallengeToPlayTicTacToeRejectedEvent is sent when a friend rejects a game, or exits a game before it is finished. The 'no thanks' message appears to the other friend initiator as:

Image:NotInterested.png

Feature creep opportunity: create the ability to disable the Tic Tac Toe feature in Tools > Options because it could become annoying to receive Tic Tac Toe requests. See MiscOptionalPanel and SwingUiSettings and be sure to check the setting before registering Tic Tac Toe support. Then when the friend challenges you to a game, you send back a 'No thanks' message.

Alternatively, you could limit which friends can play you in Tic Tac Toe. Once again, you could send back a 'No thanks' message to those friends who you don't want to play.

Accept a challenge

If you click to play, ChatHyperlinkListener's hyperlinkUpdate publishes:

new CreateTicTacToeFrameEvent(conversation.getChatFriend().getID(), conversation.getChatFriend().getName(), true).publish();

ConversationPane catches CreateTicTacToeFrameEvent and creates the Tic Tac Toe dialog box:

@EventSubscriber
 public void handleCreatePane(CreateTicTacToeFrameEvent event) {        
 
    ...        
    tictactoePane = new TicTacToePane(writer, chatFriend, loggedInID, event.isX());
    tictactoePane.fireGameStarted();
    tictactoeFrame = new JFrame();
    ...
    tictactoeFrame.getContentPane().add(tictactoePane, BorderLayout.CENTER);            
    ...
 
 }

Play the game

TicTacToePane updates the status text to show who's turn it is, whether the game is done or not. If you can make a move, your cell buttons are enabled; otherwise you have to wait your turn.

Leaving a game

You need to let your other friend know when you leave a game (the handshaking protocol for this game). Otherwise, your friend might think you're just taking a really long time to make a move. Therefore, when you close your Tic Tac Toe dialog, or even sign out of Friends, you must send a notice.

Sign out of Friends

ChatFramePanel publishes a TicTacToeSignOffFriendsEvent to show if your friend or you signed out of Friends. If you signed out of Friends:

private void handleLogoff() {
 
     ...  
 
     //Let the Tic Tac Toe UI know that you are done playing.
     //Handled in ConversationPane
     new TicTacToeSignOffFriendsEvent("me").publish(); 
 }

Conversely, if your friend signed out, ChatDocumentBuilder has:

private static void appendIsTypingMessage(StringBuilder builder, String senderName, ChatState chatState, boolean friendSignedOff, String senderID) {
     String stateMessage = null;
     if (friendSignedOff) {
         new TicTacToeSignOffFriendsEvent(senderID).publish();
         stateMessage = tr("{0} has signed off", senderName);
     } else {
         ...
     }
     ...
 }

Update your TicTacToePane status:

@EventSubscriber
 public void handleTicTacToeSignOffFriendsEvent(TicTacToeSignOffFriendsEvent event) {
 
     //Because CreateTicTacToeFrameEvent could be published for a different conversation and the event bus would handle, 
     //compare the ID sent with of the chat friend. If the IDs are the same, create a Tic Tac Toe board. Otherwise
     //else ignore the event cause it's for a different friend        
     if(!friend.getID().matches(event.getFriendsId())) {
         return;
     }
 
     if(event.getFriendsId().matches("me") ) {
         statusText.setText(I18n.tr("You signed out of Friends"));
     } else {
         statusText.setText(I18n.tr("Your friend signed out of Friends"));            
     }
 
     enableButtons(false);
 }

ConversationPane

@EventSubscriber
 public void handleTicTacToeSignOffFriendsEvent(TicTacToeSignOffFriendsEvent event) {
 
     if(!event.getFriendsId().matches(chatFriend.getID()) ) {
         //only want messages with the friend who I was playing with
         return;
     }
 
     if(event.getFriendsId().matches("me")) {
         //You signed out of Friends, close 
         tictactoeFrame.dispose();          
         signedOutOfFriends = true;
     }
 }

Close the dialog

/**
  * Write the 'no thanks' message using the chat frame writer.
  */
 private void exitTicTacToeGame() {
     tictactoeGameStarted = false;     
     String message = TicTacToeMessages.TICTACTOE + TicTacToeMessages.NO_THANKS_GAME;
     try {
         writer.writeMessage(message);
     } catch (FriendException e) {
         e.printStackTrace();
     }                       
 }

ConversationPane has a window listener which fires when the Tic Tac Toe dialog box closes:

tictactoeFrame.addWindowListener(new WindowAdapter() {
 @Override
 public void windowClosed(WindowEvent e) {
     //You need to send a message to your friend that you aren't going to play the game
     //before closing the frame.
     if(!signedOutOfFriends) {
         exitTicTacToeGame();            
         tictactoeGameStarted = false;
     }
 }            
 });

Challenge a friend

At this point, you included all the components to play Tic Tac Toe, including accepting requests to play the game. Now you need to add a way to challenge a friend ... the easiest way to do this is through a right click (control click) of his name in the chat frame. Once the action is selected, TicTacToeAction's actionPerformed kicks off the game.

For simplicity, add the context menu to ChatPopupMenu. The list of names includes friends who are online, either online through LimeWire or through the web site who provides your service. Therefore, not all your friends are on LimeWire, let alone support Tic Tac Toe. If the friend doesn't support Tic Tac Toe, disable the 'Challenge to Tic Tac Toe' context item.

Context menu pop up:

1. Add play Tic Tac Toe when either chatting, or not currently chatting with a friend inside init.

popup.add(new PlayTicTacToe(chatFriend));

2. Check Tic Tac Toe support within PlayTicTacToe:

class PlayTicTacToe extends AbstractAction {
     private final Friend friend;
 
     public PlayTicTacToe(ChatFriend chatFriend) {
         super(I18n.tr("Challenge to Tic Tac Toe"));
         if (getFeature(TicTacToeFeature.ID, chatFriend) != null && chatFriend.isSignedIn() == true) {
             setEnabled(true);
         } else {
             setEnabled(false);                
         }
         friend = chatFriend.getFriend();            
     }
 
     @Override
     public void actionPerformed(ActionEvent e) {
         if (friend != null) {
             TicTacToeAction tictactoeAction = tictactoeActionProvider.get();
             tictactoeAction.setFriendInfo(friend.getId(), friend.getName());
             tictactoeAction.playGame();
         }
     }
 
     private Feature getFeature(URI feature, ChatFriend chatFriend) {
 
         for (FriendPresence presence : chatFriend.getFriend().getPresences().values()) {
             if (presence.hasFeatures(feature)) {
                 return presence.getFeature(feature);
             }
         }
         return null;
     }
 }

PlayTicTacToe actionPerformed creates TicTacToeAction which in turn publishes CreateTicTacToeFrameEvent handled in ConversatonPane's handleCreatePane. The conversation interceptor needs to know who sent the message (Message.getSenderName()) and then must put in the appropriate TicTacToePane (You can have multiple games of Tic Tac Toe running against multiple people).

Feature creep opportunity:

  • Keep running score of wins and loses against one friend
  • Store stats for longer history (See SwingUiSettings)
    • You could save the scores to disk
    • You could publish in the Mojito DHT
      • As long as you are publishing small amounts of data
      • You need to republish every 60 minutes
  • Add hover over tool tips.
  • Add keyboard shortcuts.

Logging and JUnit Test Cases

You might want to use logging to make debugging problems much easier. This code uses the LimeWire class LogFactory which wraps Apache logging:

private static final Log LOG = LogFactory.getLog(MyClassName.class);
 String boardValues =  "\n" + board[0] + " - " + board[1] + " - " + board[2] + "\n" +
        board[3] + " - " + board[4] + " - " + board[5] + "\n" +
        board[6] + " - " + board[7] + " - " + board[8]; 
 LOG.debugf("{0} {1}", boardValues, "\n");

You can use Chainsaw, print to the console, or use LimeWire's Advanced Tools Console to see the log statements (where 'e' means the cell is empty):

Image:DebugLogging.png

JUnit

New features should have test cases. Test case classes should extend BaseTestCase and each test is in a method appended with 'test' (allows the test to be called automatically for the over night tests).

For example, TicTacToeBoardTest ensures the game is decided properly. For example:

public void testXDown() throws Exception  {
     TicTacToeBoard x1 = new TicTacToeBoard('x', 'x', 'o', 
                                            'x', 'o', 'x', 
                                            'x', 'o', 'e');  
     assertEquals(x1.result(), TicTacToeWinner.DOWN_1);
 
     TicTacToeBoard x2 = new TicTacToeBoard('o', 'x', 'o', 
                                            'o', 'x', 'o', 
                                            'x', 'x', 'e');
     assertEquals(x2.result(), TicTacToeWinner.DOWN_2);
 
     TicTacToeBoard x3 = new TicTacToeBoard('o', 'x', 'x', 
                                            'x', 'o', 'x', 
                                            'o', 'o', 'x');
     assertEquals(x3.result(), TicTacToeWinner.DOWN_3);
 
 }

Mocks

Mockery helps you remove a dependence on an object having specific behavior when testing. For example, if you needed a specific Tic Tac Toe board, mocking the board would make testing much easier. For example:

public void testWithMocksAcross() throws Exception  {
 
     final TicTacToeBoard test = new TicTacToeBoard();
     Mockery context = new Mockery();
     context = new Mockery() {
     {
         setImposteriser(ClassImposteriser.INSTANCE);
     }
     };
 
     final TicTacToeBoard board = context.mock(TicTacToeBoard.class);
     context.checking(new Expectations() {
     {
         char [] mockBoard = {'e', 'e', 'x',
                              'o', 'o', 'o',
                              'x', 'x', 'o'
                            };
 
         allowing(board);
         will(returnValue(mockBoard));
 
     }
     });
 
     assertEquals(test.result(board), TicTacToeWinner.ACROSS_2);
 }

LimeWire bugs

When you sign out, ConversationPane isn't completely garbage collected. Therefore, when you sign in again, there's a second ConversationPane which when you accept a challenge to play a game, you get two handleCreatePane calls causing two Tic Tac Toe panes. This bug is with the LimeWire chat software and the use of the event bus, not related to this tutorial.

Feature creep opportunity: re-write chat not to use the event bus.

Patch

This code was designed to work with the latest limewire HEAD code. To guarantee you can see this tutorial code working with limewire (because HEAD gets refactored often), you can checkout limewire with the tag: tictactoeTutorialTag.

Apply the following TicTacToeTutorialPatch to your source to get all this tutorial's changes; the patch is targeted towards the project 'limewire_head_ttt'. When you apply the patch, right click the Patch Contents and select 'Move' and then select the target project you want to use.