Reversi Game Polish
From Gardenwiki
This page is part of the Reversi Tutorial.
Contents |
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));
}
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.



