/*
* Copyright (c) 2012 Felix Mo. All rights reserved.
*
* CitySim is published under the terms of the MIT License. See the LICENSE file for more information.
*
*/
import greenfoot.*; // (World, Actor, GreenfootImage, Greenfoot and MouseInfo)
import java.lang.Math;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.awt.Rectangle;
import java.awt.Point;
import java.awt.Color;
import java.awt.Font;
/**
* Map
* CitySim
* v0.1
*
* Created by Felix Mo on 02-11-2012
*
* Map view, map view controller, and map data model
*
*/
public class Map extends Actor
{
// ---------------------------------------------------------------------------------------------------------------------
/*
* INSTANCE VARIABLES & CONSTANTS *
*/
private static Map instance;
// Map properties
public final int SIZE_COLUMNS = 200; // cells; horizontal
public final int SIZE_ROWS = 200; // cells; vertical
private Rectangle cityRect = new Rectangle(0, 0, (SIZE_COLUMNS * Tile.SIZE), (SIZE_ROWS * Tile.SIZE)); // rectangle representing the entirety of the map
// View configuration
private final int moveSpeed = 20; // # of px to move at a time
private final static int tileBuffer = 4; // number of additional cells to draw beyond the viewport
// Map view
private Rectangle viewport = new Rectangle(0, 0, 1024 + (tileBuffer * Tile.SIZE), 768 + (tileBuffer * Tile.SIZE) - 230); // rectangle representing the viewport | NOTE: remember to subtract HUD and other OSD elements from height/width
private GreenfootImage view = new GreenfootImage(viewport.width, viewport.height); // image layer containting all the visible map tiles; tiles are drawn onto this image instead of being drawn on-screen individually (too resource-intensive)
// Ref. to mouse info. provided by Greenfoot
private MouseInfo mouseInfo = null;
private Selection selection = new Selection(viewport.width, viewport.height);
private AnimationLayer animation = new AnimationLayer(viewport.width, viewport.height);
private boolean shouldUpdate = false;
// ---------------------------------------------------------------------------------------------------------------------
public Map() {
Map.instance = this;
if (Data.dbIsNew()) {
// New DB
CSLogger.sharedLogger().info("New database created; generating map...");
// Insert specified map properties into DB
LinkedHashMap mapSize = new LinkedHashMap();
mapSize.put(Data.MAPSIZE_ROWS, SIZE_ROWS);
mapSize.put(Data.MAPSIZE_COLUMNS, SIZE_COLUMNS);
Data.insertMapSize(mapSize);
// Generate a new map (for testing), set the image representing this 'Actor' to be the map image, and then do the inital draw of map tiles from the origin
Data.insertTiles(generateCity());
}
else {
// Existing DB
// Get map data from DB
CSLogger.sharedLogger().info("Database exists; loading tiles...");
}
// Draw map on screen, starting at the origin (0, 0)
setImage(view);
Point viewportLoc = viewport.getLocation();
viewportDidMove(viewportLoc.x, viewportLoc.y);
}
// * Greenfoot methods *
protected void addedToWorld(World world) {
this.selection.setViewport(this.viewport);
world.addObject(this.selection, 512, 333);
this.animation.setViewport(this.viewport);
world.addObject(this.animation, 512, 333);
}
// This method is called at every action step in the environment; frequently
public void act()
{
if (this.shouldUpdate) {
draw();
this.shouldUpdate = false;
}
// Listen for keystrokes and mouse movement and move accordingly, only if viewport is within bounds of map
int offset_x = 0;
int offset_y = 0;
int mouse_x = 0;
int mouse_y = 0;
// Update mouse info if mouse has moved
if (Greenfoot.getMouseInfo() != null) {
mouseInfo = Greenfoot.getMouseInfo();
}
if (mouseInfo != null) {
mouse_x = mouseInfo.getX();
mouse_y = mouseInfo.getY();
Point cell = cellForCoordinatePairInView(mouse_x, mouse_y);
this.selection.setActiveTile(tileForCoordinatePair(cell.x, cell.y));
// If selection view is NOT in selection mode; deactivate the view when cursor is out of bounds
if (mouse_y <= 28 || mouse_y >= 538 || mouse_x < 10 || mouse_x >= 1014) {
// TOP
if (!this.selection.selectionMode()) this.selection.setActive(false);
}
else {
this.selection.setActive(true);
}
}
// Vertical movement
if (Greenfoot.isKeyDown("w")) {
// UP
if (viewport.y > cityRect.y) {
offset_y = moveSpeed * -1;
}
}
else if (Greenfoot.isKeyDown("s")) {
// DOWN
if (viewport.y + viewport.height < cityRect.width) {
offset_y = moveSpeed;
}
}
// Horizontal movement
if (Greenfoot.isKeyDown("a")) {
// LEFT
if (viewport.x > cityRect.x) {
offset_x = moveSpeed;
}
}
else if (Greenfoot.isKeyDown("d")) {
// RIGHT
if (viewport.x + viewport.width < cityRect.width) {
offset_x = moveSpeed * -1;
}
}
// Only re-render map if there was movement
if (offset_x != 0 || offset_y != 0) {
viewportDidMove(offset_x, offset_y);
}
}
// * END of Greenfoot methods *
// * Helper methods *
// Translates a given pair of coordinates (for the view, in px) to indices for the representing map tile
private Point cellForCoordinatePair(int x, int y) {
return new Point((x / Tile.SIZE), (y / Tile.SIZE));
}
// Translate a given pair of coordinates (within the bounds of the view) to indices for the representing map tile
private Point cellForCoordinatePairInView(int x, int y) {
return cellForCoordinatePair((viewport.x + x), (viewport.y + y));
}
private Tile tileForCoordinatePair(int x, int y) {
return (Tile)Data.tiles().get(Math.min(x+2, SIZE_COLUMNS-1)).get(Math.min(y+2, SIZE_ROWS-1));
}
// Translates a given indice to a coordinate for the view, in px
private int coordinateForCell(int cell) {
return (cell * Tile.SIZE);
}
// Returns the numbers of the tiles that would fit within a given width
private int numberOfTilesInWidth(int width) {
return (width / Tile.SIZE);
}
// * END of helper methods *
// Generates a new map into an ArrayList a returns it
private ArrayList<ArrayList<Tile>> generateCity() {
CSLogger.sharedLogger().info("Began generating city...");
long startTime = System.currentTimeMillis();
int[][] tiles = new TerrainGenerator(null, 1.0f, SIZE_COLUMNS, SIZE_ROWS).tiles();
// The columns of the map are represnted by the elements of an ArrayList
ArrayList<ArrayList<Tile>> map = new ArrayList<ArrayList<Tile>>(SIZE_COLUMNS);
// The rows of the map are represented by the elements of an ArrayList within the ArrayList representing the columns
for (int i = 0; i < SIZE_COLUMNS; i++) {
map.add(new ArrayList<Tile>(SIZE_ROWS));
}
// Initalize each cell with the default initial type of grass
/*
* Iterates each cell (top -> botton) from each column (left -> right)
*
*/
int dbID = 0;
for (int x = 0; x < SIZE_COLUMNS; x++) {
for (int y = 0; y < SIZE_ROWS; y++) {
map.get(x).add(new Tile(dbID, new Point(x, y), tiles[x][y]));
dbID++;
// System.out.println("(" + x + ", " + y + ")" + " | " + "Value: " + value + " | Tile: " + ((Tile)map.get(x).get(y)).type() + " | Value below: " + df.format(grid[x][Math.min(Math.max(0, y-1), SIZE_ROWS-1)]));
}
}
CSLogger.sharedLogger().info("Finished generating city in " + (System.currentTimeMillis() - startTime) + " ms");
return map;
}
// Re-renders the map image layer upon movement to account for the viewport's new offset
private void viewportDidMove(int dx, int dy) {
// Shift the viewport's origin from movement offset
viewport.setLocation((viewport.x - dx), (viewport.y + dy));
Point viewportLoc = viewport.getLocation();
Point cell = cellForCoordinatePair(viewportLoc.x, viewportLoc.y);
if (getWorld() != null) ((City)getWorld()).didMoveMapTo(cell.x, cell.y);
this.selection.setViewport(this.viewport);
this.animation.setViewport(this.viewport);
draw();
}
public void viewportDidMoveTo(int x, int y) {
// Shift the viewport's origin from movement offset
viewport.setLocation(coordinateForCell(x), coordinateForCell(y));
this.selection.setViewport(this.viewport);
this.animation.setViewport(this.viewport);
draw();
}
protected void draw() {
// Get cached map data
ArrayList<ArrayList<Tile>> map = Data.tiles();
// Coordinates for the tile being drawn; set at the origin of the shifted viewport
int tile_x = viewport.x;
int tile_y = viewport.y;
Point vpLoc = viewport.getLocation(); // Viewport location
Point cell = cellForCoordinatePair(vpLoc.x, vpLoc.y); // Cell @ viewport origin
// Clear the whole map image
view.clear();
// Draw tiles onto the map image layer for the shifted viewport
for (int col = cell.x; col < numberOfTilesInWidth(viewport.width + viewport.x); col++) {
for (int row = cell.y; row < numberOfTilesInWidth(viewport.height + viewport.y); row++) {
// System.out.println(Math.min(col, SIZE_COLUMNS-1) + ", " + Math.min(row, SIZE_ROWS-1));
// Get the tile (if it is within the bounds of the map)
Tile tile = map.get(Math.min(col, SIZE_COLUMNS-1)).get(Math.min(row, SIZE_ROWS-1));
// Draw the tile image onto the whole map image
view.drawImage(tile.image(), tile_x, tile_y-Tile.SIZE);
// Update the coordinates for the next tile to be drawn
tile_x = coordinateForCell(col) - viewport.x;
tile_y = coordinateForCell(row) - viewport.y;
}
}
}
/*
* ACCESSORS *
*/
public static Map getInstance() {
return instance;
}
public Selection selection() {
return this.selection;
}
public void setShouldUpdate(boolean value) {
this.shouldUpdate = value;
}
}