EmergentOrganization/cell-rpg

View on GitHub
core/src/io/github/emergentorganization/cellrpg/components/CAGridComponents.java

Summary

Maintainability
B
4 hrs
Test Coverage
package io.github.emergentorganization.cellrpg.components;

import com.artemis.Component;
import com.badlogic.gdx.graphics.Camera;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.math.Vector2;
import io.github.emergentorganization.cellrpg.systems.CASystems.CAEdgeSpawnType;
import io.github.emergentorganization.cellrpg.systems.CASystems.CARenderSystem.CellRenderers.CellRenderer;
import io.github.emergentorganization.cellrpg.systems.CASystems.CAs.CA;
import io.github.emergentorganization.cellrpg.systems.CASystems.CAs.CACell.BaseCell;
import io.github.emergentorganization.cellrpg.systems.CASystems.CAs.CACell.CellWithHistory;
import io.github.emergentorganization.cellrpg.systems.CASystems.CAs.CACell.GeneticCell;
import io.github.emergentorganization.cellrpg.systems.CASystems.GeneticCells.GeneticCellBuilders.*;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

/**
 * This component contains a lot of information needed by a ca layer.
 * Much of these sub-components don't make much sense standing alone,
 * and it's not likely this will get used by anything other than the
 * CA grid layers. However, it may be better to separate these out
 * at a later date.
 */

public class CAGridComponents extends Component {
    private static final int OFF_SCREEN_PIXELS = 200;  // number of pixels off screen edge to run CA grid
    private final Logger logger = LogManager.getLogger(getClass());
    public long TIME_BTWN_GENERATIONS = 100;  // ms time in between generation() calls
    public final float SCALE = .025f;  // empirically derived constant... why is it this? idk...

    // location of grid center
    public float gridOriginX = 0;
    public float gridOriginY = 0;

    public long minGenTime = 999999;
    public long maxGenTime = 0;

    public CellType cellType = CellType.BASE;
    public CellRenderer renderType = CellRenderer.COLOR_MAP;
    public CA ca = CA.NO_BUFFER;

    public int intensityPerCell = 0;  // amount of intensity added to MoodSystem per live cell

    public BaseCell[][] states;
    public int cellSize = 3;  // size of each cell [px]
    public int cellCount = 0;  // number of live cells
    public CAEdgeSpawnType edgeSpawner = CAEdgeSpawnType.EMPTY;
    public Color[] stateColorMap;
    public long generation = 0;
    private int stampCount = 0;
    // === NOTE: these used by Genetic Cell NewCell only: ===
    public int lastBuilderStamp = 0;
    private int selectedBuilder = 0;
    // list of cells used to seed when stamps are placed:
    private final GeneticNetworkBuilderInterface[] builders = new GeneticNetworkBuilderInterface[]{
            //new CellAlpha(),
            new AgeDarkener(),
            new MrBlue(),
            new MrGreen(),
            new MrRed()
    };

    public int getState(Vector2 pos) {
        // returns state of cell nearest to given world-coordinates
//        logger.trace("getState(position)");
        int row = getIndexOfX(pos.x);
        int col = getIndexOfY(pos.y);
        return getState(row, col);
    }

    public int getState(final int row, final int col) {
        // returns state of given location, returns 0 for out-of-bounds
//        logger.trace("getState(int,int)");
        try {
            return _getState(row, col);
        } catch (IndexOutOfBoundsException err) {
            return 0;
        }
    }

    public int getLastState(final int row, final int col) {
        switch (cellType) {
            case WITH_HISTORY:
                return getLastState_buffered(row, col);
            case BASE:
            case DECAY:
            case GENETIC:
            default:
                return getLastState_noBuffer(row, col);
        }
    }

    public void init(Camera camera) {
        int sx = (int) (camera.viewportWidth) + 2 * OFF_SCREEN_PIXELS;
        int sy = (int) (camera.viewportHeight) + 2 * OFF_SCREEN_PIXELS;

        int w = sx / (cellSize + 1);  // +1 for border pixel between cells
        int h = sy / (cellSize + 1);

        logger.trace("initializing CAGrid " + w + "(" + sx + "px)x" + h + "(" + sy + "px). cellSize=" + cellSize);

        initStates(w, h);
    }

    private void initStates(int w, int h) {
        states = new BaseCell[w][h];
        // init states. ?required?
        for (int i = 0; i < states.length; i++) {
            for (int j = 0; j < states[0].length; j++) {
                states[i][j] = newCell(0);
            }
        }
        // init states for testing
        //randomizeState(gridComponents);
    }

    private int getLastState_noBuffer(final int row, final int col) {
        // with no buffer there is no last state, just use current
        return getState(row, col);
    }

    private int getLastState_buffered(final int row, final int col) {
        try {
            CellWithHistory cell = (CellWithHistory) states[row][col];
            return cell.getLastState();
        } catch (IndexOutOfBoundsException err) {
            return 0;  // if out-of-bounds, assume state=0
        }
    }

    private int getSizeX() {
        // returns grid size (number of cells) in x-dimension
        return states.length;
    }

    private int getSizeY() {
        // returns grid size (number of cells) in y-dimension
        return states[0].length;
    }

    public float getXOrigin(Camera camera) {
        return -OFF_SCREEN_PIXELS + gridOriginX - camera.position.x / SCALE;
    }

    public float getYOrigin(Camera camera) {
        return -OFF_SCREEN_PIXELS + gridOriginY - camera.position.y / SCALE;
    }

    private GeneticNetworkBuilderInterface getBuilder() {
        // returns most appropriate cell builder
        if (stampCount != lastBuilderStamp) { // to keep builder from changing in middle of a stamp
            lastBuilderStamp = stampCount;
            selectedBuilder++;
            if (selectedBuilder >= builders.length) {
                selectedBuilder = 0;
            }
        }  // else return previously selected builder
        return builders[selectedBuilder];
    }
    // === END Genetic-cell newcell only stuff ===

    public BaseCell newCell(int init_state) {
        // TODO: use of inteface, enum, map like w/ CellRenderer is preferred.
        switch (cellType) {
            case WITH_HISTORY:
                return new CellWithHistory(init_state);
            case GENETIC:
                return new GeneticCell(init_state, getBuilder()).incubate();
            case BASE:
            case DECAY:
            default:
                return new BaseCell(init_state);
        }
    }

    public int getIndexOfX(float x) {
        // returns x index of cell given float position relative to global origin
        float relative_x = x / SCALE - gridOriginX;
        return (int) relative_x / (cellSize + 1) + getSizeX() / 2;
    }

    public int getIndexOfY(float y) {
        float relative_y = y / SCALE - gridOriginY;
        int cell = (int) relative_y / (cellSize + 1) + getSizeY() / 2;
        logger.trace("global y " + y + " (rel:" + relative_y + ") " + " from " + gridOriginY + " nearest to " + cell);
        return cell;
    }

    public Vector2 getPositionOf(final int x, final int y) {
        // returns position vector for given cell location
        Vector2 pos = new Vector2();
        // x = (pos.x /SCALE-gridOriginX)/(cellSize+1) + getSizeX()/2;
        // x = ( xp   /S    -  xo       )/( si     +1) + SX / 2;
        // x - SX/2 = ( xp   /S    -  xo       )/( si     +1)
        // (x - SX/2) * (si + 1) = xp /S - xo
        // (x - SX/2) * (si + 1) + xo = xp/S
        // ( (x - SX/2) * (si + 1) + xo ) * S = xp
        pos.x = ((x - getSizeX() / 2) * (cellSize + 1) + gridOriginX) * SCALE;
        pos.y = ((y - getSizeY() / 2) * (cellSize + 1) + gridOriginY) * SCALE;
        return pos;
    }

    private long stampCenteredAt(final int[][] pattern, int row, int col) {
        //center the pattern
        row -= pattern.length / 2;
        col -= pattern[0].length / 2;
        return stampState(pattern, row, col);
    }

    public long stampCenteredAt(final int[][] pattern, Vector2 position) {
        // stamps a pattern onto the grid centered at the nearest grid cells to the given world position
        int row = getIndexOfX(position.x);
        int col = getIndexOfY(position.y);
        logger.trace("(" + position.x + "," + position.y + ")==>(" + row + "," + col + ")");
        return stampCenteredAt(pattern, row, col);
    }

    public long stampState(final int[][] pattern, final int row, final int col) {
        // stamps a pattern onto the state with top-left corner @ (row, col)
        // returns estimated UNIX time when the pattern will be applied (@ next generation)
        if (row > -1
                && col > -1
                && row < states.length - pattern.length
                && col < states[0].length - pattern[0].length) {

            _stampState(pattern, row, col);
            stampCount++;
            return TIME_BTWN_GENERATIONS;
        } else {
            //logger.warn("attempt to stamp pattern out of bounds (" + row + "," + col +"); error suppressed.");
            return -1;  // -1 indicates pattern not drawn, out-of-CAGrid bounds
        }
    }

    public void setState(final int row, final int col, final int newVal) {
        states[row][col].setState(newVal);
    }

    public String statesToString(int x, int y, int w, int h) {
        // returns string showing state of cells in given rect
        String res = "";
        for (int i = 0; i < w; i++) {
            res += "{";
            for (int j = 0; j < h; j++) {
                res += getState(i + x, j + y) + ",";
            }
            res += "}\n";
        }
        return res;
    }

    public String statesToString() {
        // returns string showing state of all cells
        return statesToString(0, 0, getSizeX(), getSizeY());
    }

    public void fill(int state) {
        // fills the current ca grid with a single state
        // useful mostly for testing
        for (int i = 0; i < states.length; i++) {
            for (int j = 0; j < states[0].length; j++) {
                setState(i, j, state);
            }
        }
    }

    private void _stampState(final int[][] pattern, final int row, final int col) {
        // stamps pattern immediately into given cellStates
        logger.trace("insert " + pattern.length + "x" + pattern[0].length + " pattern @ (" + row + "," + col + ")");
        for (int i = 0; i < pattern.length; i++) {
            for (int j = 0; j < pattern[0].length; j++) {
                states[row + i][col + j].setState(pattern[i][j]);
            }
        }
        logger.trace(pattern.length + "x" + pattern[0].length + " CA stamp applied!");
    }

    private int _getState(final int row, final int col) {
        // returns state, throws exception if out of bounds
        return states[row][col].getState();
    }

    private void checkCellSize(int size) {
        // checks that given size is acceptable, else throws error
        // TODO: appropriate sizes are incorrect here... should be 1, 5, 17, 53...
        if (size == 1) {
            return;
        } else {
            int acceptableSize = 3;
            while (acceptableSize <= size) {
                if (size == acceptableSize) {
                    return;
                } else {
                    acceptableSize = getNextSizeUp(acceptableSize);
                }
            } // else size not acceptable
            throw new UnsupportedOperationException("size must be in 1, 3, 11, 35...");
        }
    }

    private int getNextSizeUp(int lastSize) {
        /*
         * available sizes assuming 1px border between cells given by
         * s(n) = 3(s(n-1))+2 for n > 1 (i.e. starting at s(2)=11)
         */
        if (lastSize < 3) {
            throw new UnsupportedOperationException("previous cell size must be >= 3");
        } else {
            return 3 * lastSize + 2;
        }
    }


}