Reversi Game Polish

This page is part of the Reversi Tutorial.

Step 6. Polishing Things Up
We've got the main game interface in place and it's playable, but we're still not quite all the way to having a fun and usable game. Let's polish up a few rough edges and see where that gets us.

Tidy Up the ReversiPanel
The board is currently bigger than it needs to be at the default size and isn't properly centered. Let's fix that up with an edit to ReversiPanel. Replace the code that creates and adds the ReversiBoardView:

// create and add our board view add(bview = new ReversiBoardView(ctx, ctrl), BorderLayout.CENTER);

with the following:

// create a container that will hold our board view in its center JPanel box = GroupLayout.makeHBox; // create and add our board view box.add(bview = new ReversiBoardView(ctx, ctrl)); add(box, BorderLayout.CENTER);

This will ensure that our board view is only as big as it wants to be and that its centered in the available space.

While we're in there, let's change the title. "Your Game!" is not very exciting. To do that, we edit the translation string which is in a file called rsrc/i18n/reversi.properties:

# # Used in the main game interface m.title = Reversi m.back_to_lobby = Back to Lobby

Or you can keep the exclamation mark if you like to exclaim. While we're messing with the title, let's use the fancy Game Gardens font and render the title in antialiased text. We can do this easily with the MultiLineLabel class that is included with the toolkit. Again, edit ReversiPanel.java:

import com.samskivert.swing.MultiLineLabel; import com.threerings.toybox.client.ToyBoxUI; public ReversiPanel (ToyBoxContext ctx, ReversiController ctrl) {       // ...        // create a side panel to hold our chat and other extra interfaces JPanel sidePanel = GroupLayout.makeVStretchBox(5); // add a big fat label MultiLineLabel vlabel = new MultiLineLabel(msgs.get("m.title")); vlabel.setAntiAliased(true); vlabel.setFont(ToyBoxUI.fancyFont); sidePanel.add(vlabel, GroupLayout.FIXED);

We happen to have that fancy font already loaded up in the handy ToyBoxUI class, so we can use it directly.

Now things are looking a sight better than they were before:



Add a Turn Display
It would be nice to know who's turn it was and who was playing the game. Fortunately, we have a standard user interface element for turn based games to display exactly that. Back we go into ReversiPanel to add it:

import java.awt.Polygon; import java.awt.geom.Ellipse2D; import javax.swing.Icon; import com.samskivert.swing.ShapeIcon; import com.threerings.parlor.turn.client.TurnDisplay; public ReversiPanel (ToyBoxContext ctx, ReversiController ctrl) {       super(ctrl); _ctx = ctx; // this is used to look up localized strings MessageBundle msgs = _ctx.getMessageManager.getBundle("reversi"); // give ourselves a wee bit of a border setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); setLayout(new BorderLayout); // give ourself a soothing blue background setBackground(new Color(0x6699CC)); // create a container that will hold our board view in its center JPanel box = GroupLayout.makeHBox; // create and add our board view box.add(bview = new ReversiBoardView(ctx, ctrl)); box.setOpaque(false); add(box, BorderLayout.CENTER); // create a side panel to hold our chat and other extra interfaces JPanel sidePanel = GroupLayout.makeVStretchBox(5); sidePanel.setOpaque(false); // add a big fat label MultiLineLabel vlabel = new MultiLineLabel(msgs.get("m.title")); vlabel.setAntiAliased(true); vlabel.setFont(ToyBoxUI.fancyFont); sidePanel.add(vlabel, GroupLayout.FIXED); // add a standard turn display TurnDisplay turnDisplay = new TurnDisplay; turnDisplay.setOpaque(false); Polygon triangle = new Polygon(new int[] { 0, 12, 0 },                                      new int[] { 0, 6, 12 }, 3); turnDisplay.setTurnIcon(new ShapeIcon(triangle, Color.yellow, null)); turnDisplay.setWinnerText(ctx.xlate("reversi", "m.winner")); turnDisplay.setDrawText(ctx.xlate("reversi", "m.draw")); Ellipse2D lips = new Ellipse2D.Float(0, 0, 12, 12); turnDisplay.setPlayerIcons(new Icon[] {           new ShapeIcon(lips, Color.black, null),            new ShapeIcon(lips, Color.white, null) }); sidePanel.add(turnDisplay, GroupLayout.FIXED); // add a chat box sidePanel.add(new ChatPanel(ctx)); // add a "back to lobby" button JButton back = ReversiController.createActionButton(           msgs.get("m.back_to_lobby"), "backToLobby"); sidePanel.add(back, GroupLayout.FIXED); // add our side panel to the main display add(sidePanel, BorderLayout.EAST); }

The TurnDisplay is a very handy little widget. You provide it with icons for each of the players in the game, in this case we provide little circles that look like pieces with colors that match the colors for players at index zero and one. And then we provide it with an icon to display next to the current turn holder, in this case a little yellow triangle, and it automatically displays the player names and the turn holder icon all in a nice looking interface.

While we were in there, we also gave the game a soothing blue background and marked a few of the panels as non-opaque so that the background properly shows through. Let's have another screenshot to show off our handywork:



Declare a Winner
Currently, if you are diligent enough to play a game all the way to the finish, you are rewarded with not even an acknowledgement of who won and who lost. The game simply stops. That's no way to end a game. We need to let the players know who came out victorious! The first step to doing that is to plug into the standard GameManager mechanism for assigning winners at the end of the game. This involves overriding a method called assignWinners. Add the following to ReversiManager.java:

@Override // from GameManager protected void assignWinners (boolean[] winners) {       super.assignWinners(winners); // count up the number of black and white pieces int[] counts = new int[2]; for (ReversiObject.Piece piece : _gameobj.pieces) { counts[piece.owner]++; }       // now set a boolean indicating which player is the winner (note that        // if it is a tie, we want to set both values to true) winners[0] = (counts[0] >= counts[1]); winners[1] = (counts[1] >= counts[0]); }

The way winners are assigned is to set a boolean value to true at a particular player's index if they are a winner and false if they are not. The GameManager allows for more than one winner, but it also considers a game to be a draw if everyone is declared to be a winner. The logic above will do exactly that, if both players have an equal number of pieces, they will both be marked winners and the game will be considered a draw. Otherwise one will be marked a winner and the other a loser.

If you look closely at the TurnDisplay code above, there are two lines that configure how it displays a winner and a drawn game:

turnDisplay.setWinnerText(ctx.xlate("reversi", "m.winner")); turnDisplay.setDrawText(ctx.xlate("reversi", "m.draw"));

We need to define those translation strings in our reversi.properties file. Add:

# # Used in the main game interface m.title = Reversi m.back_to_lobby = Back to Lobby # # Used by the turn display m.winner = Winner! m.draw = Draw

And then the TurnDisplay will automatically display "Winner!" next to the winner's name at the end of the game or "Draw" next to both players' names if the game ends in a draw.

That's a good start, but the end of the game is the time to pull out all the stops. Let's add some floating animated text to let the winner know they won and to let the loser know that the game is over. We'll start by adding a method for displaying fancy floating text to ReversiBoardView:

import java.awt.Font; import com.samskivert.swing.Label; import com.threerings.media.animation.FloatingTextAnimation; import com.threerings.parlor.media.ScoreAnimation; /**    * Floats the supplied text over the board. */   public void displayFloatingText (String text) {       Label label = ScoreAnimation.createLabel(            text, Color.white, new Font("Helvetica", Font.BOLD, 48), this); int lx = (getWidth - label.getSize.width)/2; int ly = (getHeight - label.getSize.height)/2; addAnimation(new FloatingTextAnimation(label, lx, ly)); }

Then add two new translation strings to reversi.properties:

# # Displayed at the end of the game m.you_win = You Win! m.game_over = Game Over

And finally in ReversiController, trigger the appropriate display:

@Override // from GameController protected void gameDidEnd {       super.gameDidEnd; // if we are the winner of the game, display some animated text // informing us of this fact ToyBoxContext tctx = (ToyBoxContext)_ctx; String message = (!_gameobj.isDraw && _gameobj.isWinner(_color)) ? "m.you_win" : "m.game_over"; _panel.bview.displayFloatingText(tctx.xlate("reversi", message));</b> }

Now next time you finish a game, you'll be rewarded with some big floating text drawn on top of the board.

Display Number of Pieces
TBD

Next step: Upload it to Game Gardens!

Up to the Reversi Tutorial.