package vooga.towerdefense.model;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Point;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import util.Location;
import vooga.towerdefense.util.Pixmap;
import vooga.rts.util.Vector;
import vooga.towerdefense.action.Action;
import vooga.towerdefense.action.movement.FollowPath;
import vooga.towerdefense.attributes.AttributeConstantsEnum;
import vooga.towerdefense.attributes.AttributeManager;
import vooga.towerdefense.gameelements.GameElement;
/**
* The GameMap holds all of the state corresponding to an entire game at a given
* moment, including all towers and units and the tiles that comprise the map
* itself.
*
* @author Erick Gonzalez
* @author Jimmy Longley
*/
public class GameMap {
private List<GameElement> myGameElements;
private Tile[][] myGrid;
private Dimension myDimensions;
private GameElement myGhostImage;
private Pathfinder myPathfinder;
public Location myEndLocation;
public Location mySpawnLocation;
private Pixmap myBackgroundImage;
private Dimension myTileSize;
/**
*
* @param background
* a background image
* @param tileSize
* @param width
* the width of the map, in pixels
* @param height
* the height of the map, in pixels
* @param destination
* the destination point of all units
*/
public GameMap(Pixmap background, Dimension mapDimensions,
Dimension tileSize) {
myBackgroundImage = background;
myDimensions = mapDimensions;
myGameElements = new ArrayList<GameElement>();
myTileSize = tileSize;
updatePaths();
}
/**
* Updates the entire game based on an interval of time elapsed
*
* @param elapsedTime
* time elapsed since last game clock tick.
*/
public void update(double elapsedTime) {
for (int i = 0; i < myGameElements.size(); ++i) {
myGameElements.get(i).update(elapsedTime);
}
}
/**
* Adds a game element to the given tile t.
*
* @param e
* a game element
*/
public void addToMap(GameElement e) {
myGameElements.add(e);
}
/**
* tests if an element is a tower
*
* @param e
* @return
*/
public boolean isTower(GameElement e) {
int i = (int) e
.getAttributeManager()
.getAttribute(
AttributeConstantsEnum.AFFILIATION.getStatusCode())
.getValue();
double remainder = e
.getAttributeManager()
.getAttribute(
AttributeConstantsEnum.AFFILIATION.getStatusCode())
.getValue()
- i;
return remainder == .1;
}
/**
* tests if a GameElement is an enemy.
*
* @param e
* @return
*/
public boolean isEnemy(GameElement e) {
return (Math.floor(e
.getAttributeManager()
.getAttribute(
AttributeConstantsEnum.AFFILIATION.getStatusCode())
.getValue()) == 1);
}
/**
* Makes tiles covered by this game element neither buildable or walkable,
* and sets their element.
*
* @param e
*/
public void blockTiles(GameElement e) {
int tilesWide = (int) Math.ceil(e.getSize().getWidth()
/ myTileSize.getWidth());
int tilesTall = (int) Math.ceil(e.getSize().getHeight()
/ myTileSize.getHeight());
for (int i = 0; i < tilesWide; i++) {
for (int j = 0; j < tilesTall; j++) {
Location location = new Location(e.getCenter().getX() + i
* myTileSize.getWidth(), e.getCenter().getY() + j
* myTileSize.getHeight());
Tile t = getTile(location);
t.setTower(e);
t.setWalkable(false);
t.setBuildable(false);
}
}
}
/**
* Given a point on the map, returns the Tile enclosing that point.
*
* @param point
* a point (x, y) on the game map, where x and y are measured in
* pixels.
* @return a Tile object containing this point (x, y)
*/
public Tile getTile(Point point) {
return myGrid[(int) (point.getX() / getTileSize().getWidth())][(int) (point
.getY() / getTileSize().getHeight())];
}
/**
* Given a location on the map, returns the Tile enclosing that point.
*
* @param location
* a location (x, y) on the game map, where x and y are measured
* in pixels.
* @return a Tile object containing this point (x, y)
*/
public Tile getTile(Location location) {
try {
return myGrid[(int) (location.getX() / getTileSize().getWidth())][(int) (location
.getY() / getTileSize().getHeight())];
} catch (ArrayIndexOutOfBoundsException e) {
return null;
}
}
/**
* gets the background image for this map.
*
* @return pixmap that is the background
*/
public Pixmap getBackgroundImage() {
return myBackgroundImage;
}
/**
*
* @param pen
* a pen used to draw elements on this map.
*/
public void paint(Graphics2D pen) {
if (myBackgroundImage != null) {
pen.drawImage(myBackgroundImage.getImg(), 0, 0, myDimensions.width,
myDimensions.height, null);
}
paintTiles(pen);
paintGameElements(pen);
if (myGhostImage != null)
myGhostImage.paint(pen);
}
private void paintTiles(Graphics2D pen) {
for (int i = 0; i < myGrid.length; ++i) {
for (int j = 0; j < myGrid[i].length; ++j) {
myGrid[i][j].paint(pen);
}
}
}
private void paintGameElements(Graphics2D pen) {
for (int i = 0; i < myGameElements.size(); ++i) {
myGameElements.get(i).paint(pen);
}
}
/**
*
* @return a list of all available game elements.
*/
public List<GameElement> getAllGameElements() {
return myGameElements;
}
/**
*
* @param gameElement
* simply adds a game element to the map.
*/
public void addGameElement(GameElement gameElement) {
myGameElements.add(gameElement);
}
/**
*
* @param gameElement
* game element to be removed
*/
public void removeGameElement(GameElement gameElement) {
myGameElements.remove(gameElement);
}
/**
* Gets howMany number of the closest targets within a radius of a circle
* centered at source.
*
* @param source
* the center of the circle
* @param radius
* the radius of the circle
* @param howMany
* the number of units closest to source, within radius
* @return howMany number of elements within radius sorted by distance from
* source
*/
public List<GameElement> getTargetsWithinRadius(Location source,
double radius, int howMany) {
List<GameElement> elementsWithinRadius = getTargetsWithinRadius(source,
radius);
int lastIndex = howMany > elementsWithinRadius.size() ? elementsWithinRadius
.size() : howMany;
return elementsWithinRadius.subList(0, lastIndex);
}
/**
* Gets the targets within a radius of a circle centered at source.
*
* @param source
* the center of the circle
* @param radius
* the radius of the circle
* @return all elements within radius sorted by distance from source
*/
public List<GameElement> getTargetsWithinRadius(Location source,
double radius) {
List<GameElement> elementsWithinRadius = getElementsWithinRadius(
source, radius);
sortGameElementsByDistanceToSource(elementsWithinRadius, source);
return elementsWithinRadius;
}
private void sortGameElementsByDistanceToSource(
List<GameElement> elementsWithinRadius, Location source) {
class GameElementComparator implements Comparator<GameElement> {
private Location mySource;
public GameElementComparator(Location source) {
mySource = source;
}
@Override
public int compare(GameElement o1, GameElement o2) {
return Vector.distanceBetween(mySource, o1.getCenter())
- Vector.distanceBetween(mySource, o2.getCenter()) > 0 ? 1
: -1;
}
}
Collections.sort(elementsWithinRadius,
new GameElementComparator(source));
}
private List<GameElement> getElementsWithinRadius(Location source,
double radius) {
List<GameElement> elementsWithinRadius = new ArrayList<GameElement>();
for (GameElement gameElement : myGameElements) {
if (gameElement != null) {
if (Vector.distanceBetween(source, gameElement.getCenter()) <= radius) {
elementsWithinRadius.add(gameElement);
}
if (gameElement.getCenter() == source){
elementsWithinRadius.remove(gameElement);
}
}
}
return elementsWithinRadius;
}
/**
* Returns a Path object representing the shortest path between two
* locations.
*
* @param start
* the start location
* @param finish
* the end location
* @return the shortest path between these two locations
*/
public Path getShortestPath(Location start, Location finish) {
Location startIndex = getTileIndexFromLocation(start);
Location finishIndex = getTileIndexFromLocation(finish);
Path thePath = myPathfinder.getShortestPath(startIndex, finishIndex);
thePath.add(finish);
return thePath;
}
private Location getTileIndexFromLocation(Location location) {
return new Location((int) ((location.getX() - 1) / getTileSize()
.getWidth()), (int) ((location.getY() - 1) / getTileSize()
.getHeight()));
}
/**
* When the grid is modified, every element that follows a path must
* recalculate it's path.
*/
public void updatePaths() {
for (GameElement e : myGameElements) {
for (Action action : e.getActions())
if (action.getClass().equals(FollowPath.class)) {
Path p = getShortestPath(e.getCenter(), myEndLocation);
((FollowPath) action).setPath(p);
}
}
}
/**
* Temporarily adds an element onto the map for display only in order to aid
* the user in placing a tower on the map.
*
* @param itemImage
* @param location
* @param size
*/
public void addGhostImage(Pixmap itemImage, Location location,
Dimension size) {
myGhostImage = new GameElement(itemImage, location, size,
new AttributeManager());
}
/**
* Resets the ghost image so that it dissapears after the user is no longer
* placing a tower.
*/
public void resetGhostImage() {
myGhostImage = null;
}
/**
* Used to determine if a ghost image should be painted, it tests if a tower
* can be built at a particular point.
*
* @param p
* @return
*/
public boolean isBuildable(Point p) {
return getTile(p).isBuildable();
}
/**
* Used to determine if a ghost image should be painted, it tests if a tower
* can be built at a particular location.
*
* @param l
* a location
* @return
*/
public boolean isBuildable(Location l) {
Tile t = getTile(l);
return (t == null) ? false : t.isBuildable();
}
public Location getSpawnLocation() {
return mySpawnLocation;
}
public Location getEndLocation() {
return myEndLocation;
}
public Dimension getTileSize() {
return myTileSize;
}
public void setSpawnLocation(Location location) {
mySpawnLocation = location;
}
public void setDestinationLocation(Location location) {
myEndLocation = location;
}
public void setGrid(Tile[][] grid) {
myPathfinder = new Pathfinder(grid, myTileSize);
myGrid = grid;
}
public Dimension getSize() {
return myDimensions;
}
}