Top scores list

Particularly for single-person games, players appreciate being able to compare scores. The top scores list will be cleared each time a new jar file is uploaded for the game, and each time the Game Gardens server is reset, but it is still a nice touch to include.

This is the way that Pickerel implements keeping track of top scores.

TopScore class
First, make an object class for a single line in the top scores table. Example:

public class PickerelTopScore extends SimpleStreamableObject implements Comparable {   public PickerelTopScore {} public PickerelTopScore(String player, int score) { this.player    = player; this.score     = score; }   // documentation inherited from interface Comparable public int compareTo(PickerelTopScore other) { return other.score - score; }   public String   player; public int     score; }

Modify the Game Object
The next step is to add a field to the game object to store an array of top scores. Add a line similar to the following, then run ant gendobj.

public PickerelTopScore[] topScores;

TopScore panel
We need a panel to display the top scores list to the players. This is the way Pickerel does it:

public class PickerelTopScoresPanel extends JPanel implements PlaceView { public PickerelTopScoresPanel(ToyBoxContext ctx) { _ctx =ctx; _model=new TopScoresTableModel; setLayout(new BorderLayout); _table=new JTable(_model); SafeScrollPane scroll=new SafeScrollPane(_table, 250, 0); scroll.setBorder(null); add(scroll, BorderLayout.CENTER); }   // From PlaceView public void willEnterPlace(PlaceObject place) { _gameobj=(PickerelObject)place; _gameobj.addListener(_model); _model.init; }   // From PlaceView public void didLeavePlace(PlaceObject place) { if (_gameobj!=null) { _gameobj.removeListener(_model); _gameobj=null; }   }    private class TopScoresTableModel extends AbstractTableModel implements AttributeChangeListener { public void init { fireTableDataChanged; }       // From AttributeChangeListener public void attributeChanged(AttributeChangedEvent event) { String name=event.getName; if (name.equals(PickerelObject.TOP_SCORES)) { fireTableDataChanged; SwingUtil.sizeToContents(_table); }       }   // end attributeChanged // From AbstractTableModel public String getColumnName(int col) { switch (col) { case COL_POINTS: return "Score"; case COL_PLAYER: return "Player"; default: break; }           return null; }  // end getColumnName // From AbstractTableModel public Class getColumnClass(int col) { switch (col) { case COL_POINTS: return Integer.class; case COL_PLAYER: default: return String.class; }       }   // end getColumnClass // From AbstractTableModel public int getColumnCount { return 2; }       // From AbstractTableModel public int getRowCount { if ((_gameobj==null)           || (_gameobj.topScores==null)) { return 0; }           return _gameobj.topScores.length; }  // end getRowCount // From AbstractTableModel public Object getValueAt(int row, int col) { if ((_gameobj==null)           || (_gameobj.topScores==null)) { return null; }           PickerelTopScore top_score=_gameobj.topScores[row]; if (top_score==null) { return null; }           switch (col) { case COL_POINTS: return top_score.score; case COL_PLAYER: return top_score.player; default: return null; }       }   // end getValueAt }  // end TopScoresTableModel private static final int COL_POINTS =0; private static final int COL_PLAYER =1; private ToyBoxContext      _ctx; private PickerelObject     _gameobj; private JTable             _table; private TopScoresTableModel _model; }

Modify the Game Panel
Now add the TopScoresPanel to the game panel. There are many ways to do this. Pickerel uses a JTabbedPane, which also holds other displays.

_tabbed_pane = new JTabbedPane; _tabbed_pane.addTab("Top Scores",               new PickerelTopScoresPanel(ctx));

Modify the Game Manager
There are four modifications to make to the Game Manager. We need to create a static array to hold the top scores, initialize the game object's topScores array, add a method to update the topScores array, and invoke that method when a game finishes.

Pickerel has multiple difficulty levels, so it uses a two-dimensional array in the game manager, passing only the relevant difficulty level's top scores to the game object. Here are the sections of PickerelManager that relate to the top scores.

Static Array
private static PickerelTopScore[][] _top_scores = new PickerelTopScore[MAX_DIFFICULTY][MAX_TOP_SCORES];

Initialization
Either in the gameWillStart method or in a separate initialization method that is invoked by gameWillStart, add this line. _difficulty is the difficulty level (an int). We use (_difficulty - 1) because our lowest difficulty level is 1, but the first index of the array is 0.

_gameobj.setTopScores(_top_scores[_difficulty - 1]);

Updating the Top Scores
Here is a method that can be used to update the top scores lists. There are many other ways to do the same task.

private void recordFinalScore { int score=_gameobj.score; String player =_gameobj.players[0].toString; Vector newTopScores = new Vector; int ptr = -1; /** populate the vector *  remember the pointer to this player's             *   score if it is already in top score list *  for this difficulty level */           for (int ii = 0; ii < MAX_TOP_SCORES; ii++) { PickerelTopScore thisScore = _top_scores[_gameobj.difficulty - 1][ii]; if (thisScore != null) { newTopScores.addElement(thisScore); if (thisScore.player.equals(player)) ptr = ii; }           }       // end for // if we found the player, then just update *his* score if need be           // otherwise, add new score for him at bottom if (ptr > -1) { PickerelTopScore thisScore = newTopScores.elementAt(ptr); if (thisScore.score < score) newTopScores.elementAt(ptr).score = score; } else { newTopScores.addElement(new PickerelTopScore(player, score)); }           PickerelTopScore[] scoreArray = new PickerelTopScore[newTopScores.size]; newTopScores.toArray(scoreArray); Arrays.sort(scoreArray); // now update the official top scores list for this // difficulty level. Extra entries in the vector // get ignored. int max = Math.min(scoreArray.length, MAX_TOP_SCORES); try { for (int i = 0; i < max; i++) { _top_scores[_gameobj.difficulty - 1][i] = scoreArray[i]; }           } catch (ArrayIndexOutOfBoundsException e){ log.warning("Top scores list not completely updated; " +                       "ArrayIndexOutOfBoundsException"); }       _gameobj.setTopScores(_top_scores[_gameobj.difficulty - 1]); } // end recordFinalScore

Invoking recordFinalScore
The last step is to invoke recordFinalScore when a game ends. This can be done in gameDidEnd, or in a separate method that checks for an end game condition (if you already use one).