com.blockwithme.hacktors.Chunk.java Source code

Java tutorial

Introduction

Here is the source code for com.blockwithme.hacktors.Chunk.java

Source

/*
 * Copyright (C) 2013 Sebastien Diot.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.blockwithme.hacktors;

import java.util.Arrays;

import javax.annotation.ParametersAreNonnullByDefault;

import org.apache.commons.lang.ArrayUtils;

/**
 * A chunk is fixed-size a part of a game level.
 *
 * It is used to split up the game levels so that they can be distributed more
 * easily, and so that only the active parts of the world need to be in memory
 * at any time.
 *
 * It contains directly most of the game state.
 *
 * Since it represents parts of a level, data can be accessed both with
 * absolute coordinates, and with relative coordinates.
 *
 * @author monster
 */
@ParametersAreNonnullByDefault
public class Chunk {
    /** Size in X axis. */
    public static final int X = 16;

    /** Size in Y axis. */
    public static final int Y = 16;

    /** Size in total. */
    public static final int SIZE = X * Y;

    /** The Chunk position */
    private final Position position = new Position();

    /** All the chunk blocks. */
    private final Block[] blocks = new Block[SIZE];

    /** All the chunk Mobiles. */
    private final Mobile[] mobiles = new Mobile[SIZE];

    /** All the chunk Items. */
    private final Item[][] items = new Item[SIZE][];

    /** Number of mobiles contained. */
    private int mobileCount;

    /** Checks that the index are valid. */
    private void check(final int x, final int y) {
        if ((x < 0) || (x >= X)) {
            throw new IllegalArgumentException("x must be withing [0," + X + "]");
        }
        if ((y < 0) || (y >= Y)) {
            throw new IllegalArgumentException("y must be withing [0," + Y + "]");
        }
    }

    /** Computes the linear array index, from the coordinates. */
    private int index(final int x, final int y) {
        check(x, y);
        return x + X * y;
    }

    /** Creates an empty Chunk. */
    public Chunk() {
        Arrays.fill(blocks, Block.EMPTY);
        Arrays.fill(items, Item.EMPTY);
    }

    /**
     * Returns the position
     */
    public Position getPosition() {
        return position;
    }

    /** Returns the number of mobiles contained. */
    public int getMobileCount() {
        return mobileCount;
    }

    /** Returns a Block, using local coordinates. It an never be null. */
    public Block getBlockLocal(final int x, final int y) {
        return blocks[index(x, y)];
    }

    /** Returns a Block. It an never be null. */
    public Block getBlock(final int x, final int y) {
        return getBlockLocal(x - position.getX(), y - position.getY());
    }

    /** Sets a block, using local coordinates. null is mapped to Empty. */
    public void setBlockLocal(final int x, final int y, final Block block) {
        final int index = index(x, y);
        if (block == null) {
            blocks[index] = Block.EMPTY;
        } else {
            if (block.getType().isSolid() && (mobiles[index] != null)) {
                throw new IllegalArgumentException("Coordinate (" + x + "," + y + ") contains a mobile!");
            }
            blocks[index] = block;
        }
    }

    /** Sets a block. null is mapped to Empty. */
    public void setBlock(final int x, final int y, final Block block) {
        setBlockLocal(x - position.getX(), y - position.getY(), block);
    }

    /** Returns true, if the block at the given coordinate is solid, using local coordinates. */
    public boolean solidLocal(final int x, final int y) {
        return blocks[index(x, y)].getType().isSolid();
    }

    /** Returns true, if the block at the given coordinate is solid. */
    public boolean solid(final int x, final int y) {
        return solidLocal(x - position.getX(), y - position.getY());
    }

    /** Returns a Mobile, using local coordinates. Can be null. */
    public Mobile getMobileLocal(final int x, final int y) {
        return mobiles[index(x, y)];
    }

    /** Returns a Mobile. Can be null. */
    public Mobile getMobile(final int x, final int y) {
        return getMobileLocal(x - position.getX(), y - position.getY());
    }

    /** Returns true, if the block at the given coordinate is either solid, or occupied by a mobile, using local coordinates. */
    public boolean occupiedLocal(final int x, final int y) {
        final int index = index(x, y);
        return blocks[index].getType().isSolid() || (mobiles[index] != null);
    }

    /** Returns true, if the block at the given coordinate is either solid, or occupied by a mobile. */
    public boolean occupied(final int x, final int y) {
        return occupiedLocal(x - position.getX(), y - position.getY());
    }

    /** Updates the Mobile position, using local coordinates! */
    private void updateMobilePosition(final int x, final int y, final Mobile mobile) {
        final World world = position.getWorld();
        if (world != null) {
            final Level level = world.getLevel(mobile.getZ());
            if (level != null) {
                final Chunk chunk = level.getChunkOf(mobile.getX(), mobile.getY());
                if (chunk != null) {
                    final Mobile other = chunk.getMobile(mobile.getX(), mobile.getY());
                    if (other == mobile) {
                        chunk.setMobile(mobile.getX(), mobile.getY(), null);
                    }
                }
            }
        }
        final Position pos = position.clone();
        pos.setX(pos.getX() + x);
        pos.setY(pos.getY() + y);
        // We don't change direction of mobile
        pos.setDirection(mobile.getDirection());
        mobile.updatedPosition(pos);
    }

    /** Sets a Mobile, using local coordinates. */
    public void setMobileLocal(final int x, final int y, final Mobile mobile) {
        final int index = index(x, y);
        final Mobile before = mobiles[index];
        if (before != mobile) {
            if (mobile != null) {
                if (solidLocal(x, y)) {
                    throw new IllegalArgumentException("Coordinate (" + x + "," + y + ") is solid!");
                }
                final Chunk oldChunk = mobile.getChunk();
                if (oldChunk != null) {
                    oldChunk.setMobile(mobile.getX(), mobile.getY(), null);
                }
                mobiles[index] = mobile;
                updateMobilePosition(x, y, mobile);
            } else {
                mobiles[index] = null;
            }
            if (before != null) {
                before.detach();
            }
        }
        final int countBefore = mobileCount;
        if (before != null) {
            mobileCount--;
        }
        if (mobile != null) {
            mobileCount++;
        }
        final int countChange = mobileCount - countBefore;
        if (countChange != 0) {
            final World world = position.getWorld();
            if (world != null) {
                final Level level = world.getLevel(position.getZ());
                level.updateMobileCount(countChange);
            }
        }
    }

    /** Sets a Mobile. */
    public void setMobile(final int x, final int y, final Mobile mobile) {
        setMobileLocal(x - position.getX(), y - position.getY(), mobile);
    }

    /** Returns the items, using local coordinates. */
    public Item[] getItemsLocal(final int x, final int y) {
        final Item[] array = items[index(x, y)];
        return (array.length == 0) ? array : array.clone();
    }

    /** Returns the items. */
    public Item[] getItems(final int x, final int y) {
        return getItemsLocal(x - position.getX(), y - position.getY());
    }

    /** Adds an item, using local coordinates. */
    public void addItemLocal(final int x, final int y, final Item item) {
        if (item == null) {
            throw new IllegalArgumentException("item is null");
        }
        final int index = index(x, y);
        if (!ArrayUtils.contains(items[index], item)) {
            items[index] = (Item[]) ArrayUtils.add(items[index], item);
        }
    }

    /** Adds an item. */
    public void addItem(final int x, final int y, final Item item) {
        addItemLocal(x - position.getX(), y - position.getY(), item);
    }

    /** Removes an item, using local coordinates. */
    public void removeItemLocal(final int x, final int y, final Item item) {
        if (item == null) {
            throw new IllegalArgumentException("item is null");
        }
        final int index = index(x, y);
        final int where = ArrayUtils.indexOf(items[index], item);
        if (where < 0) {
            throw new IllegalArgumentException("item not found");
        }
        items[index] = (Item[]) ArrayUtils.remove(items[index], where);
    }

    /** Removes an item. */
    public void removeItem(final int x, final int y, final Item item) {
        removeItemLocal(x - position.getX(), y - position.getY(), item);
    }

    /** Informs the Chunk that it's position was updated. */
    public void updatedPosition() {
        for (int x = 0; x < X; x++) {
            for (int y = 0; y < Y; y++) {
                final Mobile mobile = getMobileLocal(x, y);
                if (mobile != null) {
                    updateMobilePosition(x, y, mobile);
                }
            }
        }
    }

    /** Runs an update cycle. */
    public void update() {
        if (mobileCount > 0) {
            final int cycle = position.getWorld().getClock().getCycle();
            for (final Mobile mobile : mobiles) {
                if (mobile != null) {
                    final MobileType type = mobile.getType();
                    if (cycle % type.getSpeed() == 0) {
                        mobile.getController().act();
                    }
                }
            }
        }
    }

    /** Passes all mobiles to the visitor. */
    public void visitMobiles(final MobileVisitor visitor) {
        if (mobileCount > 0) {
            for (final Mobile mobile : mobiles) {
                if (mobile != null) {
                    visitor.visit(mobile);
                }
            }
        }
    }
}