Revamps galore! Player info display! Working scoring for all cases (we

hope)! It's a glorious day!


git-svn-id: https://samskivert.googlecode.com/svn/trunk@367 6335cc39-0255-0410-8fd6-9bcaacd3b74c
This commit is contained in:
mdb
2001-10-17 23:27:52 +00:00
parent 758b5f9ff5
commit 2d029fe059
11 changed files with 863 additions and 350 deletions
@@ -1,5 +1,5 @@
//
// $Id: AtlantiBoard.java,v 1.9 2001/10/17 04:34:14 mdb Exp $
// $Id: AtlantiBoard.java,v 1.10 2001/10/17 23:27:52 mdb Exp $
package com.threerings.venison;
@@ -173,6 +173,18 @@ public class VenisonBoard
"find no associated tile! [key=" + key + "].");
}
/**
* Turn off the ability to place a piecen on the most recently played
* tile. This function assumes that the mouse will not be over a valid
* piecen placement at the time this function is called (it expects
* that it will be over a button of some sort that says something to
* the effect of "skip placement for this turn").
*/
public void cancelPiecenPlacement ()
{
_placingPiecen = false;
}
/**
* Sets the tile to be placed on the board. The tile will be displayed
* in the square under the mouse cursor where it can be legally placed
@@ -55,10 +55,10 @@ public class VenisonController
_venobj = (VenisonObject)plobj;
// find out what our index is and use that as our piecen color
int oidx = ListUtil.indexOfEqual(_venobj.players, _self.username);
if (oidx != -1) {
_selfIndex = ListUtil.indexOfEqual(_venobj.players, _self.username);
if (_selfIndex != -1) {
// use our player index as the piecen color directly
_panel.board.setNewPiecenColor(oidx);
_panel.board.setNewPiecenColor(_selfIndex);
}
// grab the tiles and piecens from the game object and configure
@@ -137,6 +137,13 @@ public class VenisonController
_venobj.getOid(), PLACE_TILE_REQUEST, args);
_ctx.getDObjectManager().postEvent(mevt);
// if we have no piecens to place, we immediately disable
// piecen placement in the board
int pcount = TileUtil.countPiecens(_venobj.piecens, _selfIndex);
if (pcount >= PIECENS_PER_PLAYER) {
_panel.board.cancelPiecenPlacement();
}
// enable the noplace button
_panel.noplace.setEnabled(true);
@@ -153,6 +160,9 @@ public class VenisonController
_panel.noplace.setEnabled(false);
} else if (action.getActionCommand().equals(PLACE_NOTHING)) {
// turn off piecen placement in the board
_panel.board.cancelPiecenPlacement();
// the user doesn't want to place anything this turn. send a
// place nothing request to the server
MessageEvent mevt = new MessageEvent(
@@ -177,4 +187,7 @@ public class VenisonController
/** A reference to our body object. */
protected BodyObject _self;
/** Our player index or -1 if we're not a player. */
protected int _selfIndex;
}
@@ -33,6 +33,9 @@ public class VenisonPanel
*/
public VenisonPanel (ParlorContext ctx, VenisonController controller)
{
// give ourselves a wee bit of a border
setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
HGroupLayout gl = new HGroupLayout(HGroupLayout.STRETCH);
gl.setOffAxisPolicy(HGroupLayout.STRETCH);
setLayout(gl);
@@ -47,9 +50,15 @@ public class VenisonPanel
// create our side panel
VGroupLayout sgl = new VGroupLayout(VGroupLayout.STRETCH);
sgl.setOffAxisPolicy(VGroupLayout.STRETCH);
sgl.setJustification(VGroupLayout.TOP);
JPanel sidePanel = new JPanel(sgl);
// add a player info view to the side panel
sidePanel.add(new JLabel("Scores:"), VGroupLayout.FIXED);
sidePanel.add(new PlayerInfoView(), VGroupLayout.FIXED);
// add a turn indicator to the side panel
sidePanel.add(new JLabel("Current turn:"), VGroupLayout.FIXED);
sidePanel.add(new TurnIndicatorView(), VGroupLayout.FIXED);
// add a "place nothing" button
@@ -0,0 +1,116 @@
//
// $Id: PlayerInfoView.java,v 1.1 2001/10/17 23:27:52 mdb Exp $
package com.threerings.venison;
import java.awt.Color;
import java.awt.GridBagLayout;
import java.awt.GridBagConstraints;
import javax.swing.JLabel;
import javax.swing.JPanel;
import com.threerings.presents.dobj.AttributeChangeListener;
import com.threerings.presents.dobj.AttributeChangedEvent;
import com.threerings.crowd.client.PlaceView;
import com.threerings.crowd.data.PlaceObject;
/**
* Displays each of the players in the game, their piece color and their
* score.
*/
public class PlayerInfoView
extends JPanel implements PlaceView, AttributeChangeListener
{
/**
* Constructs a new player info panel, ready for insertion into a
* Venison game panel.
*/
public PlayerInfoView ()
{
setLayout(new GridBagLayout());
// we don't add our player info until we know how many players are
// in the game (which we find out once we enter the game room)
}
// documentation inherited
public void willEnterPlace (PlaceObject plobj)
{
// we want to grab a reference to the game object and add
// ourselves as an attribute change listener
_venobj = (VenisonObject)plobj;
_venobj.addListener(this);
// we need a score labels array so that we can keep track of the
// score labels
_scoreLabels = new JLabel[_venobj.players.length];
// now that we're here, we can add an entry for every player
for (int i = 0; i < _venobj.players.length; i++) {
addPlayer(i, _venobj.players[i]);
}
// if we have scores, update them
if (_venobj.scores.length == _venobj.players.length) {
updateScores();
}
}
/**
* Adds a player to the display.
*/
protected void addPlayer (int idx, String username)
{
GridBagConstraints c = new GridBagConstraints();
// create a label for their username
c.fill = GridBagConstraints.HORIZONTAL;
c.weightx = 1.0;
JLabel unlabel = new JLabel(username);
unlabel.setForeground(Feature.PIECEN_COLOR_MAP[idx]);
add(unlabel, c);
// create a label for their score
c.weightx = 0.0;
c.fill = GridBagConstraints.NONE;
c.gridwidth = GridBagConstraints.REMAINDER;
_scoreLabels[idx] = new JLabel("0");
_scoreLabels[idx].setForeground(Color.black);
add(_scoreLabels[idx], c);
}
// documentation inherited
public void didLeavePlace (PlaceObject plobj)
{
// remove our listening self
_venobj.removeListener(this);
_venobj = null;
}
// documentation inherited
public void attributeChanged (AttributeChangedEvent event)
{
// we care about the scores (which change)
if (event.getName().equals(VenisonObject.SCORES)) {
updateScores();
}
}
/**
* Reads the scores from the game object and updates the view.
*/
protected void updateScores ()
{
for (int i = 0; i < _venobj.scores.length; i++) {
_scoreLabels[i].setText(Integer.toString(_venobj.scores[i]));
}
}
/** A reference to the game object. */
protected VenisonObject _venobj;
/** References to our score label components. */
protected JLabel[] _scoreLabels;
}
@@ -1,5 +1,5 @@
//
// $Id: AtlantiCodes.java,v 1.2 2001/10/17 02:19:54 mdb Exp $
// $Id: AtlantiCodes.java,v 1.3 2001/10/17 23:27:52 mdb Exp $
package com.threerings.venison;
@@ -8,6 +8,9 @@ package com.threerings.venison;
*/
public interface VenisonCodes
{
/** The number of piecens provided to each player. */
public static final int PIECENS_PER_PLAYER = 7;
/** The name of the command posted by the {@link VenisonBoard} when
* the user places a tile into a valid position. */
public static final String TILE_PLACED = "tile_placed";
@@ -1,5 +1,5 @@
//
// $Id: AtlantiTile.java,v 1.8 2001/10/17 02:19:54 mdb Exp $
// $Id: AtlantiTile.java,v 1.9 2001/10/17 23:27:52 mdb Exp $
package com.threerings.venison;
@@ -159,11 +159,8 @@ public class VenisonTile
* @param featureIndex the index of the feature to update.
* @param claimGroup the claim group to associate with the feature.
*/
public void setFeatureGroup (int featureIndex, int claimGroup)
public void setClaimGroup (int featureIndex, int claimGroup)
{
// Log.info("Setting feature group [tile=" + this +
// ", fidx=" + featureIndex + ", cgroup=" + claimGroup + "].");
// update the claim group slot for this feature
claims[featureIndex] = claimGroup;
@@ -179,7 +176,7 @@ public class VenisonTile
* Piecen#featureIndex} field is assumed to be initialized to the
* feature index of this tile on which the piecen is to be placed.
*
* <p> Note that this will call {@link TileUtil#setFeatureGroup} to
* <p> Note that this will call {@link TileUtil#setClaimGroup} to
* propagate the claiming of this feature to all neighboring tiles if
* a non-null tiles array is supplied to the function.
*
@@ -210,8 +207,10 @@ public class VenisonTile
int claimGroup = TileUtil.nextClaimGroup();
Log.info("Creating claim group [cgroup=" + claimGroup +
", tile=" + this + ", fidx=" + piecen.featureIndex + "].");
TileUtil.setFeatureGroup(
tiles, this, piecen.featureIndex, claimGroup, 0);
TileUtil.setClaimGroup(
tiles, this, piecen.featureIndex, claimGroup);
// update our piecen with the claim group as well
piecen.claimGroup = claimGroup;
}
}
@@ -254,7 +253,7 @@ public class VenisonTile
// if we have a piecen on this tile, render it as well
if (piecen != null && piecen.featureIndex == i) {
features[i].paintPiecen(
g, orientation, piecen.color, piecen.claimGroup);
g, orientation, piecen.owner, piecen.claimGroup);
}
}
@@ -262,6 +261,12 @@ public class VenisonTile
g.setColor(Color.black);
g.drawRect(0, 0, TILE_WIDTH, TILE_HEIGHT);
// if we have a shield, draw a square in the lower right
if (hasShield) {
g.setColor(Color.orange);
g.drawRect(TILE_WIDTH-15, TILE_HEIGHT-15, 10, 10);
}
// translate back out
g.translate(-sx, -sy);
}
@@ -1,5 +1,5 @@
//
// $Id: Piecen.java,v 1.1 2001/10/17 02:19:54 mdb Exp $
// $Id: Piecen.java,v 1.2 2001/10/17 23:27:52 mdb Exp $
package com.threerings.venison;
@@ -42,8 +42,8 @@ public class Piecen
/** A color constant. */
public static final int GREEN = 4;
/** The color of this piecen. */
public int color;
/** The owner of this piecen. */
public int owner;
/** The x and y coordinates of the tile on which this piecen is
* placed. */
@@ -59,9 +59,9 @@ public class Piecen
/**
* Construts a piecen with the specified configuration.
*/
public Piecen (int color, int x, int y, int featureIndex)
public Piecen (int owner, int x, int y, int featureIndex)
{
this.color = color;
this.owner = owner;
this.x = x;
this.y = y;
this.featureIndex = featureIndex;
@@ -85,7 +85,7 @@ public class Piecen
public void writeTo (DataOutputStream out)
throws IOException
{
out.writeInt(color);
out.writeInt(owner);
out.writeInt(x);
out.writeInt(y);
out.writeInt(featureIndex);
@@ -95,7 +95,7 @@ public class Piecen
public void readFrom (DataInputStream in)
throws IOException
{
color = in.readInt();
owner = in.readInt();
x = in.readInt();
y = in.readInt();
featureIndex = in.readInt();
@@ -127,7 +127,7 @@ public class Piecen
*/
public String toString ()
{
return "[color=" + color + ", pos=" + x + "/" + y +
", fidx=" + featureIndex + "]";
return "[owner=" + owner + ", pos=" + x + "/" + y +
", feat=" + featureIndex + ", claim=" + claimGroup + "]";
}
}
@@ -1,5 +1,5 @@
//
// $Id: TileCodes.java,v 1.5 2001/10/17 02:19:54 mdb Exp $
// $Id: TileCodes.java,v 1.6 2001/10/17 23:27:52 mdb Exp $
package com.threerings.venison;
@@ -118,4 +118,8 @@ public interface TileCodes
/** A constant indicating a cloister. */
public static final int CLOISTER = 3;
/** A flag used to mark a tile as part of a completed city. */
public static final int COMPLETED_CITY = 0x01;
}
@@ -1,5 +1,5 @@
//
// $Id: AtlantiManager.java,v 1.10 2001/10/17 05:01:51 mdb Exp $
// $Id: AtlantiManager.java,v 1.11 2001/10/17 23:27:52 mdb Exp $
package com.threerings.venison;
@@ -9,9 +9,14 @@ import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import com.samskivert.util.HashIntMap;
import com.samskivert.util.StringUtil;
import com.threerings.presents.dobj.ElementAddedEvent;
import com.threerings.presents.dobj.ElementRemovedEvent;
import com.threerings.presents.dobj.ElementUpdatedEvent;
import com.threerings.presents.dobj.DSet;
import com.threerings.presents.dobj.SetListener;
import com.threerings.presents.dobj.MessageEvent;
import com.threerings.parlor.turn.TurnGameManager;
@@ -20,7 +25,7 @@ import com.threerings.parlor.turn.TurnGameManager;
* The main coordinator of the Venison game on the server side.
*/
public class VenisonManager
extends TurnGameManager implements VenisonCodes
extends TurnGameManager implements VenisonCodes, SetListener
{
// documentation inherited
protected Class getPlaceObjectClass ()
@@ -62,6 +67,11 @@ public class VenisonManager
_tilesInBox = TileUtil.getStandardTileSet();
Collections.shuffle(_tilesInBox);
// shave off most of the tiles for the moment
while (_tilesInBox.size() > 15) {
_tilesInBox.remove(0);
}
// clear out our board tiles
_tiles.clear();
@@ -76,8 +86,9 @@ public class VenisonManager
_venobj.setPiecens(new DSet(Piecen.class));
// and add the starting tile
_venobj.addToTiles(VenisonTile.STARTING_TILE);
_tiles.add(VenisonTile.STARTING_TILE);
VenisonTile start = TileUtil.getStartingTile();
_venobj.addToTiles(start);
_tiles.add(start);
}
protected void turnWillStart ()
@@ -116,10 +127,47 @@ public class VenisonManager
}
/**
* Determines whether or not the placement of this tile has completed
* features that were previously incomplete.
* At the end of the game, we need to compute the final scores.
*/
protected void scoreCompletedFeatures (VenisonTile tile)
protected void gameDidEnd ()
{
super.gameDidEnd();
// create a piecen array that we can manipulate while scoring
Piecen[] piecens = new Piecen[_venobj.piecens.size()];
Iterator iter = _venobj.piecens.elements();
for (int i = 0; iter.hasNext(); i++) {
piecens[i] = (Piecen)iter.next();
}
// compute the final scores by iterating over each tile and
// scoring its features
iter = _venobj.tiles.elements();
while (iter.hasNext()) {
VenisonTile tile = (VenisonTile)iter.next();
scoreFeatures(tile, piecens);
}
// lastly, we have to score the farms (cue the ominous drums)...
scoreFarms();
// update the final scores
_venobj.setScores(_venobj.scores);
}
/**
* Scores the features on this tile.
*
* @param tile the tile whose features should be scored.
* @param piecens during the final tally, we don't look for piecens on
* the board and remove them when we score them, we instead look for
* the piecens in this supplied array and remove them from there both
* to preserve the appearance of the board during final scoring and to
* circumvent the need to process an element removed event every time
* we remove a piecen. Passing a non-null piecens array also signifies
* that we are doing the final tally rather than an incremental score.
*/
protected void scoreFeatures (VenisonTile tile, Piecen[] piecens)
{
// potentially score all features on the tile
for (int i = 0; i < tile.features.length; i++) {
@@ -133,12 +181,12 @@ public class VenisonManager
// see if any piecens are even on a feature in this group
int cgroup = tile.claims[i];
int[] cgv = getClaimGroupVector(cgroup);
int[] cgv = getClaimGroupVector(cgroup, piecens);
if (cgv == null) {
// if not, we don't have anything to score
Log.info("Not scoring unclaimed feature " +
"[ttype=" + tile.type + ", feat=" + f +
", cgroup=" + cgroup + "].");
// Log.info("Not scoring unclaimed feature " +
// "[ttype=" + tile.type + ", feat=" + f +
// ", cgroup=" + cgroup + "].");
continue;
}
@@ -146,46 +194,47 @@ public class VenisonManager
// this feature
int score = TileUtil.computeFeatureScore(_tiles, tile, i);
// if the score is positive, it's a completed feature, so we
// dole out the points and clear the associated piecens
if (score > 0) {
Log.info("Scoring feature [ttype=" + tile.type +
", feature=" + f + ", score=" + score + "].");
// if the score is positive, it's a completed feature and we
// score it regardless, we score incomplete features only
// during the final tally
if (score > 0 || piecens != null) {
// convert the score into a positive value
if (score > 0) {
Log.info("Scoring complete feature " +
"[ttype=" + tile.type + ", feature=" + f +
", score=" + score +
", cgv=" + StringUtil.toString(cgv) + "].");
} else {
Log.info("Scoring incomplete feature [ttype=" + tile.type +
", feature=" + f + ", score=" + score + "].");
}
score = Math.abs(score);
// adjust the scores
for (int p = 0; p < cgv.length; p++) {
_venobj.scores[p] += (score * cgv[p]);
}
// broadcast the new scores
_venobj.setScores(_venobj.scores);
Log.info("New scores: " + StringUtil.toString(_venobj.scores));
// and free up the scored piecens
Iterator iter = _venobj.piecens.elements();
while (iter.hasNext()) {
Piecen p = (Piecen)iter.next();
if (p.claimGroup == cgroup) {
Log.info("Removing piecen " + p + ".");
_venobj.removeFromPiecens(p.getKey());
}
// broadcast the new scores if this isn't the final tally
if (piecens == null) {
_venobj.setScores(_venobj.scores);
}
// and free up the scored piecens
removePiecens(cgroup, piecens);
} else {
Log.info("Not scoring incomplete feature " +
"[ttype=" + tile.type + ", feat=" + f +
", score=" + score + "].");
// Log.info("Not scoring incomplete feature " +
// "[ttype=" + tile.type + ", feat=" + f +
// ", score=" + score + "].");
}
}
// we also may have completed a cloister, so we check that as well
for (int dx = -1; dx < 2; dx++) {
for (int dy = -1; dy < 2; dy++) {
// skip ourselves
if (dx == 0 && dy == 0) {
continue;
}
// find our neighbor and make sure they exist
VenisonTile neighbor =
TileUtil.findTile(_tiles, tile.x + dx, tile.y + dy);
@@ -205,43 +254,177 @@ public class VenisonManager
// tile has a piecen
if (p == null) {
Log.info("Skipping non-piecen having " +
"cloister tile [tile=" + neighbor +
", feat=" + f + "].");
// Log.info("Skipping non-piecen having " +
// "cloister tile [tile=" + neighbor +
// ", feat=" + f + "].");
continue;
}
// piecen is on cloister feature
if (neighbor.claims[i] != p.claimGroup) {
Log.info("Skipping cloister tile with piecen on " +
"non-cloister [tile=" + neighbor +
", feat=" + f + "].");
// Log.info("Skipping cloister tile with piecen on " +
// "non-cloister [tile=" + neighbor +
// ", feat=" + f + "].");
continue;
}
// score the cloister to see if it is completed
// score the cloister
int score = TileUtil.computeFeatureScore(
_tiles, neighbor, i);
// it is, yay!
if (score > 0) {
// if it's completed or if we're doing the final
// tally, we score it
if (score > 0 || piecens != null) {
if (score > 0) {
Log.info("Scored completed cloister " +
"[tile=" + neighbor +
", f=" + f + ", p=" + p + "].");
} else {
Log.info("Scored incomplete cloister " +
"[tile=" + neighbor +
", f=" + f + ", p=" + p + "].");
}
// coerce the score into positive land
score = Math.abs(score);
// add the score to the owning player
_venobj.scores[p.color] += score;
_venobj.setScores(_venobj.scores);
// and clear out the piecen
_venobj.removeFromPiecens(p.getKey());
Log.info("Scored cloister [tile=" + neighbor +
", f=" + f + ", p=" + p + "].");
_venobj.scores[p.owner] += score;
// only broadcast the updated scores if this isn't
// the final tally
if (piecens == null) {
_venobj.setScores(_venobj.scores);
}
// and clear out the piecen (only removing it from
// the piecen set if we're not in the final tally)
removePiecen(p, piecens == null);
} else {
Log.info("Not scoring incomplete cloister " +
"[tile=" + neighbor + ", feat=" + f + "].");
// Log.info("Not scoring incomplete cloister " +
// "[tile=" + neighbor + ", feat=" + f + "].");
}
}
}
}
}
/**
* Scores the farms, which is the final act of scoring.
*/
protected void scoreFarms ()
{
HashIntMap cities = new HashIntMap();
// clear out the claims for incompleted cities and claim unclaimed
// completed cities
TileUtil.prepCitiesForScoring(_tiles);
// do the big process-ola
int tsize = _tiles.size();
for (int i = 0; i < tsize; i++) {
VenisonTile tile = (VenisonTile)_tiles.get(i);
// iterate over all of the city features in this tile
for (int f = 0; f < tile.features.length; f++) {
// get the claim group for this feature
int cityClaim = tile.claims[f];
// skip unclaimed and non-city features
if (tile.features[f].type != TileCodes.CITY ||
cityClaim == 0) {
continue;
}
// get the list associated with this claim group
int[] claims = (int[])cities.get(cityClaim);
if (claims == null) {
// create a claim vector if we've not got one. if a
// city had 35 separately claimed farms around it, all
// the piecens in the game would be in play and the
// city would not have been claimed which would be an
// extremely pathological case, but we love pathology
// (especially when we don't have a resizable int list
// class handy)
claims = new int[35];
cities.put(cityClaim, claims);
}
// iterate over all of the grass features that are
// connected to city features on this tile and add their
// claim groups the list for this city feature
int[] grasses = FeatureUtil.CITY_GRASS_MAP[tile.type-1];
for (int g = 0; g < grasses.length; g++) {
int farmClaim = tile.claims[grasses[g]];
// only worry about claimed grass regions
if (farmClaim == 0) {
Log.info("Ignoring unclaimed farm group " +
"[tile=" + tile +
", fidx=" + grasses[g] + "].");
continue;
}
// and the farm claim group to the list
for (int c = 0; c < claims.length; c++) {
// don't add the farm claim twice
if (claims[c] == farmClaim) {
break;
} else if (claims[c] == 0) {
claims[c] = farmClaim;
Log.info("Noting city/farm abuttal " +
"[tile=" + tile +
", cityClaim=" + cityClaim +
", farmClaim=" + farmClaim + "].");
break;
}
}
}
}
}
// now for each city, we look to see who has the most piecens that
// are connected to the city by farms
Iterator iter = cities.keys();
while (iter.hasNext()) {
int cityClaim = ((Integer)iter.next()).intValue();
int[] farmClaims = (int[])cities.get(cityClaim);
int[] pcount = new int[_players.length];
int max = 0;
Iterator piter = _venobj.piecens.elements();
while (piter.hasNext()) {
Piecen p = (Piecen)piter.next();
// see if the piecen is on any of the farms
for (int c = 0; c < farmClaims.length; c++) {
if (p.claimGroup == farmClaims[c]) {
Log.info("Counting piecen [cityClaim=" + cityClaim +
", farmClaim=" + farmClaims[c] +
", piecen=" + p + "].");
// increment their count and track the max
if (max < ++pcount[p.owner]) {
max = pcount[p.owner];
}
}
}
}
Log.info("Counted city [cityClaim=" + cityClaim +
", counts=" + StringUtil.toString(pcount) + "].");
// now score four points for every player that has the max
for (int i = 0; i < pcount.length; i++) {
if (pcount[i] == max) {
Log.info("Scoring city for player [cgroup=" + cityClaim +
", player=" + _players[i] +
", pcount=" + pcount[i] + "].");
_venobj.scores[i] += 4;
}
}
}
}
/**
* Returns an int array with zeros and ones in the appropriate places
* so that a score can be multiplied by a player's position in the
@@ -252,24 +435,44 @@ public class VenisonManager
* vector, so it cannot be called again without overwriting values
* returned previously.
*
* @param claimGroup the claim group that we're scoring.
* @param piecens an array to use when looking for matching piecens
* instead of looking at the piecens set in the game object.
*
* @return an array for the specified claim group or null if no
* players have a piecen claiming the specified claim group.
*/
protected int[] getClaimGroupVector (int claimGroup)
protected int[] getClaimGroupVector (int claimGroup, Piecen[] piecens)
{
// clear out the vector
Arrays.fill(_claimGroupVector, 0);
// iterate over the piecens
int max = 0;
Iterator iter = _venobj.piecens.elements();
while (iter.hasNext()) {
Piecen piecen = (Piecen)iter.next();
if (piecen.claimGroup == claimGroup) {
// color == player index... somewhat sketchy
if (++_claimGroupVector[piecen.color] > max) {
// keep track of the highest scorer
max = _claimGroupVector[piecen.color];
if (piecens == null) {
Iterator iter = _venobj.piecens.elements();
while (iter.hasNext()) {
Piecen piecen = (Piecen)iter.next();
if (piecen.claimGroup == claimGroup) {
// color == player index... somewhat sketchy
if (++_claimGroupVector[piecen.owner] > max) {
// keep track of the highest scorer
max = _claimGroupVector[piecen.owner];
}
}
}
} else {
for (int i = 0; i < piecens.length; i++) {
Piecen piecen = piecens[i];
if (piecen == null) {
continue;
} else if (piecen.claimGroup == claimGroup) {
// color == player index... somewhat sketchy
if (++_claimGroupVector[piecen.owner] > max) {
// keep track of the highest scorer
max = _claimGroupVector[piecen.owner];
}
}
}
}
@@ -282,6 +485,94 @@ public class VenisonManager
return (max == 0) ? null : _claimGroupVector;
}
/**
* Removes piecens either from the supplied array or from the game
* object piecen set if no array is supplied.
*/
protected void removePiecens (int claimGroup, Piecen[] piecens)
{
if (piecens == null) {
Iterator iter = _venobj.piecens.elements();
while (iter.hasNext()) {
Piecen p = (Piecen)iter.next();
if (p.claimGroup == claimGroup) {
removePiecen(p, true);
}
}
} else {
for (int i = 0; i < piecens.length; i++) {
if (piecens[i] == null) {
continue;
} else if (piecens[i].claimGroup == claimGroup) {
piecens[i] = null;
}
}
}
}
/**
* Removes the piecen from the board and optionally removes it from
* the piecen set.
*
* @param piecen the piecen to be removed.
* @param removeFromPiecens if true, the piecen will also be removed
* from the piecens set in the game object.
*/
protected void removePiecen (Piecen piecen, boolean removeFromPiecens)
{
// locate the tile that contains this piecen
int tidx = _tiles.indexOf(piecen);
if (tidx == -1) {
Log.warning("Requested to remove piecen that is not " +
"associated with any tile [piecen=" + piecen + "].");
} else {
VenisonTile tile = (VenisonTile)_tiles.get(tidx);
// and clear the piecen
tile.clearPiecen();
}
// also remove from the piecens dset if requested
if (removeFromPiecens) {
_venobj.removeFromPiecens(piecen.getKey());
}
}
// documentation inherited
public void elementAdded (ElementAddedEvent event)
{
// we react to piecen additions by potentially scoring the placed
// piecen. we allow the piecen to be added to the piecens set
// before scoring so that the players can see the piecen pop up on
// their screen and then disappear with a scoring notice rather
// than never show up at all; plus it simplifies our code
if (event.getName().equals(VenisonObject.PIECENS)) {
Piecen piecen = (Piecen)event.getElement();
// make sure this is a valid placement
VenisonTile tile = (VenisonTile)_venobj.tiles.get(piecen.getKey());
if (tile == null) {
Log.warning("Can't find tile for piecen scoring " +
piecen + ".");
} else {
// check to see if we added the piecen to a completed
// feature, in which case we score and remove it
scoreFeatures(tile, null);
}
}
}
// documentation inherited
public void elementUpdated (ElementUpdatedEvent event)
{
}
// documentation inherited
public void elementRemoved (ElementRemovedEvent event)
{
}
/** Handles place tile requests. */
protected class PlaceTileHandler implements MessageHandler
{
@@ -303,7 +594,7 @@ public class VenisonManager
// placing a piece may have completed road or city
// features. if it did, we score them now
scoreCompletedFeatures(tile);
scoreFeatures(tile, null);
Log.info("Placed tile " + tile + ".");
@@ -319,10 +610,17 @@ public class VenisonManager
public void handleEvent (MessageEvent event)
{
Piecen piecen = (Piecen)event.getArgs()[0];
// make sure this is a valid placement
VenisonTile tile = (VenisonTile)_venobj.tiles.get(piecen.getKey());
if (tile == null) {
int pidx = getTurnHolderIndex();
int pcount = TileUtil.countPiecens(_venobj.piecens, pidx);
// do some checking before we place the piecen
if (pcount >= PIECENS_PER_PLAYER) {
Log.warning("Requested to place piecen for player that " +
"has all of their piecens in play " +
"[event=" + event + "].");
} else if (tile == null) {
Log.warning("Can't find tile for requested piecen " +
"placement " + piecen + ".");
@@ -1,5 +1,5 @@
//
// $Id: FeatureUtil.java,v 1.2 2001/10/17 04:34:13 mdb Exp $
// $Id: FeatureUtil.java,v 1.3 2001/10/17 23:27:52 mdb Exp $
package com.threerings.venison;
@@ -61,6 +61,30 @@ public class FeatureUtil implements TileCodes
WNW_F, WEST, ENE_F,
};
/** A mapping for city tiles to the grass features that are adjacent
* to the city tiles. */
public static final int[][] CITY_GRASS_MAP = new int[][] {
{ },
{ 1 }, // CITY_THREE
{ 1, 2 }, // CITY_THREE_ROAD
{ 1 }, // CITY_TWO
{ 1 }, // CITY_TWO_ROAD
{ 1, 2 }, // CITY_TWO_ROAD_ACROSS
{ 0 }, // TWO_CITY_TWO
{ 0 }, // TWO_CITY_TWO_ACROSS
{ 0 }, // CITY_ONE
{ 0 }, // CITY_ONE_ROAD_RIGHT
{ 0 }, // CITY_ONE_ROAD_LEFT
{ 0 }, // CITY_ONE_ROAD_TEE
{ 0 }, // CITY_ONE_ROAD_STRAIGHT
{ },
{ },
{ },
{ },
{ },
{ },
};
/**
* Returns the feature array for the tile of the specified type.
*/
@@ -1,5 +1,5 @@
//
// $Id: TileUtil.java,v 1.9 2001/10/17 05:01:51 mdb Exp $
// $Id: TileUtil.java,v 1.10 2001/10/17 23:27:52 mdb Exp $
package com.threerings.venison;
@@ -12,11 +12,22 @@ import java.util.List;
import com.samskivert.util.IntTuple;
import com.threerings.presents.dobj.DSet;
/**
* Utility functions relating to the Venison tiles.
*/
public class TileUtil implements TileCodes
{
/**
* Returns an instance of the starting tile (properly cloned so that
* it can be messed with by the server).
*/
public static VenisonTile getStartingTile ()
{
return (VenisonTile)VenisonTile.STARTING_TILE.clone();
}
/**
* Returns a list containing the standard tile set for the Venison
* game. The list is a clone, so it can be bent, folded and modified
@@ -24,7 +35,14 @@ public class TileUtil implements TileCodes
*/
public static List getStandardTileSet ()
{
return (List)TILE_SET.clone();
// we need to deep copy the default tile set, so we can't just use
// clone
List tiles = new ArrayList();
int tsize = TILE_SET.size();
for (int i = 0; i < tsize; i++) {
tiles.add(((VenisonTile)TILE_SET.get(i)).clone());
}
return tiles;
}
/**
@@ -189,119 +207,48 @@ public class TileUtil implements TileCodes
*/
public static void inheritClaims (List tiles, VenisonTile tile)
{
// obtain our neighboring tiles
VenisonTile[] neighbors = new VenisonTile[4];
neighbors[NORTH] = findTile(tiles, tile.x, tile.y-1);
neighbors[EAST] = findTile(tiles, tile.x+1, tile.y);
neighbors[SOUTH] = findTile(tiles, tile.x, tile.y+1);
neighbors[WEST] = findTile(tiles, tile.x-1, tile.y);
List flist = new ArrayList();
// for each feature in the tile, determine whether or not the
// neighboring tile's matching feature is claimed
// for each feature in the tile, load up its claim group and make
// sure all features in that group (which will include our new
// feature) now have the same claim number
for (int i = 0; i < tile.features.length; i ++) {
int ftype = tile.features[i].type;
int fmask = tile.features[i].edgeMask;
int cgroup = 0;
int claimGroup = 0;
// iterate over all of the possible adjacency possibilities,
// first looking for a claim group to inherit
for (int c = 0; c < FeatureUtil.ADJACENCY_MAP.length; c += 3) {
int mask = FeatureUtil.ADJACENCY_MAP[c];
int dir = FeatureUtil.ADJACENCY_MAP[c+1];
int opp_mask = FeatureUtil.ADJACENCY_MAP[c+2];
// clear out the tilefeatures list before enumerating
flist.clear();
// if this feature doesn't have this edge, skip it
if ((fmask & mask) == 0) {
continue;
}
// enumerate the claim group for this feature
enumerateGroup(tiles, tile, i, flist);
// translate the target direction accordingly
dir = (dir + tile.orientation) % 4;
// make sure we have a neighbor in the appropriate
// direction
if (neighbors[dir] == null) {
continue;
}
// it looks like we have a match, so translate the target
// stuff into our orientation
mask = FeatureUtil.translateMask(mask, tile.orientation);
opp_mask = FeatureUtil.translateMask(
opp_mask, tile.orientation);
// inherit the group of the opposing feature
cgroup = neighbors[dir].getFeatureGroup(opp_mask);
// and bail as soon as we find a non-zero claim group
if (cgroup != 0) {
Log.info("Inherited claim [tile=" + tile +
", fidx=" + i + ", cgroup=" + cgroup +
", source=" + neighbors[dir] + "].");
// find the first non-zero claim number
for (int t = 0; t < flist.size(); t++) {
TileFeature feat = (TileFeature)flist.get(t);
int fcg = feat.tile.claims[feat.featureIndex];
if (fcg != 0) {
claimGroup = fcg;
break;
}
}
// if we didn't inherit a claim group, skip to the next
// feature
if (cgroup == 0) {
// if we found no non-zero claim number, we've nothing to
// inherit
if (claimGroup == 0) {
continue;
}
// initialize the feature's claim group
tile.claims[i] = cgroup;
// otherwise, iterate over all of the possible adjacency
// possibilities, propagating our group to connected features
for (int c = 0; c < FeatureUtil.ADJACENCY_MAP.length; c += 3) {
int mask = FeatureUtil.ADJACENCY_MAP[c];
int dir = FeatureUtil.ADJACENCY_MAP[c+1];
int opp_mask = FeatureUtil.ADJACENCY_MAP[c+2];
// if this feature doesn't have this edge, skip it
if ((fmask & mask) == 0) {
continue;
}
// translate the target direction accordingly
dir = (dir + tile.orientation) % 4;
// make sure we have a neighbor in the appropriate
// direction
if (neighbors[dir] == null) {
continue;
}
// it looks like we have a match, so translate the target
// stuff into our orientation
mask = FeatureUtil.translateMask(mask, tile.orientation);
opp_mask = FeatureUtil.translateMask(
opp_mask, tile.orientation);
// make sure the neighbor in question isn't the one from
// which we inherited the group
int ogroup = neighbors[dir].getFeatureGroup(opp_mask);
if (ogroup == cgroup) {
continue;
}
// if we've already been assigned to a group, we propagate
// our group to the opposing feature
int fidx = neighbors[dir].getFeatureIndex(opp_mask);
if (fidx >= 0) {
Log.info("Propagating group [fidx=" + i +
", cgroup=" + cgroup +
", dir=" + dir + "].");
setFeatureGroup(tiles, neighbors[dir],
fidx, cgroup, mask);
} else {
Log.warning("Can't join-propagate feature " +
"[self=" + tile +
", target=" + neighbors[dir] +
", fidx=" + fidx + ", cgroup=" + cgroup +
", destEdge=" + opp_mask +
", srcEdge=" + mask + "].");
// otherwise, assign our new claim number to all members of
// the group (potentially causing some to inherit the new
// claim number)
for (int t = 0; t < flist.size(); t++) {
TileFeature feat = (TileFeature)flist.get(t);
// set the claim group in the tile
feat.tile.claims[feat.featureIndex] = claimGroup;
// also set the claim group on the piecen if the tile has
// an associated piecen that is on this feature
Piecen p = feat.tile.piecen;
if (p != null && p.featureIndex == feat.featureIndex) {
p.claimGroup = claimGroup;
}
}
}
@@ -316,71 +263,18 @@ public class TileUtil implements TileCodes
* being set.
* @param featureIndex the index of the feature.
* @param claimGroup the claim group value to set.
* @param entryEdgeMask the edge from which we are propagating this
* claim group (to avoid traversing back over that edge when
* propagating the group further).
*/
public static void setFeatureGroup (
List tiles, VenisonTile tile, int featureIndex,
int claimGroup, int entryEdgeMask)
public static void setClaimGroup (
List tiles, VenisonTile tile, int featureIndex, int claimGroup)
{
// set the claim group for this feature on this tile
tile.setFeatureGroup(featureIndex, claimGroup);
// load up this feature group
List flist = new ArrayList();
enumerateGroup(tiles, tile, featureIndex, flist);
// now propagate this feature to connected features
int ftype = tile.features[featureIndex].type;
int fmask = tile.features[featureIndex].edgeMask;
// iterate over all of the possible adjacency possibilities
for (int c = 0; c < FeatureUtil.ADJACENCY_MAP.length; c += 3) {
int mask = FeatureUtil.ADJACENCY_MAP[c];
int dir = FeatureUtil.ADJACENCY_MAP[c+1];
int opp_mask = FeatureUtil.ADJACENCY_MAP[c+2];
VenisonTile neighbor = null;
// if this feature doesn't have this edge, skip it
if ((fmask & mask) == 0) {
continue;
}
// figure out if this would be the tile from which we
// propagated into our current tile and skip it if so
opp_mask = FeatureUtil.translateMask(opp_mask, tile.orientation);
if ((opp_mask & entryEdgeMask) != 0) {
continue;
}
// make sure we have a neighbor in this direction
dir = (dir + tile.orientation) % 4;
switch (dir) {
case NORTH: neighbor = findTile(tiles, tile.x, tile.y-1); break;
case EAST: neighbor = findTile(tiles, tile.x+1, tile.y); break;
case SOUTH: neighbor = findTile(tiles, tile.x, tile.y+1); break;
case WEST: neighbor = findTile(tiles, tile.x-1, tile.y); break;
}
if (neighbor == null) {
continue;
}
// it looks like we have a match, so translate the target mask
// into our orientation
mask = FeatureUtil.translateMask(mask, tile.orientation);
// propagate, propagate, propagate
int fidx = neighbor.getFeatureIndex(opp_mask);
if (fidx >= 0) {
// only set the feature in the neighbor if they aren't
// already set to the appropriate group (prevent loops)
if (neighbor.claims[fidx] != claimGroup) {
setFeatureGroup(tiles, neighbor, fidx, claimGroup, mask);
}
} else {
Log.warning("Can't propagate feature [self=" + tile +
", target=" + neighbor + ", fidx=" + fidx +
", cgroup=" + claimGroup + ", srcEdge=" + mask +
", destEdge=" + opp_mask + "].");
}
// and assign the claim number to all features in the group
for (int t = 0; t < flist.size(); t++) {
TileFeature feat = (TileFeature)flist.get(t);
feat.tile.claims[feat.featureIndex] = claimGroup;
}
}
@@ -401,112 +295,40 @@ public class TileUtil implements TileCodes
public static int computeFeatureScore (
List tiles, VenisonTile tile, int featureIndex)
{
// determine what kind of feature it is
Feature feature = tile.features[featureIndex];
switch (feature.type) {
case ROAD:
return computeFeatureScore(tiles, tile, feature, new ArrayList());
case CITY: {
int score =
computeFeatureScore(tiles, tile, feature, new ArrayList());
// cities receive a 2x multiplier if they are larger than size
// two and are completed
return (score > 2) ? score*2 : score;
}
case CLOISTER:
if (feature.type == CLOISTER) {
// cloister's score specially
return computeCloisterScore(tiles, tile);
default:
case GRASS:
} else if (feature.type == GRASS) {
// grass doesn't score
return 0;
}
}
/**
* A helper function for {@link
* #computeFeatureScore(List,VenisonTile,int)}.
*/
protected static int computeFeatureScore (
List tiles, VenisonTile tile, Feature feature, List seen)
{
// if we've already counted this tile, bail
if (seen.contains(tile)) {
return 0;
}
// if we're here, it's a road or city feature, which we score by
// loading up the group and counting its size
List flist = new ArrayList();
boolean complete = enumerateGroup(tiles, tile, featureIndex, flist);
int score = flist.size();
// otherwise add this tile to the seen list
seen.add(tile);
// cities have a base score of one but get a one point bonus if
// they have a shield on them
int score = (feature.type == CITY && tile.hasShield) ? 2 : 1;
boolean missedNeighbor = false;
// now figure out what connected features there are, if any
int ftype = feature.type;
int fmask = feature.edgeMask;
// iterate over all of the possible adjacency possibilities
for (int c = 0; c < FeatureUtil.ADJACENCY_MAP.length; c += 3) {
int mask = FeatureUtil.ADJACENCY_MAP[c];
int dir = FeatureUtil.ADJACENCY_MAP[c+1];
int opp_mask = FeatureUtil.ADJACENCY_MAP[c+2];
VenisonTile neighbor = null;
// if this feature doesn't have this edge, skip it
if ((fmask & mask) == 0) {
continue;
}
// make sure we have a neighbor in this direction
dir = (dir + tile.orientation) % 4;
switch (dir) {
case NORTH: neighbor = findTile(tiles, tile.x, tile.y-1); break;
case EAST: neighbor = findTile(tiles, tile.x+1, tile.y); break;
case SOUTH: neighbor = findTile(tiles, tile.x, tile.y+1); break;
case WEST: neighbor = findTile(tiles, tile.x-1, tile.y); break;
}
if (neighbor == null) {
// if we don't have a neighbor in a direction that we
// need, we're an incomplete feature. alas
missedNeighbor = true;
continue;
}
// translate the target mask into our orientation
mask = FeatureUtil.translateMask(mask, tile.orientation);
opp_mask = FeatureUtil.translateMask(opp_mask, tile.orientation);
// propagate, propagate, propagate
int fidx = neighbor.getFeatureIndex(opp_mask);
if (fidx < 0) {
Log.warning("Tile mismatch while scoring [self=" + tile +
", target=" + neighbor + ", fidx=" + fidx +
", srcEdge=" + mask +
", destEdge=" + opp_mask + "].");
} else {
// add the score for this neighbor
int nscore = computeFeatureScore(
tiles, neighbor, neighbor.features[fidx], seen);
// if our neighbor returned a negative score, we convert
// it back to positive and make a note to make it negative
// at the end
if (nscore < 0) {
score += -nscore;
missedNeighbor = true;
} else {
score += nscore;
// for city groups, we need to add a bonus of one for every tile
// that contains a shield and mutiply by two if the city is
// complete and larger than two tiles
if (feature.type == CITY) {
for (int t = 0; t < flist.size(); t++) {
TileFeature feat = (TileFeature)flist.get(t);
if (feat.tile.hasShield) {
score++;
}
}
if (complete && score > 2) {
score *= 2;
}
}
return missedNeighbor ? -score : score;
// incomplete scores are reported in the negative
return complete ? score : -score;
}
/**
@@ -531,6 +353,161 @@ public class TileUtil implements TileCodes
return (score == 9) ? 9 : -score;
}
/**
* Clears out the claim group information for incomplete cities so
* that we can ignore them during farm scoring. Assigns new claim
* group values to any completed cities that were unclaimed.
*
* @param tiles a sorted list of tiles on the board.
*/
public static void prepCitiesForScoring (List tiles)
{
List flist = new ArrayList();
// iterate over the tiles, marking every city completed or not
int tsize = tiles.size();
for (int i = 0; i < tsize; i++) {
VenisonTile tile = (VenisonTile)tiles.get(i);
// iterate over each feature on this tile
for (int f = 0; f < tile.features.length; f++) {
// skip non-city features
if (tile.features[f].type != TileCodes.CITY) {
continue;
}
// clear out the feature group list before processing
flist.clear();
// enumerate the features in the group with this feature
if (enumerateGroup(tiles, tile, f, flist)) {
// if it's complete, we want to ensure that all of
// these features have a claim number
if (tile.claims[f] != 0) {
// it's complete and has a claim number. move on
continue;
}
// assign the claim number to all features in the group
int claimGroup = nextClaimGroup();
for (int t = 0; t < flist.size(); t++) {
TileFeature feat = (TileFeature)flist.get(t);
feat.tile.claims[feat.featureIndex] = claimGroup;
if (t == 0) {
Log.info("Claiming complete city " +
"[claim=" + feat.tile.claims[
feat.featureIndex] + "].");
}
}
} else {
// it's incomplete, so we want to clear out the claim
// number from all tiles in the group
for (int t = 0; t < flist.size(); t++) {
TileFeature feat = (TileFeature)flist.get(t);
if (t == 0) {
Log.info("Clearing incomplete city " +
"[claim=" + feat.tile.claims[
feat.featureIndex] + "].");
}
feat.tile.claims[feat.featureIndex] = 0;
}
}
}
}
}
/**
* Enumerates all of the features that are in the group of which the
* specified feature is a member.
*
* @param tiles a sorted list of the tiles on the board.
* @param tile the tile that contains the feature whose group is to be
* enumerated.
* @param featureIndex the index of the feature whose group is to be
* enumerated.
* @param group the list into which instances of {@link FeatureGroup}
* will be placed that represent the features that are members of the
* group.
*
* @return true if the group is complete (has no unconnected
* features), false if it is not.
*/
protected static boolean enumerateGroup (
List tiles, VenisonTile tile, int featureIndex, List group)
{
// create a tilefeature for this feature
TileFeature feat = new TileFeature(tile, featureIndex);
// determine whether or not this feature is already in the group
if (group.contains(feat)) {
return true;
}
// otherwise add this feature to the group and process this
// feature's neighbors
group.add(feat);
boolean complete = true;
int ftype = tile.features[featureIndex].type;
int fmask = tile.features[featureIndex].edgeMask;
// iterate over all of the possible adjacency possibilities
for (int c = 0; c < FeatureUtil.ADJACENCY_MAP.length; c += 3) {
int mask = FeatureUtil.ADJACENCY_MAP[c];
int opp_mask = FeatureUtil.ADJACENCY_MAP[c+2];
// if this feature doesn't have this edge, skip it
if ((fmask & mask) == 0) {
continue;
}
// look up our neighbor
VenisonTile neighbor = null;
int dir = FeatureUtil.ADJACENCY_MAP[c+1];
dir = (dir + tile.orientation) % 4;
switch (dir) {
case NORTH: neighbor = findTile(tiles, tile.x, tile.y-1); break;
case EAST: neighbor = findTile(tiles, tile.x+1, tile.y); break;
case SOUTH: neighbor = findTile(tiles, tile.x, tile.y+1); break;
case WEST: neighbor = findTile(tiles, tile.x-1, tile.y); break;
}
// make sure we have a neighbor in this direction
if (neighbor == null) {
// if we don't have a neighbor in a direction that we
// need, we're an incomplete feature. alas
complete = false;
continue;
}
// translate the target mask into our orientation
mask = FeatureUtil.translateMask(mask, tile.orientation);
opp_mask = FeatureUtil.translateMask(opp_mask, tile.orientation);
// obtain the index of the feature on the opposing tile
int nFeatureIndex = neighbor.getFeatureIndex(opp_mask);
if (nFeatureIndex < 0) {
Log.warning("Tile mismatch while grouping [tile=" + tile +
"featIdx=" + featureIndex +
", neighbor=" + neighbor +
", nFeatIdx=" + nFeatureIndex +
", srcEdge=" + mask +
", destEdge=" + opp_mask + "].");
continue;
}
// add this feature and its neighbors to the group
if (!enumerateGroup(tiles, neighbor, nFeatureIndex, group)) {
// if our neighbor was incomplete, we become incomplete.
// as dr. evil might say, "you incomplete me."
complete = false;
}
}
return complete;
}
/**
* Locates and returns the tile with the specified coordinates.
*
@@ -546,6 +523,22 @@ public class TileUtil implements TileCodes
return (tidx >= 0) ? (VenisonTile)tiles.get(tidx) : null;
}
/**
* Returns the number of piecens on the board owned the specified
* player.
*/
public static int countPiecens (DSet piecens, int playerIndex)
{
int count = 0;
Iterator iter = piecens.elements();
while (iter.hasNext()) {
if (((Piecen)iter.next()).owner == playerIndex) {
count++;
}
}
return count;
}
/**
* Returns the edge type for specified edge of the specified tile
* type.
@@ -572,10 +565,46 @@ public class TileUtil implements TileCodes
/** Used to generate our standard tile set. */
protected static void addTiles (int count, List list, VenisonTile tile)
{
for (int i = 0; i < count-1; i++) {
list.add(tile.clone());
for (int i = 0; i < count; i++) {
list.add(tile);
}
}
/** Used to keep track of actual features on tiles. */
protected static final class TileFeature
{
/** The tile that contains the feature. */
public VenisonTile tile;
/** The index of the feature in the tile. */
public int featureIndex;
/** Constructs a new tile feature. */
public TileFeature (VenisonTile tile, int featureIndex)
{
this.tile = tile;
this.featureIndex = featureIndex;
}
/** Properly implement equality. */
public boolean equals (Object other)
{
if (other == null ||
!(other instanceof TileFeature)) {
return false;
} else {
TileFeature feat = (TileFeature)other;
return (feat.tile == tile &&
feat.featureIndex == featureIndex);
}
}
/** Generate a string representation. */
public String toString ()
{
return "[tile=" + tile + ", fidx=" + featureIndex + "]";
}
list.add(tile);
}
/** Used to generate claim group values. */