Atlanti is now part of the Game Gardens repository.

git-svn-id: https://samskivert.googlecode.com/svn/trunk@1615 6335cc39-0255-0410-8fd6-9bcaacd3b74c
This commit is contained in:
mdb
2005-03-12 07:56:57 +00:00
parent e5de687142
commit 01f19518f6
43 changed files with 0 additions and 5442 deletions
-4
View File
@@ -1,4 +0,0 @@
(jde-set-variables
'(jde-db-source-directories (quote ("/export/local/jdk1.4/src" "/home/mdb/projects/samskivert/src/java" "/home/mdb/work/narya/src/java" "/home/mdb/projects/atlanti/src/java")))
'(jde-global-classpath (quote ("/home/mdb/projects/samskivert/dist/classes" "/home/mdb/work/narya/dist/classes" "/home/mdb/projects/atlanti/dist/classes")))
)
-21
View File
@@ -1,21 +0,0 @@
#!/bin/sh
BINDIR=`dirname $0`
JAVA_ARGS=""
ARGS=""
# grab the -Dinviter if it's specified
while [ ! -z "$1" ] ; do
case $1 in
-D*)
JAVA_ARGS="$JAVA_ARGS $1"
shift
;;
*)
ARGS="$ARGS $1"
shift
;;
esac
done
$BINDIR/runjava $JAVA_ARGS com.threerings.micasa.client.MiCasaApp $ARGS
-4
View File
@@ -1,4 +0,0 @@
#!/bin/sh
bindir=`dirname $0`
$bindir/runjava com.threerings.micasa.server.MiCasaServer $*
-90
View File
@@ -1,90 +0,0 @@
#!/usr/bin/perl -w
use Getopt::Std;
my $usage = "Usage: $0 [-p pid_file] [-r root_directory] args\n";
# locations of stuff
chomp($location = `dirname $0`);
# get the server root by popping the /bin off of our parent directory
@parts = split(/\//, $location);
pop(@parts);
my $root = join("/", @parts);
my $jhome = $ENV{"JAVA_HOME"};
# make sure JAVA_HOME is set
if (!defined $jhome) {
warn "$0: Error: No JAVA_HOME specified!\n";
warn "\n";
warn "You must set your JAVA_HOME environment variable to the\n";
warn "the absolute path of your JDK installation. For example:\n";
warn "\n";
warn " % JAVA_HOME=/usr/local/jdk1.2\n";
warn " % export JAVA_HOME\n";
die "\n";
}
# make sure it's set to a directory
if (! -d $jhome) {
die "$0: Can't find a java interpreter in '$jhome'.\n";
}
my $java = "$jhome/bin/java";
my $jlib = "$jhome/lib/classes.zip";
# put everything in our class path
my $classpath = "-classpath $root/dist/classes";
# add zip and jar files from our lib/ directory and the system lib/ directory
my @dirs = ( "$root/lib" );
foreach $dir (@dirs) {
next unless (defined $dir);
if (opendir(DIR, $dir)) {
foreach $lib (grep { /.(zip|jar)/ && -f "$dir/$_" } readdir(DIR)) {
$classpath .= ":$dir/$lib";
}
closedir DIR;
}
}
# finally add the standard classes
$classpath = "$classpath:$jlib";
# specify our application root (the resource manager needs this)
my $rootarg = "-Dresource_url=file:$root/rsrc";
my $pid_file = undef;
my $i = 0;
# strip out the -p and -r args (we'd use getopt() but the damned thing
# provides no way of escaping arguments so that we can pass args to
# runjava that get passed down to the JVM)
for ($i = 0; $i < @ARGV; $i++) {
my $arg = $ARGV[$i];
# stop when we see -- (and strip it out because Java don't dig --)
if ($arg eq "--") {
splice(@ARGV, $i, 1);
last;
}
if ($arg eq "-p") {
$pid_file = $ARGV[$i+1];
splice(@ARGV, $i, 2);
$i -= 1; # decrement i so that things stay in sync
} elsif ($arg eq "-r") {
$rootarg = "-DDapplication.root=" . $ARGV[$i+1];
splice(@ARGV, $i, 2);
$i -= 1; # decrement i so that things stay in sync
}
}
# log the pid file if requested to do so
print `echo $$ > $pid_file` if (defined $pid_file);
my $cmd = "$java -mx256M $classpath $rootarg " . join(" ", @ARGV);
# print "$cmd\n";
exec($cmd);
-83
View File
@@ -1,83 +0,0 @@
<!-- build configuration -->
<project name="atlanti" default="compile" basedir=".">
<!-- things you may want to change -->
<property name="app.name" value="atlanti"/>
<property name="doc.packages" value="com.threerings.atlanti.*"/>
<property name="doc.overview" value="com/threerings/atlanti/overview.html"/>
<property name="copyright.holder" value="Michael Bayne"/>
<property name="narya.home" value="../../work/narya"/>
<!-- things you probably don't want to change -->
<property name="src.dir" value="src/java"/>
<property name="deploy.dir" value="dist"/>
<property name="dist.jar" value="${app.name}.jar"/>
<property name="javadoc.dir" value="${deploy.dir}/docs"/>
<property name="gendobj.path" value="${narya.home}/bin/gendobj"/>
<!-- declare our classpath -->
<path id="classpath">
<fileset dir="lib" includes="**/*.jar"/>
<pathelement location="${deploy.dir}/classes"/>
</path>
<!-- generates .java files for all .dobj files -->
<target name="gendobj">
<apply executable="${gendobj.path}" failonerror="true">
<srcfile/>
<fileset dir="src/java" includes="**/*.dobj"/>
</apply>
</target>
<!-- prepares the application directories -->
<target name="prepare" depends="gendobj">
<mkdir dir="${deploy.dir}"/>
<mkdir dir="${deploy.dir}/classes"/>
<mkdir dir="${deploy.dir}/classes/rsrc"/>
<mkdir dir="${javadoc.dir}"/>
<copy todir="${deploy.dir}/classes">
<fileset dir="${src.dir}" includes="**/*.properties"/>
</copy>
<copy todir="${deploy.dir}/classes/rsrc">
<fileset dir="rsrc" includes="**/*"/>
</copy>
</target>
<!-- cleans out the installed application -->
<target name="clean">
<delete dir="${deploy.dir}"/>
</target>
<!-- build the java class files -->
<target name="compile" depends="prepare">
<javac srcdir="${src.dir}" destdir="${deploy.dir}/classes"
debug="on" optimize="off" deprecation="on">
<classpath refid="classpath"/>
</javac>
</target>
<!-- build the javadoc documentation -->
<target name="javadoc" depends="prepare">
<javadoc sourcepath="${src.dir}"
packagenames="${doc.packages}"
windowtitle="${app.name} API"
doctitle="${app.name} API"
overview="${src.dir}/${doc.overview}"
bottom="Copyright &#169; 2001 ${copyright.holder}. All Rights Reserved."
destdir="${javadoc.dir}"
additionalparam="-breakiterator">
<classpath refid="classpath"/>
<link href="http://java.sun.com/j2se/1.3/docs/api/"/>
</javadoc>
</target>
<!-- a target for rebuilding everything -->
<target name="all" depends="clean,prepare,compile,javadoc,dist"/>
<!-- builds our distribution files (war and jar) -->
<target name="dist" depends="prepare,compile">
<jar file="${deploy.dir}/${dist.jar}"
basedir="${deploy.dir}/classes"/>
</target>
</project>
-1
View File
@@ -1 +0,0 @@
i686-Linux
-22
View File
@@ -1,22 +0,0 @@
Required external libraries
---------------------------
The Atlantissonne code requires the following external Java libraries (JAR
files) to be placed (or symlinked) here in the lib/ directory.
* Apache Commons Lang library (from the sanbox)
http://jakarta.apache.org/commons/index.html
* The samskivert library:
http://www.waywardgeeks.org/code/samskivert/
* The Narya game development libraries; specifically: base, distrib,
media, micasa and parlor
http://www.threerings.net/code/narya/
Once the jar files are in place (regardless of what they are named), the
build system will automatically include them in the compile-time
classpath.
--
$Id: README,v 1.3 2002/12/12 06:10:13 mdb Exp $
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,14 +0,0 @@
#
# $Id: dbmap.properties,v 1.3 2003/03/23 05:02:31 mdb Exp $
#
# This provides information on the JDBC connections that are used by
# Atlantisonne server services.
# The default database mapping. All other definitions will inherit from
# this and need only be specified in cases where they differ from the
# defaults.
default.driver = org.gjt.mm.mysql.Driver
default.url = jdbc:mysql://DBHOST:3306/usermgmt
default.username = USERNAME
default.password = PASSWORD
@@ -1,4 +0,0 @@
#
# $Id: server.properties,v 1.2 2002/12/12 05:51:53 mdb Exp $
#
# Configuration for the Atlantissonne server
@@ -1,20 +0,0 @@
#
# $Id: server.properties,v 1.3 2002/12/12 05:51:53 mdb Exp $
#
# Configuration for the MiCasa server
_package = atlanti.server
_overrides = micasa.server
#
# The list of lobby identifiers to be loaded into this server.
#
lobby_ids = atlanti
#
# The cofiguration for the test lobby.
#
atlanti.config = com.threerings.micasa.lobby.table.TableLobbyConfig
atlanti.ugi = atlanti,board,tile,strategy
atlanti.name = Atlanti Lobby
atlanti.game_config = com.samskivert.atlanti.data.AtlantiConfig
@@ -1,4 +0,0 @@
#
# $Id: atlanti.properties,v 1.1 2002/05/21 04:45:09 mdb Exp $
#
# Translation messages
Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

@@ -1,54 +0,0 @@
//
// $Id: Log.java,v 1.3 2002/12/12 05:51:53 mdb Exp $
package com.samskivert.atlanti;
/**
* A placeholder class that contains a reference to the log object used by
* this package. This is a useful pattern to use when using the samskivert
* logging facilities. One creates a top-level class like this one that
* instantiates a log object with an name that identifies log messages
* from that package and then provides static methods that generate log
* messages using that instance. Then, classes in that package need only
* import the log wrapper class and can easily use it to generate log
* messages. For example:
*
* <pre>
* import com.samskivert.atlanti.Log;
* // ...
* Log.warning("All hell is breaking loose!");
* // ...
* </pre>
*
* @see com.samskivert.util.Log
*/
public class Log
{
/** The static log instance configured for use by this package. */
public static com.samskivert.util.Log log =
new com.samskivert.util.Log("atlanti");
/** Convenience function. */
public static void debug (String message)
{
log.debug(message);
}
/** Convenience function. */
public static void info (String message)
{
log.info(message);
}
/** Convenience function. */
public static void warning (String message)
{
log.warning(message);
}
/** Convenience function. */
public static void logStackTrace (Throwable t)
{
log.logStackTrace(com.samskivert.util.Log.WARNING, t);
}
}
@@ -1,75 +0,0 @@
//
// $Id: TileGeometryTest.java,v 1.7 2003/03/23 02:22:51 mdb Exp $
package com.samskivert.atlanti;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import com.samskivert.swing.util.SwingUtil;
import com.threerings.media.image.ImageManager;
import com.threerings.media.tile.TileManager;
import com.threerings.resource.ResourceManager;
import com.samskivert.atlanti.data.AtlantiTile;
import com.samskivert.atlanti.data.TileCodes;
import com.samskivert.atlanti.util.PiecenUtil;
/**
* A simple class for testing the tile geometry specifications by drawing
* them.
*/
public class TileGeometryTest extends JPanel
implements TileCodes
{
public TileGeometryTest ()
{
for (int i = 0; i < TILE_TYPES; i++) {
_tiles[i] = new AtlantiTile(i+1, true, NORTH, i % 5, i / 5);
}
}
public void paintComponent (Graphics g)
{
super.paintComponent(g);
// paint our tiles
for (int i = 0; i < _tiles.length; i++) {
_tiles[i].paint((Graphics2D)g, 0, 0);
}
}
public Dimension getPreferredSize ()
{
// we want to be five tiles wide by four tiles tall
return new Dimension(TILE_WIDTH * 5, TILE_HEIGHT * 4);
}
public static void main (String[] args)
{
JFrame frame = new JFrame("Tile geometry test");
ResourceManager rmgr = new ResourceManager("rsrc");
ImageManager imgr = new ImageManager(rmgr, frame);
TileManager tmgr = new TileManager(imgr);
AtlantiTile.setManagers(imgr, tmgr);
AtlantiTile.piecenDebug = true;
PiecenUtil.init(tmgr);
// quit if we're closed
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
TileGeometryTest panel = new TileGeometryTest();
frame.getContentPane().add(panel);
frame.pack();
SwingUtil.centerWindow(frame);
frame.show();
}
protected AtlantiTile[] _tiles = new AtlantiTile[TILE_TYPES];
}
@@ -1,693 +0,0 @@
//
// $Id: AtlantiBoard.java,v 1.19 2003/03/23 05:12:43 mdb Exp $
package com.samskivert.atlanti.client;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import com.samskivert.swing.Controller;
import com.samskivert.swing.util.SwingUtil;
import com.samskivert.util.CollectionUtil;
import com.threerings.presents.dobj.DSet;
import com.samskivert.atlanti.Log;
import com.samskivert.atlanti.data.AtlantiCodes;
import com.samskivert.atlanti.data.AtlantiTile;
import com.samskivert.atlanti.data.Piecen;
import com.samskivert.atlanti.data.TileCodes;
import com.samskivert.atlanti.util.TileUtil;
/**
* Displays the tiles that make up the board.
*/
public class AtlantiBoard extends JPanel
implements TileCodes, AtlantiCodes
{
/**
* Constructs a board.
*/
public AtlantiBoard ()
{
// create mouse adapters that will let us know when interesting
// mouse events happen
addMouseListener(new MouseAdapter() {
public void mouseClicked (MouseEvent evt) {
AtlantiBoard.this.mouseClicked(evt);
}
});
addMouseMotionListener(new MouseMotionAdapter() {
public void mouseMoved (MouseEvent evt) {
AtlantiBoard.this.mouseMoved(evt);
}
});
}
/**
* Sets the piecen color to use when creating new piecens.
*/
public void setNewPiecenColor (int color)
{
_newPiecenColor = color;
}
/**
* Sets the tiles to be displayed by this board. Any previous tiles
* are forgotten and the new tiles are initialized according to their
* geometry to set up initial claim groups.
*
* @param tset the set of {@link AtlantiTile} objects to be displayed
* by this board.
*/
public void setTiles (DSet tset)
{
// clear out our old tiles list
_tiles.clear();
// copy the tiles from the set into our local list
CollectionUtil.addAll(_tiles, tset.entries());
// sort the list
Collections.sort(_tiles);
// recompute our desired dimensions and then have our parent
// adjust to our changed size
computeDimensions();
}
/**
* Sets the piecens to be placed on the appropriate tiles of the
* board. This should only be done when first entering the game room
* and subsequent piecen placement should be done via {@link
* #placePiecen}.
*/
public void setPiecens (DSet piecens)
{
// just iterate over the set placing each of the piecens in turn
Iterator iter = piecens.entries();
while (iter.hasNext()) {
placePiecen((Piecen)iter.next());
}
}
/**
* Instructs the board to add the specified tile to the display. The
* tile will have its claims inherited accordingly.
*/
public void addTile (AtlantiTile tile)
{
Log.info("Adding tile to board " + tile + ".");
// if we add a tile that is the same as our most recently placed
// tile, leave the placed tile. otherwise clear it out
if (!tile.equals(_placedTile)) {
_placedTile = null;
}
// add the tile
_tiles.add(tile);
// reference this as our most recently placed tile
_lastPlacedTile = tile;
// resort the list
Collections.sort(_tiles);
// have the new tile inherit its claim groups
TileUtil.inheritClaims(_tiles, tile);
// recompute our desired dimensions and then have our parent
// adjust to our changed size
computeDimensions();
}
/**
* Places the specified piecen on the appropriate tile and updates
* claim groups as necessary.
*/
public void placePiecen (Piecen piecen)
{
// if we still have a placed tile, we get rid of it
_placedTile = null;
Log.info("Placing " + piecen + ".");
// locate the tile associated with this piecen
int tidx = _tiles.indexOf(piecen);
if (tidx != -1) {
AtlantiTile tile = (AtlantiTile)_tiles.get(tidx);
// set the piecen on the tile (supplying our tile list so that
// the necessary claim group adjustments can be made)
tile.setPiecen(piecen, _tiles);
// and repaint
repaintTile(tile);
} else {
Log.warning("Requested to place piecen for which we could " +
"find no associated tile! [piecen=" + piecen + "].");
}
}
/**
* When a piecen is removed (after scoring it), this method should be
* called to update the board display.
*/
public void clearPiecen (Object key)
{
// locate the tile associated with this piecen key
int tsize = _tiles.size();
for (int i = 0; i < tsize; i++) {
AtlantiTile tile = (AtlantiTile)_tiles.get(i);
if (tile.getKey().equals(key)) {
// clear the piecen out of the tile
tile.clearPiecen();
// and repaint
repaintTile(tile);
// and get on out
return;
}
}
Log.warning("Requested to clear piecen for which we could " +
"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;
}
/**
* If we freed up a placeable piecen that we didn't have when we
* placed our tile, this can be called to reenable piecen placement.
*/
public void enablePiecenPlacement ()
{
_placingPiecen = true;
}
/**
* 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
* and its orientation will be determined based on the pointer's
* proximity to the edges of the target square. When the user clicks
* the mouse while the tile is in a placeable position, a
* <code>TILE_PLACED</code> command will be dispatched to the
* controller in scope. The coordinates and orientation of the tile
* will be available by fetching the tile back via {@link
* #getPlacedTile}. The tile provided to this method will not be
* modified.
*
* @param tile the new tile to be placed or null if no tile is to
* currently be placed.
*/
public void setTileToBePlaced (AtlantiTile tile)
{
Log.info("Setting tile to be placed [tile=" + tile + "].");
// make a copy of this tile so that we can play with it
_placingTile = (AtlantiTile)tile.clone();;
// update our internal state based on this new placing tile
if (_placingTile != null) {
updatePlacingInfo(true);
}
}
/**
* Returns the last tile placed by the user.
*/
public AtlantiTile getPlacedTile ()
{
return _placedTile;
}
// documentation inherited
public void doLayout ()
{
super.doLayout();
// compute our translation coordinates based on our size
_tx = (getWidth() - TILE_WIDTH * _width)/2;
_ty = (getHeight() - TILE_HEIGHT * _height)/2;
}
/**
* Causes the supplied tile to be repainted.
*/
public void repaintTile (AtlantiTile tile)
{
int offx = _tx + (tile.x + _origX) * TILE_WIDTH;
int offy = _ty + (tile.y + _origY) * TILE_HEIGHT;
repaint(offx, offy, TILE_WIDTH, TILE_HEIGHT);
}
// documentation inherited
public void paintComponent (Graphics g)
{
super.paintComponent(g);
Graphics2D g2 = (Graphics2D)g;
// center the tile display if we are bigger than we need to be
g.translate(_tx, _ty);
// iterate over our tiles, painting each of them
int tsize = _tiles.size();
for (int i = 0; i < tsize; i++) {
AtlantiTile tile = (AtlantiTile)_tiles.get(i);
tile.paint(g2, _origX, _origY);
}
// if we have a placing tile, draw that one as well
if (_placingTile != null && _validPlacement) {
// if the current position is valid, draw the placing tile
_placingTile.paint(g2, _origX, _origY);
// draw a green rectangle around the placing tile
g.setColor(Color.blue);
int sx = (_placingTile.x + _origX) * TILE_WIDTH;
int sy = (_placingTile.y + _origY) * TILE_HEIGHT;
g.drawRect(sx, sy, TILE_WIDTH-1, TILE_HEIGHT-1);
}
// if we have a recently placed tile, draw that one as well
if (_placedTile != null) {
// draw the tile
_placedTile.paint(g2, _origX, _origY);
// draw a white rectangle around the placed tile
g.setColor(Color.white);
int sx = (_placedTile.x + _origX) * TILE_WIDTH;
int sy = (_placedTile.y + _origY) * TILE_HEIGHT;
g.drawRect(sx, sy, TILE_WIDTH-1, TILE_HEIGHT-1);
}
// draw a white rectangle around the last placed
if (_lastPlacedTile != null) {
g.setColor(Color.white);
int sx = (_lastPlacedTile.x + _origX) * TILE_WIDTH;
int sy = (_lastPlacedTile.y + _origY) * TILE_HEIGHT;
g.drawRect(sx, sy, TILE_WIDTH-1, TILE_HEIGHT-1);
}
// undo our translations
g.translate(-_tx, -_ty);
}
/** Called by our adapter when the mouse moves. */
protected void mouseMoved (MouseEvent evt)
{
// we always want to know about our last mouse coordinates
_mouseX = evt.getX() - _tx;
_mouseY = evt.getY() - _ty;
if (_placingTile != null) {
// if we have a tile to be placed, update its coordinates (it
// will automatically be repainted)
updatePlacingInfo(false);
} else if (_placedTile != null && _placingPiecen) {
// if we have a recently placed tile, we're doing piecen
// placement; first convert the mouse coords into tile coords
int mx = _mouseX - (_placedTile.x + _origX) * TILE_WIDTH;
int my = _mouseY - (_placedTile.y + _origY) * TILE_HEIGHT;
boolean changed = false;
// now see if we're inside the placing tile
if (mx >= 0 && mx < TILE_WIDTH && my >= 0 && my < TILE_HEIGHT) {
int fidx = _placedTile.getFeatureIndex(mx, my);
// if the feature is not already claimed, we can put a
// piece there to indicate that it can be claimed
if (_placedTile.claims[fidx] == 0) {
if (_placedTile.piecen == null ||
_placedTile.piecen.featureIndex != fidx) {
Piecen p = new Piecen(_newPiecenColor, 0, 0, fidx);
_placedTile.setPiecen(p, null);
changed = true;
}
} else {
// we may need to clear out a piecen since we've moved
if (_placedTile.piecen != null) {
_placedTile.clearPiecen();
changed = true;
}
}
} else {
// we may need to clear out a piecen since we've moved
if (_placedTile.piecen != null) {
_placedTile.clearPiecen();
changed = true;
}
}
if (changed) {
repaintTile(_placedTile);
}
}
}
/** Called by our adapter when the mouse is clicked. */
protected void mouseClicked (MouseEvent evt)
{
int modifiers = evt.getModifiers();
// if this is a right button click, and we're in piecen placing
// mode, generate a PLACE_NOTHING notification instead
if (_placingPiecen && (modifiers & MouseEvent.BUTTON3_MASK) != 0) {
// stop piecen placement
_placingPiecen = false;
// clear out any placed piecen because we're placing nothing
if (_placedTile != null && _placedTile.piecen != null) {
_placedTile.piecen = null;
repaintTile(_placedTile);
}
// post the action
Controller.postAction(this, PLACE_NOTHING);
} else {
// ignore non-button one presses other than cancel piecen
// placement
if ((modifiers & MouseEvent.BUTTON1_MASK) == 0) {
return;
}
}
// if we have a placing tile and it's in a valid position, we want
// to dispatch an action letting the controller know that the user
// placed it
if (_placingTile != null && _validPlacement) {
// move the placing tile to the placed tile
_placedTile = _placingTile;
_placingTile = null;
// inherit claims on the placed tile
TileUtil.inheritClaims(_tiles, _placedTile);
// post the action
Controller.postAction(this, TILE_PLACED);
// move into placing piecen mode
_placingPiecen = true;
// recompute our dimensions (which will relayout or repaint)
computeDimensions();
}
// if we're placing a piecen and the piecen is in a valid
// position, we want to dispatch an action letting the controller
// know that the user placed it
if (_placingPiecen && _placedTile != null &&
_placedTile.piecen != null) {
// clear out placing piecen mode
_placingPiecen = false;
// post the action
Controller.postAction(this, PIECEN_PLACED);
}
}
/**
* Updates the coordinates and orientation of the placing tile based
* on the last known coordinates of the mouse and causes it to be
* repainted.
*/
protected void updatePlacingInfo (boolean force)
{
boolean updated = false;
// convert mouse coordinates into tile coordinates and offset them
// by the origin
int x = divFloor(_mouseX, TILE_WIDTH) - _origX;
int y = divFloor(_mouseY, TILE_HEIGHT) - _origY;
// if these are different than the values currently in the placing
// tile, update the tile coordinates
if (_placingTile.x != x || _placingTile.y != y || force) {
// if we have a valid orientation presently, and we're moving,
// we need to clear out the old orientation
if (_validPlacement) {
repaintTile(_placingTile);
}
// update the coordinates of the tile
_placingTile.x = x;
_placingTile.y = y;
// make a note that we moved
updated = true;
// we also need to recompute the valid orientations for the
// tile in this new position
_validOrients = TileUtil.computeValidOrients(_tiles, _placingTile);
// if we've changed positions, clear out our valid placement
// flag
_validPlacement = false;
}
// determine if we should change the orientation based on the
// position of the mouse within the tile boundaries
int rx = _mouseX % TILE_WIDTH;
int ry = _mouseY % TILE_HEIGHT;
int orient = coordToOrient(rx, ry);
// scan for a legal orientation that is closest to our desired
// orientation
for (int i = 0; i < 4; i++) {
int candOrient = (orient+i)%4;
if (_validOrients[candOrient]) {
if (_placingTile.orientation != candOrient) {
_placingTile.orientation = candOrient;
// make a note that we moved
updated = true;
}
_validPlacement = true;
break;
}
}
// if we now have a valid orientation and something was changed,
// we want to repaint at the new tile location
if (_validPlacement && updated) {
repaintTile(_placingTile);
}
}
/**
* Converts mouse coordinates which are relative to a particular tile,
* into an orientation based on the position within that tile. A tile
* is divided up into four quadrants by lines connecting its four
* corners. If the tile is in a quadrant closes to an edge, it is
* converted to the orientation corresponding with that edge.
*
* @param rx the mouse coordinates modulo tile width.
* @param ry the mouse coordinates modulo tile height.
*
* @return the orientation desired for the tile in which the mouse
* resides.
*/
protected int coordToOrient (int rx, int ry)
{
if (rx > ry) {
if (rx > (TILE_HEIGHT - ry)) {
return EAST;
} else {
return NORTH;
}
} else {
if (rx > (TILE_HEIGHT - ry)) {
return SOUTH;
} else {
return WEST;
}
}
}
/** Divides the two integers returning the floor of the divided value
* rather than its truncation. */
protected static int divFloor (int value, int divisor)
{
return (int)Math.floor((double)value/divisor);
}
// documentation inherited
public Dimension getPreferredSize ()
{
if (_tiles.size() == 0) {
return new Dimension(100, 100);
} else {
return new Dimension(TILE_WIDTH * _width, TILE_HEIGHT * _height);
}
}
/**
* Determines how big we want to be based on where the tiles have been
* laid out. This will cause the component to be re-layed out if the
* dimensions change or repainted if not.
*/
protected void computeDimensions ()
{
int maxX = 0, maxY = 0;
int minX = 0, minY = 0;
// if we have a recently placed tile, start with that one
if (_placedTile != null) {
minX = maxX = _placedTile.x;
minY = maxY = _placedTile.y;
}
// figure out what our boundaries are
int tsize = _tiles.size();
for (int i = 0; i < tsize; i++) {
AtlantiTile tile = (AtlantiTile)_tiles.get(i);
if (tile.x > maxX) {
maxX = tile.x;
} else if (tile.x < minX) {
minX = tile.x;
}
if (tile.y > maxY) {
maxY = tile.y;
} else if (tile.y < minY) {
minY = tile.y;
}
}
// spread our bounds out by one
minX -= 1; minY -= 1;
maxX += 1; maxY += 1;
// keep track of these to know if we've change dimensions
int oldOrigX = _origX, oldOrigY = _origY;
int oldWidth = _width, oldHeight = _height;
// now we can compute our width and the origin offset
_origX = -minX;
_origY = -minY;
_width = maxX - minX + 1;
_height = maxY - minY + 1;
if (_origX != oldOrigX || _origY != oldOrigY ||
oldWidth != _width || oldHeight != _height) {
// if the dimensions changed, we need to relayout
revalidate();
}
// always repaint because revalidation doesn't always seem to
// result in a repaint
repaint();
}
/** Test code. */
public static void main (String[] args)
{
JFrame frame = new JFrame("Board test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
AtlantiBoard board = new AtlantiBoard();
TestDSet set = new TestDSet();
set.addTile(new AtlantiTile(CITY_TWO, true, WEST, 0, 0));
set.addTile(new AtlantiTile(CITY_TWO, false, WEST, -1, 1));
set.addTile(new AtlantiTile(CITY_ONE, false, SOUTH, -1, -1));
AtlantiTile zero = new AtlantiTile(CURVED_ROAD, false, WEST, 0, 2);
set.addTile(zero);
AtlantiTile one = new AtlantiTile(TWO_CITY_TWO, false, NORTH, 0, 1);
set.addTile(one);
set.addTile(new AtlantiTile(CITY_THREE, false, WEST, 1, 1));
set.addTile(new AtlantiTile(CITY_THREE_ROAD, false, EAST, 1, 2));
set.addTile(new AtlantiTile(CITY_THREE, false, NORTH, -1, 0));
AtlantiTile two = new AtlantiTile(CITY_ONE, false, EAST, -2, 0);
set.addTile(two);
board.setTiles(set);
AtlantiTile placing = new AtlantiTile(CITY_TWO, false, NORTH, 0, 0);
board.setTileToBePlaced(placing);
// set a feature group to test propagation
List tiles = new ArrayList();
CollectionUtil.addAll(tiles, set.entries());
Collections.sort(tiles);
zero.setPiecen(new Piecen(Piecen.GREEN, 0, 0, 2), tiles);
one.setPiecen(new Piecen(Piecen.BLUE, 0, 0, 0), tiles);
two.setPiecen(new Piecen(Piecen.RED, 0, 0, 1), tiles);
Log.info("Incomplete road: " +
TileUtil.computeFeatureScore(tiles, zero, 2));
Log.info("Completed city: " +
TileUtil.computeFeatureScore(tiles, two, 1));
Log.info("Incomplete city: " +
TileUtil.computeFeatureScore(tiles, one, 2));
frame.getContentPane().add(board, BorderLayout.CENTER);
frame.pack();
SwingUtil.centerWindow(frame);
frame.show();
}
protected static class TestDSet extends DSet
{
public void addTile (AtlantiTile tile)
{
add(tile);
}
}
/** A reference to our tile set. */
protected ArrayList _tiles = new ArrayList();
/** The tile currently being placed by the user. */
protected AtlantiTile _placingTile;
/** The last tile being placed by the user. */
protected AtlantiTile _placedTile;
/** The last tile placed on the board via {@link #addTile}. */
protected AtlantiTile _lastPlacedTile;
/** A flag indicating whether or not we're placing a piecen. */
protected boolean _placingPiecen = false;
/** Whether or not the current position and orientation of the placing
* tile is valid. */
protected boolean _validPlacement = false;
/** An array indicating which of the four directions are valid
* placements based on the current position of the placing tile. */
protected boolean[] _validOrients;
/** The color to use when creating new piecens. */
protected int _newPiecenColor = Piecen.BLUE;
/** Our render offset in pixels. */
protected int _tx, _ty;
/** The offset in tile coordinates of the origin. */
protected int _origX, _origY;
/** The width and height of the board in tile coordinates. */
protected int _width, _height;
/** The last known position of the mouse. */
protected int _mouseX, _mouseY;
}
@@ -1,54 +0,0 @@
//
// $Id: AtlantiConfigurator.java,v 1.1 2002/12/12 05:51:53 mdb Exp $
package com.samskivert.atlanti.client;
import com.samskivert.swing.SimpleSlider;
import com.samskivert.swing.VGroupLayout;
import com.threerings.micasa.lobby.table.TableGameConfigurator;
import com.samskivert.atlanti.data.AtlantiConfig;
/**
* Provides a user interface for configuring a game.
*/
public class AtlantiConfigurator extends TableGameConfigurator
{
// documentation inherited
protected void createConfigInterface ()
{
super.createConfigInterface();
// _boardSize = new SimpleSlider("Board size:", 16, 48, 32);
// add(_boardSize, VGroupLayout.FIXED);
// _handSize = new SimpleSlider("Hand size:", 2, 5, 5);
// add(_handSize, VGroupLayout.FIXED);
}
// documentation inherited
protected void gotGameConfig ()
{
super.gotGameConfig();
_vconfig = (AtlantiConfig)_config;
// // configure our elements
// _boardSize.setValue(_vconfig.boardWidth);
// _handSize.setValue(_vconfig.handSize);
}
// documentation inherited
protected void flushGameConfig ()
{
super.flushGameConfig();
// // configure our elements
// _vconfig.boardWidth = _boardSize.getValue();
// _vconfig.boardHeight = _boardSize.getValue();
// _vconfig.handSize = _handSize.getValue();
}
protected AtlantiConfig _vconfig;
// protected SimpleSlider _boardSize;
// protected SimpleSlider _handSize;
}
@@ -1,223 +0,0 @@
//
// $Id
package com.samskivert.atlanti.client;
import java.awt.event.ActionEvent;
import com.samskivert.util.ListUtil;
import com.threerings.util.Name;
import com.threerings.presents.dobj.AttributeChangedEvent;
import com.threerings.presents.dobj.DSet;
import com.threerings.presents.dobj.EntryAddedEvent;
import com.threerings.presents.dobj.EntryRemovedEvent;
import com.threerings.presents.dobj.EntryUpdatedEvent;
import com.threerings.presents.dobj.MessageEvent;
import com.threerings.presents.dobj.SetListener;
import com.threerings.crowd.data.BodyObject;
import com.threerings.crowd.data.PlaceObject;
import com.threerings.crowd.client.PlaceView;
import com.threerings.parlor.game.GameController;
import com.threerings.parlor.turn.TurnGameController;
import com.threerings.parlor.turn.TurnGameControllerDelegate;
import com.threerings.parlor.util.ParlorContext;
import com.threerings.micasa.util.MiCasaContext;
import com.samskivert.atlanti.Log;
import com.samskivert.atlanti.data.AtlantiCodes;
import com.samskivert.atlanti.data.AtlantiObject;
import com.samskivert.atlanti.data.AtlantiTile;
import com.samskivert.atlanti.data.Piecen;
import com.samskivert.atlanti.util.TileUtil;
/**
* The main coordinator of user interface activities on the client-side of
* the game.
*/
public class AtlantiController extends GameController
implements TurnGameController, AtlantiCodes, SetListener
{
/**
* Creates our controller and prepares it for operation.
*/
public AtlantiController ()
{
addDelegate(_delegate = new TurnGameControllerDelegate(this));
}
// documentation inherited
protected void didInit ()
{
super.didInit();
// get a handle on our body object
_self = (BodyObject)_ctx.getClient().getClientObject();
}
// documentation inherited
protected PlaceView createPlaceView ()
{
_panel = new AtlantiPanel((MiCasaContext)_ctx, this);
return _panel;
}
// documentation inherited
public void willEnterPlace (PlaceObject plobj)
{
super.willEnterPlace(plobj);
// get a casted reference to our game object
_venobj = (AtlantiObject)plobj;
// find out what our index is and use that as our piecen color
_selfIndex = ListUtil.indexOfEqual(_venobj.players, _self.username);
if (_selfIndex != -1) {
// use our player index as the piecen color directly
_panel.board.setNewPiecenColor(_selfIndex);
}
// grab the tiles and piecens from the game object and configure
// the board with them
_panel.board.setTiles(_venobj.tiles);
_panel.board.setPiecens(_venobj.piecens);
// if it's our turn, set the tile to be placed
if (_delegate.isOurTurn()) {
_panel.board.setTileToBePlaced(_venobj.currentTile);
}
}
// documentation inherited
public void turnDidChange (Name turnHolder)
{
// if it's our turn, set the tile to be placed
if (turnHolder.equals(_self.username)) {
_panel.board.setTileToBePlaced(_venobj.currentTile);
}
}
// documentation inherited
public void attributeChanged (AttributeChangedEvent event)
{
super.attributeChanged(event);
// handle the setting of the board state
if (event.getName().equals(AtlantiObject.TILES)) {
_panel.board.setTiles(_venobj.tiles);
} else if (event.getName().equals(AtlantiObject.PIECENS)) {
_panel.board.setPiecens(_venobj.piecens);
}
}
// documentation inherited
public void entryAdded (EntryAddedEvent event)
{
// we care about additions to TILES and PIECENS
if (event.getName().equals(AtlantiObject.TILES)) {
// a tile was added, add it to the board
AtlantiTile tile = (AtlantiTile)event.getEntry();
_panel.board.addTile(tile);
} else if (event.getName().equals(AtlantiObject.PIECENS)) {
// a piecen was added, place it on the board
Piecen piecen = (Piecen)event.getEntry();
_panel.board.placePiecen(piecen);
}
}
// documentation inherited
public void entryUpdated (EntryUpdatedEvent event)
{
}
// documentation inherited
public void entryRemoved (EntryRemovedEvent event)
{
if (event.getName().equals(AtlantiObject.PIECENS)) {
// a piecen was removed, update the board
_panel.board.clearPiecen(event.getKey());
}
}
// documentation inherited
public boolean handleAction (ActionEvent action)
{
if (action.getActionCommand().equals(TILE_PLACED)) {
AtlantiTile tile = _panel.board.getPlacedTile();
// the user placed the tile into a valid location. grab the
// placed tile from the board and submit it to the server
Object[] args = new Object[] { tile };
MessageEvent mevt = new MessageEvent(
_venobj.getOid(), PLACE_TILE_REQUEST, args);
_ctx.getDObjectManager().postEvent(mevt);
// if we have no piecens to place or if there are no unclaimed
// features on the placed tile, we immediately disable piecen
// placement in the board and expect that the server will end
// our turn
int pcount = TileUtil.countPiecens(_venobj.piecens, _selfIndex);
if (pcount >= PIECENS_PER_PLAYER || !tile.hasUnclaimedFeature()) {
_panel.board.cancelPiecenPlacement();
_panel.noplace.setEnabled(false);
} else {
// otherwise, enable the noplace button
_panel.noplace.setEnabled(true);
}
} else if (action.getActionCommand().equals(PIECEN_PLACED)) {
// the user placed a piecen on the tile. grab the piecen from
// the placed tile and submit it to the server
Object[] args = new Object[] {
_panel.board.getPlacedTile().piecen };
MessageEvent mevt = new MessageEvent(
_venobj.getOid(), PLACE_PIECEN_REQUEST, args);
_ctx.getDObjectManager().postEvent(mevt);
// disable the noplace button
_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(
_venobj.getOid(), PLACE_NOTHING_REQUEST, null);
_ctx.getDObjectManager().postEvent(mevt);
// disable the noplace button
_panel.noplace.setEnabled(false);
} else if (action.getActionCommand().equals(BACK_TO_LOBBY)) {
// bail on out
_ctx.getLocationDirector().moveBack();
} else {
return super.handleAction(action);
}
return true;
}
/** Our turn game delegate. */
protected TurnGameControllerDelegate _delegate;
/** A reference to our game panel. */
protected AtlantiPanel _panel;
/** A reference to our game panel. */
protected AtlantiObject _venobj;
/** A reference to our body object. */
protected BodyObject _self;
/** Our player index or -1 if we're not a player. */
protected int _selfIndex;
}
@@ -1,156 +0,0 @@
//
// $Id
package com.samskivert.atlanti.client;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Font;
import java.awt.Frame;
import javax.swing.*;
import javax.swing.event.AncestorEvent;
import javax.swing.event.AncestorListener;
import com.samskivert.swing.Controller;
import com.samskivert.swing.ControllerProvider;
import com.samskivert.swing.HGroupLayout;
import com.samskivert.swing.VGroupLayout;
import com.threerings.media.image.ImageManager;
import com.threerings.media.tile.TileManager;
import com.threerings.resource.ResourceManager;
import com.threerings.crowd.data.PlaceObject;
import com.threerings.crowd.client.PlaceView;
import com.threerings.micasa.client.ChatPanel;
import com.threerings.micasa.util.MiCasaContext;
import com.samskivert.atlanti.Log;
import com.samskivert.atlanti.data.AtlantiCodes;
import com.samskivert.atlanti.data.AtlantiTile;
import com.samskivert.atlanti.util.PiecenUtil;
/**
* The top-level user interface component for the game display.
*/
public class AtlantiPanel extends JPanel
implements PlaceView, ControllerProvider, AtlantiCodes
{
/** A reference to the board that is accessible to the controller. */
public AtlantiBoard board;
/** A reference to our _noplace button. */
public JButton noplace;
/**
* Constructs a new game display.
*/
public AtlantiPanel (MiCasaContext ctx, AtlantiController 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);
// create the board
board = new AtlantiBoard();
// create a scroll area to contain the board
JScrollPane scrolly = new JScrollPane(board);
add(scrolly);
// 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 big fat label because we love it!
JLabel vlabel = new JLabel("Atlantissonne!");
vlabel.setFont(new Font("Helvetica", Font.BOLD, 24));
vlabel.setForeground(Color.black);
sidePanel.add(vlabel, VGroupLayout.FIXED);
// 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
noplace = new JButton("Place nothing");
noplace.setEnabled(false);
noplace.setActionCommand(PLACE_NOTHING);
noplace.addActionListener(Controller.DISPATCHER);
sidePanel.add(noplace, VGroupLayout.FIXED);
// add a chat box
ChatPanel chat = new ChatPanel(ctx);
chat.removeSendButton();
sidePanel.add(chat);
// add a "back" button
JButton back = new JButton("Back to lobby");
back.setActionCommand(BACK_TO_LOBBY);
back.addActionListener(Controller.DISPATCHER);
sidePanel.add(back, VGroupLayout.FIXED);
// add our side panel to the main display
add(sidePanel, HGroupLayout.FIXED);
// we'll need this later to provide it
_controller = controller;
}
// documentation inherited
public void addNotify ()
{
super.addNotify();
// we can't create our image manager until we have access to our
// containing frame
JRootPane rpane = getRootPane();
ImageManager imgr = new ImageManager(_rmgr, rpane);
TileManager tmgr = new TileManager(imgr);
AtlantiTile.setManagers(imgr, tmgr);
PiecenUtil.init(tmgr);
}
// documentation inherited
public void willEnterPlace (PlaceObject plobj)
{
Log.info("Panel entered place.");
}
// documentation inherited
public void didLeavePlace (PlaceObject plobj)
{
Log.info("Panel left place.");
}
// documentation inherited
public Controller getController ()
{
return _controller;
}
/** A reference to our controller that we need to implement the {@link
* ControllerProvider} interface. */
protected AtlantiController _controller;
// this stuff is all a bit of a hack right now. by all rights, the
// MiCasa game app should set up the resource manager, because it
// knows about that sort of stuff, and make it available via the
// MiCasa context and we may have some better place for the tile
// manager to live. but it's late and i want to get this working, so
// fooey.
/** Our resource manager. */
protected static ResourceManager _rmgr = new ResourceManager("rsrc");
}
@@ -1,170 +0,0 @@
//
// $Id: PlayerInfoView.java,v 1.5 2002/12/12 05:51:54 mdb Exp $
package com.samskivert.atlanti.client;
import java.awt.Color;
import java.awt.GridBagLayout;
import java.awt.GridBagConstraints;
import javax.swing.JLabel;
import javax.swing.JPanel;
import com.threerings.util.Name;
import com.threerings.presents.dobj.AttributeChangeListener;
import com.threerings.presents.dobj.AttributeChangedEvent;
import com.threerings.presents.dobj.SetListener;
import com.threerings.presents.dobj.EntryAddedEvent;
import com.threerings.presents.dobj.EntryUpdatedEvent;
import com.threerings.presents.dobj.EntryRemovedEvent;
import com.threerings.crowd.client.PlaceView;
import com.threerings.crowd.data.PlaceObject;
import com.samskivert.atlanti.data.AtlantiCodes;
import com.samskivert.atlanti.data.AtlantiObject;
import com.samskivert.atlanti.data.Feature;
import com.samskivert.atlanti.util.TileUtil;
/**
* Displays each of the players in the game, their piece color and their
* score.
*/
public class PlayerInfoView
extends JPanel
implements PlaceView, AttributeChangeListener, SetListener
{
/**
* Constructs a new player info panel, ready for insertion into the
* 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
_atlobj = (AtlantiObject)plobj;
_atlobj.addListener(this);
// we need score and piecen labels arrays so that we can keep
// track of per player information
_scoreLabels = new JLabel[_atlobj.players.length];
_piecenLabels = new JLabel[_atlobj.players.length];
// now that we're here, we can add an entry for every player
for (int i = 0; i < _atlobj.players.length; i++) {
addPlayer(i, _atlobj.players[i]);
}
// if we have scores, update them
if (_atlobj.scores != null) {
updateScores();
// and the piecen count
updatePiecenCount();
}
}
/**
* Adds a player to the display.
*/
protected void addPlayer (int idx, Name username)
{
GridBagConstraints c = new GridBagConstraints();
// create a label for their username
c.fill = GridBagConstraints.HORIZONTAL;
c.weightx = 1.0; // use up extra space on this label
JLabel unlabel = new JLabel(username.toString());
unlabel.setForeground(Feature.PIECEN_COLOR_MAP[idx]);
add(unlabel, c);
// create a label for their score
c.weightx = 0.0; // size label to its preferred width
c.ipadx = 5; // 5 pixels between this label and its neighbors
c.fill = GridBagConstraints.NONE;
_scoreLabels[idx] = new JLabel("0");
_scoreLabels[idx].setForeground(Color.black);
add(_scoreLabels[idx], c);
// create a label for their piecen count
c.gridwidth = GridBagConstraints.REMAINDER;
c.ipadx = 0; // turn off padding
_piecenLabels[idx] = new JLabel("(0)");
_piecenLabels[idx].setForeground(Feature.PIECEN_COLOR_MAP[idx]);
add(_piecenLabels[idx], c);
}
// documentation inherited
public void didLeavePlace (PlaceObject plobj)
{
// remove our listening self
_atlobj.removeListener(this);
_atlobj = null;
}
// documentation inherited
public void attributeChanged (AttributeChangedEvent event)
{
// we care about the scores (which change)
if (event.getName().equals(AtlantiObject.SCORES)) {
updateScores();
}
}
// documentation inherited
public void entryAdded (EntryAddedEvent event)
{
updatePiecenCount();
}
// documentation inherited
public void entryUpdated (EntryUpdatedEvent event)
{
}
// documentation inherited
public void entryRemoved (EntryRemovedEvent event)
{
updatePiecenCount();
}
/**
* Reads the scores from the game object and updates the view.
*/
protected void updateScores ()
{
for (int i = 0; i < _atlobj.scores.length; i++) {
_scoreLabels[i].setText(Integer.toString(_atlobj.scores[i]));
}
}
/**
* Updates the count of remaining piecens for all of the players.
*/
protected void updatePiecenCount ()
{
for (int i = 0; i < _atlobj.scores.length; i++) {
int pcount = TileUtil.countPiecens(_atlobj.piecens, i);
int pleft = AtlantiCodes.PIECENS_PER_PLAYER - pcount;
_piecenLabels[i].setText("(" + pleft + ")");
}
}
/** A reference to the game object. */
protected AtlantiObject _atlobj;
/** References to our score label components. */
protected JLabel[] _scoreLabels;
/** References to our piecen label components. */
protected JLabel[] _piecenLabels;
}
@@ -1,53 +0,0 @@
//
// $Id: TileLabel.java,v 1.2 2002/12/12 05:51:54 mdb Exp $
package com.samskivert.atlanti.client;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.JComponent;
import com.samskivert.atlanti.data.AtlantiTile;
import com.samskivert.atlanti.data.TileCodes;
/**
* Displays a single tile in a Swing component.
*/
public class TileLabel extends JComponent
implements TileCodes
{
/**
* Configures the component to display the specified tile.
*
* @param tile a reference to the tile to display or null if no tile
* should be displayed.
*/
public void setTile (AtlantiTile tile)
{
_tile = tile;
repaint();
}
// documentation inherited
public void paintComponent (Graphics g)
{
super.paintComponent(g);
// simply paint the tile if we have one
if (_tile != null) {
_tile.paint((Graphics2D)g, 0, 0);
}
}
// documentation inherited
public Dimension getPreferredSize ()
{
return new Dimension(TILE_WIDTH, TILE_HEIGHT);
}
/** The tile we are displaying. */
protected AtlantiTile _tile;
}
@@ -1,140 +0,0 @@
//
// $Id: TurnIndicatorView.java,v 1.5 2002/12/12 06:07:49 mdb Exp $
package com.samskivert.atlanti.client;
import javax.swing.JLabel;
import javax.swing.JPanel;
import com.samskivert.swing.VGroupLayout;
import com.threerings.presents.dobj.AttributeChangeListener;
import com.threerings.presents.dobj.AttributeChangedEvent;
import com.threerings.crowd.data.PlaceObject;
import com.threerings.crowd.client.PlaceView;
import com.samskivert.atlanti.data.AtlantiObject;
/**
* Displays who the current turn holder is as well as the tile they are
* currently placing.
*/
public class TurnIndicatorView extends JPanel
implements PlaceView, AttributeChangeListener
{
/**
* Creates a new turn indicator view which in turn creates its
* subcomponents.
*/
public TurnIndicatorView ()
{
setLayout(new VGroupLayout());
// add our turn holder display
_whoLabel = new JLabel();
add(_whoLabel);
// and add our tile display
_tileLabel = new TileLabel();
add(_tileLabel);
// and add a tile's remaining label
_countLabel = new JLabel();
add(_countLabel);
}
// 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
_atlobj = (AtlantiObject)plobj;
_atlobj.addListener(this);
// update our displays
updateCurrentTile();
updateGameState();
updateTurnHolder();
updateRemainingTiles();
}
// documentation inherited
public void didLeavePlace (PlaceObject plobj)
{
// remove our listening self
_atlobj.removeListener(this);
_atlobj = null;
}
// documentation inherited
public void attributeChanged (AttributeChangedEvent event)
{
if (event.getName().equals(AtlantiObject.CURRENT_TILE)) {
// update the current tile display
updateCurrentTile();
} else if (event.getName().equals(AtlantiObject.STATE)) {
// update the game state display
updateGameState();
// update the tiles remaining
updateRemainingTiles();
} else if (event.getName().equals(AtlantiObject.TURN_HOLDER)) {
// update the turn holder
updateTurnHolder();
// update the tiles remaining when the turn changes
updateRemainingTiles();
}
}
protected void updateGameState ()
{
switch (_atlobj.state) {
case AtlantiObject.AWAITING_PLAYERS:
_whoLabel.setText("Awaiting players...");
_tileLabel.setTile(null);
break;
case AtlantiObject.GAME_OVER:
_whoLabel.setText("Game over.");
_tileLabel.setTile(null);
break;
case AtlantiObject.CANCELLED:
_whoLabel.setText("Cancelled.");
_tileLabel.setTile(null);
break;
}
}
protected void updateTurnHolder ()
{
if (_atlobj.state == AtlantiObject.IN_PLAY) {
_whoLabel.setText(_atlobj.turnHolder.toString());
}
}
protected void updateCurrentTile ()
{
// display the tile to be placed
_tileLabel.setTile(_atlobj.currentTile);
}
protected void updateRemainingTiles ()
{
_countLabel.setText("Tiles remaining: " +
Math.max(71-_atlobj.tiles.size(), 0));
}
/** The label displaying whose turn it is. */
protected JLabel _whoLabel;
/** The label displaying the tile to be placed. */
protected TileLabel _tileLabel;
/** The label displaying the number of tiles remaining. */
protected JLabel _countLabel;
/** A reference to the game object. */
protected AtlantiObject _atlobj;
}
@@ -1,44 +0,0 @@
//
// $Id: AtlantiCodes.java,v 1.6 2002/12/12 05:51:54 mdb Exp $
package com.samskivert.atlanti.data;
/**
* Constants used by the Atlanti game code.
*/
public interface AtlantiCodes
{
/** The message bundle identifier for translation messages. */
public static final String ATLANTI_MESSAGE_BUNDLE = "atlanti";
/** 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 AtlantiBoard} when
* the user places a tile into a valid position. */
public static final String TILE_PLACED = "tile_placed";
/** The name of the command posted by the {@link AtlantiBoard} when
* the user places a piecen onto an unclaimed feature. */
public static final String PIECEN_PLACED = "piecen_placed";
/** The name of the command posted by the "place nothing" button in
* the side bar. */
public static final String PLACE_NOTHING = "place_nothing";
/** The message submitted by the client to the server when they have
* chosen where they wish to place their tile. */
public static final String PLACE_TILE_REQUEST = "place_tile";
/** The message submitted by the client to the server when they have
* chosen where they wish to place their piecen. */
public static final String PLACE_PIECEN_REQUEST = "place_piecen";
/** The message submitted by the client to the server when they decide
* that they don't want to (or can't) place any piecen this turn. */
public static final String PLACE_NOTHING_REQUEST = "place_nothing";
/** The name of the command posted by the "Back to lobby" button in
* the side bar. */
public static final String BACK_TO_LOBBY = "back_to_lobby";
}
@@ -1,84 +0,0 @@
//
// $Id: AtlantiConfig.java,v 1.4 2002/12/12 05:51:54 mdb Exp $
package com.samskivert.atlanti.data;
import java.io.IOException;
import com.threerings.io.ObjectInputStream;
import com.threerings.io.ObjectOutputStream;
import com.threerings.parlor.data.TableConfig;
import com.threerings.parlor.game.GameConfig;
import com.samskivert.atlanti.client.AtlantiConfigurator;
import com.samskivert.atlanti.client.AtlantiController;
/**
* Describes the configuration parameters for the game.
*/
public class AtlantiConfig extends GameConfig
implements TableConfig
{
// documentation inherited
public Class getControllerClass ()
{
return AtlantiController.class;
}
// documentation inherited from interface
public Class getConfiguratorClass ()
{
return AtlantiConfigurator.class;
}
// documentation inherited
public String getManagerClassName ()
{
return "com.samskivert.atlanti.server.AtlantiManager";
}
// documentation inherited
public int getMinimumPlayers ()
{
return 2;
}
// documentation inherited
public int getMaximumPlayers ()
{
return 5;
}
// documentation inherited
public int getDesiredPlayers ()
{
return _desiredPlayers;
}
// documentation inherited
public void setDesiredPlayers (int desiredPlayers)
{
_desiredPlayers = desiredPlayers;
}
// documentation inherited
public void writeObject (ObjectOutputStream out)
throws IOException
{
out.defaultWriteObject();
out.writeInt(_desiredPlayers);
}
// documentation inherited
public void readObject (ObjectInputStream in)
throws IOException, ClassNotFoundException
{
in.defaultReadObject();
_desiredPlayers = in.readInt();
}
/** The desired number of players for this game or -1 if there is no
* specific desired number of players. */
protected int _desiredPlayers;
}
@@ -1,53 +0,0 @@
//
// $Id: AtlantiObject.dobj,v 1.2 2002/12/12 05:51:54 mdb Exp $
package com.samskivert.atlanti.data;
import com.threerings.presents.dobj.DSet;
import com.threerings.util.Name;
import com.threerings.parlor.game.GameObject;
import com.threerings.parlor.turn.TurnGameObject;
/**
* The distributed object used to maintain state for the game.
*/
public class AtlantiObject extends GameObject
implements TurnGameObject
{
/** The username of the current turn holder. */
public Name turnHolder;
/** A set containing all of the tiles that are in play in this
* game. */
public DSet tiles = new DSet();
/** The tile being placed by the current turn holder. This value is
* only valid while it is someone's turn. */
public AtlantiTile currentTile = AtlantiTile.STARTING_TILE;
/** A set containing all of the piecens that are placed on the
* board. */
public DSet piecens = new DSet();
/** The scores for each player. */
public int[] scores;
// documentation inherited from interface
public Name[] getPlayers ()
{
return players;
}
// documentation inherited from interface
public String getTurnHolderFieldName ()
{
return TURN_HOLDER;
}
// documentation inherited from interface
public Name getTurnHolder ()
{
return turnHolder;
}
}
@@ -1,216 +0,0 @@
//
// $Id: AtlantiObject.java,v 1.10 2003/03/23 02:22:51 mdb Exp $
package com.samskivert.atlanti.data;
import com.threerings.presents.dobj.DSet;
import com.threerings.util.Name;
import com.threerings.parlor.game.GameObject;
import com.threerings.parlor.turn.TurnGameObject;
/**
* The distributed object used to maintain state for the game.
*/
public class AtlantiObject extends GameObject
implements TurnGameObject
{
/** The field name of the <code>turnHolder</code> field. */
public static final String TURN_HOLDER = "turnHolder";
/** The field name of the <code>tiles</code> field. */
public static final String TILES = "tiles";
/** The field name of the <code>currentTile</code> field. */
public static final String CURRENT_TILE = "currentTile";
/** The field name of the <code>piecens</code> field. */
public static final String PIECENS = "piecens";
/** The field name of the <code>scores</code> field. */
public static final String SCORES = "scores";
/** The username of the current turn holder. */
public Name turnHolder;
/** A set containing all of the tiles that are in play in this
* game. */
public DSet tiles = new DSet();
/** The tile being placed by the current turn holder. This value is
* only valid while it is someone's turn. */
public AtlantiTile currentTile = AtlantiTile.STARTING_TILE;
/** A set containing all of the piecens that are placed on the
* board. */
public DSet piecens = new DSet();
/** The scores for each player. */
public int[] scores;
// documentation inherited from interface
public Name[] getPlayers ()
{
return players;
}
// documentation inherited from interface
public String getTurnHolderFieldName ()
{
return TURN_HOLDER;
}
// documentation inherited from interface
public Name getTurnHolder ()
{
return turnHolder;
}
/**
* Requests that the <code>turnHolder</code> field be set to the specified
* value. The local value will be updated immediately and an event
* will be propagated through the system to notify all listeners that
* the attribute did change. Proxied copies of this object (on
* clients) will apply the value change when they received the
* attribute changed notification.
*/
public void setTurnHolder (Name turnHolder)
{
requestAttributeChange(TURN_HOLDER, turnHolder);
this.turnHolder = turnHolder;
}
/**
* Requests that the specified entry be added to the
* <code>tiles</code> set. The set will not change until the event is
* actually propagated through the system.
*/
public void addToTiles (DSet.Entry elem)
{
requestEntryAdd(TILES, elem);
}
/**
* Requests that the entry matching the supplied key be removed from
* the <code>tiles</code> set. The set will not change until the
* event is actually propagated through the system.
*/
public void removeFromTiles (Comparable key)
{
requestEntryRemove(TILES, key);
}
/**
* Requests that the specified entry be updated in the
* <code>tiles</code> set. The set will not change until the event is
* actually propagated through the system.
*/
public void updateTiles (DSet.Entry elem)
{
requestEntryUpdate(TILES, elem);
}
/**
* Requests that the <code>tiles</code> field be set to the
* specified value. Generally one only adds, updates and removes
* entries of a distributed set, but certain situations call for a
* complete replacement of the set value. The local value will be
* updated immediately and an event will be propagated through the
* system to notify all listeners that the attribute did
* change. Proxied copies of this object (on clients) will apply the
* value change when they received the attribute changed notification.
*/
public void setTiles (DSet tiles)
{
requestAttributeChange(TILES, tiles);
this.tiles = tiles;
}
/**
* Requests that the <code>currentTile</code> field be set to the specified
* value. The local value will be updated immediately and an event
* will be propagated through the system to notify all listeners that
* the attribute did change. Proxied copies of this object (on
* clients) will apply the value change when they received the
* attribute changed notification.
*/
public void setCurrentTile (AtlantiTile currentTile)
{
requestAttributeChange(CURRENT_TILE, currentTile);
this.currentTile = currentTile;
}
/**
* Requests that the specified entry be added to the
* <code>piecens</code> set. The set will not change until the event is
* actually propagated through the system.
*/
public void addToPiecens (DSet.Entry elem)
{
requestEntryAdd(PIECENS, elem);
}
/**
* Requests that the entry matching the supplied key be removed from
* the <code>piecens</code> set. The set will not change until the
* event is actually propagated through the system.
*/
public void removeFromPiecens (Comparable key)
{
requestEntryRemove(PIECENS, key);
}
/**
* Requests that the specified entry be updated in the
* <code>piecens</code> set. The set will not change until the event is
* actually propagated through the system.
*/
public void updatePiecens (DSet.Entry elem)
{
requestEntryUpdate(PIECENS, elem);
}
/**
* Requests that the <code>piecens</code> field be set to the
* specified value. Generally one only adds, updates and removes
* entries of a distributed set, but certain situations call for a
* complete replacement of the set value. The local value will be
* updated immediately and an event will be propagated through the
* system to notify all listeners that the attribute did
* change. Proxied copies of this object (on clients) will apply the
* value change when they received the attribute changed notification.
*/
public void setPiecens (DSet piecens)
{
requestAttributeChange(PIECENS, piecens);
this.piecens = piecens;
}
/**
* Requests that the <code>scores</code> field be set to the specified
* value. The local value will be updated immediately and an event
* will be propagated through the system to notify all listeners that
* the attribute did change. Proxied copies of this object (on
* clients) will apply the value change when they received the
* attribute changed notification.
*/
public void setScores (int[] scores)
{
requestAttributeChange(SCORES, scores);
this.scores = scores;
}
/**
* Requests that the <code>index</code>th element of
* <code>scores</code> field be set to the specified value. The local
* value will be updated immediately and an event will be propagated
* through the system to notify all listeners that the attribute did
* change. Proxied copies of this object (on clients) will apply the
* value change when they received the attribute changed notification.
*/
public void setScoresAt (int value, int index)
{
requestElementUpdate(SCORES, new Integer(value), index);
this.scores[index] = value;
}
}
@@ -1,536 +0,0 @@
//
// $Id: AtlantiTile.java,v 1.17 2003/03/23 02:22:51 mdb Exp $
package com.samskivert.atlanti.data;
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Composite;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Polygon;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.io.IOException;
import java.util.List;
import com.samskivert.util.IntTuple;
import com.samskivert.util.StringUtil;
import com.threerings.media.image.ImageManager;
import com.threerings.media.tile.Tile;
import com.threerings.media.tile.TileManager;
import com.threerings.media.tile.UniformTileSet;
import com.threerings.io.ObjectInputStream;
import com.threerings.presents.dobj.DSet;
import com.samskivert.atlanti.Log;
import com.samskivert.atlanti.util.FeatureUtil;
import com.samskivert.atlanti.util.PiecenUtil;
import com.samskivert.atlanti.util.TileUtil;
/**
* Represents a single tile in play on the game board.
*
* <p><em>Note:</em> this should really be split into a pure data class
* and an associated visualization class in the
* <code>atlanti.client</code> package.
*/
public class AtlantiTile
implements DSet.Entry, TileCodes, Cloneable, Comparable
{
/** The starting tile. */
public static final AtlantiTile STARTING_TILE =
new AtlantiTile(CITY_ONE_ROAD_STRAIGHT, false, NORTH, 0, 0);
/** Activate this to render a piecen on every feature (useful for the
* tile geometry test). */
public static boolean piecenDebug = false;
/** The tile type. */
public int type;
/** Whether this tile has a shield on it. */
public boolean hasShield;
/** The tile's orientation. */
public int orientation;
/** The tile's x and y coordinates. */
public int x, y;
/** An array of claim group values that correspond to the features of
* this tile. If a piecen has claimed a feature on this tile or that
* connects to this tile, it will be represented here by a non-zero
* claim group in the array slot that corresponds to the claimed
* feature. */
public transient int[] claims;
/** A reference to our static feature descriptions. */
public transient Feature[] features;
/** A reference to the piecen on this tile or null if no piecen has
* been placed on this tile. */
public transient Piecen piecen;
/**
* Constructs a tile with all of the supplied tile information.
*/
public AtlantiTile (int type, boolean hasShield, int orientation,
int x, int y)
{
this.type = type;
this.hasShield = hasShield;
this.orientation = orientation;
this.x = x;
this.y = y;
// initialize our feature info
initFeatures();
}
/**
* Constructs a tile with the type information set, but in the default
* <code>NORTH</code> orientation and with no position.
*/
public AtlantiTile (int type, boolean hasShield)
{
this(type, hasShield, NORTH, 0, 0);
}
/**
* Constructs a blank tile, suitable for unserialization.
*/
public AtlantiTile ()
{
// nothing doing
}
/**
* Returns true if this tile has at leats one unclaimed feature.
*/
public boolean hasUnclaimedFeature ()
{
boolean unclaimed = false;
for (int i = 0; i < claims.length; i++) {
unclaimed = (unclaimed || (claims[i] == 0));
}
return unclaimed;
}
/**
* Looks for a feature in this tile that matches the supplied feature
* edge mask and returns the index of that feature in this tile's
* {@link #claims} array.
*
* @return the index of the matching feature or -1 if no feature
* matched.
*/
public int getFeatureIndex (int edgeMask)
{
// translate the feature mask into our orientation
edgeMask = FeatureUtil.translateMask(edgeMask, -orientation);
// look for a feature with a matching edge mask
for (int i = 0; i < features.length; i ++) {
if ((features[i].edgeMask & edgeMask) != 0) {
return i;
}
}
// no match
return -1;
}
/**
* Returns the index of the feature that contains the supplied mouse
* coordinates (which will have been translated relative to the tile's
* origin).
*
* @return the index of the feature that contains the mouse
* coordinates. Some feature should always contain the mouse.
*/
public int getFeatureIndex (int mouseX, int mouseY)
{
// we search our features in reverse order because road features
// overlap grass features geometrically and are known to be
// specified after the grass features
for (int i = features.length-1; i >= 0; i--) {
if (features[i].contains(mouseX, mouseY, orientation)) {
return i;
}
}
// something is hosed; fake it
Log.warning("Didn't find matching feature for mouse coordinates!? " +
"[tile=" + this + ", mx=" + mouseX +
", my=" + mouseY + "].");
return 0;
}
/**
* Looks for a feature in this tile that matches the supplied feature
* edge mask and returns the claim group to which that feature belongs
* (which may be zero).
*
* @return the claim group to which the feature that matches the
* supplied mask belongs, or zero if no feature matched the supplied
* mask.
*/
public int getFeatureGroup (int edgeMask)
{
int fidx = getFeatureIndex(edgeMask);
return fidx < 0 ? 0 : claims[fidx];
}
/**
* Sets the claim group for the feature with the specified index. This
* also updates the claim group for any piecen that was placed on that
* feature as well.
*
* @param featureIndex the index of the feature to update.
* @param claimGroup the claim group to associate with the feature.
*/
public void setClaimGroup (int featureIndex, int claimGroup)
{
// update the claim group slot for this feature
claims[featureIndex] = claimGroup;
// if we have a piecen placed on the feature identified by this
// feature index, we need to update its claim group as well
if (piecen != null && piecen.featureIndex == featureIndex) {
piecen.claimGroup = claimGroup;
}
}
/**
* Places the specified piecen on this tile. The {@link
* 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#setClaimGroup} to
* propagate the claiming of this feature to all neighboring tiles if
* a non-null tiles array is supplied to the function.
*
* @param piecen the piecen to place on this tile (with an
* appropriately configured feature index).
* @param tiles a sorted list of all of the tiles on the board that
* we can use to propagate our new claim group to all features
* connected to this newly claimed feature or null if propagation of
* the claim group is not desired at this time.
*/
public void setPiecen (Piecen piecen, List tiles)
{
int claimGroup = 0;
// if we're adding a piecen to a feature that's already claimed,
// we want to inherit the claim number (this could happen when we
// show up in an in progress game)
if (claims[piecen.featureIndex] != 0) {
Log.info("Requested to add a piecen to a feature " +
"that has already been claimed [tile=" + this +
", piecen=" + piecen + "]. Inheriting.");
claimGroup = claims[piecen.featureIndex];
} else {
// otherwise we generate a new claim group
claimGroup = TileUtil.nextClaimGroup();
Log.info("Creating claim group [cgroup=" + claimGroup +
", tile=" + this + ", fidx=" + piecen.featureIndex + "].");
}
// keep a reference to this piecen and configure its position
this.piecen = piecen;
piecen.x = x;
piecen.y = y;
// assign a brand spanking new claim group to the feature and the
// piecen and propagate it to neighboring features
if (tiles != null) {
TileUtil.setClaimGroup(
tiles, this, piecen.featureIndex, claimGroup);
// update our piecen with the claim group as well
piecen.claimGroup = claimGroup;
}
}
/**
* Clears out any piecen reference that was previously set (does not
* clear out its associated claim group, however).
*/
public void clearPiecen ()
{
piecen = null;
}
/**
* Paints this tile to the specified graphics context at its assigned
* location, accounting for the supplied x and y offsets of the
* origin.
*
* @param g the graphics context to use when painting the tile.
* @param xoff the offset (in tile units) of the origin in the x
* direction.
* @param yoff the offset (in tile units) of the origin in the y
* direction.
*/
public void paint (Graphics2D g, int xoff, int yoff)
{
int tidx = type-1;
// obtain our tile image
if (_tileImage == null) {
_tileImage = getTileImage(type);
}
// compute our screen coordinates
int sx = (x + xoff) * TILE_WIDTH;
int sy = (y + yoff) * TILE_HEIGHT;
// translate to our screen coordinates
g.translate(sx, sy);
// render our tile image
if (_tileImage != null) {
if (orientation > 0) {
g.drawImage(_tileImage, _xforms[orientation], null);
} else {
g.drawImage(_tileImage, 0, 0, null);
}
} else {
Log.warning("No tile image!? [type=" + type +
", img=" + _tileImage + "].");
}
// // render our features
// for (int i = 0; i < features.length; i++) {
// // paint the feature
// features[i].paint(g, orientation, claims[i]);
// }
// if we have a shield, draw a square in the lower right
if (hasShield) {
Point2D sspot = FeatureUtil.getShieldSpot(orientation);
Image image = getShieldImage();
if (image != null) {
int iwidth = image.getWidth(null);
int iheight = image.getHeight(null);
Composite ocomp = g.getComposite();
g.setComposite(ALPHA_PLACING);
g.drawImage(image, (int)(sspot.getX() - iwidth/2),
(int)(sspot.getY() - iheight/2), null);
g.setComposite(ocomp);
} else {
g.setColor(Color.orange);
g.drawRect((int)(sspot.getX()-5),
(int)(sspot.getY()-5), 10, 10);
}
}
// if we have a piecen on this tile, render it as well
if (piecen != null || piecenDebug) {
for (int i = 0; i < features.length; i++) {
if (piecenDebug) {
Image pimg = PiecenUtil.getPiecenImage(0);
features[i].paintPiecen(g, orientation, pimg, 0);
} else if (piecen.featureIndex == i) {
Image pimg = PiecenUtil.getPiecenImage(piecen.owner);
features[i].paintPiecen(
g, orientation, pimg, piecen.claimGroup);
}
}
}
// // draw a rectangular outline
// g.setColor(Color.black);
// g.drawRect(0, 0, TILE_WIDTH-1, TILE_HEIGHT-1);
// translate back out
g.translate(-sx, -sy);
}
/**
* Returns a copy of this tile object.
*/
public Object clone ()
{
return new AtlantiTile(type, hasShield, orientation, x, y);
}
/**
* Used to order tiles (which is done by board position).
*/
public int compareTo (Object other)
{
// we will either be compared to another tile, to a piecen or to a
// coordinate object (int tuple)
if (other == null) {
return -1;
} else if (other instanceof AtlantiTile) {
AtlantiTile tile = (AtlantiTile)other;
return (tile.x == x) ? y - tile.y : x - tile.x;
} else if (other instanceof IntTuple) {
IntTuple coord = (IntTuple)other;
return (coord.left == x) ? y - coord.right : x - coord.left;
} else if (other instanceof Piecen) {
Piecen piecen = (Piecen)other;
return (piecen.x == x) ? y - piecen.y : x - piecen.x;
} else {
// who knows...
return -1;
}
}
// documentation inherited
public boolean equals (Object other)
{
return (compareTo(other) == 0);
}
// documentation inherited
public Comparable getKey ()
{
// our key is our coordinates conflated into one integer
return new Integer((x + 128) * 256 + y + 128);
}
/**
* After we've been unserialized we have to initialize some derived
* fields.
*/
public void readObject (ObjectInputStream in)
throws IOException, ClassNotFoundException
{
in.defaultReadObject();
initFeatures();
}
/**
* Initializes this tile's feature management data structures.
*/
protected void initFeatures ()
{
if (type > 0) {
// grab a reference to our feature information
features = FeatureUtil.getTileFeatures(type);
// create our claims array
claims = new int[features.length];
} else {
Log.warning("Requested to init features without valid type " +
this + ".");
Thread.dumpStack();
}
}
/**
* Generates a string representation of this tile instance.
*/
public String toString ()
{
return "[type=" + type + ", shield=" + hasShield +
", orient=" + ORIENT_NAMES[orientation] +
", pos=" + x + "/" + y +
", claims=" + StringUtil.toString(claims) +
", piecen=" + piecen + "]";
}
/**
* Someone needs to configure this so that we can display tiles on
* screen.
*/
public static void setManagers (ImageManager imgr, TileManager tmgr)
{
_imgr = imgr;
_tmgr = tmgr;
}
/**
* Fetches the image for the tile of the specified type.
*/
protected static Image getTileImage (int type)
{
// load up the tile set if we haven't already
if (_tset == null) {
_tset = _tmgr.loadTileSet(TILES_IMG_PATH,
TILE_WIDTH, TILE_HEIGHT);
}
// fetch the tile
return _tset.getRawTileImage(type-1);
}
/**
* Fetches the shield image.
*/
protected static Image getShieldImage ()
{
// load up the tile set if we haven't already
if (_stset == null) {
_stset = _tmgr.loadTileSet(SHIELD_IMG_PATH,
SHIELD_SIZE, SHIELD_SIZE);
}
// fetch the tile
return _stset.getRawTileImage(0);
}
/** The tile image that we use to render this tile. */
protected transient Image _tileImage;
/** Our image manager. */
protected static ImageManager _imgr;
/** Our tile manager. */
protected static TileManager _tmgr;
/** Our tile image tileset. */
protected static UniformTileSet _tset;
/** Our shield image tileset. */
protected static UniformTileSet _stset;
/** The path to our tileset image. */
protected static final String TILES_IMG_PATH = "media/tiles.png";
/** The path to our shield image. */
protected static final String SHIELD_IMG_PATH = "media/shield.png";
/** The size of the shield image (width and height). */
protected static final int SHIELD_SIZE = 17;
/** Three affine transforms for rendering an image in three rotated
* orientations. */
protected static AffineTransform[] _xforms;
static {
// the first element will be left blank
_xforms = new AffineTransform[4];
// create our three orientation transforms
AffineTransform xform = new AffineTransform();
for (int orient = 1; orient < 4; orient++) {
// rotate the xform into the next orientation
xform.translate(TILE_WIDTH, 0);
xform.rotate(Math.PI/2);
// and save it
_xforms[orient] = (AffineTransform)xform.clone();
}
}
/** For rendering shields with alpha. */
protected static final Composite ALPHA_PLACING =
AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.6f);
}
@@ -1,375 +0,0 @@
//
// $Id: Feature.java,v 1.6 2002/12/12 05:51:54 mdb Exp $
package com.samskivert.atlanti.data;
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Composite;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.GeneralPath;
import java.awt.geom.Point2D;
import java.awt.geom.RoundRectangle2D;
import com.samskivert.util.StringUtil;
import com.samskivert.atlanti.Log;
import com.samskivert.atlanti.util.FeatureUtil;
import com.samskivert.atlanti.util.TileUtil;
/**
* Represents all of the necessary information for a particular feature of
* a particular tile.
*/
public class Feature
implements TileCodes
{
/** Maps piecen color codes to colors. */
public static Color[] PIECEN_COLOR_MAP = {
new Color(0x0246B7), // BLUE
new Color(0x028A12), // GREEN
new Color(0xF47A02), // ORANGE
new Color(0xC20292), // MAGENTA
new Color(0xB90202), // RED
new Color(0xFECA11), // YELLOW
};
/** The type of this feature. */
public int type;
/** The edge mask associated with this feature. */
public int edgeMask;
/** The location at which a piecen on this feature would be drawn (for
* each of the four tile orientations. */
public Point2D[] piecenSpots = new Point2D[4];
/** The polygons used to render and hit test this feature (one for
* each orientation). */
public GeneralPath[] polys = new GeneralPath[4];
/**
* Constructs a feature with a feature description array containing
* all of the necessary feature information.
*/
public Feature (int[] desc)
{
type = desc[0];
edgeMask = desc[1];
// fetch our natural piecen spot, scale it and adjust for half units
int px = (desc[2] * TILE_WIDTH) / 4;
if (px < 0) {
px *= -1;
px -= TILE_WIDTH/8;
}
int py = (desc[3] * TILE_HEIGHT) / 4;
if (py < 0) {
py *= -1;
py -= TILE_HEIGHT/8;
}
// oh, just a teeny hack for aesthetic shield placement
if (type == -1) {
px += 2; py += 2;
}
piecenSpots[NORTH] = new Point2D.Float(px, py);
// create our natural feature polygon
if (type == ROAD) {
// roads are handled specially; they are either one or two
// segment roads
if (desc.length == 8) {
polys[NORTH] = roadSegmentToPolygon(
desc[4], desc[5], desc[6], desc[7]);
} else if (desc.length == 10) {
polys[NORTH] = roadSegmentToPolygon(
desc[4], desc[5], desc[6], desc[7], desc[8], desc[9]);
} else {
Log.warning("Feature constructed with bogus road geometry " +
"[desc=" + StringUtil.toString(desc) + "].");
}
} else {
GeneralPath poly = new GeneralPath();
for (int i = 4; i < desc.length; i += 2) {
// scale the coords accordingly
int fx = (desc[i] * TILE_WIDTH) / 4;
int fy = (desc[i+1] * TILE_HEIGHT) / 4;
if (i == 4) {
poly.moveTo(fx, fy);
} else {
poly.lineTo(fx, fy);
}
}
poly.closePath();
polys[NORTH] = poly;
}
// now create the three other orientations
AffineTransform xform = new AffineTransform();
for (int orient = 1; orient < 4; orient++) {
// rotate the xform into the next orientation
xform.translate(TILE_WIDTH, 0);
xform.rotate(Math.PI/2);
// transform the polygon
polys[orient] = (GeneralPath)polys[NORTH].clone();
polys[orient].transform(xform);
// transform the piecen spot
piecenSpots[orient] = new Point2D.Float();
xform.transform(piecenSpots[NORTH], piecenSpots[orient]);
}
}
/**
* Returns true if the feature contains the supplied mouse coordinates
* (which should be relative to the tile origin) given the supplied
* orientation information.
*/
public boolean contains (int mouseX, int mouseY, int orientation)
{
return polys[orientation].contains(mouseX, mouseY);
}
/**
* Paints this feature to the specified graphics context with the
* specified orientation, accounting for the supplied x and y offsets
* of the origin. The graphics context is assumed to be appropriately
* translated so that we may render this feature relative to the
* origin and have it painted in the proper place.
*
* @param g the graphics context to use when painting the feature.
* @param orientation the orientation at which to paint this feature.
* @param claimGroup the claim group to which this feature belongs (or
* zero if it belongs to no claim group).
*/
public void paint (Graphics2D g, int orientation, int claimGroup)
{
// set the color according to the claim group
g.setColor(claimGroup > 0 ? CLAIMED_COLOR_MAP[type] :
COLOR_MAP[type]);
// now render the appropriate polygon for this orientation
g.fill(polys[orientation]);
// // paint our features for debugging (they aren't rotated and thus
// // are only valid in the natural orientation)
// int w = TILE_WIDTH, hw = w/2, qw = hw/2, tqw = 3*qw;
// int h = TILE_HEIGHT, hh = h/2, qh = hh/2, tqh = 3*qh;
// g.setColor(Color.gray);
// if ((edgeMask & FeatureUtil.NORTH_F) != 0) {
// g.drawLine(hw, 0, hw, qh);
// }
// if ((edgeMask & FeatureUtil.EAST_F) != 0) {
// g.drawLine(tqw, hh, w, hh);
// }
// if ((edgeMask & FeatureUtil.SOUTH_F) != 0) {
// g.drawLine(hw, tqh, hw, h);
// }
// if ((edgeMask & FeatureUtil.WEST_F) != 0) {
// g.drawLine(0, hh, qw, hh);
// }
// if ((edgeMask & FeatureUtil.NNE_F) != 0) {
// g.drawLine(tqw, 0, tqw, qh);
// }
// if ((edgeMask & FeatureUtil.NNW_F) != 0) {
// g.drawLine(qw, 0, qw, qh);
// }
// if ((edgeMask & FeatureUtil.SSE_F) != 0) {
// g.drawLine(tqw, tqh, tqw, h);
// }
// if ((edgeMask & FeatureUtil.SSW_F) != 0) {
// g.drawLine(qw, tqh, qw, h);
// }
// if ((edgeMask & FeatureUtil.ENE_F) != 0) {
// g.drawLine(tqw, qh, w, qh);
// }
// if ((edgeMask & FeatureUtil.ESE_F) != 0) {
// g.drawLine(tqw, tqh, w, tqh);
// }
// if ((edgeMask & FeatureUtil.WNW_F) != 0) {
// g.drawLine(0, qh, qw, qh);
// }
// if ((edgeMask & FeatureUtil.WSW_F) != 0) {
// g.drawLine(0, tqh, qw, tqh);
// }
}
/**
* Paints a piecen in a position indicating that it is placed on this
* feature. The graphics context is assumed to be appropriately
* translated so that we may render this feature relative to the
* origin and have it painted in the proper place.
*
* @param g the graphics context to use when painting the feature.
* @param orientation the orientation at which to paint this feature.
* @param image the piecen image to be painted.
* @param claimGroup the claim group to which this piecen belongs (or
* zero if it belongs to no claim group).
*/
public void paintPiecen (Graphics2D g, int orientation, Image image,
int claimGroup)
{
Point2D point = piecenSpots[orientation];
int iwidth = image.getWidth(null);
int iheight = image.getHeight(null);
// render the piecen image slightly transparent
Composite ocomp = g.getComposite();
g.setComposite(ALPHA_PLACING);
g.drawImage(image, (int)(point.getX() - iwidth/2),
(int)(point.getY() - iheight/2), null);
g.setComposite(ocomp);
// // for now, draw the claim group next to the piecen
// g.drawString(Integer.toString(claimGroup),
// (float)(point.getX() - swidth/2 + 2),
// (float)(point.getY() - sheight/2 - 2));
}
/**
* Massages a road segment (specified in tile feature coordinates)
* into a polygon (in screen coordinates) that can be used to render
* or hit test the road. The coordinates must obey the following
* constraints: (x1 < x2 and y1 == y2) or (x1 == x2 and y1 < y2) or
* (x1 < x2 and y1 > y2).
*
* @return a polygon representing the road segment (with origin at 0,
* 0).
*/
protected static GeneralPath roadSegmentToPolygon (
int x1, int y1, int x2, int y2)
{
// first convert the coordinates into screen coordinates
x1 = (x1 * TILE_WIDTH) / 4;
y1 = (y1 * TILE_HEIGHT) / 4;
x2 = (x2 * TILE_WIDTH) / 4;
y2 = (y2 * TILE_HEIGHT) / 4;
GeneralPath poly = new GeneralPath();
int dx = 4, dy = 4;
// figure out what sort of line segment it is
if (x1 == x2) { // vertical
// make adjustments to ensure that we stay inside the tile
// bounds
if (y1 == 0) {
y1 += dy;
}
if (y2 == TILE_HEIGHT) {
y2 -= dy;
}
poly.moveTo(x1 - dx, y1 - dy);
poly.lineTo(x1 + dx, y1 - dy);
poly.lineTo(x2 + dx, y2 + dy);
poly.lineTo(x2 - dx, y2 + dy);
} else if (y1 == y2) { // horizontal
// make adjustments to ensure that we stay inside the tile
// bounds
if (x1 == 0) {
x1 += dx;
}
if (x2 == TILE_WIDTH) {
x2 -= dx;
}
poly.moveTo(x1 - dx, y1 - dy);
poly.lineTo(x1 - dx, y1 + dy);
poly.lineTo(x2 + dx, y2 + dy);
poly.lineTo(x2 + dx, y2 - dy);
} else { // diagonal
poly.moveTo(x1 - dx, y1);
poly.lineTo(x1 + dx, y1);
poly.lineTo(x2, y2 + dy);
poly.lineTo(x2, y2 - dy);
}
poly.closePath();
return poly;
}
/**
* Massages a road segment (specified in tile feature coordinates)
* into a polygon (in screen coordinates) that can be used to render
* or hit test the road. The coordinates must obey the following
* constraints: (y1 == y2) and (y2 > y3) and (x2 == x3).
*
* @return a polygon representing the road segment (with origin at 0,
* 0).
*/
protected static GeneralPath roadSegmentToPolygon (
int x1, int y1, int x2, int y2, int x3, int y3)
{
// first convert the coordinates into screen coordinates
x1 = (x1 * TILE_WIDTH) / 4;
y1 = (y1 * TILE_HEIGHT) / 4;
x2 = (x2 * TILE_WIDTH) / 4;
y2 = (y2 * TILE_HEIGHT) / 4;
x3 = (x3 * TILE_WIDTH) / 4;
y3 = (y3 * TILE_HEIGHT) / 4;
GeneralPath poly = new GeneralPath();
int dx = 4, dy = 4;
// figure out what sort of road segment it is
if (x1 < x2) { // left turn
poly.moveTo(x1, y1-dy);
poly.lineTo(x2+dx, y2-dy);
poly.lineTo(x3+dx, y3);
poly.lineTo(x3-dx, y3);
poly.lineTo(x2-dx, y2+dy);
poly.lineTo(x1, y1+dy);
} else { // right turn
poly.moveTo(x1, y1-dy);
poly.lineTo(x2-dx, y2-dy);
poly.lineTo(x3-dx, y3);
poly.lineTo(x3+dx, y3);
poly.lineTo(x2+dx, y2+dy);
poly.lineTo(x1, y1+dy);
}
poly.closePath();
return poly;
}
/**
* Generates a string representation of this feature.
*/
public String toString ()
{
return "[type=" + FeatureUtil.typeToString(type) +
", edgeMask=" + FeatureUtil.edgeMaskToString(edgeMask) + "]";
}
/** Maps feature types to colors. */
protected static Color[] COLOR_MAP = {
Color.red, // CITY
Color.green, // GRASS
Color.black, // ROAD
Color.yellow // CLOISTER
};
/** Maps feature types to colors for claimed features. */
protected static Color[] CLAIMED_COLOR_MAP = {
Color.red.darker(), // CITY
Color.green.darker(), // GRASS
Color.darkGray, // ROAD
Color.yellow.darker(), // CLOISTER
};
/** For rendering piecens with alpha. */
protected static final Composite ALPHA_PLACING =
AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.7f);
}
@@ -1,133 +0,0 @@
//
// $Id: Piecen.java,v 1.4 2002/12/12 05:51:54 mdb Exp $
package com.samskivert.atlanti.data;
import java.io.IOException;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import com.threerings.presents.dobj.DSet;
/**
* A piecen is a person and a piece all rolled into one! Players can play
* a single piecen on a tile feature as a part of their turn and that
* piecen then claims that feature and all of the features that are
* connected to it forming a claim group. Once a group of features has
* been claimed, no further piecens can be placed on the group. Note,
* however, that two or more separately claimed feature groups can be
* joined by placing a tile that connects the previously disconnected
* groups together. In that case, the groups are merged into a single
* claim group and all the piecens that were part of the disparate groups
* become claimees in the new group. At scoring time, the player with the
* most piecens in a group gets the points for the group. If two or more
* players have equal numbers of piecens in a group, they each get the
* points for the group.
*/
public class Piecen
implements DSet.Entry
{
/** A color constant. */
public static final int RED = 0;
/** A color constant. */
public static final int BLACK = 1;
/** A color constant. */
public static final int BLUE = 2;
/** A color constant. */
public static final int YELLOW = 3;
/** A color constant. */
public static final int GREEN = 4;
/** The owner of this piecen. */
public int owner;
/** The x and y coordinates of the tile on which this piecen is
* placed. */
public int x, y;
/** The index in the tile's feature array of the feature on which this
* piecen is placed. */
public int featureIndex;
/** The claim group to which this piecen belongs. */
public int claimGroup;
/**
* Construts a piecen with the specified configuration.
*/
public Piecen (int owner, int x, int y, int featureIndex)
{
this.owner = owner;
this.x = x;
this.y = y;
this.featureIndex = featureIndex;
}
/**
* Constructs a blank piecen, suitable for unserialization.
*/
public Piecen ()
{
}
// documentation inherited
public Comparable getKey ()
{
// our key is our coordinates conflated into one integer
return new Integer((x + 128) * 256 + y + 128);
}
// documentation inherited
public void writeTo (DataOutputStream out)
throws IOException
{
out.writeInt(owner);
out.writeInt(x);
out.writeInt(y);
out.writeInt(featureIndex);
}
// documentation inherited
public void readFrom (DataInputStream in)
throws IOException
{
owner = in.readInt();
x = in.readInt();
y = in.readInt();
featureIndex = in.readInt();
}
// documentation inherited
public boolean equals (Object other)
{
// we will either be compared to a tile or to a piecen
if (other == null) {
return false;
} else if (other instanceof AtlantiTile) {
AtlantiTile tile = (AtlantiTile)other;
return (tile.x == x) ? (y == tile.y) : false;
} else if (other instanceof Piecen) {
Piecen piecen = (Piecen)other;
return (piecen.x == x) ? (y == piecen.y) : false;
} else {
// who knows...
return false;
}
}
/**
* Generates a string representation of this piecen.
*/
public String toString ()
{
return "[owner=" + owner + ", pos=" + x + "/" + y +
", feat=" + featureIndex + ", claim=" + claimGroup + "]";
}
}
@@ -1,127 +0,0 @@
//
// $Id: TileCodes.java,v 1.10 2002/12/12 05:51:54 mdb Exp $
package com.samskivert.atlanti.data;
/**
* A repository for constants related to the tiles that are used in the
* game of Atlantissonne.
*/
public interface TileCodes
{
/** A four-sided city tile. */
public static final int CITY_FOUR = 1;
/** A three-sided city tile. */
public static final int CITY_THREE = 2;
/** A three-sided city tile with a road. */
public static final int CITY_THREE_ROAD = 3;
/** A two-sided city tile with city openings adjacent to one
* another. */
public static final int CITY_TWO = 4;
/** A two-sided city tile with city openings adjacent to one another
* and a road connecting the other two sides. */
public static final int CITY_TWO_ROAD = 5;
/** A two-sided city tile with city openings on opposite sides of the
* tile. */
public static final int CITY_TWO_ACROSS = 6;
/** A two-sided city tile with two separate city arcs adjacent to one
* another and not connected to each other. */
public static final int TWO_CITY_TWO = 7;
/** A two-sided city tile with two separate city arcs on opposite
* sides of the tile. */
public static final int TWO_CITY_TWO_ACROSS = 8;
/** A one-sided city tile. */
public static final int CITY_ONE = 9;
/** A one-sided city tile with a city arc on top and a right facing
* curved road segment beneath it. */
public static final int CITY_ONE_ROAD_RIGHT = 10;
/** A one-sided city tile with a city arc on top and a left facing
* curved road segment beneath it. */
public static final int CITY_ONE_ROAD_LEFT = 11;
/** A one-sided city tile with a city arc on top and a road tee
* beneath it. */
public static final int CITY_ONE_ROAD_TEE = 12;
/** A one-sided city tile with a city arc on top and straight road
* segment beneath it. */
public static final int CITY_ONE_ROAD_STRAIGHT = 13;
/** A cloister tile. */
public static final int CLOISTER_PLAIN = 14;
/** A cloister tile with a road extending from the cloister. */
public static final int CLOISTER_ROAD = 15;
/** A four-way road intersection. */
public static final int FOUR_WAY_ROAD = 16;
/** A three-way road intersection. */
public static final int THREE_WAY_ROAD = 17;
/** A straight road segment. */
public static final int STRAIGHT_ROAD = 18;
/** A curved road segment. */
public static final int CURVED_ROAD = 19;
/** The number of different tile types. */
public static final int TILE_TYPES = 19;
/** A tile orientation constant indicating the tile is in its default
* orientation. */
public static final int NORTH = 0;
/** A tile orientation constant indicating the tile is rotated 90
* degrees clockwise from its default orientation. */
public static final int EAST = 1;
/** A tile orientation constant indicating the tile is rotated 180
* degrees clockwise from its default orientation. */
public static final int SOUTH = 2;
/** A tile orientation constant indicating the tile is rotated 270
* degrees clockwise from its default orientation. */
public static final int WEST = 3;
/** A mapping from orientation codes to a string representation. */
public static final String[] ORIENT_NAMES = { "N", "E", "S", "W" };
/** The tile image width in pixels. */
public static int TILE_WIDTH = 64;
/** The tile image height in pixels. */
public static int TILE_HEIGHT = 64;
/** A tile edge constant indicating a city edge. */
public static final int CITY = 0;
/** A tile edge constant indicating a grass edge. */
public static final int GRASS = 1;
/** A tile edge constant indicating a road edge. */
public static final int ROAD = 2;
/** A constant indicating a cloister. */
public static final int CLOISTER = 3;
/** Human readable names to go along with the feature constants. */
public static final String[] FEATURE_NAMES = {
"island", "fishery", "boardwalk", "temple" };
/** A flag used to mark a tile as part of a completed city. */
public static final int COMPLETED_CITY = 0x01;
}
@@ -1,763 +0,0 @@
//
// $Id: AtlantiManager.java,v 1.26 2004/08/28 02:17:06 mdb Exp $
package com.samskivert.atlanti.server;
import java.util.Arrays;
import java.util.ArrayList;
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.EntryAddedEvent;
import com.threerings.presents.dobj.EntryRemovedEvent;
import com.threerings.presents.dobj.EntryUpdatedEvent;
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.crowd.chat.server.SpeakProvider;
import com.threerings.crowd.data.PlaceObject;
import com.threerings.crowd.data.PlaceConfig;
import com.threerings.crowd.server.PlaceManager;
import com.threerings.parlor.game.GameManager;
import com.threerings.parlor.turn.TurnGameManager;
import com.samskivert.atlanti.Log;
import com.samskivert.atlanti.data.AtlantiCodes;
import com.samskivert.atlanti.data.AtlantiObject;
import com.samskivert.atlanti.data.AtlantiTile;
import com.samskivert.atlanti.data.Feature;
import com.samskivert.atlanti.data.Piecen;
import com.samskivert.atlanti.data.TileCodes;
import com.samskivert.atlanti.util.FeatureUtil;
import com.samskivert.atlanti.util.TileUtil;
/**
* The main coordinator of the Atlantissonne game on the server side.
*/
public class AtlantiManager extends GameManager
implements TurnGameManager, AtlantiCodes, SetListener
{
public AtlantiManager ()
{
addDelegate(_delegate = new AtlantiManagerDelegate(this));
}
// documentation inherited
protected Class getPlaceObjectClass ()
{
return AtlantiObject.class;
}
public int getTilesInBox ()
{
return _tilesInBox.size();
}
// documentation inherited
public void didInit ()
{
super.didInit();
// register our message handlers
registerMessageHandler(PLACE_TILE_REQUEST, new PlaceTileHandler());
registerMessageHandler(
PLACE_PIECEN_REQUEST, new PlacePiecenHandler());
registerMessageHandler(
PLACE_NOTHING_REQUEST, new PlaceNothingHandler());
}
// documentation inherited
public void didStartup ()
{
super.didStartup();
// grab our own casted game object reference
_atlobj = (AtlantiObject)_gameobj;
}
/**
* In preparation for starting the game, we clear out the tile set and
* put the starting tile into place.
*/
protected void gameWillStart ()
{
super.gameWillStart();
// generate a shuffled tile list
_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();
// create a claim group vector
_claimGroupVector = new int[getPlayerCount()];
// clear out the scores
_atlobj.setScores(new int[getPlayerCount()]);
// clear out the tile and piecen set
_atlobj.setTiles(new DSet());
_atlobj.setPiecens(new DSet());
// and add the starting tile
AtlantiTile start = TileUtil.getStartingTile();
_atlobj.addToTiles(start);
_tiles.add(start);
}
// documentation inherited
public void turnWillStart ()
{
// let the players know what the next tile is that should be
// played
AtlantiTile tile = (AtlantiTile)_tilesInBox.remove(0);
_atlobj.setCurrentTile(tile);
}
// documentation inherited
public void turnDidStart ()
{
// nothing doing
}
// documentation inherited
public void turnDidEnd ()
{
// if there are no tiles left, we end the game
if (_tilesInBox.size() == 0) {
endGame();
}
}
/**
* At the end of the game, we need to compute the final scores.
*/
protected void gameDidEnd ()
{
super.gameDidEnd();
// compute the final scores by iterating over each tile and
// scoring its features
Piecen[] piecens = getPiecens();
Iterator iter = _atlobj.tiles.entries();
while (iter.hasNext()) {
AtlantiTile tile = (AtlantiTile)iter.next();
scoreFeatures(tile, piecens, true);
}
// lastly, we have to score the farms (cue the ominous drums)...
scoreFarms();
// update the final scores
_atlobj.setScores(_atlobj.scores);
}
/**
* Creates an array of piecens based on the contents of the piecens
* set in the game object, suitable for passing to {@link
* #scoreFeatures}.
*/
protected Piecen[] getPiecens ()
{
// create a piecen array that we can manipulate while scoring
Piecen[] piecens = new Piecen[_atlobj.piecens.size()];
Iterator iter = _atlobj.piecens.entries();
for (int i = 0; iter.hasNext(); i++) {
piecens[i] = (Piecen)iter.next();
}
return piecens;
}
/**
* Scores the features on this tile.
*
* @param tile the tile whose features should be scored.
* @param piecens an array of the pieces on the board which we can
* manipulate directly without having to wait for entry removed
* events to be dispatched.
* @param finalTally during the final tally, we score differently and
* we don't remove piecens from the board as we score them.
*/
protected void scoreFeatures (
AtlantiTile tile, Piecen[] piecens, boolean finalTally)
{
// potentially score all features on the tile
for (int i = 0; i < tile.features.length; i++) {
// we only need to worry about ROAD and CITY features because
// those are the only features on this tile that we might have
// completed
Feature f = tile.features[i];
if (f.type != TileCodes.CITY && f.type != TileCodes.ROAD) {
continue;
}
// see if any piecens are even on a feature in this group
int cgroup = tile.claims[i];
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 + "].");
continue;
}
// we do have something to score, so we compute the score for
// this feature
int score = TileUtil.computeFeatureScore(_tiles, tile, i);
// 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 || finalTally) {
String qual = (score > 0) ? "Completed" : "Incomplete";
// convert the score into a positive value
score = Math.abs(score);
// adjust and report the scores
StringBuffer names = new StringBuffer();
for (int p = 0; p < cgv.length; p++) {
// adjust the score
_atlobj.scores[p] += (score * cgv[p]);
// append the scorers name to the list
if (cgv[p] > 0) {
if (names.length() > 0) {
names.append(", ");
}
names.append(getPlayerName(p));
}
}
String message = qual + " " + TileCodes.FEATURE_NAMES[f.type] +
" scored " + score + " points for " + names + ".";
SpeakProvider.sendInfo(
_atlobj, ATLANTI_MESSAGE_BUNDLE, message);
Log.info("New scores: " + StringUtil.toString(_atlobj.scores));
// broadcast the new scores if this isn't the final tally
if (!finalTally) {
_atlobj.setScores(_atlobj.scores);
}
// and free up the scored piecens
removePiecens(cgroup, piecens, finalTally);
} else {
// 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++) {
// find our neighbor and make sure they exist
AtlantiTile neighbor =
TileUtil.findTile(_tiles, tile.x + dx, tile.y + dy);
if (neighbor == null) {
continue;
}
// scan their features arrays for claimed cloisters
for (int i = 0; i < neighbor.features.length; i++) {
Feature f = neighbor.features[i];
Piecen p = neighbor.piecen;
// is a cloister
if (f.type != TileCodes.CLOISTER) {
continue;
}
// tile has a piecen
if (p == null) {
// 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 + "].");
continue;
}
// score the cloister
int score = TileUtil.computeFeatureScore(
_tiles, neighbor, i);
// if it's completed or if we're doing the final
// tally, we score it
if (score > 0 || finalTally) {
String qual = (score > 0) ?
"complete" : "incomplete";
// coerce the score into positive land
score = Math.abs(score);
// deliver a chat notification to tell the
// players about the score
String message = getPlayerName(p.owner) + " scored " +
score + " points for " + qual + " temple.";
SpeakProvider.sendInfo(
_atlobj, ATLANTI_MESSAGE_BUNDLE, message);
// add the score to the owning player
_atlobj.scores[p.owner] += score;
// only broadcast the updated scores if this isn't
// the final tally
if (!finalTally) {
_atlobj.setScores(_atlobj.scores);
}
// and clear out the piecen (only removing it from
// the piecen set if we're not in the final tally)
removePiecen(p, !finalTally);
}
}
}
}
}
/**
* Scores the farms, which is the final act of scoring.
*/
protected void scoreFarms ()
{
HashIntMap cities = new HashIntMap();
int[] cityScores = new int[getPlayerCount()];
// 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++) {
AtlantiTile tile = (AtlantiTile)_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[getPlayerCount()];
int max = 0;
Iterator piter = _atlobj.piecens.entries();
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) + "].");
// ignore this city if no one has any farmers nearby
if (max == 0) {
continue;
}
// 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=" + getPlayerName(i) +
", pcount=" + pcount[i] + "].");
cityScores[i] += 4;
}
}
}
// now report the scoring and transfer the counts to the score
// array
for (int i = 0; i < getPlayerCount(); i++) {
if (cityScores[i] > 0) {
_atlobj.scores[i] += cityScores[i];
String message = getPlayerName(i) + " scores " +
cityScores[i] + " points for fisheries.";
SpeakProvider.sendInfo(
_atlobj, ATLANTI_MESSAGE_BUNDLE, message);
}
}
}
/**
* 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
* vector to determine whether or not they receive any points for the
* scoring of a particular claim group.
*
* <p> Note that this function returns a static (to this instance)
* 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.
*
* @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, Piecen[] piecens)
{
// clear out the vector
Arrays.fill(_claimGroupVector, 0);
// iterate over the piecens
int max = 0;
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];
}
}
}
// now cut out everyone with scores less than the highest score
for (int i = 0; i < _claimGroupVector.length; i++) {
_claimGroupVector[i] = (_claimGroupVector[i] < max) ? 0 : 1;
}
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,
boolean finalTally)
{
// we always clear the piecens from the array
for (int i = 0; i < piecens.length; i++) {
if (piecens[i] == null) {
continue;
} else if (piecens[i].claimGroup == claimGroup) {
piecens[i] = null;
}
}
// if this isn't the final tally, we also clear 'em from the board
if (!finalTally) {
Object[] pvec = _atlobj.piecens.toArray(null);
for (int ii = 0; ii < pvec.length; ii++) {
Piecen p = (Piecen)pvec[ii];
if (p.claimGroup == claimGroup) {
removePiecen(p, true);
}
}
}
}
/**
* 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 {
AtlantiTile tile = (AtlantiTile)_tiles.get(tidx);
// and clear the piecen
tile.clearPiecen();
}
// also remove from the piecens dset if requested
if (removeFromPiecens) {
_atlobj.removeFromPiecens(piecen.getKey());
}
}
// documentation inherited
public void entryAdded (EntryAddedEvent 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(AtlantiObject.PIECENS)) {
Piecen piecen = (Piecen)event.getEntry();
// make sure this is a valid placement
AtlantiTile tile = (AtlantiTile)_atlobj.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, getPiecens(), false);
}
// now that we've scored the piecen, we can end the turn
_delegate.endTurn();
}
}
// documentation inherited
public void entryUpdated (EntryUpdatedEvent event)
{
}
// documentation inherited
public void entryRemoved (EntryRemovedEvent event)
{
}
/**
* Called when the user requests to place a tile.
*/
protected void handlePlaceTileRequest (MessageEvent event)
{
AtlantiTile tile = (AtlantiTile)event.getArgs()[0];
int pidx = _delegate.getTurnHolderIndex();
// make sure it's this player's turn
if (_playerOids[pidx] != event.getSourceOid()) {
Log.warning("Requested to place tile by non-turn holder " +
"[event=" + event +
", turnHolder=" + _atlobj.turnHolder + "].");
// make sure this is a valid placement
} else if (TileUtil.isValidPlacement(_tiles, tile)) {
// add the tile to the list and resort it
_tiles.add(tile);
Collections.sort(_tiles);
// inherit its claim groups
TileUtil.inheritClaims(_tiles, tile);
// add the tile to the tiles set
_atlobj.addToTiles(tile);
// placing a piece may have completed road or city
// features. if it did, we score them now
scoreFeatures(tile, getPiecens(), false);
Log.info("Placed tile " + tile + ".");
// if the player has no free piecens or if there are no
// unclaimed features on this tile, we end their turn
// straight away
int pcount = TileUtil.countPiecens(_atlobj.piecens, pidx);
if (pcount >= PIECENS_PER_PLAYER ||
!tile.hasUnclaimedFeature()) {
_delegate.endTurn();
}
} else {
Log.warning("Received invalid placement " + event + ".");
}
}
/**
* Called when the user requests to place a piecen.
*/
protected void handlePlacePiecenRequest (MessageEvent event)
{
Piecen piecen = (Piecen)event.getArgs()[0];
AtlantiTile tile = (AtlantiTile)_atlobj.tiles.get(piecen.getKey());
int pidx = _delegate.getTurnHolderIndex();
int pcount = TileUtil.countPiecens(_atlobj.piecens, pidx);
// make sure it's this player's turn
if (_playerOids[pidx] != event.getSourceOid()) {
Log.warning("Requested to place piecen by non-turn holder " +
"[event=" + event +
", turnHolder=" + _atlobj.turnHolder + "].");
// do some checking before we place the piecen
} else 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 + ".");
} else if (tile.claims[piecen.featureIndex] != 0) {
Log.warning("Requested to place piecen on claimed feature " +
"[tile=" + tile + ", piecen=" + piecen + "].");
} else {
// otherwise stick the piece in the tile to update the
// claim groups
tile.setPiecen(piecen, _tiles);
// and add the piecen to the game object. when we receive
// the piecen added event, we'll score it and then end the
// turn
_atlobj.addToPiecens(piecen);
}
}
/**
* Called when the user requests to forgo their piecen placement for
* this turn.
*/
protected void handlePlaceNothingRequest (MessageEvent event)
{
int pidx = _delegate.getTurnHolderIndex();
if (_playerOids[pidx] != event.getSourceOid()) {
Log.warning("Requested to place nothing by non-turn holder " +
"[event=" + event +
", turnHolder=" + _atlobj.turnHolder + "].");
} else {
// player doesn't want to place anything, so we just end
// the turn
_delegate.endTurn();
}
}
/** Handles place tile requests. */
protected class PlaceTileHandler implements MessageHandler
{
public void handleEvent (MessageEvent event, PlaceManager pmgr)
{
handlePlaceTileRequest(event);
}
}
/** Handles place piecen requests. */
protected class PlacePiecenHandler implements MessageHandler
{
public void handleEvent (MessageEvent event, PlaceManager pmgr)
{
handlePlacePiecenRequest(event);
}
}
/** Handles place nothing requests. */
protected class PlaceNothingHandler implements MessageHandler
{
public void handleEvent (MessageEvent event, PlaceManager pmgr)
{
handlePlaceNothingRequest(event);
}
}
/** Our turn game delegate. */
protected AtlantiManagerDelegate _delegate;
/** A casted reference to our Atlanti game object. */
protected AtlantiObject _atlobj;
/** The (shuffled) list of tiles remaining to be played in this
* game. */
protected List _tilesInBox;
/** A sorted list of the tiles that have been placed on the board. */
protected List _tiles = new ArrayList();
/** Used to score features groups. */
protected int[] _claimGroupVector;
}
@@ -1,42 +0,0 @@
//
// $Id: AtlantiManagerDelegate.java,v 1.2 2002/12/12 05:51:54 mdb Exp $
package com.samskivert.atlanti.server;
import com.threerings.parlor.turn.TurnGameManagerDelegate;
import com.samskivert.atlanti.data.AtlantiCodes;
/**
* Handles the turn-based gameplay.
*/
public class AtlantiManagerDelegate extends TurnGameManagerDelegate
implements AtlantiCodes
{
/**
* Constructs the delegate and prepares it for operation.
*/
public AtlantiManagerDelegate (AtlantiManager vmgr)
{
super(vmgr);
_amgr = vmgr;
}
/**
* Continue the game until we're out of tiles.
*/
protected void setNextTurnHolder ()
{
// if we have tiles left, we move to the next player as normal
if (_amgr.getTilesInBox() > 0) {
super.setNextTurnHolder();
} else {
// if we don't, we ensure that a new turn isn't started by
// setting _turnIdx to -1
_turnIdx = -1;
}
}
/** The manager for whom we're delegating. */
protected AtlantiManager _amgr;
}
@@ -1,400 +0,0 @@
//
// $Id: FeatureUtil.java,v 1.6 2002/12/12 05:51:54 mdb Exp $
package com.samskivert.atlanti.util;
import java.awt.geom.Point2D;
import com.samskivert.atlanti.data.Feature;
import com.samskivert.atlanti.data.TileCodes;
/**
* Feature related constants and utility functions.
*/
public class FeatureUtil implements TileCodes
{
/** Bit mask for a north connecting feature. */
public static final int NORTH_F = 0x1 << 0;
/** Bit mask for an east connecting feature. */
public static final int EAST_F = 0x1 << 1;
/** Bit mask for a south connecting feature. */
public static final int SOUTH_F = 0x1 << 2;
/** Bit mask for a west connecting feature. */
public static final int WEST_F = 0x1 << 3;
/** Bit mask for a north by northeast connecting feature. */
public static final int NNE_F = 0x1 << 4;
/** Bit mask for an east by northeast connecting feature. */
public static final int ENE_F = 0x1 << 5;
/** Bit mask for an east by southeast connecting feature. */
public static final int ESE_F = 0x1 << 6;
/** Bit mask for a south by southeast connecting feature. */
public static final int SSE_F = 0x1 << 7;
/** Bit mask for a south by southwest connecting feature. */
public static final int SSW_F = 0x1 << 8;
/** Bit mask for a west by southwest connecting feature. */
public static final int WSW_F = 0x1 << 9;
/** Bit mask for a west by northwest connecting feature. */
public static final int WNW_F = 0x1 << 10;
/** Bit mask for a north by northwest connecting feature. */
public static final int NNW_F = 0x1 << 11;
/** A mapping from feature edge masks to tile directions and
* corresponding feature edge masks. */
public static final int[] ADJACENCY_MAP = new int[] {
NORTH_F, NORTH, SOUTH_F,
EAST_F, EAST, WEST_F,
SOUTH_F, SOUTH, NORTH_F,
WEST_F, WEST, EAST_F,
NNW_F, NORTH, SSW_F,
NNE_F, NORTH, SSE_F,
ENE_F, EAST, WNW_F,
ESE_F, EAST, WSW_F,
SSE_F, SOUTH, NNE_F,
SSW_F, SOUTH, NNW_F,
WSW_F, WEST, ESE_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.
*/
public static Feature[] getTileFeatures (int type)
{
// create the features array
Feature[] features = new Feature[TILE_FEATURES[type-1].length];
// initialize it with features from the repeated feature table or
// with newly constructed features
for (int i = 0; i < features.length; i++) {
int[] desc = TILE_FEATURES[type-1][i];
// if the description is a length one array, it is the index
// into the reused feature table of the desired feature
if (desc.length == 1) {
features[i] = _reusedFeatures[desc[0]];
} else {
// otherwise it is the description of this unique feature
features[i] = new Feature(desc);
}
}
return features;
}
/**
* Returns the position of the shield on a tile of the given
* orientation.
*/
public static Point2D getShieldSpot (int orientation)
{
return _shieldFeature.piecenSpots[orientation];
}
/**
* Returns a string describing the supplied type code.
*/
public static String typeToString (int type)
{
return TYPE_CODES[type];
}
/**
* Returns a string describing the supplied edge mask.
*/
public static String edgeMaskToString (int edgeMask)
{
StringBuffer buf = new StringBuffer();
if ((edgeMask & NORTH_F) != 0) buf.append("NORTH_F|");
if ((edgeMask & EAST_F) != 0) buf.append("EAST_F|");
if ((edgeMask & SOUTH_F) != 0) buf.append("SOUTH_F|");
if ((edgeMask & WEST_F) != 0) buf.append("WEST_F|");
if ((edgeMask & NNW_F) != 0) buf.append("NNW_F|");
if ((edgeMask & NNE_F) != 0) buf.append("NNE_F|");
if ((edgeMask & SSW_F) != 0) buf.append("SSW_F|");
if ((edgeMask & SSE_F) != 0) buf.append("SSE_F|");
if ((edgeMask & WNW_F) != 0) buf.append("WNW_F|");
if ((edgeMask & WSW_F) != 0) buf.append("WSW_F|");
if ((edgeMask & ENE_F) != 0) buf.append("ENE_F|");
if ((edgeMask & ESE_F) != 0) buf.append("ESE_F|");
// strip off the trailing bar if there is one
if (buf.length() > 0) {
buf.deleteCharAt(buf.length()-1);
}
return buf.toString();
}
/**
* Translates the feature edge mask into the orientation specified.
* For a forward translation, provide a positive valued orientation
* constant. For a backward translation, provide a negative valued
* orientation constant.
*
* @return the translated feature mask.
*/
public static int translateMask (int featureMask, int orientation)
{
int[] map = FEATURE_ORIENT_MAP[0];
if ((featureMask & (NNE_F|ESE_F|SSW_F|WNW_F)) != 0) {
map = FEATURE_ORIENT_MAP[1];
} else if ((featureMask & (ENE_F|SSE_F|WSW_F|NNW_F)) != 0) {
map = FEATURE_ORIENT_MAP[2];
}
return xlateMask(map, featureMask, orientation);
}
/** {@link #translateMask} helper function. */
protected static int xlateMask (
int[] map, int featureMask, int orientation)
{
int index = 0;
for (int i = 0; i < map.length; i++) {
if (map[i] == featureMask) {
return map[(i + 4 + orientation) % 4];
}
}
return featureMask;
}
/** A reused feature identifier. */
protected static final int NESW_CITY = 0;
/** A reused feature identifier. */
protected static final int NEW_CITY = 1;
/** A reused feature identifier. */
protected static final int EW_CITY = 2;
/** A reused feature identifier. */
protected static final int NW_CITY = 3;
/** A reused feature identifier. */
protected static final int N_CITY = 4;
/** A reused feature identifier. */
protected static final int E_CITY = 5;
/** A reused feature identifier. */
protected static final int W_CITY = 6;
/** A reused feature identifier. */
protected static final int E_ROAD = 7;
/** A reused feature identifier. */
protected static final int S_ROAD = 8;
/** A reused feature identifier. */
protected static final int W_ROAD = 9;
/** A reused feature identifier. */
protected static final int S_GRASS = 10;
/** A reused feature identifier. */
protected static final int SE_GRASS = 11;
/** A reused feature identifier. */
protected static final int SW_GRASS = 12;
/** An array of features used more than once. */
protected static final int[][] FEATURES = new int[][] {
{ CITY, NORTH_F|EAST_F|SOUTH_F|WEST_F, // NESW_CITY
2,2, 0,0, 4,0, 4,4, 0,4 },
{ CITY, NORTH_F|EAST_F|WEST_F, // NEW_CITY
2,2, 0,0, 4,0, 4,4, 3,3, 1,3, 0,4 },
{ CITY, EAST_F|WEST_F, // EW_CITY
2,2, 0,0, 1,1, 3,1, 4,0, 4,4, 3,3, 1,3, 0,4 },
{ CITY, NORTH_F|WEST_F, // NW_CITY
1,2, 0,0, 4,0, 0,4 },
{ CITY, NORTH_F, // N_CITY
2,-1, 0,0, 1,1, 3,1, 4,0 },
{ CITY, EAST_F, // E_CITY
-4,2, 4,0, 3,1, 3,3, 4,4 },
{ CITY, WEST_F, // W_CITY
-1,2, 0,0, 1,1, 1,3, 0,4 },
{ ROAD, EAST_F, // E_ROAD
3,2, 2,2, 4,2 },
{ ROAD, SOUTH_F, // S_ROAD
2,3, 2,2, 2,4 },
{ ROAD, WEST_F, // W_ROAD
1,2, 0,2, 2,2 },
{ GRASS, SOUTH_F, // S_GRASS
2,-4, 0,4, 1,3, 3,3, 4,4 },
{ GRASS, ESE_F|SSE_F, // SE_GRASS
-4,-4, 2,2, 4,2, 4,4, 2,4 },
{ GRASS, WSW_F|SSW_F, // SW_GRASS
-1,-4, 0,2, 2,2, 2,4, 0,4 },
};
/** A feature that is used to obtain the position information for
* rendering shields on city tiles that use shields. */
protected static final int[] SHIELD_FEATURE = new int[]
{ -1, NORTH_F|WEST_F, -1,-1, 0,0, 4,0, 0,4 };
/** A table describing the features of each tile. */
protected static final int[][][] TILE_FEATURES = new int[][][] {
// one must offset tile type by one when indexing into this array
{ { NESW_CITY } }, // CITY_FOUR
{ { NEW_CITY }, // CITY_THREE
{ S_GRASS } },
{ { NEW_CITY }, // CITY_THREE_ROAD
{ GRASS, SSW_F, 1,-4, 0,4, 1,3, 2,3, 2,4 },
{ GRASS, SSE_F, 3,-4, 2,4, 2,3, 3,3, 4,4 },
{ ROAD, SOUTH_F, 2,-4, 2,3, 2,4 } },
{ { NW_CITY }, // CITY_TWO
{ GRASS, EAST_F|SOUTH_F, 3,-4, 0,4, 4,0, 4,4 } },
{ { NW_CITY }, // CITY_TWO_ROAD
{ GRASS, ENE_F|SSW_F, -3,-2, 0,4, 4,0, 4,2, 2,4 },
{ GRASS, ESE_F|SSE_F, -4,-4, 2,4, 4,2, 4,4 },
{ ROAD, EAST_F|SOUTH_F, 3,3, 2,4, 4,2 } },
{ { EW_CITY }, // CITY_TWO_ACROSS
{ GRASS, NORTH_F, 2,-1, 0,0, 1,1, 3,1, 4,0 },
{ S_GRASS } },
{ { GRASS, WEST_F|SOUTH_F, // TWO_CITY_TWO
-1,-3, 0,0, 4,0, 4,4, 0,4 },
{ CITY, NORTH_F,
2,-1, 0,0, 2,1, 4,0 },
{ CITY, EAST_F,
-4,2, 4,0, 3,2, 4,4 } },
{ { GRASS, NORTH_F|SOUTH_F, // TWO_CITY_TWO_ACROSS
2,-4, 0,0, 4,0, 3,1, 3,3, 4,4, 0,4, 1,3, 1,1 },
{ W_CITY },
{ E_CITY } },
{ { GRASS, EAST_F|SOUTH_F|WEST_F, // CITY_ONE
2,-3, 0,0, 1,1, 3,1, 4,0, 4,4, 0,4 },
{ N_CITY } },
{ { GRASS, ENE_F|SSW_F|WEST_F, // CITY_ONE_ROAD_RIGHT
1,2, 0,0, 1,1, 3,1, 4,0, 4,2, 2,2, 2,4, 0,4 },
{ SE_GRASS },
{ ROAD, EAST_F|SOUTH_F, 3,2, 4,2, 2,2, 2,4 },
{ N_CITY } },
{ { GRASS, EAST_F|SSE_F|WNW_F, // CITY_ONE_ROAD_LEFT
3,2, 0,0, 1,1, 3,1, 4,0, 4,4, 2,4, 2,2, 0,2 },
{ SW_GRASS },
{ ROAD, SOUTH_F|WEST_F, 1,2, 0,2, 2,2, 2,4 },
{ N_CITY } },
{ { GRASS, ENE_F|WNW_F, // CITY_ONE_ROAD_TEE
-1,1, 0,0, 1,1, 3,1, 4,0, 4,2, 0,2 },
{ SE_GRASS },
{ SW_GRASS },
{ E_ROAD },
{ S_ROAD },
{ W_ROAD },
{ N_CITY } },
{ { GRASS, ENE_F|WNW_F, // CITY_ONE_ROAD_STRAIGHT
-1,1, 0,0, 1,1, 3,1, 4,0, 4,2, 0,2 },
{ GRASS, ESE_F|SOUTH_F|WSW_F, 2,3, 0,2, 4,2, 4,4, 0,4 },
{ ROAD, EAST_F|WEST_F, 2,2, 0,2, 4,2 },
{ N_CITY } },
{ { GRASS, NORTH_F|EAST_F|SOUTH_F|WEST_F, // CLOISTER_PLAIN
-1,-1, 0,0, 4,0, 4,4, 0,4 },
{ CLOISTER, 0, 2,2, 1,1, 3,1, 3,3, 1,3 } },
{ { GRASS, NORTH_F|EAST_F|WEST_F|SSE_F|SSW_F, // CLOISTER_ROAD
-1,-1, 0,0, 4,0, 4,4, 0,4 },
{ CLOISTER, 0, 2,2, 1,1, 3,1, 3,3, 1,3 },
{ ROAD, SOUTH_F, 2,-4, 2,3, 2,4 } },
{ { GRASS, WNW_F|NNW_F, // FOUR_WAY_ROAD
-1,-1, 0,0, 2,0, 2,2, 0,2 },
{ GRASS, NNE_F|ENE_F, -4,-1, 2,0, 4,0, 4,2, 2,2 },
{ SE_GRASS },
{ SW_GRASS },
{ ROAD, NORTH_F, 2,1, 2,0, 2,2 },
{ E_ROAD },
{ S_ROAD },
{ W_ROAD } },
{ { GRASS, WNW_F|NORTH_F|ENE_F, // THREE_WAY_ROAD
2,-1, 0,0, 4,0, 4,2, 0,2 },
{ SE_GRASS },
{ SW_GRASS },
{ E_ROAD },
{ S_ROAD },
{ W_ROAD } },
{ { GRASS, NNW_F|WEST_F|SSW_F, // STRAIGHT_ROAD
-1,2, 0,0, 2,0, 2,4, 0,4 },
{ GRASS, SSE_F|EAST_F|NNE_F, -4,2, 2,0, 4,0, 4,4, 2,4 },
{ ROAD, NORTH_F|SOUTH_F, 2,2, 2,0, 2,4 } },
{ { GRASS, WNW_F|NORTH_F|EAST_F|SSE_F, // CURVED_ROAD
3,1, 0,0, 4,0, 4,4, 2,4, 2,2, 0,2 },
{ SW_GRASS },
{ ROAD, SOUTH_F|WEST_F, 1,2, 0,2, 2,2, 2,4 } },
};
/** Mapping table used to rotate feature facements. */
protected static final int[][] FEATURE_ORIENT_MAP = new int[][] {
// orientations rotate through one of three four-cycles
{ NORTH_F, EAST_F, SOUTH_F, WEST_F },
{ NNE_F, ESE_F, SSW_F, WNW_F },
{ ENE_F, SSE_F, WSW_F, NNW_F },
};
/** String representations of the feature type codes. */
protected static final String[] TYPE_CODES = {
"CITY", "GRASS", "ROAD", "CLOISTER" };
/** A table of features that are used on more than one tile. */
protected static Feature[] _reusedFeatures;
/** The feature that provides the location information for shield
* rendering. */
protected static Feature _shieldFeature;
// create our reused features table
static {
_reusedFeatures = new Feature[FEATURES.length];
for (int i = 0; i < FEATURES.length; i++) {
_reusedFeatures[i] = new Feature(FEATURES[i]);
}
_shieldFeature = new Feature(SHIELD_FEATURE);
}
}
@@ -1,50 +0,0 @@
//
// $Id: PiecenUtil.java,v 1.4 2003/03/23 02:22:51 mdb Exp $
package com.samskivert.atlanti.util;
import java.awt.Image;
import com.threerings.media.tile.Tile;
import com.threerings.media.tile.TileManager;
import com.threerings.media.tile.UniformTileSet;
public class PiecenUtil
{
/**
* Loads up the piecen images using the supplied tile manager.
*/
public static void init (TileManager tmgr)
{
UniformTileSet piecenSet = tmgr.loadTileSet(
PIECEN_IMG_PATH, PIECEN_WIDTH, PIECEN_HEIGHT);
_images = new Image[PIECEN_TYPES];
for (int i = 0; i < PIECEN_TYPES; i++) {
_images[i] = piecenSet.getRawTileImage(i);
}
}
/**
* Returns the piecen image for the specified piecen color.
*/
public static Image getPiecenImage (int color)
{
return _images[color];
}
/** Our piecen images. */
protected static Image[] _images;
/** The number of different colors of piecen. */
protected static final int PIECEN_TYPES = 6;
/** The width of the piecen image in pixels. */
protected static final int PIECEN_WIDTH = 16;
/** The height of the piecen image in pixels. */
protected static final int PIECEN_HEIGHT = 16;
/** The path to the piecen image (relative to the resource
* directory). */
protected static final String PIECEN_IMG_PATH = "media/piecens.png";
}
@@ -1,734 +0,0 @@
//
// $Id: TileUtil.java,v 1.17 2002/12/12 05:51:55 mdb Exp $
package com.samskivert.atlanti.util;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import com.samskivert.util.IntTuple;
import com.threerings.presents.dobj.DSet;
import com.samskivert.atlanti.Log;
import com.samskivert.atlanti.data.AtlantiTile;
import com.samskivert.atlanti.data.Feature;
import com.samskivert.atlanti.data.Piecen;
import com.samskivert.atlanti.data.TileCodes;
/**
* Utility functions relating to the Atlantissonne 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 AtlantiTile getStartingTile ()
{
return (AtlantiTile)AtlantiTile.STARTING_TILE.clone();
}
/**
* Returns a list containing the standard tile set for the
* Atlantissonne game. The list is a clone, so it can be bent, folded
* and modified by the caller.
*/
public static List getStandardTileSet ()
{
// 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(((AtlantiTile)TILE_SET.get(i)).clone());
}
return tiles;
}
/**
* Scans the supplied tile set to determine which of the four
* orientations of the supplied target tile would result in a valid
* placement of that tile (valid placement meaning that all of its
* edges match up with neighboring tiles, it abuts at least one tile
* and it does not occupy the same space as any existing tile). The
* position of the target tile is assumed to be the desired placement
* position and the current orientation of the target tile is ignored.
*
* @param tiles a list of the tiles on the board.
* @param target the tile whose valid orientations we wish to compute.
*
* @return an array of boolean values indicating whether or not the
* tile can be placed in each of the cardinal directions (which match
* up with the direction constants specified in {@link TileCodes}.
*/
public static boolean[] computeValidOrients (
List tiles, AtlantiTile target)
{
// this contains a count of tiles that match up with the candidate
// tile in each of its four orientations
int[] matches = new int[4];
int tsize = tiles.size();
for (int i = 0; i < tsize; i++) {
AtlantiTile tile = (AtlantiTile)tiles.get(i);
// figure out where this tile is in relation to the candidate
int xdiff = tile.x - target.x;
int ydiff = tile.y - target.y;
int sum = Math.abs(xdiff) + Math.abs(ydiff);
if (sum == 0) {
// they overlap, nothing doing
return new boolean[4];
} else if (sum == 1) {
// they're neighbors, we may have a match
int targetEdge = EDGE_MAP[(ydiff+1)*3 + xdiff+1];
// we want the edge of the placed tile that matches up
// with the tile in the candidate location, but we also
// need to take into account the orientation of the placed
// tile
int tileEdge = (targetEdge+(4-tile.orientation)+2) % 4;
// we iterate over the four possible orientations of the
// target tile
for (int o = 0; o < 4; o++) {
// we compare the edge of the placed tile (which never
// changes) with the edge of the target tile which is
// adjusted based on the target tile's orientation
if (getEdge(tile.type, tileEdge) ==
getEdge(target.type, (targetEdge+(4-o)) % 4)) {
// increment the edge matches
matches[o]++;
} else {
// if we have a mismatch, we want to ensure that
// we screw this orientation up for good, so we
// deduct a large value from the array to ensure
// that it will remain less than zero regardless
// of which of the other three tiles match in this
// orientation
matches[o] -= 10;
}
}
}
}
// for every orientation that we have a positive number of edge
// matches, we have a valid orientation
boolean[] orients = new boolean[4];
for (int i = 0; i < matches.length; i++) {
orients[i] = (matches[i] > 0);
}
return orients;
}
/**
* Returns true if the position and orientation of the target tile is
* legal given the placement of all of the existing tiles.
*
* @param tiles a list of the tiles already on the board.
* @param target the tile whose validity we want to determine.
*
* @return true if the target tile is configured with a valid position
* and orientation, false if it is not.
*/
public static boolean isValidPlacement (List tiles, AtlantiTile target)
{
boolean matchedAnEdge = false;
int tsize = tiles.size();
for (int i = 0; i < tsize; i++) {
AtlantiTile tile = (AtlantiTile)tiles.get(i);
// figure out where this tile is in relation to the candidate
int xdiff = tile.x - target.x;
int ydiff = tile.y - target.y;
int sum = Math.abs(xdiff) + Math.abs(ydiff);
if (sum == 0) {
// they overlap, nothing doing
Log.warning("Tile overlaps another [candidate=" + target +
", overlapped=" + tile + "].");
return false;
} else if (sum == 1) {
// they're neighbors, we may have a match
int targetEdge = EDGE_MAP[(ydiff+1)*3 + xdiff+1];
// we want the edge of the placed tile that matches up
// with the tile in the candidate location, but we also
// need to take into account the orientation of the placed
// tile
int tileEdge = (targetEdge+(4-tile.orientation)+2) % 4;
// now rotate the target edge according to our orientation
targetEdge = ((targetEdge+(4-target.orientation)) % 4);
// see if the edges match
if (getEdge(tile.type, tileEdge) ==
getEdge(target.type, targetEdge)) {
// make a note that we matched at least one edge
matchedAnEdge = true;
} else {
// the edges don't match, nothing doing
Log.warning("Edge mismatch [candidate=" + target +
", tile=" + tile +
", candidateEdge=" + targetEdge +
", tileEdge=" + tileEdge + "].");
return false;
}
}
}
// if we got this far, we didn't have any mismatches, so we need
// only know that we matched at least one edge
return matchedAnEdge;
}
/**
* When a tile is placed on the board, this method should be called on
* it to propagate existing claims to the appropriate features on this
* tile. It will determine if any city features are connected to
* cities that are already claimed, and if any road features are
* connected to roads that are already claimed and if any grassland is
* connected to grassland that is claimed.
*
* <p> If, in the process of initializing the claims for this tile, we
* discover that this tile connects two previously disconnected
* claims, those claims will be joined. The affected tiles and piecens
* will have their claim groups updated.
*
* @param tiles a sorted list of the tiles on the board (which need
* not include the tile whose features are being configured).
* @param tile the tile whose features should be configured.
*/
public static void inheritClaims (List tiles, AtlantiTile tile)
{
List flist = new ArrayList();
// 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 claimGroup = 0;
// clear out the tilefeatures list before enumerating
flist.clear();
// enumerate the claim group for this feature
enumerateGroup(tiles, tile, i, flist);
// 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 found no non-zero claim number, we've nothing to
// inherit
if (claimGroup == 0) {
continue;
}
// 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;
}
}
}
}
/**
* Sets the claim group for the specified feature in this tile and
* propagates that claim group to all connected features.
*
* @param tiles a sorted list of the tiles on the board.
* @param tile the tile that contains the feature whose claim group is
* being set.
* @param featureIndex the index of the feature.
* @param claimGroup the claim group value to set.
*/
public static void setClaimGroup (
List tiles, AtlantiTile tile, int featureIndex, int claimGroup)
{
// load up this feature group
List flist = new ArrayList();
enumerateGroup(tiles, tile, featureIndex, flist);
// 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;
}
}
/**
* Computes the score for the specified feature and returns it. If the
* feature is complete (has no unconnected edges), the score will be
* positive. If it is incomplete, the score will be negative.
*
* @param tiles a sorted list of the tiles on the board.
* @param tile the tile that contains the feature whose score should
* be computed.
* @param featureIndex the index of the feature in the containing
* tile.
*
* @return a positive score for a completed feature group, a negative
* score for a partial feature group.
*/
public static int computeFeatureScore (
List tiles, AtlantiTile tile, int featureIndex)
{
Feature feature = tile.features[featureIndex];
if (feature.type == CLOISTER) {
// cloister's score specially
return computeCloisterScore(tiles, tile);
} else if (feature.type == GRASS) {
// grass doesn't score
return 0;
}
// if we're here, it's a road or city feature, which we score by
// loading up the group and counting the number of tiles in it
List flist = new ArrayList();
boolean complete = enumerateGroup(tiles, tile, featureIndex, flist);
// we sort the group which will order the tile feature objects by
// their tiles, ensuring that features on the same tile are next
// to one another, so that we can only count them once
Collections.sort(flist);
// now iterate over the list, counting only unique tiles
int score = 0;
AtlantiTile lastTile = null;
int fsize = flist.size();
for (int i = 0; i < fsize; i++) {
TileFeature feat = (TileFeature)flist.get(i);
if (feat.tile != lastTile) {
score++;
lastTile = feat.tile;
}
}
// 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;
}
}
// incomplete scores are reported in the negative
return complete ? score : -score;
}
/**
* A helper function for {@link
* #computeFeatureScore(List,AtlantiTile,int)}.
*/
protected static int computeCloisterScore (List tiles, AtlantiTile tile)
{
int score = 0;
// all we need to know are how many neighbors this guy has (we
// count ourselves as well, just for code simplicity)
for (int dx = -1; dx < 2; dx++) {
for (int dy = -1; dy < 2; dy++) {
if (findTile(tiles, tile.x + dx, tile.y + dy) != null) {
score++;
}
}
}
// incomplete cloisters return a negative 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++) {
AtlantiTile tile = (AtlantiTile)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 TileFeature}
* 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, AtlantiTile 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
AtlantiTile 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.
*
* @param tiles a sorted list of tiles.
*
* @return the tile with the requested coordinates or null if no tile
* exists at those coordinates.
*/
public static AtlantiTile findTile (List tiles, int x, int y)
{
IntTuple coord = new IntTuple(x, y);
int tidx = Collections.binarySearch(tiles, coord);
return (tidx >= 0) ? (AtlantiTile)tiles.get(tidx) : null;
}
/**
* Returns the number of piecens on the board owned the specified
* player. This can be used when we need to count piecens and the
* server potentially hasn't gotten around to processing piecen
* removal events quite yet.
*
* @param tiles a list of the tiles on the board.
* @param playerIndex the index of the player whose piecen count is
* desired.
*/
public static int countPiecens (List tiles, int playerIndex)
{
int count = 0;
for (int i = 0; i < tiles.size(); i++) {
AtlantiTile tile = (AtlantiTile)tiles.get(i);
if (tile.piecen != null &&
tile.piecen.owner == playerIndex) {
count++;
}
}
return count;
}
/**
* Returns the number of piecens on the board owned the specified
* player.
*
* @param piecens the piecens set from the game object.
* @param playerIndex the index of the player whose piecen count is
* desired.
*/
public static int countPiecens (DSet piecens, int playerIndex)
{
int count = 0;
Iterator iter = piecens.entries();
while (iter.hasNext()) {
if (((Piecen)iter.next()).owner == playerIndex) {
count++;
}
}
return count;
}
/**
* Returns the edge type for specified edge of the specified tile
* type.
*
* @param tileType the type of the tile in question.
* @param edge the direction constant indicating the edge in which we
* are interested.
*
* @return the edge constant for the edge in question.
*/
public static int getEdge (int tileType, int edge)
{
return TILE_EDGES[4*tileType + edge];
}
/**
* Returns the next unused claim group value.
*/
public static int nextClaimGroup ()
{
return ++_claimGroupCounter;
}
/** Used to generate our standard tile set. */
protected static void addTiles (int count, List list, AtlantiTile tile)
{
for (int i = 0; i < count; i++) {
list.add(tile);
}
}
/** Used to keep track of actual features on tiles. */
protected static final class TileFeature implements Comparable
{
/** The tile that contains the feature. */
public AtlantiTile tile;
/** The index of the feature in the tile. */
public int featureIndex;
/** Constructs a new tile feature. */
public TileFeature (AtlantiTile 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);
}
}
/** We sort based on our tiles. */
public int compareTo (Object other)
{
return tile.compareTo(((TileFeature)other).tile);
}
/** Generate a string representation. */
public String toString ()
{
return "[tile=" + tile + ", fidx=" + featureIndex + "]";
}
}
/** Used to generate claim group values. */
protected static int _claimGroupCounter;
/** Used to figure out which edges match up to which when comparing
* adjacent tiles. */
protected static final int[] EDGE_MAP = {
-1, NORTH, -1,
WEST, -1, EAST,
-1, SOUTH, -1
};
/** A table indicating which tiles have which edges. */
protected static final int[] TILE_EDGES = {
-1, -1, -1, -1, // null tile
CITY, CITY, CITY, CITY, // CITY_FOUR
CITY, CITY, GRASS, CITY, // CITY_THREE
CITY, CITY, ROAD, CITY, // CITY_THREE_ROAD
CITY, GRASS, GRASS, CITY, // CITY_TWO
CITY, ROAD, ROAD, CITY, // CITY_TWO_ROAD
GRASS, CITY, GRASS, CITY, // CITY_TWO_ACROSS
CITY, CITY, GRASS, GRASS, // TWO_CITY_TWO
GRASS, CITY, GRASS, CITY, // TWO_CITY_TWO_ACROSS
CITY, GRASS, GRASS, GRASS, // CITY_ONE
CITY, ROAD, ROAD, GRASS, // CITY_ONE_ROAD_RIGHT
CITY, GRASS, ROAD, ROAD, // CITY_ONE_ROAD_LEFT
CITY, ROAD, ROAD, ROAD, // CITY_ONE_ROAD_TEE
CITY, ROAD, GRASS, ROAD, // CITY_ONE_ROAD_STRAIGHT
GRASS, GRASS, GRASS, GRASS, // CLOISTER_PLAIN
GRASS, GRASS, ROAD, GRASS, // CLOISTER_ROAD
ROAD, ROAD, ROAD, ROAD, // FOUR_WAY_ROAD
GRASS, ROAD, ROAD, ROAD, // THREE_WAY_ROAD
ROAD, GRASS, ROAD, GRASS, // STRAIGHT_ROAD
GRASS, GRASS, ROAD, ROAD, // CURVED_ROAD
};
/** The standard tile set for a game of Atlantissonne. */
protected static ArrayList TILE_SET = new ArrayList();
// create our standard tile set
static {
addTiles(1, TILE_SET, new AtlantiTile(CITY_FOUR, true));
addTiles(3, TILE_SET, new AtlantiTile(CITY_THREE, false));
addTiles(1, TILE_SET, new AtlantiTile(CITY_THREE, true));
addTiles(1, TILE_SET, new AtlantiTile(CITY_THREE_ROAD, false));
addTiles(2, TILE_SET, new AtlantiTile(CITY_THREE_ROAD, true));
addTiles(3, TILE_SET, new AtlantiTile(CITY_TWO, false));
addTiles(2, TILE_SET, new AtlantiTile(CITY_TWO, true));
addTiles(3, TILE_SET, new AtlantiTile(CITY_TWO_ROAD, false));
addTiles(2, TILE_SET, new AtlantiTile(CITY_TWO_ROAD, true));
addTiles(1, TILE_SET, new AtlantiTile(CITY_TWO_ACROSS, false));
addTiles(2, TILE_SET, new AtlantiTile(CITY_TWO_ACROSS, true));
addTiles(2, TILE_SET, new AtlantiTile(TWO_CITY_TWO, false));
addTiles(3, TILE_SET, new AtlantiTile(TWO_CITY_TWO_ACROSS, false));
addTiles(5, TILE_SET, new AtlantiTile(CITY_ONE, false));
addTiles(3, TILE_SET, new AtlantiTile(CITY_ONE_ROAD_RIGHT, false));
addTiles(3, TILE_SET, new AtlantiTile(CITY_ONE_ROAD_LEFT, false));
addTiles(3, TILE_SET, new AtlantiTile(CITY_ONE_ROAD_TEE, false));
addTiles(3, TILE_SET, new AtlantiTile(CITY_ONE_ROAD_STRAIGHT, false));
addTiles(4, TILE_SET, new AtlantiTile(CLOISTER_PLAIN, false));
addTiles(2, TILE_SET, new AtlantiTile(CLOISTER_ROAD, false));
addTiles(1, TILE_SET, new AtlantiTile(FOUR_WAY_ROAD, false));
addTiles(4, TILE_SET, new AtlantiTile(THREE_WAY_ROAD, false));
addTiles(8, TILE_SET, new AtlantiTile(STRAIGHT_ROAD, false));
addTiles(9, TILE_SET, new AtlantiTile(CURVED_ROAD, false));
}
}