/**
* Copyright (C) 2002-2012 The FreeCol Team
*
* This file is part of FreeCol.
*
* FreeCol is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* FreeCol is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with FreeCol. If not, see <http://www.gnu.org/licenses/>.
*/
package net.sf.freecol.server.generator;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import java.util.logging.Logger;
import net.sf.freecol.common.model.Map;
import net.sf.freecol.common.model.Map.Direction;
import net.sf.freecol.common.model.Tile;
import net.sf.freecol.common.model.TileImprovement;
import net.sf.freecol.common.model.TileImprovementType;
import net.sf.freecol.common.model.TileItemContainer;
import net.sf.freecol.common.model.TileType;
import net.sf.freecol.common.model.Map.Position;
import net.sf.freecol.server.model.ServerRegion;
/**
* A river for the map generator.
*/
public class River {
private static final Logger logger = Logger.getLogger(SimpleMapGenerator.class.getName());
private TileImprovementType riverType;
/**
* Possible direction changes for a river.
* @see net.sf.freecol.common.model.Map
*/
private static enum DirectionChange {
STRAIGHT_AHEAD,
RIGHT_TURN,
LEFT_TURN;
public Direction getNewDirection(Direction oldDirection) {
switch(this) {
case STRAIGHT_AHEAD:
return oldDirection;
case RIGHT_TURN:
switch(oldDirection) {
case NE:
return Direction.SE;
case SE:
return Direction.SW;
case SW:
return Direction.NW;
case NW:
return Direction.NE;
default:
return oldDirection;
}
case LEFT_TURN:
switch(oldDirection) {
case NE:
return Direction.NW;
case SE:
return Direction.NE;
case SW:
return Direction.SE;
case NW:
return Direction.SW;
default:
return oldDirection;
}
}
return oldDirection;
}
}
/**
* Current direction the river is flowing in.
*/
private Direction direction;
/**
* The map on which the river flows.
*/
private Map map;
/**
* A list of river sections.
*/
private List<RiverSection> sections = new ArrayList<RiverSection>();
/**
* The next river.
*/
private River nextRiver = null;
/**
* The ServerRegion this River belongs to.
*/
private ServerRegion region;
/**
* The random number source.
*/
private final Random random;
/**
* A hashtable of position-river pairs.
*/
private java.util.Map<Position, River> riverMap;
/**
* Whether the river is connected to Europe.
*/
private boolean connected = false;
/**
* Constructor.
*
* @param map The map on which the river flows.
* @param riverMap A hashtable of position-river pairs.
* @param region The region for this river.
* @param random The <code>Random</code> number source to use.
*/
public River(Map map, java.util.Map<Position, River> riverMap,
ServerRegion region, Random random) {
this.map = map;
this.riverMap = riverMap;
this.region = region;
this.random = random;
this.riverType = map.getSpecification()
.getTileImprovementType("model.improvement.river");
int index = random.nextInt(Direction.longSides.length);
direction = Direction.longSides[index];
logger.fine("Starting new river flowing " + direction.toString());
}
public List<RiverSection> getSections() {
return sections;
}
/**
* Returns the length of this river.
*
* @return the length of this river.
*/
public int getLength() {
return this.sections.size();
}
public RiverSection getLastSection() {
return this.sections.get(sections.size() - 1);
}
/**
* Get the <code>ServerRegion</code> value.
*
* @return a <code>ServerRegion</code> value
*/
public final ServerRegion getRegion() {
return region;
}
/**
* Set the <code>ServerRegion</code> value.
*
* @param newServerRegion The new ServerRegion value.
*/
public final void setRegion(final ServerRegion newServerRegion) {
this.region = newServerRegion;
}
/**
* Adds a new section to this river.
*
* @param position Where this section is located.
* @param direction The direction the river is flowing in.
*/
public void add(Position position, Direction direction) {
this.sections.add(new RiverSection(position, direction));
}
/**
* Increases the size of this river.
*
* @param lastSection The last section of the river flowing into this one.
* @param position The position of the confluence.
*/
public void grow(RiverSection lastSection, Position position) {
boolean found = false;
for (RiverSection section : sections) {
if (found) {
section.grow();
} else if (section.getPosition().equals(position)) {
section.setBranch(lastSection.direction.getReverseDirection(),
lastSection.getSize());
section.grow();
found = true;
}
}
drawToMap(sections);
if (nextRiver != null) {
RiverSection section = sections.get(sections.size() - 1);
Position neighbor = section.getPosition().getAdjacent(section.direction);
nextRiver.grow(section, neighbor);
}
}
/**
* Returns true if the given position is next to this river.
*
* @param p A map position.
* @return true if the given position is next to this river.
*/
public boolean isNextToSelf(Position p) {
for (Direction direction : Direction.longSides) {
Position px = p.getAdjacent(direction);
if (this.contains(px)) {
return true;
}
}
return false;
}
/**
* Returns true if the given position is next to a river, lake or sea.
*
* @param p A map position.
* @return true if the given position is next to a river, lake or sea.
*/
public boolean isNextToWater(Position p) {
for (Direction direction : Direction.longSides) {
Position px = p.getAdjacent(direction);
final Tile tile = map.getTile(px);
if (tile == null) {
continue;
}
if (!tile.isLand() || tile.hasRiver()) {
return true;
}
}
return false;
}
/**
* Returns true if this river already contains the given position.
*
* @param p A map position.
* @return true if this river already contains the given position.
*/
public boolean contains(Position p) {
Iterator<RiverSection> sectionIterator = sections.iterator();
while (sectionIterator.hasNext()) {
Position q = sectionIterator.next().getPosition();
if (p.equals(q)) {
return true;
}
}
return false;
}
/**
* Creates a river flowing from the given position if possible.
*
* @param position A map position.
* @return true if a river was created, false otherwise.
*/
public boolean flowFromSource(Position position) {
TileImprovementType riverType =
map.getSpecification().getTileImprovementType("model.improvement.river");
Tile tile = map.getTile(position);
if (!tile.getType().canHaveImprovement(riverType)) {
// Mountains, ocean cannot have rivers
logger.fine("Tile (" + tile.getType().toString() + ") at "
+ position + " cannot have rivers.");
return false;
} else if (isNextToWater(position)) {
logger.fine("Tile at " + position + " is next to water.");
return false;
} else {
logger.fine("Tile at " + position + " is suitable source.");
return flow(position);
}
}
/**
* Lets the river flow from the given position.
*
* @param source A map position.
* @return true if a river was created, false otherwise.
*/
private boolean flow(Position source) {
if (sections.size() % 2 == 0) {
// get random new direction
int length = DirectionChange.values().length;
int index = random.nextInt(length);
DirectionChange change = DirectionChange.values()[index];
this.direction = change.getNewDirection(this.direction);
logger.fine("Direction is now " + direction);
}
for (DirectionChange change : DirectionChange.values()) {
Direction dir = change.getNewDirection(direction);
Position newPosition = source.getAdjacent(dir);
Tile nextTile = map.getTile(newPosition);
if (nextTile == null) {
continue;
}
// is the tile suitable for this river?
if (!nextTile.getType().canHaveImprovement(riverType)) {
// Mountains, ocean cannot have rivers
logger.fine("Tile (" + nextTile.getType().toString() + ") at "
+ newPosition + " cannot have rivers.");
continue;
} else if (this.contains(newPosition)) {
logger.fine("Tile at " + newPosition + " is already in river.");
continue;
} else if (isNextToSelf(newPosition)) {
logger.fine("Tile at " + newPosition + " is next to the river.");
continue;
} else {
// find out if an adjacent tile is next to water
for (DirectionChange change2 : DirectionChange.values()) {
Direction lastDir = change2.getNewDirection(dir);
Position px = newPosition.getAdjacent(lastDir);
Tile tile = map.getTile(px);
if (tile != null && (!tile.isLand() || tile.hasRiver())) {
sections.add(new RiverSection(source, dir));
RiverSection lastSection = new RiverSection(newPosition, lastDir);
sections.add(lastSection);
if (tile.hasRiver() && tile.isLand()) {
logger.fine("Point " + newPosition + " is next to another river.");
// increase the size of the other river
nextRiver = riverMap.get(px);
nextRiver.grow(lastSection, px);
// if the other river is connected, so is this one
connected = nextRiver.connected;
// add this region to other river if too small
if (getLength() < 10) {
region = nextRiver.region;
}
drawToMap(sections);
} else {
// flow into the sea (or a lake)
logger.fine("Point " + newPosition + " is next to water.");
River someRiver = riverMap.get(px);
if (someRiver == null) {
sections.add(new RiverSection(px, lastDir.getReverseDirection()));
if (lastSection.getSize() < TileImprovement.FJORD_RIVER) {
createDelta(newPosition, lastDir, lastSection);
}
} else {
RiverSection waterSection = someRiver.getLastSection();
waterSection.setBranch(lastDir.getReverseDirection(),
TileImprovement.SMALL_RIVER);
}
connected = tile.isConnected();
drawToMap(sections);
}
return true;
}
}
// this is not the case
logger.fine("Tile at " + newPosition + " is suitable.");
sections.add(new RiverSection(source, dir));
return flow(newPosition);
}
}
sections = new ArrayList<RiverSection>();
return false;
}
/**
* Describe <code>createDelta</code> method here.
*
* @param position a <code>Position</code> value
* @param direction a <code>Direction</code> value
*/
private void createDelta(Position position, Direction direction, RiverSection section) {
delta(position, direction, section, DirectionChange.LEFT_TURN.getNewDirection(direction));
delta(position, direction, section, DirectionChange.RIGHT_TURN.getNewDirection(direction));
}
private void delta(Position position, Direction direction, RiverSection section, Direction d) {
Position p = position.getAdjacent(d);
Tile tile = map.getTile(p);
if (!tile.isLand()) {
List<RiverSection> deltaSections = new ArrayList<RiverSection>();
section.setBranch(d, TileImprovement.SMALL_RIVER);
deltaSections.add(new RiverSection(p, d.getReverseDirection()));
drawToMap(deltaSections);
} else if (tile.getType().canHaveImprovement(riverType)) {
Position p2 = p.getAdjacent(direction);
Tile tile2 = map.getTile(p2);
if (!tile2.isLand() && random.nextInt(2) == 0) {
List<RiverSection> deltaSections = new ArrayList<RiverSection>();
section.setBranch(d, TileImprovement.SMALL_RIVER);
RiverSection rs = new RiverSection(p, direction);
rs.setBranch(d.getReverseDirection(), TileImprovement.SMALL_RIVER);
deltaSections.add(rs);
rs = new RiverSection(p2, direction.getReverseDirection());
deltaSections.add(rs);
drawToMap(deltaSections);
}
}
}
/**
* Draws the completed river to the map.
*/
private void drawToMap(List<RiverSection> sections) {
RiverSection oldSection = null;
for (RiverSection section : sections) {
riverMap.put(section.getPosition(), this);
if (oldSection != null) {
section.setBranch(oldSection.direction.getReverseDirection(),
oldSection.getSize());
}
Tile tile = map.getTile(section.getPosition());
if (tile.isLand()) {
if (section.getSize() >= TileImprovement.FJORD_RIVER) {
TileType greatRiver = map.getSpecification().getTileType("model.tile.greatRiver");
tile.setType(greatRiver); // changing the type resets the improvements
//container.addRiver(section.getSize(), section.encodeStyle());
if (connected) {
tile.setConnected(true);
}
logger.fine("Added fjord (magnitude: " + section.getSize() +
") to tile at " + section.getPosition());
} else if (section.getSize() > TileImprovement.NO_RIVER) {
TileItemContainer container = tile.getTileItemContainer();
if (container == null) {
container = new TileItemContainer(tile.getGame(), tile);
tile.setTileItemContainer(container);
}
container.addRiver(section.getSize(), section.encodeStyle());
logger.fine("Added river (magnitude: " + section.getSize() +
") to tile at " + section.getPosition());
}
region.addTile(tile);
oldSection = section;
}
}
}
}