/** Called when an entry is added to {@link AtlantiObject#TILES}. */
  public void tilesAdded(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();
  }
  /**
   * 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);
    }
  }
  /** Test code. */
  public static void main(String[] args) {
    JFrame frame = new JFrame("Board test");
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    AtlantiBoardView board = new AtlantiBoardView(null);

    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.tilesChanged(set);

    AtlantiTile placing = new AtlantiTile(CITY_TWO, false, NORTH, 0, 0);
    board.setTileToBePlaced(placing);

    // set a feature group to test propagation
    List<AtlantiTile> tiles = new ArrayList<AtlantiTile>();
    CollectionUtil.addAll(tiles, set.iterator());
    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.setVisible(true);
  }
  /** 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);
      }
      // tell the controller we're done
      _ctrl.placeNothing();

    } 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
      _ctrl.tilePlaced(_placedTile);

      // 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 let the controller know that the user placed it
    if (_placingPiecen && _placedTile != null && _placedTile.piecen != null) {
      _ctrl.piecenPlaced(_placedTile.piecen);
      // clear out placing piecen mode
      _placingPiecen = false;
    }
  }