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; package com.threerings.venison;
@@ -173,6 +173,18 @@ public class VenisonBoard
"find no associated tile! [key=" + key + "]."); "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 * 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 * in the square under the mouse cursor where it can be legally placed
@@ -55,10 +55,10 @@ public class VenisonController
_venobj = (VenisonObject)plobj; _venobj = (VenisonObject)plobj;
// find out what our index is and use that as our piecen color // find out what our index is and use that as our piecen color
int oidx = ListUtil.indexOfEqual(_venobj.players, _self.username); _selfIndex = ListUtil.indexOfEqual(_venobj.players, _self.username);
if (oidx != -1) { if (_selfIndex != -1) {
// use our player index as the piecen color directly // 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 // grab the tiles and piecens from the game object and configure
@@ -137,6 +137,13 @@ public class VenisonController
_venobj.getOid(), PLACE_TILE_REQUEST, args); _venobj.getOid(), PLACE_TILE_REQUEST, args);
_ctx.getDObjectManager().postEvent(mevt); _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 // enable the noplace button
_panel.noplace.setEnabled(true); _panel.noplace.setEnabled(true);
@@ -153,6 +160,9 @@ public class VenisonController
_panel.noplace.setEnabled(false); _panel.noplace.setEnabled(false);
} else if (action.getActionCommand().equals(PLACE_NOTHING)) { } 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 // the user doesn't want to place anything this turn. send a
// place nothing request to the server // place nothing request to the server
MessageEvent mevt = new MessageEvent( MessageEvent mevt = new MessageEvent(
@@ -177,4 +187,7 @@ public class VenisonController
/** A reference to our body object. */ /** A reference to our body object. */
protected BodyObject _self; 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) 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); HGroupLayout gl = new HGroupLayout(HGroupLayout.STRETCH);
gl.setOffAxisPolicy(HGroupLayout.STRETCH); gl.setOffAxisPolicy(HGroupLayout.STRETCH);
setLayout(gl); setLayout(gl);
@@ -47,9 +50,15 @@ public class VenisonPanel
// create our side panel // create our side panel
VGroupLayout sgl = new VGroupLayout(VGroupLayout.STRETCH); VGroupLayout sgl = new VGroupLayout(VGroupLayout.STRETCH);
sgl.setOffAxisPolicy(VGroupLayout.STRETCH); sgl.setOffAxisPolicy(VGroupLayout.STRETCH);
sgl.setJustification(VGroupLayout.TOP);
JPanel sidePanel = new JPanel(sgl); 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 // add a turn indicator to the side panel
sidePanel.add(new JLabel("Current turn:"), VGroupLayout.FIXED);
sidePanel.add(new TurnIndicatorView(), VGroupLayout.FIXED); sidePanel.add(new TurnIndicatorView(), VGroupLayout.FIXED);
// add a "place nothing" button // 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; package com.threerings.venison;
@@ -8,6 +8,9 @@ package com.threerings.venison;
*/ */
public interface VenisonCodes 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 name of the command posted by the {@link VenisonBoard} when
* the user places a tile into a valid position. */ * the user places a tile into a valid position. */
public static final String TILE_PLACED = "tile_placed"; 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; package com.threerings.venison;
@@ -159,11 +159,8 @@ public class VenisonTile
* @param featureIndex the index of the feature to update. * @param featureIndex the index of the feature to update.
* @param claimGroup the claim group to associate with the feature. * @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 // update the claim group slot for this feature
claims[featureIndex] = claimGroup; claims[featureIndex] = claimGroup;
@@ -179,7 +176,7 @@ public class VenisonTile
* Piecen#featureIndex} field is assumed to be initialized to the * Piecen#featureIndex} field is assumed to be initialized to the
* feature index of this tile on which the piecen is to be placed. * 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 * propagate the claiming of this feature to all neighboring tiles if
* a non-null tiles array is supplied to the function. * a non-null tiles array is supplied to the function.
* *
@@ -210,8 +207,10 @@ public class VenisonTile
int claimGroup = TileUtil.nextClaimGroup(); int claimGroup = TileUtil.nextClaimGroup();
Log.info("Creating claim group [cgroup=" + claimGroup + Log.info("Creating claim group [cgroup=" + claimGroup +
", tile=" + this + ", fidx=" + piecen.featureIndex + "]."); ", tile=" + this + ", fidx=" + piecen.featureIndex + "].");
TileUtil.setFeatureGroup( TileUtil.setClaimGroup(
tiles, this, piecen.featureIndex, claimGroup, 0); 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 we have a piecen on this tile, render it as well
if (piecen != null && piecen.featureIndex == i) { if (piecen != null && piecen.featureIndex == i) {
features[i].paintPiecen( 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.setColor(Color.black);
g.drawRect(0, 0, TILE_WIDTH, TILE_HEIGHT); 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 // translate back out
g.translate(-sx, -sy); 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; package com.threerings.venison;
@@ -42,8 +42,8 @@ public class Piecen
/** A color constant. */ /** A color constant. */
public static final int GREEN = 4; public static final int GREEN = 4;
/** The color of this piecen. */ /** The owner of this piecen. */
public int color; public int owner;
/** The x and y coordinates of the tile on which this piecen is /** The x and y coordinates of the tile on which this piecen is
* placed. */ * placed. */
@@ -59,9 +59,9 @@ public class Piecen
/** /**
* Construts a piecen with the specified configuration. * 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.x = x;
this.y = y; this.y = y;
this.featureIndex = featureIndex; this.featureIndex = featureIndex;
@@ -85,7 +85,7 @@ public class Piecen
public void writeTo (DataOutputStream out) public void writeTo (DataOutputStream out)
throws IOException throws IOException
{ {
out.writeInt(color); out.writeInt(owner);
out.writeInt(x); out.writeInt(x);
out.writeInt(y); out.writeInt(y);
out.writeInt(featureIndex); out.writeInt(featureIndex);
@@ -95,7 +95,7 @@ public class Piecen
public void readFrom (DataInputStream in) public void readFrom (DataInputStream in)
throws IOException throws IOException
{ {
color = in.readInt(); owner = in.readInt();
x = in.readInt(); x = in.readInt();
y = in.readInt(); y = in.readInt();
featureIndex = in.readInt(); featureIndex = in.readInt();
@@ -127,7 +127,7 @@ public class Piecen
*/ */
public String toString () public String toString ()
{ {
return "[color=" + color + ", pos=" + x + "/" + y + return "[owner=" + owner + ", pos=" + x + "/" + y +
", fidx=" + featureIndex + "]"; ", 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; package com.threerings.venison;
@@ -118,4 +118,8 @@ public interface TileCodes
/** A constant indicating a cloister. */ /** A constant indicating a cloister. */
public static final int CLOISTER = 3; 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; package com.threerings.venison;
@@ -9,9 +9,14 @@ import java.util.Collections;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import com.samskivert.util.HashIntMap;
import com.samskivert.util.StringUtil; 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.DSet;
import com.threerings.presents.dobj.SetListener;
import com.threerings.presents.dobj.MessageEvent; import com.threerings.presents.dobj.MessageEvent;
import com.threerings.parlor.turn.TurnGameManager; 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. * The main coordinator of the Venison game on the server side.
*/ */
public class VenisonManager public class VenisonManager
extends TurnGameManager implements VenisonCodes extends TurnGameManager implements VenisonCodes, SetListener
{ {
// documentation inherited // documentation inherited
protected Class getPlaceObjectClass () protected Class getPlaceObjectClass ()
@@ -62,6 +67,11 @@ public class VenisonManager
_tilesInBox = TileUtil.getStandardTileSet(); _tilesInBox = TileUtil.getStandardTileSet();
Collections.shuffle(_tilesInBox); Collections.shuffle(_tilesInBox);
// shave off most of the tiles for the moment
while (_tilesInBox.size() > 15) {
_tilesInBox.remove(0);
}
// clear out our board tiles // clear out our board tiles
_tiles.clear(); _tiles.clear();
@@ -76,8 +86,9 @@ public class VenisonManager
_venobj.setPiecens(new DSet(Piecen.class)); _venobj.setPiecens(new DSet(Piecen.class));
// and add the starting tile // and add the starting tile
_venobj.addToTiles(VenisonTile.STARTING_TILE); VenisonTile start = TileUtil.getStartingTile();
_tiles.add(VenisonTile.STARTING_TILE); _venobj.addToTiles(start);
_tiles.add(start);
} }
protected void turnWillStart () protected void turnWillStart ()
@@ -116,10 +127,47 @@ public class VenisonManager
} }
/** /**
* Determines whether or not the placement of this tile has completed * At the end of the game, we need to compute the final scores.
* features that were previously incomplete.
*/ */
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 // potentially score all features on the tile
for (int i = 0; i < tile.features.length; i++) { 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 // see if any piecens are even on a feature in this group
int cgroup = tile.claims[i]; int cgroup = tile.claims[i];
int[] cgv = getClaimGroupVector(cgroup); int[] cgv = getClaimGroupVector(cgroup, piecens);
if (cgv == null) { if (cgv == null) {
// if not, we don't have anything to score // if not, we don't have anything to score
Log.info("Not scoring unclaimed feature " + // Log.info("Not scoring unclaimed feature " +
"[ttype=" + tile.type + ", feat=" + f + // "[ttype=" + tile.type + ", feat=" + f +
", cgroup=" + cgroup + "]."); // ", cgroup=" + cgroup + "].");
continue; continue;
} }
@@ -146,46 +194,47 @@ public class VenisonManager
// this feature // this feature
int score = TileUtil.computeFeatureScore(_tiles, tile, i); int score = TileUtil.computeFeatureScore(_tiles, tile, i);
// if the score is positive, it's a completed feature, so we // if the score is positive, it's a completed feature and we
// dole out the points and clear the associated piecens // score it regardless, we score incomplete features only
if (score > 0) { // during the final tally
Log.info("Scoring feature [ttype=" + tile.type + if (score > 0 || piecens != null) {
", feature=" + f + ", score=" + score + "]."); // 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 // adjust the scores
for (int p = 0; p < cgv.length; p++) { for (int p = 0; p < cgv.length; p++) {
_venobj.scores[p] += (score * cgv[p]); _venobj.scores[p] += (score * cgv[p]);
} }
// broadcast the new scores
_venobj.setScores(_venobj.scores);
Log.info("New scores: " + StringUtil.toString(_venobj.scores)); Log.info("New scores: " + StringUtil.toString(_venobj.scores));
// and free up the scored piecens // broadcast the new scores if this isn't the final tally
Iterator iter = _venobj.piecens.elements(); if (piecens == null) {
while (iter.hasNext()) { _venobj.setScores(_venobj.scores);
Piecen p = (Piecen)iter.next();
if (p.claimGroup == cgroup) {
Log.info("Removing piecen " + p + ".");
_venobj.removeFromPiecens(p.getKey());
}
} }
// and free up the scored piecens
removePiecens(cgroup, piecens);
} else { } else {
Log.info("Not scoring incomplete feature " + // Log.info("Not scoring incomplete feature " +
"[ttype=" + tile.type + ", feat=" + f + // "[ttype=" + tile.type + ", feat=" + f +
", score=" + score + "]."); // ", score=" + score + "].");
} }
} }
// we also may have completed a cloister, so we check that as well // we also may have completed a cloister, so we check that as well
for (int dx = -1; dx < 2; dx++) { for (int dx = -1; dx < 2; dx++) {
for (int dy = -1; dy < 2; dy++) { for (int dy = -1; dy < 2; dy++) {
// skip ourselves
if (dx == 0 && dy == 0) {
continue;
}
// find our neighbor and make sure they exist // find our neighbor and make sure they exist
VenisonTile neighbor = VenisonTile neighbor =
TileUtil.findTile(_tiles, tile.x + dx, tile.y + dy); TileUtil.findTile(_tiles, tile.x + dx, tile.y + dy);
@@ -205,43 +254,177 @@ public class VenisonManager
// tile has a piecen // tile has a piecen
if (p == null) { if (p == null) {
Log.info("Skipping non-piecen having " + // Log.info("Skipping non-piecen having " +
"cloister tile [tile=" + neighbor + // "cloister tile [tile=" + neighbor +
", feat=" + f + "]."); // ", feat=" + f + "].");
continue; continue;
} }
// piecen is on cloister feature // piecen is on cloister feature
if (neighbor.claims[i] != p.claimGroup) { if (neighbor.claims[i] != p.claimGroup) {
Log.info("Skipping cloister tile with piecen on " + // Log.info("Skipping cloister tile with piecen on " +
"non-cloister [tile=" + neighbor + // "non-cloister [tile=" + neighbor +
", feat=" + f + "]."); // ", feat=" + f + "].");
continue; continue;
} }
// score the cloister to see if it is completed // score the cloister
int score = TileUtil.computeFeatureScore( int score = TileUtil.computeFeatureScore(
_tiles, neighbor, i); _tiles, neighbor, i);
// it is, yay! // if it's completed or if we're doing the final
if (score > 0) { // 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 // add the score to the owning player
_venobj.scores[p.color] += score; _venobj.scores[p.owner] += score;
_venobj.setScores(_venobj.scores);
// and clear out the piecen // only broadcast the updated scores if this isn't
_venobj.removeFromPiecens(p.getKey()); // the final tally
Log.info("Scored cloister [tile=" + neighbor + if (piecens == null) {
", f=" + f + ", p=" + p + "]."); _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 { } else {
Log.info("Not scoring incomplete cloister " + // Log.info("Not scoring incomplete cloister " +
"[tile=" + neighbor + ", feat=" + f + "]."); // "[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 * 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 * 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 * vector, so it cannot be called again without overwriting values
* returned previously. * 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 * @return an array for the specified claim group or null if no
* players have a piecen claiming the specified claim group. * 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 // clear out the vector
Arrays.fill(_claimGroupVector, 0); Arrays.fill(_claimGroupVector, 0);
// iterate over the piecens // iterate over the piecens
int max = 0; int max = 0;
Iterator iter = _venobj.piecens.elements(); if (piecens == null) {
while (iter.hasNext()) { Iterator iter = _venobj.piecens.elements();
Piecen piecen = (Piecen)iter.next(); while (iter.hasNext()) {
if (piecen.claimGroup == claimGroup) { Piecen piecen = (Piecen)iter.next();
// color == player index... somewhat sketchy if (piecen.claimGroup == claimGroup) {
if (++_claimGroupVector[piecen.color] > max) { // color == player index... somewhat sketchy
// keep track of the highest scorer if (++_claimGroupVector[piecen.owner] > max) {
max = _claimGroupVector[piecen.color]; // 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; 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. */ /** Handles place tile requests. */
protected class PlaceTileHandler implements MessageHandler protected class PlaceTileHandler implements MessageHandler
{ {
@@ -303,7 +594,7 @@ public class VenisonManager
// placing a piece may have completed road or city // placing a piece may have completed road or city
// features. if it did, we score them now // features. if it did, we score them now
scoreCompletedFeatures(tile); scoreFeatures(tile, null);
Log.info("Placed tile " + tile + "."); Log.info("Placed tile " + tile + ".");
@@ -319,10 +610,17 @@ public class VenisonManager
public void handleEvent (MessageEvent event) public void handleEvent (MessageEvent event)
{ {
Piecen piecen = (Piecen)event.getArgs()[0]; Piecen piecen = (Piecen)event.getArgs()[0];
// make sure this is a valid placement
VenisonTile tile = (VenisonTile)_venobj.tiles.get(piecen.getKey()); 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 " + Log.warning("Can't find tile for requested piecen " +
"placement " + 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; package com.threerings.venison;
@@ -61,6 +61,30 @@ public class FeatureUtil implements TileCodes
WNW_F, WEST, ENE_F, 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. * 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; package com.threerings.venison;
@@ -12,11 +12,22 @@ import java.util.List;
import com.samskivert.util.IntTuple; import com.samskivert.util.IntTuple;
import com.threerings.presents.dobj.DSet;
/** /**
* Utility functions relating to the Venison tiles. * Utility functions relating to the Venison tiles.
*/ */
public class TileUtil implements TileCodes 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 * 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 * 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 () 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) public static void inheritClaims (List tiles, VenisonTile tile)
{ {
// obtain our neighboring tiles List flist = new ArrayList();
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);
// for each feature in the tile, determine whether or not the // for each feature in the tile, load up its claim group and make
// neighboring tile's matching feature is claimed // 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 ++) { for (int i = 0; i < tile.features.length; i ++) {
int ftype = tile.features[i].type; int claimGroup = 0;
int fmask = tile.features[i].edgeMask;
int cgroup = 0;
// iterate over all of the possible adjacency possibilities, // clear out the tilefeatures list before enumerating
// first looking for a claim group to inherit flist.clear();
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 // enumerate the claim group for this feature
if ((fmask & mask) == 0) { enumerateGroup(tiles, tile, i, flist);
continue;
}
// translate the target direction accordingly // find the first non-zero claim number
dir = (dir + tile.orientation) % 4; for (int t = 0; t < flist.size(); t++) {
TileFeature feat = (TileFeature)flist.get(t);
// make sure we have a neighbor in the appropriate int fcg = feat.tile.claims[feat.featureIndex];
// direction if (fcg != 0) {
if (neighbors[dir] == null) { claimGroup = fcg;
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] + "].");
break; break;
} }
} }
// if we didn't inherit a claim group, skip to the next // if we found no non-zero claim number, we've nothing to
// feature // inherit
if (cgroup == 0) { if (claimGroup == 0) {
continue; continue;
} }
// initialize the feature's claim group // otherwise, assign our new claim number to all members of
tile.claims[i] = cgroup; // the group (potentially causing some to inherit the new
// claim number)
// otherwise, iterate over all of the possible adjacency for (int t = 0; t < flist.size(); t++) {
// possibilities, propagating our group to connected features TileFeature feat = (TileFeature)flist.get(t);
for (int c = 0; c < FeatureUtil.ADJACENCY_MAP.length; c += 3) { // set the claim group in the tile
int mask = FeatureUtil.ADJACENCY_MAP[c]; feat.tile.claims[feat.featureIndex] = claimGroup;
int dir = FeatureUtil.ADJACENCY_MAP[c+1]; // also set the claim group on the piecen if the tile has
int opp_mask = FeatureUtil.ADJACENCY_MAP[c+2]; // an associated piecen that is on this feature
Piecen p = feat.tile.piecen;
// if this feature doesn't have this edge, skip it if (p != null && p.featureIndex == feat.featureIndex) {
if ((fmask & mask) == 0) { p.claimGroup = claimGroup;
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 + "].");
} }
} }
} }
@@ -316,71 +263,18 @@ public class TileUtil implements TileCodes
* being set. * being set.
* @param featureIndex the index of the feature. * @param featureIndex the index of the feature.
* @param claimGroup the claim group value to set. * @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 ( public static void setClaimGroup (
List tiles, VenisonTile tile, int featureIndex, List tiles, VenisonTile tile, int featureIndex, int claimGroup)
int claimGroup, int entryEdgeMask)
{ {
// set the claim group for this feature on this tile // load up this feature group
tile.setFeatureGroup(featureIndex, claimGroup); List flist = new ArrayList();
enumerateGroup(tiles, tile, featureIndex, flist);
// now propagate this feature to connected features // and assign the claim number to all features in the group
int ftype = tile.features[featureIndex].type; for (int t = 0; t < flist.size(); t++) {
int fmask = tile.features[featureIndex].edgeMask; TileFeature feat = (TileFeature)flist.get(t);
feat.tile.claims[feat.featureIndex] = claimGroup;
// 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 + "].");
}
} }
} }
@@ -401,112 +295,40 @@ public class TileUtil implements TileCodes
public static int computeFeatureScore ( public static int computeFeatureScore (
List tiles, VenisonTile tile, int featureIndex) List tiles, VenisonTile tile, int featureIndex)
{ {
// determine what kind of feature it is
Feature feature = tile.features[featureIndex]; Feature feature = tile.features[featureIndex];
switch (feature.type) { if (feature.type == CLOISTER) {
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:
// cloister's score specially // cloister's score specially
return computeCloisterScore(tiles, tile); return computeCloisterScore(tiles, tile);
default: } else if (feature.type == GRASS) {
case GRASS:
// grass doesn't score // grass doesn't score
return 0; return 0;
} }
}
/** // if we're here, it's a road or city feature, which we score by
* A helper function for {@link // loading up the group and counting its size
* #computeFeatureScore(List,VenisonTile,int)}. List flist = new ArrayList();
*/ boolean complete = enumerateGroup(tiles, tile, featureIndex, flist);
protected static int computeFeatureScore ( int score = flist.size();
List tiles, VenisonTile tile, Feature feature, List seen)
{
// if we've already counted this tile, bail
if (seen.contains(tile)) {
return 0;
}
// otherwise add this tile to the seen list // for city groups, we need to add a bonus of one for every tile
seen.add(tile); // that contains a shield and mutiply by two if the city is
// complete and larger than two tiles
// cities have a base score of one but get a one point bonus if if (feature.type == CITY) {
// they have a shield on them for (int t = 0; t < flist.size(); t++) {
int score = (feature.type == CITY && tile.hasShield) ? 2 : 1; TileFeature feat = (TileFeature)flist.get(t);
boolean missedNeighbor = false; if (feat.tile.hasShield) {
score++;
// 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;
} }
} }
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; 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. * 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; 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 * Returns the edge type for specified edge of the specified tile
* type. * type.
@@ -572,10 +565,46 @@ public class TileUtil implements TileCodes
/** Used to generate our standard tile set. */ /** Used to generate our standard tile set. */
protected static void addTiles (int count, List list, VenisonTile tile) protected static void addTiles (int count, List list, VenisonTile tile)
{ {
for (int i = 0; i < count-1; i++) { for (int i = 0; i < count; i++) {
list.add(tile.clone()); 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. */ /** Used to generate claim group values. */