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}