package propra2012.gruppe33.bomberman.graphics.rendering.scenegraph.grid;
import java.awt.Color;
import java.awt.Image;
import java.awt.Point;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import propra2012.gruppe33.bomberman.GameConstants;
import propra2012.gruppe33.bomberman.GameRoutines;
import propra2012.gruppe33.bomberman.graphics.rendering.scenegraph.grid.items.CollectableItem;
import propra2012.gruppe33.bomberman.graphics.rendering.scenegraph.grid.transform.DeltaPositionBroadcaster;
import com.indyforge.twod.engine.graphics.rendering.scenegraph.Entity;
import com.indyforge.twod.engine.graphics.rendering.scenegraph.GraphicsEntity;
import com.indyforge.twod.engine.graphics.rendering.scenegraph.RenderedImage;
import com.indyforge.twod.engine.graphics.rendering.scenegraph.Scene;
import com.indyforge.twod.engine.graphics.rendering.scenegraph.math.Grid;
import com.indyforge.twod.engine.graphics.rendering.scenegraph.math.MathExt;
import com.indyforge.twod.engine.graphics.rendering.scenegraph.math.Vector2f;
import com.indyforge.twod.engine.resources.Resource;
import com.indyforge.twod.engine.resources.assets.AssetLoader;
import com.indyforge.twod.engine.resources.assets.AssetManager;
/**
* This class loads the map for the Game and generates randomly destructible
* blocks on it. Is able to throw IOExceptions.
*
* @author Malte Schmidt
*/
public final class GridLoader implements GameConstants {
public static List<Point> find(char[][] map, char sign) {
if (map == null) {
throw new NullPointerException("map");
}
// Calc the dim
int rx = map[0].length, ry = map.length;
// Create new array list
List<Point> points = new ArrayList<Point>();
for (int y = 0; y < ry; y++) {
for (int x = 0; x < rx; x++) {
if (map[y][x] == sign) {
points.add(new Point(x, y));
}
}
}
return points;
}
/**
* Parses the given char array to setup the scene.
*
* @param map
* The map.
* @param scene
* The scene.
* @param broadcastTime
* The time of broadcasting the positions.
* @param seed
* The item generation seed.
* @return a graphics entity which contains the parsed map.
* @throws Exception
* If an exception occurs.
*/
public static GraphicsEntity parse(char[][] map, Scene scene, float w,
float h, float broadcastTime, long seed, int minPlayers,
float defBombChance, float nukeBombChance, float fastBombChance,
float paliChance, float shieldPotionChance, float slowShroomChance,
float fastShroomChance) throws Exception {
if (map == null) {
throw new NullPointerException("map");
} else if (scene == null) {
throw new NullPointerException("scene");
}
/*
* Clamp all items chances.
*/
defBombChance = MathExt.clamp(defBombChance, 0, 1);
nukeBombChance = MathExt.clamp(nukeBombChance, 0, 1);
fastBombChance = MathExt.clamp(fastBombChance, 0, 1);
paliChance = MathExt.clamp(paliChance, 0, 1);
shieldPotionChance = MathExt.clamp(shieldPotionChance, 0, 1);
slowShroomChance = MathExt.clamp(slowShroomChance, 0, 1);
fastShroomChance = MathExt.clamp(fastShroomChance, 0, 1);
// Check the values
if (defBombChance + nukeBombChance + fastBombChance + paliChance
+ shieldPotionChance + slowShroomChance + fastShroomChance > 1f + MathExt.kEpsilon) {
throw new IllegalArgumentException("The sum of all items chances "
+ "is > 1. Please choose appropriate values.");
}
// Calc the dim
int rx = map[0].length, ry = map.length;
// Create a new grid entity
GraphicsEntity gridEntity = GameRoutines.createGridEntity(rx, ry);
// Add shift
gridEntity.position().x += 0.5f;
gridEntity.position().y += 0.5f;
// Lookup the grid
Grid grid = gridEntity.typeProp(Grid.class);
// Create a new grid holder
GraphicsEntity gridHolder = new GraphicsEntity();
// Set correct scale
gridHolder.scale(new Vector2f(w, h).scale(grid.sizeAsVector()
.invertLocal()));
// Attach grid to holder
gridHolder.attach(gridEntity);
// Create new broadcaster
DeltaPositionBroadcaster broadcaster = new DeltaPositionBroadcaster(
broadcastTime, minPlayers);
// Attach the broadcaster
scene.attach(broadcaster);
// Add broadcaster
scene.addTypeProp(broadcaster);
/*
* The breakable image.
*/
Resource<? extends Image> breakable = scene.imageProp(BREAKABLE_IMAGE);
/*
* The solid image.
*/
Resource<? extends Image> solidImage = scene.imageProp(SOLID_IMAGE);
/*
* The ground image.
*/
Resource<? extends Image> groundImage = scene.imageProp(GROUND_IMAGE);
/*
* Load all components!
*/
Resource<? extends Image> wallUP = scene.imageProp(WALL_U_IMAGE);
Resource<? extends Image> wallDOWN = scene.imageProp(WALL_D_IMAGE);
Resource<? extends Image> wallLEFT = scene.imageProp(WALL_L_IMAGE);
Resource<? extends Image> wallRIGHT = scene.imageProp(WALL_R_IMAGE);
Resource<? extends Image> ulc = scene.imageProp(CORNER_LU_IMAGE);
Resource<? extends Image> urc = scene.imageProp(CORNER_RU_IMAGE);
Resource<? extends Image> dlc = scene.imageProp(CORNER_LD_IMAGE);
Resource<? extends Image> drc = scene.imageProp(CORNER_RD_IMAGE);
// The ground
GraphicsEntity ground = new GraphicsEntity();
// The barriers
GraphicsEntity solids = new GraphicsEntity();
// The list which contains all breakable nodes
List<GraphicsEntity> breakableNodes = new LinkedList<GraphicsEntity>();
for (int y = 0; y < ry; y++) {
for (int x = 0; x < rx; x++) {
/*
* PARSE FIELD!
*/
// Create and add tile
RenderedImage tile = new RenderedImage(groundImage);
tile.centered(false).position().set(x, y);
ground.attach(tile);
// Tmp barrier
RenderedImage solid = null;
// Switch
switch (map[y][x]) {
case DOWN_BARRIER:
solid = new RenderedImage(wallDOWN);
break;
case UP_BARRIER:
solid = new RenderedImage(wallUP);
break;
case RIGHT_BARRIER:
solid = new RenderedImage(wallRIGHT);
break;
case LEFT_BARRIER:
solid = new RenderedImage(wallLEFT);
break;
case DOWN_LEFT_CORNER:
solid = new RenderedImage(dlc);
break;
case DOWN_RIGHT_CORNER:
solid = new RenderedImage(drc);
break;
case UP_LEFT_CORNER:
solid = new RenderedImage(ulc);
break;
case UP_RIGHT_CORNER:
solid = new RenderedImage(urc);
break;
case SOLID:
solid = new RenderedImage(solidImage);
break;
}
// Add barrier if valid
if (solid != null) {
solid.position().set(x, y);
solid.centered(false).tag(SOLID_TAG);
solids.attach(solid);
// Fill with solid
gridEntity.typeProp(int[][][].class)[y][x] = null;
} else {
// Get the field entity
Entity fieldNode = gridEntity.childAt(grid.index(x, y));
// Add speed float prop
fieldNode.addTypeProp(3f);
if (map[y][x] >= BREAKABLE_OFFSET) {
// Not solid ? Well, maybe a breakable component ?
fieldNode.attach(new RenderedImage(breakable).centered(
true).tag(BREAKABLE_TAG));
// Add the breakable node!
breakableNodes.add((GraphicsEntity) fieldNode);
}
}
}
}
// Create new field chooser using the seed
Random fieldChooser = new Random(seed);
// Truncate everything
int defBombs = (int) (breakableNodes.size() * defBombChance);
int nukeBombs = (int) (breakableNodes.size() * nukeBombChance);
int fastBombs = (int) (breakableNodes.size() * fastBombChance);
int palis = (int) (breakableNodes.size() * paliChance);
int shieldPotions = (int) (breakableNodes.size() * shieldPotionChance);
int slowShrooms = (int) (breakableNodes.size() * slowShroomChance);
int fastShrooms = (int) (breakableNodes.size() * fastShroomChance);
/*
* The def bombs.
*/
for (int i = 0; i < defBombs; i++) {
// Create new default bomb item
GameRoutines.createItem(breakableNodes.remove(fieldChooser
.nextInt(breakableNodes.size())),
CollectableItem.DefaultBomb, 1);
}
/*
* The nuke bombs.
*/
for (int i = 0; i < nukeBombs; i++) {
// Create new nuke bomb item
GameRoutines.createItem(breakableNodes.remove(fieldChooser
.nextInt(breakableNodes.size())), CollectableItem.NukeBomb,
1);
}
/*
* The time bombs.
*/
for (int i = 0; i < fastBombs; i++) {
// Create new time bomb item
GameRoutines.createItem(breakableNodes.remove(fieldChooser
.nextInt(breakableNodes.size())), CollectableItem.FastBomb,
1);
}
/*
* The shield potions.
*/
for (int i = 0; i < shieldPotions; i++) {
// Create new shield item
GameRoutines.createItem(breakableNodes.remove(fieldChooser
.nextInt(breakableNodes.size())),
CollectableItem.ShieldPotion, 1);
}
/*
* The palisade item.
*/
for (int i = 0; i < palis; i++) {
// Create new palisade item
GameRoutines.createItem(breakableNodes.remove(fieldChooser
.nextInt(breakableNodes.size())), CollectableItem.Palisade,
1);
}
/*
* The s-shroom item.
*/
for (int i = 0; i < slowShrooms; i++) {
// Create new shroom item
GameRoutines
.createItem(breakableNodes.remove(fieldChooser
.nextInt(breakableNodes.size())),
CollectableItem.Speed, -1);
}
/*
* The f-shroom item.
*/
for (int i = 0; i < fastShrooms; i++) {
// Create new shroom item
GameRoutines.createItem(breakableNodes.remove(fieldChooser
.nextInt(breakableNodes.size())), CollectableItem.Speed, 1);
}
// Create a static root
GraphicsEntity staticRoot = new GraphicsEntity();
staticRoot.attach(ground);
staticRoot.attach(solids);
staticRoot.scale().set(gridHolder.scale());
// Merge to rendered entity
scene.attach(scene.renderedOpaqueEntity(Color.white, staticRoot).index(
BACKGROUND_INDEX));
// Attach the grid holder
scene.attach(gridHolder);
return gridEntity;
}
/*
* The grid loader.
*/
public static final AssetLoader<char[][]> LOADER = new AssetLoader<char[][]>() {
/**
*
*/
private static final long serialVersionUID = 1L;
@Override
public char[][] loadAsset(AssetManager assetManager, String assetPath)
throws Exception {
return GridLoader.load(assetManager.open(assetPath));
}
};
/**
* Berechnet die Map aus der Textdatei und gibt ein Array mit dem Inhalt der
* Map zurueck. Wirft IOException, wenn die Karte nicht geladen werden kann,
* z.B. wenn die File nicht geladen werden kann.
*
* @param input
* the stream which loads the map
* @return a 2D char array with the loaded map.
* @throws IOException
* If the map is empty or not compatible (g.E. if the length of
* the strings differs from each other)
*/
public static char[][] load(InputStream input) throws IOException {
if (input == null) {
throw new NullPointerException("input");
}
// Eine Zeile der Karte.
String line;
// Die map
char[][] map;
// Liste in der Zwischengespeichert wird
List<String> cache = new LinkedList<String>();
// Laden des files
BufferedReader br = new BufferedReader(new InputStreamReader(input));
try {
// Lies Zeile fuer Zeile
while ((line = br.readLine()) != null) {
cache.add(line);
}
// Nichts zu lesen
if (cache.isEmpty()) {
throw new IOException("Empty grid");
}
} finally {
// Schliesse den Stream
br.close();
}
// Neues array erstellen
map = new char[cache.size()][];
// Alles in das char array kopieren
int i = 0, len = -1;
for (String row : cache) {
if (len == -1) {
len = row.length();
} else if (len != row.length()) {
throw new IOException("Zeilen nicht alle gleich lang!");
}
map[i++] = row.toCharArray();
}
return map;
}
// Muss nicht erstellbar sein.
private GridLoader() {
}
/**
* Generates random destructible blocks through a seed. Gets the map as a 2D
* char array and the seed to create the map.
*
* @param map
* the array/map
* @param seed
* the needed seed to create the destructible blocks
* @return the map with the changed values for destructible blocks
*/
public static char[][] generate(char[][] map, long seed) {
// int blockcount = Math.round((map.length * map[0].length) * 0.8f);
Random ran = new Random(seed);
for (int y = 1; y < map.length - 1; y++) {
for (int x = 1; x < map[0].length - 1; x++) {
if (!nextTo(map, x, y, SPAWN) && map[y][x] != SOLID
&& map[y][x] != SPAWN) {
if (ran.nextInt(10 - nextToCount(map, x, y)) > 2) {
map[y][x] += BREAKABLE_OFFSET;
}
}
}
}
return map;
}
/**
* Get's one field of an array and controls whether their is a field of the
* expected type next to it.
*
* @param map
* the array
* @param x
* x coordinate of the field
* @param y
* y coordinate of the field
* @param typ
* the expected type
* @return true, for their is a block next to it and false, for their is no
* block of teh expected typ
*/
private static boolean nextTo(char[][] map, int x, int y, char typ) {
if (map[y - 1][x] == typ) {
return true;
} else if (map[y][x + 1] == typ) {
return true;
} else if (map[y + 1][x] == typ) {
return true;
} else if (map[y][x - 1] == typ) {
return true;
} else {
return false;
}
}
/**
* Get's one field of an array and counts the number of destructible blocks
* around it.
*
* @param map
* the array
* @param x
* x coordinate of the field
* @param y
* y coordinate of the field
* @return the number of destructible blocks around the field.
*/
private static int nextToCount(char[][] map, int x, int y) {
int count = 0;
if (map[y - 1][x] >= BREAKABLE_OFFSET) {
count++;
}
if (map[y][x + 1] >= BREAKABLE_OFFSET) {
count++;
}
if (map[y + 1][x] >= BREAKABLE_OFFSET) {
count++;
}
if (map[y][x - 1] >= BREAKABLE_OFFSET) {
count++;
}
return count;
}
}