Level.java

/*
 * Copyright © 2012 Nokia Corporation. All rights reserved.
 * Nokia and Nokia Connecting People are registered trademarks of Nokia Corporation.
 * Oracle and Java are trademarks or registered trademarks of Oracle and/or its
 * affiliates. Other product and company names mentioned herein may be trademarks
 * or trade names of their respective owners.
 * See LICENSE.TXT for license information.
 */
package com.nokia.example.battletank.game;

import com.nokia.example.battletank.game.entities.Entity;
import com.nokia.example.battletank.game.entities.Bullet;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;
import javax.microedition.lcdui.Image;
import javax.microedition.lcdui.game.Layer;
import javax.microedition.lcdui.game.TiledLayer;

public class Level {

    private static final int BRICK_COLOR = 0x00f00000;
    private static final int STEEL_COLOR = 0x00d0d0d0;
    private static final int WATER_COLOR = 0x000000f0;
    private static final int TREE_COLOR = 0x00009000;
    private static final int ENEMY_SPAWN_POINT_COLOR = 0x0000f000;
    private static final int PLAYER_SPAWN_POINT_COLOR = 0x00f0f000;
    private static final int BASE_COLOR = 0x00707070;
    private static final byte BRICK_WALL = 1;
    private static final byte STEEL_WALL = 2;
    private static final byte WATER = 3;
    private static final int[] WATER_SEQ = {9, 13, 17, 21, 17, 13};
    private final byte[][] level;
    public final int width;
    public final int height;
    public final int widthInPixels;
    public final int heightInPixels;
    private final Point[] enemies;
    private final Point player;
    private final Point base;
    private final Point[] trees;
    private final Resources resources;
    private TiledLayer ground = null;
    private TiledLayer walls = null;
    private volatile boolean refreshWalls = true;
    private final int[] waterTiles = new int[4];
    private int currentwaterFrame = 0;

    private Level(byte[][] level, Point[] enemies, Point player, Point base,
        Point[] trees, Resources resources) {
        this.level = level;
        this.width = level.length;
        this.height = level[0].length;
        this.enemies = enemies;
        this.player = player;
        this.base = base;
        this.trees = trees;
        this.widthInPixels = width * resources.gridSizeInPixels;
        this.heightInPixels = height * resources.gridSizeInPixels;
        this.resources = resources;
    }

    /**
     * Loads a level.
     *
     * @param levelNumber Number of the wanted level.
     * @param resources Resource object for images
     * @return Level object
     * @throws ProtectedContentException
     * @throws IOException
     */
    public static Level load(int levelNumber, Resources resources)
        throws ProtectedContentException, IOException {
        final Image image = Levels.getImage(levelNumber);
        final int w = image.getWidth();
        final int h = image.getHeight();
        final int[] raw = new int[w * h];
        image.getRGB(raw, 0, w, 0, 0, w, h);
        byte[][] level = new byte[w][h];
        Vector enemySpawnPointsVector = new Vector();
        Point playerSpawnPoint = null;
        Point base = null;
        Vector treePointsVector = new Vector();
        byte value;
        int x, y;
        Hashtable unknownColors = new Hashtable();

        // add the level elements set in level image
        for (int i = 0; i < raw.length; i++) {
            value = 0;
            x = i % w;
            y = i / w;
            switch (raw[i] & 0x00F0F0F0) {
                case BRICK_COLOR:
                    value = BRICK_WALL;
                    break;
                case STEEL_COLOR:
                    value = STEEL_WALL;
                    break;
                case WATER_COLOR:
                    value = WATER;
                    break;
                case TREE_COLOR:
                    treePointsVector.addElement(new Point(x, y));
                    break;
                case ENEMY_SPAWN_POINT_COLOR:
                    enemySpawnPointsVector.addElement(new Point(x, y));
                    break;
                case PLAYER_SPAWN_POINT_COLOR:
                    playerSpawnPoint = new Point(x, y);
                    break;
                case BASE_COLOR:
                    base = new Point(x, y);
                    break;
                default:
                    if ((raw[i] & 0x00FFFFFF) != 0x00FFFFFF) {
                        Integer key = new Integer(raw[i] & 0x00FFFFFF);
                        unknownColors.put(key, key);
                    }
            }
            level[x][y] = value;
        }
        Point[] enemySpawnPoints = new Point[enemySpawnPointsVector.size()];
        enemySpawnPointsVector.copyInto(enemySpawnPoints);
        Point[] trees = new Point[treePointsVector.size()];
        treePointsVector.copyInto(trees);
        StringBuffer debug = new StringBuffer();
        Enumeration e = unknownColors.keys();
        while (e.hasMoreElements()) {
            debug.append(Integer.toHexString(((Integer) e.nextElement()).
                intValue()));
            debug.append(", ");
        }
        if (enemySpawnPoints.length == 0) {
            throw new IllegalArgumentException("no enemy spawn points " + debug.
                toString());
        }
        if (playerSpawnPoint == null) {
            throw new IllegalArgumentException("no player spawn point " + debug.
                toString());
        }
        if (base == null) {
            throw new IllegalArgumentException("no base " + debug.toString());
        }
        return new Level(level, enemySpawnPoints, playerSpawnPoint, base, trees,
            resources);
    }

    public Layer getGroundLayer() {
        if (ground == null) {
            Image image = resources.ground;
            int iw = image.getWidth();
            int ih = image.getHeight();
            int w = widthInPixels / iw + 1;
            int h = heightInPixels / ih + 1;
            ground = new TiledLayer(w, h, image, iw, ih);
            ground.fillCells(0, 0, w, h, 1);
        }
        return ground;
    }

    /**
     * Creates a tiled layer for walls if not already created.
     *
     * @return Tiled layer for walls
     */
    public Layer getWallLayer() {
        if (walls == null) {
            walls = new TiledLayer(width, height, resources.tiles,
                resources.gridSizeInPixels, resources.gridSizeInPixels);
            for (int i = 0; i < 4; i++) {
                waterTiles[i] = walls.createAnimatedTile(getNextWaterTile(i));
            }
        }
        return walls;
    }

    /**
     * Refreshes level tiles.
     */
    public void refresh() {
        if (refreshWalls) {
            refreshWalls = false;
            for (int x = 0; x < width; x++) {
                for (int y = 0; y < height; y++) {
                    walls.setCell(x, y, getWallTileIndex(level[x][y], x, y));
                }
            }
        }
        currentwaterFrame++;
        for (int i = 0; i < waterTiles.length; i++) {
            walls.setAnimatedTile(waterTiles[i], getNextWaterTile(i));
        }
    }

    private int getNextWaterTile(int tileIndex) {
        if (currentwaterFrame / 4 == WATER_SEQ.length) {
            currentwaterFrame = 0;
        }
        return WATER_SEQ[currentwaterFrame / 4] + tileIndex;
    }

    private int getWallTileIndex(int type, int x, int y) {
        switch (type) {
            case BRICK_WALL:
                return x % 2 + y % 2 * 2 + 1;
            case STEEL_WALL:
                return x % 2 + y % 2 * 2 + 5;
            case WATER:
                return waterTiles[x % 2 + y % 2 * 2];
        }
        return 0;
    }

    private boolean collidesBorder(int left, int top, int width, int height) {
        return left < 0 || top < 0 || left + width > widthInPixels || top
            + height > heightInPixels;
    }

    private boolean collidesWalls(int left, int top, int width, int height) {
        int leftGrid = toGrid(left, this.width);
        int topGrid = toGrid(top, this.height);
        int rightGrid = toGrid(left + width - 1, this.width);
        int bottomGrid = toGrid(top + height - 1, this.height);
        for (int i = leftGrid; i <= rightGrid; i++) {
            for (int j = topGrid; j <= bottomGrid; j++) {
                if (isWall(i, j)) {
                    return true;
                }
            }
        }
        return false;
    }

    private boolean isWall(int i, int j) {
        return level[i][j] == BRICK_WALL || level[i][j] == STEEL_WALL;
    }

    private int toGrid(int x, int max) {
        return checkBounds(resources.toGrid(x), max);
    }

    private int checkBounds(int i, int length) {
        return Math.min(Math.max(i, 0), length - 1);
    }

    public boolean collideAndDestroy(Bullet b) {
        int left = b.getX();
        int top = b.getY();
        if (!collides(left, top, b.width, b.height)) {
            return false;
        }
        int leftGrid = toGrid(left, width);
        int topGrid = toGrid(top, height);
        int rightGrid = toGrid(left + b.width - 1, width);
        int bottomGrid = toGrid(top + b.height - 1, height);
        switch (b.getDirection()) {
            case Entity.DIRECTION_UP:
                bottomGrid = checkBounds(topGrid + 1, height);
                leftGrid = checkBounds(leftGrid - 1, width);
                rightGrid = checkBounds(rightGrid + 1, width);
                break;
            case Entity.DIRECTION_DOWN:
                topGrid = checkBounds(bottomGrid - 1, height);
                leftGrid = checkBounds(leftGrid - 1, width);
                rightGrid = checkBounds(rightGrid + 1, width);
                break;
            case Entity.DIRECTION_LEFT:
                rightGrid = checkBounds(leftGrid + 1, width);
                topGrid = checkBounds(topGrid - 1, height);
                bottomGrid = checkBounds(bottomGrid + 1, height);
                break;
            case Entity.DIRECTION_RIGHT:
                leftGrid = checkBounds(rightGrid - 1, width);
                topGrid = checkBounds(topGrid - 1, height);
                bottomGrid = checkBounds(bottomGrid + 1, height);
                break;
        }
        for (int i = leftGrid; i <= rightGrid; i++) {
            for (int j = topGrid; j <= bottomGrid; j++) {
                destroyWall(i, j);
            }
        }
        return true;
    }

    public void destroyWall(int i, int j) {
        if (level[i][j] == BRICK_WALL) {
            level[i][j] = 0;
            refreshWalls = true;
        }
    }

    public boolean collides(int left, int top, int width, int height) {
        return collidesBorder(left, top, width, height) || collidesWalls(left,
            top, width, height);
    }

    public boolean isInWater(Entity e) {
        return isInWater(e.getX(), e.getY(), e.width, e.height);
    }

    public boolean isInWater(int left, int top, int width, int height) {
        int leftGrid = toGrid(left, this.width);
        int topGrid = toGrid(top, this.height);
        int rightGrid = toGrid(left + width - 1, this.width);
        int bottomGrid = toGrid(top + height - 1, this.height);
        for (int i = leftGrid; i <= rightGrid; i++) {
            for (int j = topGrid; j <= bottomGrid; j++) {
                if (level[i][j] == WATER) {
                    return true;
                }
            }
        }
        return false;
    }

    public int getPlayerSpawnX() {
        return resources.toPixels(player.x);
    }

    public int getPlayerSpawnY() {
        return resources.toPixels(player.y);
    }

    public int getEnemySpawnPointsLength() {
        return enemies.length;
    }

    public int getEnemySpawnX(int index) {
        return resources.toPixels(enemies[index].x);
    }

    public int getEnemySpawnY(int index) {
        return resources.toPixels(enemies[index].y);
    }

    public int getBaseX() {
        return resources.toPixels(base.x);
    }

    public int getBaseY() {
        return resources.toPixels(base.y);
    }

    public int getTreesLength() {
        return trees.length;
    }

    public int getTreeX(int index) {
        return resources.toPixels(trees[index].x) + resources.gridSizeInPixels
            / 2;
    }

    public int getTreeY(int index) {
        return resources.toPixels(trees[index].y) + resources.gridSizeInPixels
            / 2;
    }

    public void writeTo(DataOutputStream dout)
        throws IOException {
        dout.writeInt(width);
        dout.writeInt(height);
        for (int i = 0; i < width; i++) {
            for (int j = 0; j < height; j++) {
                dout.writeByte(level[i][j]);
            }
        }
        dout.writeInt(enemies.length);
        for (int i = 0; i < enemies.length; i++) {
            enemies[i].writeTo(dout);
        }
        player.writeTo(dout);
        base.writeTo(dout);
        dout.writeInt(trees.length);
        for (int i = 0; i < trees.length; i++) {
            trees[i].writeTo(dout);
        }
    }

    public static Level readFrom(DataInputStream din, Resources resources)
        throws IOException {
        int width = din.readInt();
        int height = din.readInt();
        byte[][] level = new byte[width][height];
        for (int i = 0; i < width; i++) {
            for (int j = 0; j < height; j++) {
                level[i][j] = din.readByte();
            }
        }
        Point[] enemies = new Point[din.readInt()];
        for (int i = 0; i < enemies.length; i++) {
            enemies[i] = Point.readFrom(din);
        }
        Point player = Point.readFrom(din);
        Point base = Point.readFrom(din);
        Point[] trees = new Point[din.readInt()];
        for (int i = 0; i < trees.length; i++) {
            trees[i] = Point.readFrom(din);
        }
        return new Level(level, enemies, player, base, trees, resources);
    }
}