com.djd.fun.tachchapter.demo014swing.maze.models.Floor.java Source code

Java tutorial

Introduction

Here is the source code for com.djd.fun.tachchapter.demo014swing.maze.models.Floor.java

Source

//  Copyright (c) 2016 JGD Licensed under the MIT license
package com.djd.fun.tachchapter.demo014swing.maze.models;

import java.io.PrintStream;

import com.djd.fun.util.MorePreconditions;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableSet;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static com.google.common.base.Preconditions.checkNotNull;

/**
 * This class represents a dungeon floor at specified dimension.
 * dimension must be at least 1x1
 *
 * @author JGD
 * @since 8/15/16
 */
public class Floor {

    private static final Logger log = LoggerFactory.getLogger(Floor.class);
    private final int numOfRows;
    private final int numOfCols;

    /**
     * Note: Do create public accessor to this reference. Keep it immutable.
     */
    private final Tile[][] tiles;

    /**
     * This is player's original location. Floor does NOT keep current player location.
     */
    private final Location playerOriginalLocation;
    private final ImmutableSet<Location> enemyLocations;
    private final ImmutableSet<Location> tokenLocations;
    private final ImmutableSet<Location> gemLocations;

    /**
     * Create an instance of floor based on given floorPlan
     *
     * @param floorPlan has to be at least 1x1 size.
     */
    public Floor(String[][] floorPlan) {
        checkNotNull(floorPlan);

        // TODO do floorPlan size validation
        if (floorPlan.length < 1 || floorPlan[0].length < 1) {
            throw new IllegalArgumentException("floor must be at least 1x1");
        }

        this.numOfRows = floorPlan.length;
        this.numOfCols = floorPlan[0].length;

        ImmutableSet.Builder<Location> tokenLocationBuilder = ImmutableSet.builder();
        ImmutableSet.Builder<Location> enemyLocationBuilder = ImmutableSet.builder();
        ImmutableSet.Builder<Location> gemLocationBuilder = ImmutableSet.builder();
        tiles = new Tile[floorPlan.length][floorPlan[0].length];
        Location playerLocation = null;
        for (int row = 0; row < numOfRows; row++) {
            for (int col = 0; col < numOfCols; col++) {
                String letter = floorPlan[row][col];
                if (Strings.isNullOrEmpty(letter)) {
                    throw new IllegalArgumentException(
                            String.format("Bad floorPlan on [%d][%d] is null", row, col));
                }
                char tileType = letter.charAt(0);
                // identify player location
                if ('P' == tileType) {
                    playerLocation = Location.of(row, col);
                } else if ('T' == tileType) {
                    tokenLocationBuilder.add(Location.of(row, col));
                } else if ('E' == tileType) {
                    enemyLocationBuilder.add(Location.of(row, col));
                } else if ('G' == tileType) {
                    gemLocationBuilder.add(Location.of(row, col));
                }
                tiles[row][col] = Tile.of(row, col, tileType);
            }
        }
        this.playerOriginalLocation = checkNotNull(playerLocation);
        this.enemyLocations = enemyLocationBuilder.build();
        this.tokenLocations = tokenLocationBuilder.build();
        this.gemLocations = gemLocationBuilder.build();
    }

    public int getNumOfRows() {
        return numOfRows;
    }

    public int getNumOfCols() {
        return numOfCols;
    }

    public Tile.TileType getTileType(Location location) {
        return getTileType(location.row, location.col);
    }

    public Tile.TileType getTileType(int row, int col) {
        MorePreconditions.checkNonNegativeIntegers(row, col);
        if (row >= numOfRows || col >= numOfCols) {
            throw new IllegalArgumentException("[row,col] is out of bound.");
        }
        return tiles[row][col].getTileType();
    }

    public ImmutableSet<Location> getTokenLocations() {
        return tokenLocations;
    }

    public ImmutableSet<Location> getEnemyLocations() {
        return enemyLocations;
    }

    public ImmutableSet<Location> getGemLocations() {
        return gemLocations;
    }

    public boolean isEnemyAt(Location location) {
        isValidLocation(location);
        return Tile.TileType.E == tiles[location.row][location.col].getTileType();
    }

    /**
     * Return north location of specified location on the grid.
     *
     * @param location current player location
     * @return north {@link Location} if the player character is allowed to enter the target location.
     * current location otherwise.
     */
    public Location getNorthLocation(Location location) {
        log.info("location {}", location);
        if (location.row > 0 && location.row < numOfRows) {
            Location destination = Location.of(location.row - 1, location.col);
            if (canEnter(destination)) {
                return destination;
            }
        }
        return location;
    }

    /**
     * Return east location of specified location on the grid.
     *
     * @param location current player location
     * @return east {@link Location} if the player character is allowed to enter the target location.
     * current location otherwise.
     */
    public Location getEastLocation(Location location) {
        if (location.col < numOfCols - 1) {
            Location destination = Location.of(location.row, location.col + 1);
            if (canEnter(destination)) {
                return destination;
            }
        }
        return location;
    }

    /**
     * Return south location of specified location on the grid.
     *
     * @param location current player location
     * @return south {@link Location} if the player character is allowed to enter the target location.
     * current location otherwise.
     */
    public Location getSouthLocation(Location location) {
        if (location.row < numOfRows - 1) {
            Location destination = Location.of(location.row + 1, location.col);
            if (canEnter(destination)) {
                return destination;
            }
        }
        return location;
    }

    /**
     * Return west location of specified location on the grid.
     *
     * @param location current player location
     * @return west {@link Location} if the player character is allowed to enter the target location.
     * current location otherwise.
     */
    public Location getWestLocation(Location location) {
        if (location.col > 0 && location.col < numOfCols) {
            Location destination = Location.of(location.row, location.col - 1);
            if (canEnter(destination)) {
                return destination;
            }
        }
        return location;
    }

    /**
     * Check if specified tile can be stepable
     *
     * @param location may or may not be valid on the grid
     * @return {@code true} if tile at the specified location is not Wall nor out of boundary.
     */
    public boolean canEnter(Location location) {
        if (isValidLocation(location)) {
            return tiles[location.row][location.col].isSteppable();
        }
        log.warn("Invalid location {}", location);
        return false;
    }

    public void printBoard(PrintStream out) {
        for (int row = 0; row < tiles.length; row++) {
            for (int col = 0; col < tiles[0].length; col++) {
                out.print(tiles[row][col]);
            }
            out.println();
        }
    }

    /**
     * This should be used only in initialization phase
     *
     * @return original player location.
     */
    public Location getOriginalPlayerLocation() {
        return playerOriginalLocation;
    }

    @VisibleForTesting
    boolean isValidLocation(Location location) {
        return location.row >= 0 && location.row < numOfRows && location.col >= 0 && location.col < numOfCols;
    }
}