001package squidpony.squidmath;
002
003import squidpony.ArrayTools;
004
005/**
006 * Created by Tommy Ettinger on 7/3/2017.
007 */
008public class CellularAutomaton {
009    /**
010     * Returned directly by some methods, but you may want to change this at some other point.
011     */
012    public GreasedRegion current;
013    private GreasedRegion[] neighbors = new GreasedRegion[9];
014    private int[][] sums;
015
016    /**
017     * Constructs a CellularAutomaton with an unfilled 64x64 GreasedRegion, that can be altered later via {@link #current}.
018     */
019    public CellularAutomaton()
020    {
021        this(new GreasedRegion(64, 64));
022    }
023
024    /**
025     * Constructs a CellularAutomaton with an unfilled GreasedRegion of the specified width and height, that can be
026     * altered later via {@link #current}.
027     * @param width the width of the CellularAutomaton
028     * @param height the height of the CellularAutomaton
029     */
030    public CellularAutomaton(int width, int height)
031    {
032        this(new GreasedRegion(Math.max(1, width), Math.max(1, height)));
033    }
034
035    /**
036     * Stores a direct reference to {@code current} as this object's {@link #current} field, and initializes the other
037     * necessary fields.
038     * @param current a GreasedRegion that will be used directly; changes will be shared
039     */
040    public CellularAutomaton(GreasedRegion current) {
041        this.current = current;
042        for (int i = 0; i < 9; i++) {
043            neighbors[i] = current.copy();
044        }
045        sums = new int[current.width][current.height];
046    }
047
048    /**
049     * Re-initializes this CellularAutomaton using a different GreasedRegion as a basis. If the previous GreasedRegion
050     * used has the same dimensions as {@code next}, then this performs no allocations and simply sets the existing
051     * contents. Otherwise, it makes one new 2D array and also has all 9 of the internal GreasedRegions adjust in size,
052     * which involves some allocation. If {@code next} is null, this does nothing and returns itself without changes.
053     * @param next a GreasedRegion to set this CellularAutomaton to read from and adjust
054     * @return this, for chaining
055     */
056    public CellularAutomaton remake(GreasedRegion next)
057    {
058        if(next == null)
059            return this;
060        if(current.width != next.width || current.height != next.height)
061            sums = new int[next.width][next.height];
062        else
063            ArrayTools.fill(sums, 0);
064        current = next;
065        for (int i = 0; i < 9; i++) {
066            neighbors[i].remake(current);
067        }
068        return this;
069    }
070
071    /**
072     * Reduces the sharpness of corners by only considering a cell on if the previous version has 5 of the 9 cells in
073     * the containing 3x3 area as "on." Typically, this method is run repeatedly. It does not return itself for
074     * chaining, because it returns a direct reference to the {@link #current} GreasedRegion that this will use for
075     * any future calls to this, and changes to current will be used here.
076     * @return a direct reference to the changed GreasedRegion this considers its main state, {@link #current}
077     */
078    public GreasedRegion runBasicSmoothing()
079    {
080        neighbors[0].remake(current).neighborUp();
081        neighbors[1].remake(current).neighborDown();
082        neighbors[2].remake(current).neighborLeft();
083        neighbors[3].remake(current).neighborRight();
084        neighbors[4].remake(current).neighborUpLeft();
085        neighbors[5].remake(current).neighborUpRight();
086        neighbors[6].remake(current).neighborDownLeft();
087        neighbors[7].remake(current).neighborDownRight();
088        neighbors[8].remake(current);
089        ArrayTools.fill(sums, 0);
090        GreasedRegion.sumInto(sums, neighbors);
091        return current.refill(sums, 5, 10);
092    }
093
094    /**
095     * Runs one step of the simulation called Conway's Game of Life, which has relatively simple rules:
096     * <ul>
097     *     <li>Any "on" cell with fewer than two "on" neighbors becomes "off."</li>
098     *     <li>Any "on" cell with two or three "on" neighbors (no more than three) stays "on."</li>
099     *     <li>Any "on" cell with more than three "on" neighbors becomes "off."</li>
100     *     <li>Any "off" cell with exactly three "on" neighbors becomes "on."</li>
101     * </ul>
102     * These rules can bring about complex multi-step patterns in many cases, eventually stabilizing to predictable
103     * patterns in most cases. Filling the whole state of this CellularAutomaton won't produce interesting patterns
104     * most of the time, even if the fill is randomized; you might have better results by using known patterns. Some
105     * key well-known patterns are covered on <a href="https://en.wikipedia.org/wiki/Conway's_Game_of_Life">Wikipedia's
106     * detailed article on Conway's Game of Life</a>.
107     * @return a direct reference to the changed GreasedRegion this considers its main state, {@link #current}
108     */
109    public GreasedRegion runGameOfLife()
110    {
111        neighbors[0].remake(current).neighborUp();
112        neighbors[1].remake(current).neighborDown();
113        neighbors[2].remake(current).neighborLeft();
114        neighbors[3].remake(current).neighborRight();
115        neighbors[4].remake(current).neighborUpLeft();
116        neighbors[5].remake(current).neighborUpRight();
117        neighbors[6].remake(current).neighborDownLeft();
118        neighbors[7].remake(current).neighborDownRight();
119        neighbors[8].remake(current);
120        ArrayTools.fill(sums, 0);
121        GreasedRegion.sumInto(sums, neighbors);
122        return current.refill(sums,3).or(neighbors[0].refill(sums, 4).and(neighbors[8]));
123    }
124
125    /**
126     * This takes the {@link #current} GreasedRegion and removes any cells that have a diagonal neighbor if that
127     * neighbor cannot be accessed from shared orthogonal neighbors. That is, if a 2x2 area contains two "off" cells
128     * that are diagonally adjacent and contains two "on" cells that are diagonally adjacent, this sets that whole 2x2
129     * area to "off."
130     * @return {@link #current} after orthogonally-inaccessible pairs of diagonal "on" cells are removed
131     */
132    public GreasedRegion runDiagonalGapCleanup()
133    {
134        neighbors[0].remake(current.not()).neighborUp();
135        neighbors[1].remake(current).neighborDown();
136        neighbors[2].remake(current).neighborLeft();
137        neighbors[3].remake(current).neighborRight();
138        neighbors[4].remake(current.not()).neighborUpLeft();
139        neighbors[5].remake(current).neighborUpRight();
140        neighbors[6].remake(current).neighborDownLeft();
141        neighbors[7].remake(current).neighborDownRight();
142//        neighbors[8].remake(current);
143        current.andNot(neighbors[4].and(neighbors[0]).and(neighbors[2]));
144        current.andNot(neighbors[5].and(neighbors[0]).and(neighbors[3]));
145        current.andNot(neighbors[6].and(neighbors[1]).and(neighbors[2]));
146        current.andNot(neighbors[7].and(neighbors[1]).and(neighbors[3]));
147        return current;
148    }
149    
150//    public static void main(String[] args)
151//    {
152//        GWTRNG rng = new GWTRNG(-3005655405530708008L);
153//        final int bigWidth = 80, bigHeight = 48;
154//        DungeonGenerator dungeonGen = new DungeonGenerator(bigWidth, bigHeight, rng);
155//        dungeonGen.addWater(15);
156//        dungeonGen.addGrass(10);
157//        DungeonBoneGen gen = new DungeonBoneGen(rng);
158//        CellularAutomaton ca = new CellularAutomaton(bigWidth, bigHeight);
159//        gen.generate(TilesetType.DEFAULT_DUNGEON, bigWidth, bigHeight);
160//        ca.remake(gen.region);
161//        gen.region.and(ca.runBasicSmoothing()).deteriorate(rng, 0.9);
162//        gen.region.and(ca.runBasicSmoothing()).deteriorate(rng, 0.9);
163//        ca.current.remake(gen.region.deteriorate(rng, 0.9));
164//        gen.region.or(ca.runBasicSmoothing());
165//        gen.region.remake(gen.region.removeEdges().largestPart());
166//        ca.current.remake(gen.region);
167//        gen.region.remake(ca.runDiagonalGapCleanup());
168//        DungeonUtility.debugPrint(DungeonUtility.hashesToLines(dungeonGen.generate(gen.region.intoChars(gen.getDungeon(), '.', '#'))));
169//    }
170}