diff --git a/projects/atlanti/src/java/com/samskivert/atlanti/TileGeometryTest.java b/projects/atlanti/src/java/com/samskivert/atlanti/TileGeometryTest.java index db2a7e1c..bc8376fa 100644 --- a/projects/atlanti/src/java/com/samskivert/atlanti/TileGeometryTest.java +++ b/projects/atlanti/src/java/com/samskivert/atlanti/TileGeometryTest.java @@ -1,5 +1,5 @@ // -// $Id: TileGeometryTest.java,v 1.1 2001/10/15 19:55:15 mdb Exp $ +// $Id: TileGeometryTest.java,v 1.2 2001/10/16 01:41:55 mdb Exp $ package com.threerings.venison; @@ -23,7 +23,7 @@ public class TileGeometryTest ArrayList colors = new ArrayList(); // create polygons from the various tile features - for (int i = 1; i < TileUtil.TILE_FEATURES.length; i++) { + for (int i = 0; i < TileUtil.TILE_FEATURES.length; i++) { // convert tile index into x and y coordinates (in tile // feature coords which will be converted to screen coords) int x = 4 * (i % 5), y = 4 * (i / 5); diff --git a/projects/atlanti/src/java/com/samskivert/atlanti/client/AtlantiBoard.java b/projects/atlanti/src/java/com/samskivert/atlanti/client/AtlantiBoard.java index 671922ba..8dc4bf23 100644 --- a/projects/atlanti/src/java/com/samskivert/atlanti/client/AtlantiBoard.java +++ b/projects/atlanti/src/java/com/samskivert/atlanti/client/AtlantiBoard.java @@ -1,5 +1,5 @@ // -// $Id: AtlantiBoard.java,v 1.5 2001/10/15 19:54:53 mdb Exp $ +// $Id: AtlantiBoard.java,v 1.6 2001/10/16 01:41:55 mdb Exp $ package com.threerings.venison; @@ -85,14 +85,18 @@ public class VenisonBoard * the mouse while the tile is in a placeable position, a * TILE_PLACED command will be dispatched to the * controller in scope. The coordinates and orientation of the tile - * will have been set to the values specified by the user. + * 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 (VenisonTile tile) { - _placingTile = tile; + // make a copy of this tile so that we can play with it + _placingTile = (VenisonTile)tile.clone();; + // update our internal state based on this new placing tile if (_placingTile != null) { updatePlacingInfo(true); @@ -101,6 +105,14 @@ public class VenisonBoard repaint(); } + /** + * Returns the last tile placed by the user. + */ + public VenisonTile getPlacedTile () + { + return _placedTile; + } + // documentation inherited public void layout () { @@ -115,6 +127,7 @@ public class VenisonBoard 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); @@ -124,17 +137,17 @@ public class VenisonBoard Iterator iter = _tiles.elements(); while (iter.hasNext()) { VenisonTile tile = (VenisonTile)iter.next(); - tile.paint(g, _origX, _origY); + 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(g, _origX, _origY); + _placingTile.paint(g2, _origX, _origY); // draw a green rectangle around the placing tile - g.setColor(Color.green); + g.setColor(Color.blue); int sx = (_placingTile.x + _origX) * TILE_WIDTH; int sy = (_placingTile.y + _origY) * TILE_HEIGHT; g.drawRect(sx, sy, TILE_WIDTH, TILE_HEIGHT); @@ -166,7 +179,8 @@ public class VenisonBoard // to dispatch an action letting the controller know that the user // placed it if (_placingTile != null && _validPlacement) { - // clear out the placing tile + // move the placing tile to the placed tile + _placedTile = _placingTile; _placingTile = null; // post the action @@ -330,8 +344,8 @@ public class VenisonBoard TestDSet set = new TestDSet(); set.addTile(new VenisonTile(CITY_TWO, false, WEST, 0, 0)); set.addTile(new VenisonTile(CITY_FOUR, false, NORTH, 0, 1)); - set.addTile(new VenisonTile(CITY_THREE, false, NORTH, 1, 1)); - set.addTile(new VenisonTile(CITY_THREE_ROAD, false, NORTH, 1, 2)); + set.addTile(new VenisonTile(CITY_THREE, false, WEST, 1, 1)); + set.addTile(new VenisonTile(CITY_THREE_ROAD, false, EAST, 1, 2)); set.addTile(new VenisonTile(CITY_THREE, false, NORTH, -1, 0)); set.addTile(new VenisonTile(CITY_FOUR, false, NORTH, -2, 0)); board.setTiles(set); @@ -364,6 +378,9 @@ public class VenisonBoard /** The tile currently being placed by the user. */ protected VenisonTile _placingTile; + /** The last tile being placed by the user. */ + protected VenisonTile _placedTile; + /** Whether or not the current position and orientation of the placing * tile is valid. */ protected boolean _validPlacement = false; diff --git a/projects/atlanti/src/java/com/samskivert/atlanti/client/AtlantiController.java b/projects/atlanti/src/java/com/samskivert/atlanti/client/AtlantiController.java index 5352384a..85f9e21f 100644 --- a/projects/atlanti/src/java/com/samskivert/atlanti/client/AtlantiController.java +++ b/projects/atlanti/src/java/com/samskivert/atlanti/client/AtlantiController.java @@ -81,11 +81,10 @@ public class VenisonController public boolean handleAction (ActionEvent action) { if (action.getActionCommand().equals(TILE_PLACED)) { - // the user placed the tile into a valid location. the board - // will have updated the position and orientation information - // in the tile object accordingly, so we simply submit our - // move to the server - Object[] args = new Object[] { _venobj.currentTile }; + // the user placed the tile into a valid location. grab the + // placed tile from the board and submit it to the server as + // our move + Object[] args = new Object[] { _panel.board.getPlacedTile() }; MessageEvent mevt = new MessageEvent( _venobj.getOid(), PLACE_TILE_REQUEST, args); _ctx.getDObjectManager().postEvent(mevt); diff --git a/projects/atlanti/src/java/com/samskivert/atlanti/client/AtlantiPanel.java b/projects/atlanti/src/java/com/samskivert/atlanti/client/AtlantiPanel.java index 5e9530fb..81f16cbb 100644 --- a/projects/atlanti/src/java/com/samskivert/atlanti/client/AtlantiPanel.java +++ b/projects/atlanti/src/java/com/samskivert/atlanti/client/AtlantiPanel.java @@ -8,6 +8,8 @@ import javax.swing.*; import com.samskivert.swing.Controller; import com.samskivert.swing.ControllerProvider; +import com.samskivert.swing.HGroupLayout; +import com.samskivert.swing.VGroupLayout; import com.threerings.crowd.data.PlaceObject; import com.threerings.crowd.client.PlaceView; @@ -28,9 +30,27 @@ public class VenisonPanel */ public VenisonPanel (ParlorContext ctx, VenisonController controller) { - // add the board + HGroupLayout gl = new HGroupLayout(HGroupLayout.STRETCH); + gl.setOffAxisPolicy(HGroupLayout.STRETCH); + setLayout(gl); + + // create the board board = new VenisonBoard(); - add(board, BorderLayout.CENTER); + + // 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); + JPanel sidePanel = new JPanel(sgl); + + // add a turn indicator to the side panel + sidePanel.add(new TurnIndicatorView(), VGroupLayout.FIXED); + + // add our side panel to the main display + add(sidePanel, HGroupLayout.FIXED); // we'll need this later to provide it _controller = controller; diff --git a/projects/atlanti/src/java/com/samskivert/atlanti/client/TileLabel.java b/projects/atlanti/src/java/com/samskivert/atlanti/client/TileLabel.java new file mode 100644 index 00000000..90ccf1e2 --- /dev/null +++ b/projects/atlanti/src/java/com/samskivert/atlanti/client/TileLabel.java @@ -0,0 +1,50 @@ +// +// $Id: TileLabel.java,v 1.1 2001/10/16 01:41:55 mdb Exp $ + +package com.threerings.venison; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Graphics2D; + +import javax.swing.JComponent; + +/** + * 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 (VenisonTile 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 VenisonTile _tile; +} diff --git a/projects/atlanti/src/java/com/samskivert/atlanti/client/TurnIndicatorView.java b/projects/atlanti/src/java/com/samskivert/atlanti/client/TurnIndicatorView.java new file mode 100644 index 00000000..28999048 --- /dev/null +++ b/projects/atlanti/src/java/com/samskivert/atlanti/client/TurnIndicatorView.java @@ -0,0 +1,115 @@ +// +// $Id: TurnIndicatorView.java,v 1.1 2001/10/16 01:41:55 mdb Exp $ + +package com.threerings.venison; + +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; + +/** + * 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 + _venobj = (VenisonObject)plobj; + _venobj.addListener(this); + } + + // documentation inherited + public void didLeavePlace (PlaceObject plobj) + { + // remove our listening self + _venobj.removeListener(this); + _venobj = null; + } + + // documentation inherited + public void attributeChanged (AttributeChangedEvent event) + { + // we care about the current tile display + if (event.getName().equals(VenisonObject.CURRENT_TILE)) { + // display the tile to be placed + _tileLabel.setTile(_venobj.currentTile); + + } else if (event.getName().equals(VenisonObject.STATE)) { + switch (_venobj.state) { + case VenisonObject.AWAITING_PLAYERS: + _whoLabel.setText("Awaiting players..."); + _tileLabel.setTile(null); + break; + case VenisonObject.GAME_OVER: + _whoLabel.setText("Game over."); + _tileLabel.setTile(null); + break; + case VenisonObject.CANCELLED: + _whoLabel.setText("Cancelled."); + _tileLabel.setTile(null); + break; + } + + // update the tiles remaining + updateRemainingTiles(); + + } else if (event.getName().equals(VenisonObject.TURN_HOLDER)) { + if (_venobj.state == VenisonObject.IN_PLAY) { + _whoLabel.setText(_venobj.turnHolder); + } + + // update the tiles remaining when the turn changes + updateRemainingTiles(); + } + } + + protected void updateRemainingTiles () + { + _countLabel.setText("Tiles remaining: " + (72-_venobj.tiles.size())); + } + + /** 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 VenisonObject _venobj; +} diff --git a/projects/atlanti/src/java/com/samskivert/atlanti/data/AtlantiTile.java b/projects/atlanti/src/java/com/samskivert/atlanti/data/AtlantiTile.java index 0c372dc6..9975b8cb 100644 --- a/projects/atlanti/src/java/com/samskivert/atlanti/data/AtlantiTile.java +++ b/projects/atlanti/src/java/com/samskivert/atlanti/data/AtlantiTile.java @@ -1,15 +1,23 @@ // -// $Id: AtlantiTile.java,v 1.4 2001/10/12 20:34:13 mdb Exp $ +// $Id: AtlantiTile.java,v 1.5 2001/10/16 01:41:55 mdb Exp $ package com.threerings.venison; import java.awt.Color; -import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Polygon; + +import java.awt.geom.AffineTransform; +import java.awt.geom.GeneralPath; import java.io.IOException; import java.io.DataInputStream; import java.io.DataOutputStream; +import java.util.ArrayList; + +import com.samskivert.util.IntTuple; + import com.threerings.presents.dobj.DSet; /** @@ -71,51 +79,35 @@ public class VenisonTile * @param yoff the offset (in tile units) of the origin in the y * direction. */ - public void paint (Graphics g, int xoff, int yoff) + public void paint (Graphics2D g, int xoff, int yoff) { + int tidx = type-1; + + // create our shapes if we haven't already + if (_shapes[tidx][orientation] == null) { + createShapes(); + } + // compute our screen coordinates int sx = (x + xoff) * TILE_WIDTH; int sy = (y + yoff) * TILE_HEIGHT; - // draw a rectangle - g.setColor(Color.black); - g.drawRect(sx, sy, TILE_WIDTH, TILE_HEIGHT); + // translate to our screen coordinates + g.translate(sx, sy); - // and draw our tile id in the middle for now (we'll eventually - // draw tile images) - String txt = type + "/" + x + "/" + y + "/" + - ORIENT_NAMES[orientation]; - g.drawString(txt, sx + 20, sy + 20); - - // draw little dots indicating the color of our sides - for (int i = 0; i < 4; i++) { - // adjust for our orientation - switch (TileUtil.getEdge(type, (i+(4-orientation))%4)) { - case GRASS: - g.setColor(Color.green); - break; - case ROAD: - g.setColor(Color.white); - break; - case CITY: - g.setColor(Color.red); - break; - } - switch (i) { - case NORTH: - g.fillOval(sx + TILE_WIDTH/2 - 2, sy + 2, 4, 4); - break; - case EAST: - g.fillOval(sx + TILE_WIDTH - 6, sy + TILE_HEIGHT/2 - 2, 4, 4); - break; - case SOUTH: - g.fillOval(sx + TILE_WIDTH/2 - 2, sy + TILE_HEIGHT - 6, 4, 4); - break; - case WEST: - g.fillOval(sx + 2, sy + TILE_HEIGHT/2 - 2, 4, 4); - break; - } + // draw our shapes using the proper orientation + GeneralPath[] paths = _shapes[tidx][orientation]; + for (int i = 0; i < paths.length; i++) { + g.setColor(COLOR_MAP[_types[tidx][i].left]); + g.fill(paths[i]); } + + // draw a rectangular outline + g.setColor(Color.black); + g.drawRect(0, 0, TILE_WIDTH, TILE_HEIGHT); + + // translate back out + g.translate(-sx, -sy); } /** @@ -155,10 +147,112 @@ public class VenisonTile y = in.readInt(); } + /** + * Creates the path objects that describe the shapes that make up this + * tile and sticks it into the appropriate slot in the shapes array. + */ + protected void createShapes () + { + System.out.println("Creating shapes " + this + "."); + + int tidx = type-1; + ArrayList polys = new ArrayList(); + ArrayList types = new ArrayList(); + + // the first feature is the background color + Object[] features = (Object[])TileUtil.TILE_FEATURES[tidx]; + IntTuple base = (IntTuple)features[0]; + + // add a polygon containing the whole tile + types.add(base); + Polygon poly = new Polygon(); + poly.addPoint(0, 0); + poly.addPoint(TILE_WIDTH, 0); + poly.addPoint(TILE_WIDTH, TILE_HEIGHT); + poly.addPoint(0, TILE_HEIGHT); + polys.add(poly); + + // the remainder are tuple/coordinate pairs + for (int f = 1; f < features.length; f += 2) { + IntTuple ftype = (IntTuple)features[f]; + int[] coords = (int[])features[f+1]; + + // keep track of this shape's type + types.add(ftype); + + // if this is a road segment, we need to create a special + // polygon + if (ftype.left == ROAD) { + poly = TileUtil.roadSegmentToPolygon( + coords[0], coords[1], coords[2], coords[3]); + + } else { + // otherwise create the polygon directly from the coords + poly = new Polygon(); + for (int c = 0; c < coords.length; c += 2) { + // scale the coords accordingly + int fx = (coords[c] * TILE_WIDTH) / 4; + int fy = (coords[c+1] * TILE_HEIGHT) / 4; + poly.addPoint(fx, fy); + } + } + + polys.add(poly); + } + + // now create general paths from our polygons and convert + // everything into the appropriate arrays + GeneralPath[] paths = new GeneralPath[polys.size()]; + for (int i = 0; i < polys.size(); i++) { + GeneralPath path = new GeneralPath(); + path.append(((Polygon)polys.get(i)).getPathIterator(null), false); + path.closePath(); + paths[i] = path; + } + + // keep the first one around + _shapes[tidx][NORTH] = paths; + + // and rotate it three times to get the other orientations + AffineTransform xform = new AffineTransform(); + for (int o = 1; o < 4; o++) { + xform.translate(TILE_WIDTH, 0); + xform.rotate(Math.PI/2); + GeneralPath[] rpaths = new GeneralPath[paths.length]; + for (int i = 0; i < paths.length; i++) { + rpaths[i] = (GeneralPath)paths[i].clone(); + rpaths[i].transform(xform); + } + _shapes[tidx][o] = rpaths; + } + + // also fill in the feature type info + _types[tidx] = new IntTuple[types.size()]; + types.toArray(_types[tidx]); + } + + /** + * Generates a string representation of this tile instance. + */ public String toString () { return "[type=" + type + ", shield=" + hasShield + ", orient=" + ORIENT_NAMES[orientation] + ", pos=" + x + "/" + y + "]"; } + + /** Path objects that describe closed shapes which be used to render + * the tiles (one for each orientation of each tile type). */ + protected static GeneralPath[][][] _shapes = + new GeneralPath[TILE_TYPES][4][]; + + /** The feature type of each shape in shapes array. */ + protected static IntTuple[][] _types = new IntTuple[TILE_TYPES][]; + + /** Maps feature types to colors. */ + protected static Color[] COLOR_MAP = { + Color.red, // CITY + Color.green, // GRASS + Color.black // ROAD + }; } diff --git a/projects/atlanti/src/java/com/samskivert/atlanti/data/TileCodes.java b/projects/atlanti/src/java/com/samskivert/atlanti/data/TileCodes.java index d87aa602..74221308 100644 --- a/projects/atlanti/src/java/com/samskivert/atlanti/data/TileCodes.java +++ b/projects/atlanti/src/java/com/samskivert/atlanti/data/TileCodes.java @@ -1,5 +1,5 @@ // -// $Id: TileCodes.java,v 1.2 2001/10/10 06:14:57 mdb Exp $ +// $Id: TileCodes.java,v 1.3 2001/10/16 01:41:55 mdb Exp $ package com.threerings.venison; @@ -75,6 +75,8 @@ public interface TileCodes /** 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. */ diff --git a/projects/atlanti/src/java/com/samskivert/atlanti/server/AtlantiManager.java b/projects/atlanti/src/java/com/samskivert/atlanti/server/AtlantiManager.java index d031ba87..f35569de 100644 --- a/projects/atlanti/src/java/com/samskivert/atlanti/server/AtlantiManager.java +++ b/projects/atlanti/src/java/com/samskivert/atlanti/server/AtlantiManager.java @@ -1,5 +1,5 @@ // -// $Id: AtlantiManager.java,v 1.5 2001/10/12 20:34:13 mdb Exp $ +// $Id: AtlantiManager.java,v 1.6 2001/10/16 01:41:55 mdb Exp $ package com.threerings.venison; @@ -62,12 +62,39 @@ public class VenisonManager protected void turnWillStart () { + super.turnWillStart(); + // let the players know what the next tile is that should be // played VenisonTile tile = (VenisonTile)_tiles.remove(0); _venobj.setCurrentTile(tile); } + protected void turnDidEnd () + { + super.turnDidEnd(); + + // if there are no tiles left, we end the game + if (_tiles.size() == 0) { + endGame(); + } + } + + /** + * 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 (_tiles.size() > 0) { + super.setNextTurnHolder(); + } else { + // if we don't, we ensure that a new turn isn't started by + // setting _turnIdx to -1 + _turnIdx = -1; + } + } + /** Handles place tile requests. */ protected class PlaceTileHandler implements MessageHandler { @@ -76,7 +103,7 @@ public class VenisonManager VenisonTile tile = (VenisonTile)event.getArgs()[0]; // don't do no checking at present _venobj.addToTiles(tile); - // and since they placed their tile, we end the turn + // end the turn endTurn(); } } diff --git a/projects/atlanti/src/java/com/samskivert/atlanti/util/TileUtil.java b/projects/atlanti/src/java/com/samskivert/atlanti/util/TileUtil.java index 20e3c23b..fc4f2d76 100644 --- a/projects/atlanti/src/java/com/samskivert/atlanti/util/TileUtil.java +++ b/projects/atlanti/src/java/com/samskivert/atlanti/util/TileUtil.java @@ -1,5 +1,5 @@ // -// $Id: TileUtil.java,v 1.3 2001/10/15 19:55:15 mdb Exp $ +// $Id: TileUtil.java,v 1.4 2001/10/16 01:41:55 mdb Exp $ package com.threerings.venison; @@ -230,6 +230,8 @@ public class TileUtil implements TileCodes poly.addPoint(x2 + dx, y2 - dy); } else { // diagonal + dx = 6; // widen these a bit so that the diagonal line appears + dy = 6; // to have the same weight as the straight ones poly.addPoint(x1 - dx, y1); poly.addPoint(x1 + dx, y1); poly.addPoint(x2, y2 + dy); @@ -321,7 +323,7 @@ public class TileUtil implements TileCodes /** A table describing the geometry of the features (cities, roads, * etc.) of each tile. */ protected static final Object[] TILE_FEATURES = new Object[] { - new Object[0], // null tile + // one must offset tile type by one when indexing into this array new Object[] { new IntTuple(CITY, 0), }, // CITY_FOUR @@ -344,10 +346,10 @@ public class TileUtil implements TileCodes new Object[] { new IntTuple(CITY, 0), // CITY_TWO_ROAD new IntTuple(GRASS, 0), new int[] { 0, 4, 4, 0, 4, 2, 2, 4 }, - new IntTuple(ROAD, 0), - new int[] { 2, 4, 4, 2 }, new IntTuple(GRASS, 0), - new int[] { 2, 4, 4, 2, 4, 4 }}, + new int[] { 2, 4, 4, 2, 4, 4 }, + new IntTuple(ROAD, 0), + new int[] { 2, 4, 4, 2 }}, new Object[] { new IntTuple(CITY, 0), // CITY_TWO_ACROSS new IntTuple(GRASS, 0),