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:
@@ -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. */
|
||||
|
||||
Reference in New Issue
Block a user