Search Unity

Making a Complex Grid

Discussion in 'Scripting' started by EternalAmbiguity, Jan 16, 2017.

  1. EternalAmbiguity

    EternalAmbiguity

    Joined:
    Dec 27, 2014
    Posts:
    3,144
    I'm in school getting a master's degree, and part of my research uses cellular automata. The current programs I use were all made by a few guys twenty years ago (literally), so they're hard to work with and have some issues (that won't ever be fixed, because the guy who made the program isn't coding anymore).

    I'm trying to make a cellular automaton in Unity. I need to be able:

    i. To set up time to progress in iterations, rather than continuously,

    1. to make a grid of variable dimensions

    2. with three shapes: a rectangle (all sides closed), a cylinder (so something at one side would jump to the opposite side, but top/bottom or left/right would be closed), and a torus (all sides open like the cylinder),

    3. with each point or node on the grid holding an "agent" able to be one of several "states" (represented by colors), with the change between states per iteration defined by a probability (p=0.25 for A to B, p=0.25 for A to C, p=0.50 for A to A

    4. with each agent on the grid able to detect the state of the other points around it (Moore or von Neumann neighborhood), and able to update its probability values for state change based on those other cells/agents around it,

    5. with each agent able to move to different spots on the grid based on both a random jump system, and some preferential system (such as a gravitational field),

    6. with each agent able to join or break with others around it (based on some probability).


    I think that's everything.

    I. I'm not too sure about how I'll set up time in iterations, but I don't think it will be super hard.

    1. I've had some trouble finding a decent grid system. This looks like it might be useful.

    2. Setting up the shapes is probably easy, but I don't have any idea how to go about it.

    3. This seems like it will be very simple to do, though I have no experience with using probabilities in Unity and its RNG.

    4. Absolutely no clue how this will be done. None whatsoever.

    5. Same as above. Interaction between different parts of a grid seems very hard.

    6. Probably not too hard, as long as one can do 4.


    I realize I'll probably have to do most of this myself, but can anyone help me out with any of this at all? I would really appreciate anything anyone can offer.
     
  2. GroZZleR

    GroZZleR

    Joined:
    Feb 1, 2015
    Posts:
    3,201
    Do you have not a lot of programming experience? This seems like a fairly straight forward list of problems to solve.

    Grids can be represented with 1D arrays quite easily:
    Code (csharp):
    1.  
    2. Cell[] cells = new Cell[width * height];
    3.  
    4. void SetCell(int x, int y, Cell value)
    5. {
    6.     cells[y * width + x] = value;
    7. }
    8.  
    9. Cell GetCell(int x, int y)
    10. {
    11.   return cells[y * width + x];
    12. }
    13.  
    So that solves problems 4-6.

    Problem 1 can be solved via a timer or coroutine.

    Code (csharp):
    1.  
    2. float time = 0.0;
    3. float timeStep = 0.2f;
    4.  
    5. void Update()
    6. {
    7.    time += Time.deltaTime;
    8.  
    9.    while(time >= timeStep)
    10.    {
    11.      // do iteration logic
    12.  
    13.     time -= timeStep;
    14.    }
    15. }
    16.  
    Problem 2 is solvable with Unity's default primitives (GameObject -> 3D Object -> choose a shape).

    Problem 3 uses material.color and Random.Range().
     
  3. EternalAmbiguity

    EternalAmbiguity

    Joined:
    Dec 27, 2014
    Posts:
    3,144
    I don't have a whole lot of programming experience, no.

    Thanks for the help. This seems like it will help a lot. However, for problem 2--it isn't literally making a rectangle or a torus. It's making a representation of one with the grid. So for a rectangle, the cells or agents or whatever can move anywhere in the square, but cannot go through any of the edges. This means that if I have a bunch of red cells and one green one, the green one can move to the edge but cannot move any further.

    Then, for a cylinder, they can pass through one barrier to the opposite one. So the green cell could go all the way to the right edge of the boundary, then cross it to wind up on the left side. However, the other "set" of boundaries are blocked, so the green cell could not go through the top or bottom edges.

    Then for a torus, all edges are accessible, and lead to the opposite edge.

    It's still a regular grid, it's not some 3D object. But the cells or whatever can move in different ways.

    Again, thanks for the help. I really should have seen that about 4-6 myself.
     
  4. takatok

    takatok

    Joined:
    Aug 18, 2016
    Posts:
    1,496
    I would suggest separating the actual automata and its data from the graphics implementation. Then you could have it as a backend and implement whatever graphics you needed.

    Here is a simple data class for an Array the implements Moore and VonNeumann neighborhoods as well as bounded rectangle, 2 types of cylinders and a torus. I wrote a simple Conway's life implementation in the OneTick method. I put check on the neighborhood and wrote the standard rules for a Moore, as well as made up rules for a VonNeumann one to show you could. This script would just put put in your project folder not attached to a game object.

    A GO that was handling graphics would have this class as a member and just run the OneTick method each time it wanted to update the CA. You would just implement whatever rules you wanted int the OneTick method.

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4.  
    5. public class CAGrid  {
    6.  
    7.     private NeighborHood nHood;
    8.     private GridType gType;
    9.     private int width;
    10.     private int height;
    11.     private int[] cells;
    12.     private int[] backup;
    13.     List<CellOffset> neighbors;
    14.  
    15.     public CAGrid()
    16.         : this(10,10,NeighborHood.Moore,GridType.Rectangle)
    17.     {}
    18.  
    19.     public CAGrid(int width, int height,NeighborHood hood, GridType type)
    20.     {
    21.         nHood = hood;
    22.         gType = type;
    23.         cells = new int[width * height];
    24.         backup = new int[width * height];
    25.         this.width = width;
    26.         this.height = height;
    27.         InitNeighbors();
    28.  
    29.     }
    30.  
    31.     public void OneTick()
    32.     {
    33.         // Code here to run your CA
    34.         for (int i = 0; i < width; ++i)
    35.             for (int j = 0; j < height; ++j)
    36.                 SetBackupCell(i, j, 0);
    37.  
    38.         for (int i = 0; i < width; ++i)
    39.         {
    40.             for (int j = 0; j < height; ++j)
    41.             {
    42.                 List<int> neighbors = GetNeighbors(i, j);
    43.                 int sum = 0;
    44.                 foreach (int oneNeighbor in neighbors)
    45.                     sum += oneNeighbor;
    46.                 if (nHood == NeighborHood.VonNeumann)
    47.                 {
    48.                     if (sum < 2 || sum > 3)
    49.                         SetBackupCell(i, j, 0);
    50.                     else
    51.                         SetBackupCell(i, j, 1);
    52.                 }
    53.                 else if (nHood == NeighborHood.Moore)
    54.                 {
    55.                     if (sum < 1 || sum > 2)
    56.                         SetBackupCell(i, j, 0);
    57.                     else
    58.                         SetBackupCell(i, j, 1);
    59.                 }
    60.             }
    61.         }
    62.         backup.CopyTo(cells, 0);
    63.  
    64.     }
    65.  
    66.     void SetCell(int x, int y, int value)
    67.     {
    68.         cells[y * width + x] = value;
    69.     }
    70.  
    71.     int GetCell(int x, int y)
    72.     {
    73.         return cells[y * width + x];
    74.     }
    75.  
    76.     void SetBackupCell(int x, int y, int value)
    77.     {
    78.         backup[y * width + x] = value;
    79.     }
    80.  
    81.     List<int> GetNeighbors(int x, int y)
    82.     {
    83.         List<int> neighborValues = new List<int>();
    84.         foreach(CellOffset offset in neighbors)
    85.         {
    86.             int currentX = x + offset.x;
    87.             int currentY = y + offset.y;
    88.             if (ValidSpot(ref currentX, ref currentY))
    89.                 neighborValues.Add(GetCell(currentX, currentY));
    90.         }
    91.         return neighborValues;
    92.     }
    93.  
    94.     bool ValidSpot(ref int x, ref int y)
    95.     {
    96.         switch (gType)
    97.         {
    98.             case GridType.Rectangle:
    99.                 if (x < 0 || x >= width || y < 0 || y >= height)
    100.                     return false;
    101.                 return true;
    102.             case GridType.CylinderWidth:
    103.                 if (y < 0 || y >= height)
    104.                     return false;
    105.                 if (x < 0)
    106.                     x = width + x;
    107.                 if (x >= width)
    108.                     x -= width;
    109.                 return true;
    110.             case GridType.CylinderHeight:
    111.                 if (x < 0 || x >= width)
    112.                     return false;
    113.                 if (y < 0)
    114.                     y = height + y;
    115.                 if (y >= height)
    116.                     y -= height;
    117.                 return true;
    118.             case GridType.Torus:
    119.                 if (x < 0)
    120.                     x = width + x;
    121.                 if (x >= width)
    122.                     x -= width;
    123.                 if (y < 0)
    124.                     y = height + y;
    125.                 if (y >= height)
    126.                     y -= height;
    127.                 return true;
    128.             default:
    129.                 return false;
    130.         }
    131.     }
    132.  
    133.     void InitNeighbors()
    134.     {
    135.         switch (nHood)
    136.         {
    137.             case NeighborHood.VonNeumann:
    138.                 neighbors = new List<CellOffset>();
    139.                 neighbors.Add(new CellOffset(0, -1));
    140.                 neighbors.Add(new CellOffset(1, 0));
    141.                 neighbors.Add(new CellOffset(0, 1));
    142.                 neighbors.Add(new CellOffset(-1, 0));
    143.                 break;
    144.             case NeighborHood.Moore:
    145.                 neighbors = new List<CellOffset>();
    146.                 neighbors.Add(new CellOffset(0, -1));
    147.                 neighbors.Add(new CellOffset(1, -1));
    148.                 neighbors.Add(new CellOffset(1, 0));
    149.                 neighbors.Add(new CellOffset(1, 1));
    150.                 neighbors.Add(new CellOffset(0, 1));
    151.                 neighbors.Add(new CellOffset(-1, 1));
    152.                 neighbors.Add(new CellOffset(-1, 0));
    153.                 neighbors.Add(new CellOffset(-1, -1));
    154.                 break;
    155.         }
    156.     }
    157.  
    158.  
    159. }
    160.  
    161. public struct CellOffset
    162. {
    163.     public int x;
    164.     public int y;
    165.  
    166.     public CellOffset(int x, int y)
    167.     {
    168.         this.x = x;
    169.         this.y = y;
    170.     }
    171. }
    172. public enum NeighborHood
    173. {
    174.     Moore,
    175.     VonNeumann
    176. }
    177.  
    178. public enum GridType
    179. {
    180.     Rectangle,
    181.     CylinderWidth,
    182.     CylinderHeight,
    183.     Torus
    184. }
    185.  
    Hopefully its pretty straightforward in what I did.. reply here or privately to me if you need help with any of it.

    One thing it doesn't implement is an infinite grid. You could easily to do that by adding another boolean variable, and methods for expanding the array. But I would suggest that for something later after you get everything you want working right.
     
  5. EternalAmbiguity

    EternalAmbiguity

    Joined:
    Dec 27, 2014
    Posts:
    3,144
    Hmm. I'm guessing this is probably faster than my 2000+ line script that I've come up with (that isn't complete yet).

    Thanks a bunch. I'll have to take a look at this. There are a few differences (the CA we use are probabilistic, we use more cell types than "on" or "off") but I imagine that would be simple to implement once I understand this.

    Where I have the most trouble is connecting the visuals with the backend in something like this. With that in mind, if I wanted to represent what you have here visually, what exactly would I do? Once I know that, I can probably mess around with it to get what I need.
     
  6. takatok

    takatok

    Joined:
    Aug 18, 2016
    Posts:
    1,496
    Well since the grids in my class are ints you can have as many states as you want. (well up to 2^32 anyway :).
    As far as graphics there are lots of options. To me the easiest would be to make a 2D project and just create a lot of sprites that are just solid blocks of color. Then you can iterate through them and set their color to whatever you wanted.
    You would create one Script that is a Monobehaviour attach it to a Empty GameObject. Have the the your CA class as a variable in this script, as well as List<SpriteRenderer> that holds all your sprites your created. You just call the CA class to get the value of each cell, and then change the color of that spriteRenderer in your list.

    Heading out for a bit I'll post some code later on creating an array of Sprites and changing their color later if you need it.
     
  7. EternalAmbiguity

    EternalAmbiguity

    Joined:
    Dec 27, 2014
    Posts:
    3,144
    I would like that. Thanks a bunch.

    The way I'm currently creating a grid is with this:

    Code (csharp):
    1. gridWidth = int.Parse(GameObject.Find("Horigrid").GetComponent<Text>().text);
    2.   gridHeight = int.Parse(GameObject.Find("Vertgrid").GetComponent<Text>().text);
    3.   float xOffset = 0.0f, yOffset = 0.0f;
    4.   for (int xTilesCreated = 0; xTilesCreated < gridWidth; xTilesCreated += 1)
    5.   {
    6.   for (int yTilesCreated = 0; yTilesCreated < gridHeight; yTilesCreated += 1)
    7.   {
    8.   Vector3 position = new Vector3(transform.position.x + xOffset, transform.position.y + yOffset, transform.position.z);
    9.  
    10.   GameObject tile = Instantiate(tilePrefab, position, Quaternion.identity) as GameObject;
    11.   yOffset += transform.localScale.y + tileDistance;
    12.   tile.name = ("Cell" + i++);
    13.   tile.tag = ("Cells");
    14.   }
    15.   xOffset += transform.localScale.x + tileDistance;
    16.   yOffset = 0.0f;
    17.   }
    18.   tilePrefab.GetComponent<MeshRenderer>().enabled = false;
    19.   tiles = GameObject.FindGameObjectsWithTag("Cells");
    20.   foreach (GameObject tile in tiles)
    21.   {
    22.   tile.GetComponent<MeshRenderer>().enabled = true;
    23.   }
    That seems to work fine (though I still needed to recenter and scale it to fit the screen), and I was moving on to figure out how to do the color changes. That would have worked like so:

    Code (csharp):
    1. int newColor1 = 0;
    2. Color color1 = Color.clear;
    3. if (newColor1 == 1) color1 = Color.black;
    4.   if (newColor1 == 2) color1 = Color.blue;
    5.   if (newColor1 == 3) color1 = Color.cyan;
    6.   if (newColor1 == 4) color1 = Color.gray;
    7.   if (newColor1 == 5) color1 = Color.green;
    8.   if (newColor1 == 6) color1 = Color.magenta;
    9.   if (newColor1 == 7) color1 = Color.red;
    10.   if (newColor1 == 8) color1 = Color.white;
    11.   if (newColor1 == 9) color1 = Color.yellow;
    12. newColor1 = GameObject.Find("NMP1.2").transform.Find("ColorDropdown").GetComponent<Dropdown>().value;
    13. while (time >= timeStep)
    14.   {
    15.   float result = Random.Range(0f, 100f);
    16.   // do iteration logic
    17.   foreach (GameObject tile in tiles)
    18.   {
    19.   int cellNumber = int.Parse(GameObject.FindGameObjectWithTag("numbercelltype").GetComponent<Text>().text);
    20.   if (cellNumber == 2)
    21.   {
    22.   if (cellType == 1)
    23.   {
    24.   GameObject basePage = GameObject.Find("NMP1." + cellType);
    25.   GameObject prob = basePage.transform.Find("1stProbContent").gameObject;
    26.   float prob1 = float.Parse(prob.transform.Find("ProbInputField1.1").Find("ProbTransValue").GetComponent<Text>().text);
    27.   if (result <= prob1)
    28.   {
    29.   tile.GetComponent<MeshRenderer>().material.color = color2;
    30.   cellType = 2;
    31.   }
    32.   }
    33.   if (cellType == 2)
    34.   {
    35.   GameObject basePage = GameObject.Find("NMP1." + cellType);
    36.   GameObject prob = basePage.transform.Find("1stProbContent").gameObject;
    37.   float prob1 = float.Parse(prob.transform.Find("ProbInputField1.1").Find("ProbTransValue").GetComponent<Text>().text);
    38.   if (result <= prob1)
    39.   {
    40.   tile.GetComponent<MeshRenderer>().material.color = color1;
    41.   cellType = 1;
    42.   }
    43.   }
    44.   }
    That's all part of the same script. Thing is, there are nine colors, so I have to call each one. Additionally, because there's no easy way to convert a string or int into a color, I had to manually assign the colors to cell types. Then I had the probabilistic part.

    I hadn't yet tried that out.
     
    Last edited: Jan 18, 2017
  8. EternalAmbiguity

    EternalAmbiguity

    Joined:
    Dec 27, 2014
    Posts:
    3,144
    Alright, I'm getting it to work on my own here. I've got a functioning probabilistic CA at the very least. It's slow going, and the majority of my difficulty is in the graphical representation, but it is working.

    If you want to still post something, feel free, but I think I know what to do for the time being. Not too sure about 5 and 6 yet, but that will come in time..
     
  9. takatok

    takatok

    Joined:
    Aug 18, 2016
    Posts:
    1,496
    Was sick the last 2 days. Here is some code that creates a grid of spirtes with a SetColor public method.
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4.  
    5. public class GenericTest : MonoBehaviour
    6. {
    7.    
    8.     public int width;
    9.     public int height;
    10.     public Color[] stateColors;
    11.     public Material mat;
    12.  
    13.     List<SpriteRenderer> cells;
    14.    
    15.  
    16.     void Start()
    17.     {
    18.      
    19.         cells = new List<SpriteRenderer>();
    20.         float screenHeight = 2 * Camera.main.orthographicSize;
    21.         float screenWidth = screenHeight * Camera.main.aspect;
    22.  
    23.         // figure out how width/height of one cell
    24.         float heightOffset = screenHeight / height;
    25.         float widthOffset = screenWidth / width;
    26.  
    27.         // the 0,0 point is in the middle of the screen
    28.         // so we start at -1/2 width, -1/2 height
    29.         float xStart = -screenWidth / 2f;
    30.         float yStart = -screenHeight / 2f;
    31.  
    32.        
    33.         for (int j=0;j<height;++j)
    34.         {
    35.             float yOffset = yStart + j * heightOffset;
    36.            
    37.             for (int i=0;i<width;++i)
    38.             {
    39.                 float xOffset = xStart + i * widthOffset;
    40.                 SpriteRenderer rend = MakeSprite(widthOffset, heightOffset);
    41.                 rend.gameObject.transform.position = new Vector3(xOffset, yOffset, 0);
    42.                 cells.Add(rend);
    43.             }
    44.         }
    45.     }
    46.  
    47.     public void SetColor(int i,int j, int state)
    48.     {
    49.         cells[j * width + i].material.color = stateColors[state]
    50.     }
    51.  
    52.     SpriteRenderer MakeSprite(float widthOffset, float heightOffset)
    53.     {
    54.         int texWidth = (int)(widthOffset * 100f);
    55.         int texHeight = (int)(heightOffset * 100f);
    56.  
    57.        
    58.         Texture2D tex = new Texture2D(texWidth, texHeight);
    59.         Sprite sprite = Sprite.Create(tex, new Rect(Vector2.zero, new Vector2(texWidth,texHeight)), Vector2.zero);
    60.         GameObject oneSprite = new GameObject();
    61.         SpriteRenderer rend = oneSprite.AddComponent<SpriteRenderer>();
    62.         rend.sprite = sprite;
    63.         Material spriteMat = new Material(mat);
    64.         rend.material = spriteMat;
    65.        
    66.         return rend;
    67.     }
    68. }
    69.  
    Some things to know about 2D orthographic space in Unity. When you have an orthographic camera the main thing to set is the orthographic size. It defaults to 5. Unity will then setup a space that is 2*size tall (so default its 10 units tall). Then depending on your aspect ratio it will set the x-axis. So if you were plyaing on a 800x600.. the x-axis would be 13.333 (1.333 x 10).

    By default 100 pixels = 1 unit. and 0,0 is the middle of the screen so on a default 800x600 the x,y positions are -6.666,-5 on the bottom left to +6.666,5 on the top right

    This script just makes a bunch of sprites on the fly based on the width/height of your CA and scales them to fit. It then keeps a list of the SpriteRenderers so we can set the color. I added a SetColor method to change the color of any cell.
     
  10. EternalAmbiguity

    EternalAmbiguity

    Joined:
    Dec 27, 2014
    Posts:
    3,144
    Many thanks. I really appreciate the help.
     
  11. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    Particle effects can also be useful for visualising this kind of thing. And can be dramatically cheaper then a bunch of GameObjects.
     
  12. EternalAmbiguity

    EternalAmbiguity

    Joined:
    Dec 27, 2014
    Posts:
    3,144
    Well I tried commenting out the code which changes material color as a test in my current implementation, and it didn't have any significant effect on the speed. I'm currently in the process of re-writing it (with the help of someone I know in "real life") to make the iterative loop as short as possible and make it all modular (so the same short set of code works for 2 cell types, 3, 4, etc.).

    Though I don't know if the simple fact that the code is working on X (10,000 in my case, and I want to go up to at least 1,000,000) GameObjects rather than something else is the slow part. Can you instantiate particle effects like that, and have them all follow a script independently?
     
  13. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,859
    Sorry for chiming in late here, but if you want to display a big grid of colors efficiently, it'll be hard to beat PixelSurface.

    I've used it to implement cellular automata before, such as in the simple fluid simulation described (with source code!) about halfway through this thread.

    Full disclosure: I wrote PixelSurface, so I may be considered biased. :) But I wrote it because I wanted to do the sort of thing you're doing, and needed an efficient way to display. So maybe you'll find it useful too.
     
  14. EternalAmbiguity

    EternalAmbiguity

    Joined:
    Dec 27, 2014
    Posts:
    3,144
    Thanks. I appreciate all the suggestions.
     
  15. EternalAmbiguity

    EternalAmbiguity

    Joined:
    Dec 27, 2014
    Posts:
    3,144
    Alright, so over the past couple of weeks I've managed to put a little something together...but I'm stumped at one point. I've been working on this for about 6 hours today, chipping away at little things here and there, and I've finally figured out WHAT exactly is going wrong...but I don't have any clue why it's happening.

    See the relevant code here:

    Code (csharp):
    1. using System;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using System.IO;
    5. using System.Linq;
    6. using System.Threading;
    7. using UnityEngine;
    8. using UnityEngine.UI;
    9. public class GridMake : MonoBehaviour
    10. {
    11.   GameObject tilePrefab;
    12.   public float gridWidth, gridHeight;
    13.   GameObject[] tiles;
    14.   GameObject[,] gridOfCells;
    15.   public GameObject gridCheck;
    16.   public float time = 0.0f;
    17.   public float timeToWait = 0f;
    18.   public int cellType;
    19.   public int cellNumber;
    20.   int maxIterations = 0;
    21.   int currentIteration;
    22.   public int arrayCopyRangeStart;
    23.   int result;
    24.   public float trueScale;
    25.   public float scaleHeight;
    26.   public float scaleWidth;
    27.   int maxRuns = 0;
    28.   int currentRun;
    29.   GameObject IterationText;
    30.   GameObject RunText;
    31.   public Toggle saveFileCheck;
    32.   Dropdown neighborhoodType;
    33.   Dictionary<int, Color> dict;
    34.   int[] newColors;
    35.   Dropdown orderselect;
    36.   int[] probs;
    37.   int[] neighborProbs;
    38.   int[] cellTypeTransitions;
    39.   int xTilesCreated, yTilesCreated;
    40.   public GameObject neighborUp;
    41.   public GameObject neighborDown;
    42.   public GameObject neighborLeft;
    43.   public GameObject neighborRight;
    44.   Color currentColor;
    45.   GameObject basePage;
    46.   GameObject probParent;
    47.   GameObject currentInputField;
    48.   public string inputFieldText;
    49.   public int testNumber1;
    50.   public int testNumber2 = 0;
    51.   public int testNumber3;
    52.   public int testNumber4;
    53.   public string tempTransition;
    54.   public string tempTransition2;
    55.   public float tileDistance = 0.0f; //Spacing between tiles
    56.   void Start()
    57.   {
    58.   }
    59.   void Update()
    60.   {
    61.  
    62.   }
    63.   public IEnumerator CreateTiles()
    64.   {
    65.   orderselect = GameObject.FindGameObjectWithTag("OrderDropDown").GetComponent<Dropdown>();
    66.   cellNumber = int.Parse(GameObject.FindGameObjectWithTag("numbercelltype").GetComponent<Text>().text);
    67.   maxIterations = int.Parse(GameObject.Find("IterationCount").GetComponent<Text>().text);
    68.   maxRuns = int.Parse(GameObject.Find("RunCount").GetComponent<Text>().text);
    69.   timeToWait = float.Parse(GameObject.Find("WaitTimeCount").GetComponent<Text>().text);
    70.   IterationText = GameObject.Find("IterationText");
    71.   RunText = GameObject.Find("RunText");
    72.   newColors = new int[cellNumber];
    73.   probs = new int[(cellNumber) * (cellNumber - 1)];
    74.   int[] cellNumberCounts = new int[cellNumber];
    75.   Array[] startingCellAmountGroups = new Array[cellNumber];
    76.   neighborhoodType = GameObject.FindGameObjectWithTag("NeighborhoodDropdown").GetComponent<Dropdown>();
    77.   if (orderselect.value == 0)
    78.   {
    79.   cellTypeTransitions = new int[(cellNumber) * (cellNumber - 1)];
    80.   for (int i = 0; i < cellNumber; ++i)
    81.   {
    82.   string numstring = (i + 1).ToString();
    83.   newColors[i] = GameObject.Find("NMP" + (orderselect.value + 1) + numstring).transform.Find("ColorDropdown").GetComponent<Dropdown>().value;
    84.   GameObject basePage = GameObject.Find("NMP" + (orderselect.value + 1) + numstring);
    85.   startingCellAmountGroups[i] = new GameObject[int.Parse(GameObject.Find("NMP" + (orderselect.value + 1) + numstring).transform.Find("StartNumberInputField").transform.Find("Text").GetComponent<Text>().text)];
    86.   for (int j = 1; j < cellNumber; ++j)
    87.   {
    88.   string secondNumstring = (j).ToString();
    89.   GameObject prob = basePage.transform.Find("ProbabilityScrollView").transform.Find("1stProbContent").gameObject;
    90.   probs[((j + ((cellNumber - 1) * i)) - 1)] = Mathf.RoundToInt((float.Parse(prob.transform.Find("ProbInputField1." + secondNumstring).Find("ProbTransValue").GetComponent<Text>().text)) * 1000000000) * (int.MaxValue / 1000000000);
    91.   string tempTransition = prob.transform.Find("ProbInputField1." + secondNumstring).transform.Find("ProbTransText").GetComponent<Text>().text.Remove(0, 33);
    92.   string tempTransition2 = tempTransition.Remove(1, 1);
    93.   int transitionNumber = int.Parse(tempTransition2);
    94.   cellTypeTransitions[((j + ((cellNumber - 1) * i)) - 1)] = transitionNumber;
    95.   }
    96.   }
    97.   //NOTE!!! The above sets a minimum probability value of 0.000000001. Changing to using floats might solve the problem.
    98.   }
    99.   if (orderselect.value == 1)
    100.   {
    101.   if (neighborhoodType.value == 0)
    102.   {
    103.   cellTypeTransitions = new int[((cellNumber - 1) * 5) * cellNumber];
    104.   neighborProbs = new int[((cellNumber - 1) * 5) * cellNumber];
    105.   for (int i = 0; i < cellNumber; ++i)
    106.   {
    107.   string numstring = (i + 1).ToString();
    108.   basePage = GameObject.Find("NMP2" + numstring);
    109.   probParent = basePage.transform.Find("ProbabilityScrollView").transform.Find("1stProbContent").gameObject;
    110.   startingCellAmountGroups[i] = new GameObject[int.Parse(GameObject.Find("NMP" + (orderselect.value + 1) + numstring).transform.Find("StartNumberInputField").transform.Find("Text").GetComponent<Text>().text)];
    111.   newColors[i] = GameObject.Find("NMP2" + numstring).transform.Find("ColorDropdown").GetComponent<Dropdown>().value;
    112.   for (int j = 0; j < (cellNumber - 1); ++j)
    113.   {
    114.   for (int k = 0; k < 5; ++k)
    115.   {
    116.   string secondNumString = (j + 1).ToString();
    117.   currentInputField = probParent.transform.Find("ProbInputField2." + secondNumString + "." + k.ToString()).gameObject;
    118.   inputFieldText = currentInputField.transform.Find("ProbTransValue").GetComponent<Text>().text;
    119.   neighborProbs[(i * ((cellNumber - 1) * 5)) + (j * 5) + k] = Mathf.RoundToInt((float.Parse(probParent.transform.Find("ProbInputField2." + secondNumString + "." + k.ToString()).transform.Find("ProbTransValue").GetComponent<Text>().text)) * 1000000000) * (int.MaxValue / 1000000000);
    120.   tempTransition = probParent.transform.Find("ProbInputField2." + secondNumString + "." + k).transform.Find("ProbTransText").GetComponent<Text>().text.Remove(0, 61);
    121.   tempTransition2 = tempTransition.Remove(1, 8);
    122.   int transitionNumber = int.Parse(tempTransition2);
    123.   cellTypeTransitions[(i * ((cellNumber - 1) * 5)) + (j * 5) + k] = transitionNumber;
    124.   }
    125.   }
    126.   }
    127.   }
    128.   }
    129.   //This dictionary only uses the Unity-named colors because it's easier to pick these in a drop-down than to have a color selector.
    130.   //Future updates might adjust the UI page to allow for direct color selection, which could then be passed to this script and avoid the artificial limitations here.
    131.   //After all, this can only show up to 9 cell types. Any number is possible, but with only 9 colors you can only differentiate between nine types.
    132.   dict = new Dictionary<int, Color>();
    133.   dict.Add(0, Color.black);
    134.   dict.Add(1, Color.blue);
    135.   dict.Add(2, Color.cyan);
    136.   dict.Add(3, Color.gray);
    137.   dict.Add(4, Color.green);
    138.   dict.Add(5, Color.magenta);
    139.   dict.Add(6, Color.red);
    140.   dict.Add(7, Color.white);
    141.   dict.Add(8, Color.yellow);
    142.   //UI stuff and grid formation
    143.   {
    144.   tilePrefab = GameObject.FindGameObjectWithTag("tilePrefab");
    145.   gridWidth = int.Parse(GameObject.Find("Horigrid").GetComponent<Text>().text);
    146.   gridHeight = int.Parse(GameObject.Find("Vertgrid").GetComponent<Text>().text);
    147.   float xOffset = 0.0f, yOffset = 0.0f;
    148.   scaleHeight = ((Screen.height / (Screen.height / 10)) / gridHeight);
    149.   scaleWidth = ((Screen.width / (Screen.width / 10)) / gridWidth);
    150.   trueScale = Mathf.Min(scaleHeight, scaleWidth);
    151.   tilePrefab.transform.localScale = new Vector3(trueScale, trueScale, trueScale);
    152.   gridOfCells = new GameObject[Convert.ToInt32(Math.Ceiling(gridWidth)), Convert.ToInt32(Math.Ceiling(gridHeight))];
    153.   for (xTilesCreated = 0; xTilesCreated < gridWidth; xTilesCreated += 1)
    154.   {
    155.   for (yTilesCreated = 0; yTilesCreated < gridHeight; yTilesCreated += 1)
    156.   {
    157.   Vector3 position = new Vector3(transform.position.x + xOffset + (trueScale / 2), transform.position.y + yOffset + (trueScale / 2), transform.position.z);
    158.   GameObject tile = Instantiate(tilePrefab, position, Quaternion.identity) as GameObject;
    159.   yOffset += trueScale + tileDistance;
    160.   tile.name = ("Cell" + ((xTilesCreated * gridWidth) + yTilesCreated));
    161.   tile.tag = ("Cells");
    162.   gridOfCells[xTilesCreated, yTilesCreated] = tile;
    163.   SetNeighbor();
    164.   }
    165.   xOffset += trueScale + tileDistance;
    166.   yOffset = 0.0f;
    167.   }
    168.   tilePrefab.GetComponent<Renderer>().enabled = false;
    169.   tiles = GameObject.FindGameObjectsWithTag("Cells");
    170.   foreach (GameObject tile in tiles)
    171.   {
    172.   tile.transform.localPosition = new Vector3((tile.transform.position.x - (gridWidth / 2) * trueScale), (tile.transform.position.y - ((gridHeight / 2)) * trueScale), tile.transform.position.z);
    173.   tile.GetComponent<Renderer>().enabled = true;
    174.   }
    175.  
    176.   }
    177.   gridCheck = gridOfCells[1, 0];
    178.   //This sets up the probability array to have the appropriate values for probability ranges
    179.   if (orderselect.value == 0)
    180.   {
    181.   for (int i = 0; i < (cellNumber * (cellNumber - 1)); i += (cellNumber - 1))
    182.   {
    183.   for (int j = (i + (cellNumber - 2)); j > (i - 1); --j)
    184.   {
    185.   for (int k = (j - 1); k > (i - 1); --k)
    186.   {
    187.   probs[j] = probs[j] + probs[k];
    188.   }
    189.   }
    190.   }
    191.   }
    192.   //This is the actual iteration loop
    193.   for (currentRun = 1; currentRun < (maxRuns + 1); ++currentRun)
    194.   {
    195.   //This first shuffles the group of objects, then changes the colors of the objects based on cell type starting amounts
    196.   tiles.Shuffle();
    197.   for (int i = 0; i < cellNumber; ++i)
    198.   {
    199.   arrayCopyRangeStart = 0;
    200.   int j = 0;
    201.   for (int k = (i - 1); k > (-1); --k)
    202.   {
    203.   arrayCopyRangeStart = j + startingCellAmountGroups[k].Length;
    204.   j = arrayCopyRangeStart;
    205.   }
    206.   Array.Copy(tiles, arrayCopyRangeStart, startingCellAmountGroups[i], 0, startingCellAmountGroups[i].Length);
    207.   foreach (GameObject tile in startingCellAmountGroups[i])
    208.   {
    209.   tile.GetComponent<Renderer>().material.color = dict[newColors[i]];
    210.   cellType = (i + 1);
    211.   }
    212.   }
    213.   for (currentIteration = 0; currentIteration < (maxIterations); currentIteration++)
    214.   {
    215.   tiles.Shuffle();
    216.   for (int k = 0; k < cellNumber; ++k) cellNumberCounts[k] = 0;
    217.   foreach (GameObject tile in tiles)
    218.   {
    219.   if (orderselect.value == 1)
    220.   {
    221.   CheckNeighbors();
    222.   //organizeProbs();
    223.   testNumber1 = probs[1];
    224.   }
    225.  
    226.   result = UnityEngine.Random.Range(0, int.MaxValue);
    227.   int i = ((cellType - 1) * (cellNumber - 1));
    228.   {
    229.   for (int j = (i + (cellNumber - 2)); j > i; --j)
    230.   {
    231.   if (result <= probs[j] && result > probs[j - 1])
    232.   {
    233.   tile.GetComponent<Renderer>().material.color = dict[newColors[(cellTypeTransitions[j] - 1)]];
    234.   cellType = cellTypeTransitions[j];
    235.   goto Label;
    236.   }
    237.   }
    238.   if (result <= probs[i])
    239.   {
    240.   tile.GetComponent<Renderer>().material.color = dict[newColors[(cellTypeTransitions[i] - 1)]];
    241.   cellType = cellTypeTransitions[i];
    242.   goto Label;
    243.   }
    244.   }
    245.   Label:
    246.   ;
    247.   cellNumberCounts[(cellType - 1)] += 1;
    248.   }
    249.  
    250.   if (saveFileCheck.isOn == true)
    251.   {
    252.   using (StreamWriter wt = File.AppendText(Application.persistentDataPath + "Run " + currentRun + ".txt"))
    253.   {
    254.   wt.Write("Iteration: " + (currentIteration + 1));
    255.   for (int i = 0; i < cellNumber; ++i)
    256.   {
    257.   string cellTypeString = (i + 1).ToString();
    258.   wt.Write(" Cell Type " + cellTypeString + ": " + cellNumberCounts[i]);
    259.   }
    260.   wt.WriteLine();
    261.   wt.Close();
    262.   }
    263.   }
    264.   IterationText.GetComponent<Text>().text = "Iteration: " + (currentIteration + 1).ToString();
    265.   RunText.GetComponent<Text>().text = "Run: " + currentRun.ToString();
    266.   yield return new WaitForSeconds(timeToWait);
    267.   }
    268.   }
    269.   }
    270.   void organizeProbs()
    271.   {
    272.   for (int i = 0; i < (cellNumber * (cellNumber - 1)); i += (cellNumber - 1))
    273.   {
    274.   for (int j = (i + (cellNumber - 2)); j > (i - 1); --j)
    275.   {
    276.   for (int k = (j - 1); k > (i - 1); --k)
    277.   {
    278.   probs[j] = probs[j] + probs[k];
    279.   }
    280.   }
    281.   }
    282.   }
    283.   void SetNeighbor()
    284.   {
    285.   if (orderselect.value != 0)
    286.   {
    287.   // if (gridType.value == 0)
    288.   if (neighborhoodType.value == 0)
    289.   {
    290.   try
    291.   {
    292.   neighborUp = gridOfCells[(xTilesCreated), (yTilesCreated + 1)];
    293.   }
    294.   catch (System.IndexOutOfRangeException)
    295.   {
    296.   }
    297.   try
    298.   {
    299.   neighborDown = gridOfCells[(xTilesCreated), (yTilesCreated - 1)];
    300.   }
    301.   catch (System.IndexOutOfRangeException)
    302.   {
    303.   }
    304.   try
    305.   {
    306.   neighborLeft = gridOfCells[(xTilesCreated - 1), (yTilesCreated)];
    307.   }
    308.   catch (System.IndexOutOfRangeException)
    309.   {
    310.   }
    311.   try
    312.   {
    313.   neighborRight = gridOfCells[(xTilesCreated + 1), (yTilesCreated)];
    314.   }
    315.   catch (System.IndexOutOfRangeException)
    316.   {
    317.   }
    318.   }
    319.   }
    320.   else
    321.   {
    322.   }
    323.   }
    324.   void CheckNeighbors()
    325.   {
    326.   Color upColor = Color.clear;
    327.   Color downColor = Color.clear;
    328.   Color leftColor = Color.clear;
    329.   Color rightColor = Color.clear;
    330.   try
    331.   {
    332.   upColor = neighborUp.GetComponent<Renderer>().material.color;
    333.   }
    334.   catch (System.NullReferenceException)
    335.   {
    336.   }
    337.   try
    338.   {
    339.   downColor = neighborDown.GetComponent<Renderer>().material.color;
    340.   }
    341.   catch (System.NullReferenceException)
    342.   {
    343.   }
    344.   try
    345.   {
    346.   leftColor = neighborLeft.GetComponent<Renderer>().material.color;
    347.   }
    348.   catch (System.NullReferenceException)
    349.   {
    350.   }
    351.   try
    352.   {
    353.   rightColor = neighborRight.GetComponent<Renderer>().material.color;
    354.   }
    355.   catch (System.NullReferenceException)
    356.   {
    357.   }
    358.   Color[] vonNeumannGroup = new Color[4] { upColor, downColor, leftColor, rightColor };
    359.   List<Color> filteredVonNeumann = new List<Color>();
    360.   for (int currentCheck = 0; currentCheck < cellNumber; ++currentCheck)
    361.   {
    362.   int tempValue = 0;
    363.   if (currentCheck == (cellType - 1)) continue;
    364.   if (currentCheck < (cellType - 1))
    365.   {
    366.   for (int j = 0; j < 4; ++j)
    367.   {
    368.   if (vonNeumannGroup[j] == dict[newColors[currentCheck]])
    369.   {
    370.   filteredVonNeumann.Add(vonNeumannGroup[j]);
    371.   }
    372.   }
    373.   if (filteredVonNeumann.Count == 4)
    374.   {
    375.   tempValue = neighborProbs[(((cellType - 1) * ((cellNumber - 1) * 5)) + (currentCheck * 5)) + 4];
    376.   }
    377.   if (filteredVonNeumann.Count == 3)
    378.   {
    379.   tempValue = neighborProbs[(((cellType - 1) * ((cellNumber - 1) * 5)) + (currentCheck * 5)) + 3];
    380.   }
    381.   if (filteredVonNeumann.Count == 2)
    382.   {
    383.   tempValue = neighborProbs[(((cellType - 1) * ((cellNumber - 1) * 5)) + (currentCheck * 5)) + 2];
    384.   }
    385.   if (filteredVonNeumann.Count == 1)
    386.   {
    387.   tempValue = neighborProbs[(((cellType - 1) * ((cellNumber - 1) * 5)) + (currentCheck * 5)) + 1];
    388.   }
    389.   if (filteredVonNeumann.Count == 0)
    390.   {
    391.   tempValue = neighborProbs[(((cellType - 1) * ((cellNumber - 1) * 5)) + (currentCheck * 5))];
    392.   }
    393.   probs[((cellType - 1) * (cellNumber - 1)) + currentCheck] = tempValue;
    394.   }
    395.   if (currentCheck > (cellType - 1))
    396.   {
    397.   for (int j = 0; j < 4; ++j)
    398.   {
    399.   if (vonNeumannGroup[j] == dict[newColors[currentCheck]])
    400.   {
    401.   filteredVonNeumann.Add(vonNeumannGroup[j]);
    402.   }
    403.   }
    404.   if (filteredVonNeumann.Count == 4)
    405.   {
    406.   tempValue = neighborProbs[(((cellType - 1) * ((cellNumber - 1) * 5)) + ((currentCheck - 1) * 5)) + 4];
    407.   }
    408.   if (filteredVonNeumann.Count == 3)
    409.   {
    410.   tempValue = neighborProbs[(((cellType - 1) * ((cellNumber - 1) * 5)) + ((currentCheck - 1) * 5)) + 3];
    411.   }
    412.   if (filteredVonNeumann.Count == 2)
    413.   {
    414.   tempValue = neighborProbs[(((cellType - 1) * ((cellNumber - 1) * 5)) + ((currentCheck - 1) * 5)) + 2];
    415.   }
    416.   if (filteredVonNeumann.Count == 1)
    417.   {
    418.   tempValue = neighborProbs[(((cellType - 1) * ((cellNumber - 1) * 5)) + ((currentCheck - 1) * 5)) + 1];
    419.   }
    420.   if (filteredVonNeumann.Count == 0)
    421.   {
    422.   tempValue = neighborProbs[(((cellType - 1) * ((cellNumber - 1) * 5)) + ((currentCheck - 1) * 5))];
    423.   }
    424.   probs[((cellType - 1) * (cellNumber - 1)) + (currentCheck - 1)] = tempValue;
    425.   }
    426.   testNumber4 = probs[((cellType - 1) * (cellNumber - 1)) + (currentCheck - 1)];
    427.   testNumber3 = probs.Length;
    428.   }
    429.   }
    430. }
    431. public static class ThreadSafeRandom
    432. {
    433.   [ThreadStatic]
    434.   private static System.Random Local;
    435.   public static System.Random ThisThreadsRandom
    436.   {
    437.   get { return Local ?? (Local = new System.Random(unchecked(Environment.TickCount * 31 + Thread.CurrentThread.ManagedThreadId))); }
    438.   }
    439. }
    440. static class MyExtensions
    441. {
    442.   public static void Shuffle<T>(this IList<T> list)
    443.   {
    444.   int n = list.Count;
    445.   while (n > 1)
    446.   {
    447.   n--;
    448.   int k = ThreadSafeRandom.ThisThreadsRandom.Next(n + 1);
    449.   T value = list[k];
    450.   list[k] = list[n];
    451.   list[n] = value;
    452.   }
    453.   }
    454. }
    The project is also attached if needed to better understand things. I realize the code is kind of jumbled, but I'll point out the important parts.

    I'll describe the issue. A "1-dimensional" grid works fine, but this is all for what I term a "1.5-dimensional" grid, a grid where each cell has neighbor effects but does not move.

    The user enters some number of cell types. They enter a bunch of other data about making the grid, and input the probabilities of conversions between cell types. This includes the probabilities based on the number of neighbors of a certain type. They also pick how many cells of each cell type the grid starts with.

    The grid is formed. At this point, each tile finds its neighbors (up, down, left, and right), with edge cases simply skipping the out-of-bounds areas. The tiles are turned into the user-chosen amounts of each cell type.

    The iteration begins. Each tile checks its neighbors to see if they are of a certain cell type. If they are, a count is made (from 0 to 4, because 4 neighbors). The count is checked, then the appropriate probability from the ones the user entered is chosen.

    Next, that chosen probability is put into a smaller group of numbers which only includes the few probabilities needed for the cell after all neighbors have been determined.

    Basically, there's an array of possible probabilities, and I move the appropriate value to the array of needed probabilities. This starts on line 363 or so.


    I can query the array of possible probabilities and return values. I can query the array of needed probabilities using the formula within the loop used to assign values, and return values. But I cannot query an index number and return a value. I get 0, while the other method (using the special formula) gives a number.

    If one wanted to replicate the issue in the project:
    start it up. Select New CA or whatever. Select Order: 1.5 in dropdown. Select grid size (10 x 10 is simple). Select number of cell types (I typically test with 3). Select iterations, runs, time to wait between iterations (I typically do 1000, 1, 0). Click Next on bottom right.

    Select three different colors for next three (or however many cell types you have) pages, and put probability values for the input fields (note! Scroll down or click-and-drag inside the field to move down). Make sure to put starting values of each cell type, being sure not to go over grid size. Click Next on the final page to confirm your selections. Select Yes.

    The iterations will begin, but everything turns into 1 cell type for some reason I cannot ascertain. In any case, depending on your probability values chosen, you should have other colors showing up, but they don't.

    This is utterly mind-boggling to me, and if anyone can help me figure it out I would sincerely appreciate it.
     

    Attached Files:

    Last edited: Feb 9, 2017
  16. takatok

    takatok

    Joined:
    Aug 18, 2016
    Posts:
    1,496
    I was able to download your project and run it. As this is a pretty big project so far, I'm going to avoid any suggestions on changing things to work in a "better" structured way. I'll limit my responses to getting existing code up and running. If your interested in my opinions on problems with how the code is structured and better ways to go about it, feel free to start up a Conversation with me.

    1) You don't have a backup array that you actually do the calculations on. You are writing directly to the main cell array as you change states of things. Say we are on iteration 0, the start. Cell[0,0] will get changed to its correct state for Iteration 1. However, Cell[1,0] will now be using Cell[0,0] state from Iteration 1, not the correct one from Iteration 0 (its lost). The standard way to do CA is to have a backup grid that you copy the entire cell grid into. Then when you process Cell[X,Y] you check its neighbors in backup grid and write its new state into the main grid. This way you preserve a copy of the old iteration to correctly asses the new iteration.

    2) ***I'm positive this problem is why it goes to 1 color*** You UpNeighbor, DownNeighbor, etc Gameobjects are just single variables they aren't arrays. So each time you call SetNeighbor you only setting them to the neighbors of the currentCell. When you finally get done creating the initial grid, those 4 GameObjects are the neighbors of the last cell created Tile[width-1,height-1]. So your entire CA is just using those neighbors for every cell. If it happens to be that those neighbors indicate the cell should turn a particular state.. you entire grid will slowly go to that state. The only reason it doesn't instantly turn to 1 color is your doing a probabilistic chance that it might turn that color. You need to change SetNeighbors to take an x,y co-ordinate and set those objects based on that X,Y. You can take out the call to SetNeighbors in the grid creation loop. You need to call it in the iteration loop every time right before you call GetNeighbors.
     
  17. EternalAmbiguity

    EternalAmbiguity

    Joined:
    Dec 27, 2014
    Posts:
    3,144
    Yeah, I know it definitely needs optimization. But for the time being I'm mainly just trying to get it to work first.

    1. Hmm, yeah I see what you mean. So in essence the grid changes each time a cell is changed, even within an iteration. I'll have to look at how to fix this.

    2. Dang, of course. For some reason I thought it was applying those values to the cells like it does for the cell type, but that isn't necessarily the case.

    I'll try moving the SetNeighbors part, then see what happens. Thanks again for the help.

    Edit: well, in the SetNeighbors part I use parameters from the grid formation. To change that I would have to query the current location of the object within the gridofcells array, then do the neighbor calculation. Since my knowledge of code is absurdly small I don't exactly know how to do that first part. However, there's no need to--since doing SetNeighbors during iteration is a terrible idea (it only needs to be done once).

    I'll just form a new array within which to store the neighbors like you pointed out.

    GameObject[] neighborArray = [(((gridHeight - 1) * (gridWidth - 1)) * 4) + (((gridHeight * gridWidth) - 4) * 3) + (4 * 2)]

    After I get that done I'll update if it works.

    Edit: ...

    GameObject[] neighborArray = new GameObject[(((intGridHeight - 2) * (intGridWidth - 2)) * 4) + ((((2 * intGridHeight) + (2 * intGridWidth)) - 4) * 3) + (4 * 2)];
     
    Last edited: Feb 9, 2017
  18. EternalAmbiguity

    EternalAmbiguity

    Joined:
    Dec 27, 2014
    Posts:
    3,144
    Okay, I decided to go with an array that's simply (gridHeight x gridWidth) * 4, rather than deal with any issues with appropriately finding edge cases. It makes the array somewhat larger, but I don't think that's a concern, at this stage at least.

    Here's the new GridMake code:
    Code (csharp):
    1. using System;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using System.IO;
    5. using System.Linq;
    6. using System.Threading;
    7. using UnityEngine;
    8. using UnityEngine.UI;
    9. public class GridMake : MonoBehaviour
    10. {
    11.   GameObject tilePrefab;
    12.   GameObject IterationText;
    13.   GameObject RunText;
    14.   GameObject basePage;
    15.   GameObject probParent;
    16.   GameObject currentInputField;
    17.   public GameObject neighborUp;
    18.   public GameObject neighborDown;
    19.   public GameObject neighborLeft;
    20.   public GameObject neighborRight;
    21.   public GameObject gridCheck;
    22.   GameObject[] neighborArray;
    23.   GameObject[] tiles;
    24.   GameObject[,] gridOfCells;
    25.   public float gridWidth, gridHeight;
    26.   public float time = 0.0f;
    27.   public float timeToWait = 0f;
    28.   public int cellType;
    29.   public int cellNumber;
    30.   int maxIterations = 0;
    31.   int currentIteration;
    32.   public int arrayCopyRangeStart;
    33.   int result;
    34.   public float trueScale;
    35.   public float scaleHeight;
    36.   public float scaleWidth;
    37.   int maxRuns = 0;
    38.   int currentRun;
    39.   public Toggle saveFileCheck;
    40.   Dropdown neighborhoodType;
    41.   Dictionary<int, Color> dict;
    42.   int[] newColors;
    43.   Dropdown orderselect;
    44.   int[] probs;
    45.   int[] neighborProbs;
    46.   int[] cellTypeTransitions;
    47.   int xTilesCreated, yTilesCreated;
    48.  
    49.   int intGridHeight;
    50.   int intGridWidth;
    51.   public int currentCellNumber;
    52.   Color currentColor;
    53.   public string inputFieldText;
    54.   public int testNumber1;
    55.   public int testNumber2 = 0;
    56.   public int testNumber3;
    57.   public int testNumber4;
    58.   public string tempTransition;
    59.   public string tempTransition2;
    60.   public float tileDistance = 0.0f; //Spacing between tiles
    61.   void Start()
    62.   {
    63.   }
    64.   void Update()
    65.   {
    66.  
    67.   }
    68.   public IEnumerator CreateTiles()
    69.   {
    70.   orderselect = GameObject.FindGameObjectWithTag("OrderDropDown").GetComponent<Dropdown>();
    71.   cellNumber = int.Parse(GameObject.FindGameObjectWithTag("numbercelltype").GetComponent<Text>().text);
    72.   maxIterations = int.Parse(GameObject.Find("IterationCount").GetComponent<Text>().text);
    73.   maxRuns = int.Parse(GameObject.Find("RunCount").GetComponent<Text>().text);
    74.   timeToWait = float.Parse(GameObject.Find("WaitTimeCount").GetComponent<Text>().text);
    75.   IterationText = GameObject.Find("IterationText");
    76.   RunText = GameObject.Find("RunText");
    77.   newColors = new int[cellNumber];
    78.   probs = new int[(cellNumber) * (cellNumber - 1)];
    79.   int[] cellNumberCounts = new int[cellNumber];
    80.   Array[] startingCellAmountGroups = new Array[cellNumber];
    81.   neighborhoodType = GameObject.FindGameObjectWithTag("NeighborhoodDropdown").GetComponent<Dropdown>();
    82.   if (orderselect.value == 0)
    83.   {
    84.   cellTypeTransitions = new int[(cellNumber) * (cellNumber - 1)];
    85.   for (int i = 0; i < cellNumber; ++i)
    86.   {
    87.   string numstring = (i + 1).ToString();
    88.   newColors[i] = GameObject.Find("NMP" + (orderselect.value + 1) + numstring).transform.Find("ColorDropdown").GetComponent<Dropdown>().value;
    89.   GameObject basePage = GameObject.Find("NMP" + (orderselect.value + 1) + numstring);
    90.   startingCellAmountGroups[i] = new GameObject[int.Parse(GameObject.Find("NMP" + (orderselect.value + 1) + numstring).transform.Find("StartNumberInputField").transform.Find("Text").GetComponent<Text>().text)];
    91.   for (int j = 1; j < cellNumber; ++j)
    92.   {
    93.   string secondNumstring = (j).ToString();
    94.   GameObject prob = basePage.transform.Find("ProbabilityScrollView").transform.Find("1stProbContent").gameObject;
    95.   probs[((j + ((cellNumber - 1) * i)) - 1)] = Mathf.RoundToInt((float.Parse(prob.transform.Find("ProbInputField1." + secondNumstring).Find("ProbTransValue").GetComponent<Text>().text)) * 1000000000) * (int.MaxValue / 1000000000);
    96.   string tempTransition = prob.transform.Find("ProbInputField1." + secondNumstring).transform.Find("ProbTransText").GetComponent<Text>().text.Remove(0, 33);
    97.   string tempTransition2 = tempTransition.Remove(1, 1);
    98.   int transitionNumber = int.Parse(tempTransition2);
    99.   cellTypeTransitions[((j + ((cellNumber - 1) * i)) - 1)] = transitionNumber;
    100.   }
    101.   }
    102.   //NOTE!!! The above sets a minimum probability value of 0.000000001. Changing to using floats might solve the problem.
    103.   }
    104.   if (orderselect.value == 1)
    105.   {
    106.   if (neighborhoodType.value == 0)
    107.   {
    108.   cellTypeTransitions = new int[((cellNumber - 1) * 5) * cellNumber];
    109.   neighborProbs = new int[((cellNumber - 1) * 5) * cellNumber];
    110.   for (int i = 0; i < cellNumber; ++i)
    111.   {
    112.   string numstring = (i + 1).ToString();
    113.   basePage = GameObject.Find("NMP2" + numstring);
    114.   probParent = basePage.transform.Find("ProbabilityScrollView").transform.Find("1stProbContent").gameObject;
    115.   startingCellAmountGroups[i] = new GameObject[int.Parse(GameObject.Find("NMP" + (orderselect.value + 1) + numstring).transform.Find("StartNumberInputField").transform.Find("Text").GetComponent<Text>().text)];
    116.   newColors[i] = GameObject.Find("NMP2" + numstring).transform.Find("ColorDropdown").GetComponent<Dropdown>().value;
    117.   for (int j = 0; j < (cellNumber - 1); ++j)
    118.   {
    119.   for (int k = 0; k < 5; ++k)
    120.   {
    121.   string secondNumString = (j + 1).ToString();
    122.   currentInputField = probParent.transform.Find("ProbInputField2." + secondNumString + "." + k.ToString()).gameObject;
    123.   inputFieldText = currentInputField.transform.Find("ProbTransValue").GetComponent<Text>().text;
    124.   neighborProbs[(i * ((cellNumber - 1) * 5)) + (j * 5) + k] = Mathf.RoundToInt((float.Parse(probParent.transform.Find("ProbInputField2." + secondNumString + "." + k.ToString()).transform.Find("ProbTransValue").GetComponent<Text>().text)) * 1000000000) * (int.MaxValue / 1000000000);
    125.   tempTransition = probParent.transform.Find("ProbInputField2." + secondNumString + "." + k).transform.Find("ProbTransText").GetComponent<Text>().text.Remove(0, 61);
    126.   tempTransition2 = tempTransition.Remove(1, 8);
    127.   int transitionNumber = int.Parse(tempTransition2);
    128.   cellTypeTransitions[(i * ((cellNumber - 1) * 5)) + (j * 5) + k] = transitionNumber;
    129.   }
    130.   }
    131.   }
    132.   }
    133.   }
    134.   //This dictionary only uses the Unity-named colors because it's easier to pick these in a drop-down than to have a color selector.
    135.   //Future updates might adjust the UI page to allow for direct color selection, which could then be passed to this script and avoid the artificial limitations here.
    136.   //After all, this can only show up to 9 cell types. Any number is possible, but with only 9 colors you can only differentiate between nine types.
    137.   dict = new Dictionary<int, Color>();
    138.   dict.Add(0, Color.black);
    139.   dict.Add(1, Color.blue);
    140.   dict.Add(2, Color.cyan);
    141.   dict.Add(3, Color.gray);
    142.   dict.Add(4, Color.green);
    143.   dict.Add(5, Color.magenta);
    144.   dict.Add(6, Color.red);
    145.   dict.Add(7, Color.white);
    146.   dict.Add(8, Color.yellow);
    147.   //UI stuff and grid formation
    148.   {
    149.   tilePrefab = GameObject.FindGameObjectWithTag("tilePrefab");
    150.   gridWidth = int.Parse(GameObject.Find("Horigrid").GetComponent<Text>().text);
    151.   gridHeight = int.Parse(GameObject.Find("Vertgrid").GetComponent<Text>().text);
    152.   intGridWidth = Convert.ToInt32(Math.Ceiling(gridWidth));
    153.   intGridHeight = Convert.ToInt32(Math.Ceiling(gridHeight));
    154.   float xOffset = 0.0f, yOffset = 0.0f;
    155.   scaleHeight = ((Screen.height / (Screen.height / 10)) / gridHeight);
    156.   scaleWidth = ((Screen.width / (Screen.width / 10)) / gridWidth);
    157.   trueScale = Mathf.Min(scaleHeight, scaleWidth);
    158.   tilePrefab.transform.localScale = new Vector3(trueScale, trueScale, trueScale);
    159.   gridOfCells = new GameObject[intGridWidth, intGridHeight];
    160.   for (xTilesCreated = 0; xTilesCreated < gridWidth; xTilesCreated += 1)
    161.   {
    162.   for (yTilesCreated = 0; yTilesCreated < gridHeight; yTilesCreated += 1)
    163.   {
    164.   Vector3 position = new Vector3(transform.position.x + xOffset + (trueScale / 2), transform.position.y + yOffset + (trueScale / 2), transform.position.z);
    165.   GameObject tile = Instantiate(tilePrefab, position, Quaternion.identity) as GameObject;
    166.   yOffset += trueScale + tileDistance;
    167.   tile.name = ("Cell" + ((xTilesCreated * gridWidth) + yTilesCreated));
    168.   tile.tag = ("Cells");
    169.   gridOfCells[xTilesCreated, yTilesCreated] = tile;
    170.   SetNeighbor();
    171.   }
    172.   xOffset += trueScale + tileDistance;
    173.   yOffset = 0.0f;
    174.   }
    175.   tilePrefab.GetComponent<Renderer>().enabled = false;
    176.   tiles = GameObject.FindGameObjectsWithTag("Cells");
    177.   foreach (GameObject tile in tiles)
    178.   {
    179.   tile.transform.localPosition = new Vector3((tile.transform.position.x - (gridWidth / 2) * trueScale), (tile.transform.position.y - ((gridHeight / 2)) * trueScale), tile.transform.position.z);
    180.   tile.GetComponent<Renderer>().enabled = true;
    181.   }
    182.  
    183.   }
    184.   gridCheck = gridOfCells[1, 0];
    185.   //This sets up the probability array to have the appropriate values for probability ranges
    186.   if (orderselect.value == 0)
    187.   {
    188.   for (int i = 0; i < (cellNumber * (cellNumber - 1)); i += (cellNumber - 1))
    189.   {
    190.   for (int j = (i + (cellNumber - 2)); j > (i - 1); --j)
    191.   {
    192.   for (int k = (j - 1); k > (i - 1); --k)
    193.   {
    194.   probs[j] = probs[j] + probs[k];
    195.   }
    196.   }
    197.   }
    198.   }
    199.   //This is the actual iteration loop
    200.   for (currentRun = 1; currentRun < (maxRuns + 1); ++currentRun)
    201.   {
    202.   //This first shuffles the group of objects, then changes the colors of the objects based on cell type starting amounts
    203.   tiles.Shuffle();
    204.   for (int i = 0; i < cellNumber; ++i)
    205.   {
    206.   arrayCopyRangeStart = 0;
    207.   int j = 0;
    208.   for (int k = (i - 1); k > (-1); --k)
    209.   {
    210.   arrayCopyRangeStart = j + startingCellAmountGroups[k].Length;
    211.   j = arrayCopyRangeStart;
    212.   }
    213.   Array.Copy(tiles, arrayCopyRangeStart, startingCellAmountGroups[i], 0, startingCellAmountGroups[i].Length);
    214.   foreach (GameObject tile in startingCellAmountGroups[i])
    215.   {
    216.   tile.GetComponent<Renderer>().material.color = dict[newColors[i]];
    217.   cellType = (i + 1);
    218.   }
    219.   }
    220.   for (currentIteration = 0; currentIteration < (maxIterations); currentIteration++)
    221.   {
    222.   tiles.Shuffle();
    223.   for (int k = 0; k < cellNumber; ++k) cellNumberCounts[k] = 0;
    224.   foreach (GameObject tile in tiles)
    225.   {
    226.   if (orderselect.value == 1)
    227.   {
    228.   currentCellNumber = int.Parse(tile.name.Remove(0, 4));
    229.   CheckNeighbors();
    230.   organizeProbs();
    231.   testNumber1 = probs[1];
    232.   }
    233.  
    234.   result = UnityEngine.Random.Range(0, int.MaxValue);
    235.   int i = ((cellType - 1) * (cellNumber - 1));
    236.   {
    237.   for (int j = (i + (cellNumber - 2)); j > i; --j)
    238.   {
    239.   if (result <= probs[j] && result > probs[j - 1])
    240.   {
    241.   tile.GetComponent<Renderer>().material.color = dict[newColors[(cellTypeTransitions[j] - 1)]];
    242.   cellType = cellTypeTransitions[j];
    243.   goto Label;
    244.   }
    245.   }
    246.   if (result <= probs[i])
    247.   {
    248.   tile.GetComponent<Renderer>().material.color = dict[newColors[(cellTypeTransitions[i] - 1)]];
    249.   cellType = cellTypeTransitions[i];
    250.   goto Label;
    251.   }
    252.   }
    253.   Label:
    254.   ;
    255.   cellNumberCounts[(cellType - 1)] += 1;
    256.   }
    257.  
    258.   if (saveFileCheck.isOn == true)
    259.   {
    260.   using (StreamWriter wt = File.AppendText(Application.persistentDataPath + "Run " + currentRun + ".txt"))
    261.   {
    262.   wt.Write("Iteration: " + (currentIteration + 1));
    263.   for (int i = 0; i < cellNumber; ++i)
    264.   {
    265.   string cellTypeString = (i + 1).ToString();
    266.   wt.Write(" Cell Type " + cellTypeString + ": " + cellNumberCounts[i]);
    267.   }
    268.   wt.WriteLine();
    269.   wt.Close();
    270.   }
    271.   }
    272.   IterationText.GetComponent<Text>().text = "Iteration: " + (currentIteration + 1).ToString();
    273.   RunText.GetComponent<Text>().text = "Run: " + currentRun.ToString();
    274.   yield return new WaitForSeconds(timeToWait);
    275.   }
    276.   }
    277.   }
    278.   void organizeProbs()
    279.   {
    280.   for (int i = 0; i < (cellNumber * (cellNumber - 1)); i += (cellNumber - 1))
    281.   {
    282.   for (int j = (i + (cellNumber - 2)); j > (i - 1); --j)
    283.   {
    284.   for (int k = (j - 1); k > (i - 1); --k)
    285.   {
    286.   probs[j] = probs[j] + probs[k];
    287.   }
    288.   }
    289.   }
    290.   }
    291.   void SetNeighbor()
    292.   {
    293.   if (orderselect.value != 0)
    294.   {
    295.   // if (gridType.value == 0)
    296.   if (neighborhoodType.value == 0)
    297.   {
    298.   neighborArray = new GameObject[(intGridHeight * intGridWidth) * 4];
    299.   try
    300.   {
    301.   neighborUp = gridOfCells[(xTilesCreated), (yTilesCreated + 1)];
    302.   neighborArray[((((xTilesCreated * intGridWidth) + yTilesCreated) * 4) + 0)] = neighborUp;
    303.   }
    304.   catch (System.IndexOutOfRangeException)
    305.   {
    306.   }
    307.   try
    308.   {
    309.   neighborDown = gridOfCells[(xTilesCreated), (yTilesCreated - 1)];
    310.   neighborArray[((((xTilesCreated * intGridWidth) + yTilesCreated) * 4) + 1)] = neighborDown;
    311.   }
    312.   catch (System.IndexOutOfRangeException)
    313.   {
    314.   }
    315.   try
    316.   {
    317.   neighborLeft = gridOfCells[(xTilesCreated - 1), (yTilesCreated)];
    318.   neighborArray[((((xTilesCreated * intGridWidth) + yTilesCreated) * 4) + 2)] = neighborLeft;
    319.   }
    320.   catch (System.IndexOutOfRangeException)
    321.   {
    322.   }
    323.   try
    324.   {
    325.   neighborRight = gridOfCells[(xTilesCreated + 1), (yTilesCreated)];
    326.   neighborArray[((((xTilesCreated * intGridWidth) + yTilesCreated) * 4) + 3)] = neighborRight;
    327.   }
    328.   catch (System.IndexOutOfRangeException)
    329.   {
    330.   }
    331.   }
    332.   }
    333.   else
    334.   {
    335.   }
    336.   }
    337.   void CheckNeighbors()
    338.   {
    339.   Color upColor = Color.clear;
    340.   Color downColor = Color.clear;
    341.   Color leftColor = Color.clear;
    342.   Color rightColor = Color.clear;
    343.   try
    344.   {
    345.   upColor = neighborArray[((currentCellNumber * 4) + 0)].GetComponent<Renderer>().material.color;
    346.   }
    347.   catch (System.NullReferenceException)
    348.   {
    349.   }
    350.   try
    351.   {
    352.   downColor = neighborArray[((currentCellNumber * 4) + 1)].GetComponent<Renderer>().material.color;
    353.   }
    354.   catch (System.NullReferenceException)
    355.   {
    356.   }
    357.   try
    358.   {
    359.   leftColor = neighborArray[((currentCellNumber * 4) + 2)].GetComponent<Renderer>().material.color;
    360.   }
    361.   catch (System.NullReferenceException)
    362.   {
    363.   }
    364.   try
    365.   {
    366.   rightColor = neighborArray[((currentCellNumber * 4) + 3)].GetComponent<Renderer>().material.color;
    367.   }
    368.   catch (System.NullReferenceException)
    369.   {
    370.   }
    371.   Color[] vonNeumannGroup = new Color[4] { upColor, downColor, leftColor, rightColor };
    372.   List<Color> filteredVonNeumann = new List<Color>();
    373.   for (int currentCheck = 0; currentCheck < cellNumber; ++currentCheck)
    374.   {
    375.   int tempValue = 0;
    376.   if (currentCheck == (cellType - 1)) continue;
    377.   if (currentCheck < (cellType - 1))
    378.   {
    379.   for (int j = 0; j < 4; ++j)
    380.   {
    381.   if (vonNeumannGroup[j] == dict[newColors[currentCheck]])
    382.   {
    383.   filteredVonNeumann.Add(vonNeumannGroup[j]);
    384.   }
    385.   }
    386.   if (filteredVonNeumann.Count == 4)
    387.   {
    388.   tempValue = neighborProbs[(((cellType - 1) * ((cellNumber - 1) * 5)) + (currentCheck * 5)) + 4];
    389.   }
    390.   if (filteredVonNeumann.Count == 3)
    391.   {
    392.   tempValue = neighborProbs[(((cellType - 1) * ((cellNumber - 1) * 5)) + (currentCheck * 5)) + 3];
    393.   }
    394.   if (filteredVonNeumann.Count == 2)
    395.   {
    396.   tempValue = neighborProbs[(((cellType - 1) * ((cellNumber - 1) * 5)) + (currentCheck * 5)) + 2];
    397.   }
    398.   if (filteredVonNeumann.Count == 1)
    399.   {
    400.   tempValue = neighborProbs[(((cellType - 1) * ((cellNumber - 1) * 5)) + (currentCheck * 5)) + 1];
    401.   }
    402.   if (filteredVonNeumann.Count == 0)
    403.   {
    404.   tempValue = neighborProbs[(((cellType - 1) * ((cellNumber - 1) * 5)) + (currentCheck * 5))];
    405.   }
    406.   probs[((cellType - 1) * (cellNumber - 1)) + currentCheck] = tempValue;
    407.   }
    408.   if (currentCheck > (cellType - 1))
    409.   {
    410.   for (int j = 0; j < 4; ++j)
    411.   {
    412.   if (vonNeumannGroup[j] == dict[newColors[currentCheck]])
    413.   {
    414.   filteredVonNeumann.Add(vonNeumannGroup[j]);
    415.   }
    416.   }
    417.   if (filteredVonNeumann.Count == 4)
    418.   {
    419.   tempValue = neighborProbs[(((cellType - 1) * ((cellNumber - 1) * 5)) + ((currentCheck - 1) * 5)) + 4];
    420.   }
    421.   if (filteredVonNeumann.Count == 3)
    422.   {
    423.   tempValue = neighborProbs[(((cellType - 1) * ((cellNumber - 1) * 5)) + ((currentCheck - 1) * 5)) + 3];
    424.   }
    425.   if (filteredVonNeumann.Count == 2)
    426.   {
    427.   tempValue = neighborProbs[(((cellType - 1) * ((cellNumber - 1) * 5)) + ((currentCheck - 1) * 5)) + 2];
    428.   }
    429.   if (filteredVonNeumann.Count == 1)
    430.   {
    431.   tempValue = neighborProbs[(((cellType - 1) * ((cellNumber - 1) * 5)) + ((currentCheck - 1) * 5)) + 1];
    432.   }
    433.   if (filteredVonNeumann.Count == 0)
    434.   {
    435.   tempValue = neighborProbs[(((cellType - 1) * ((cellNumber - 1) * 5)) + ((currentCheck - 1) * 5))];
    436.   }
    437.   probs[((cellType - 1) * (cellNumber - 1)) + (currentCheck - 1)] = tempValue;
    438.   }
    439.   testNumber4 = probs[((cellType - 1) * (cellNumber - 1)) + (currentCheck - 1)];
    440.   testNumber3 = probs.Length;
    441.   }
    442.   }
    443. }
    444. public static class ThreadSafeRandom
    445. {
    446.   [ThreadStatic]
    447.   private static System.Random Local;
    448.   public static System.Random ThisThreadsRandom
    449.   {
    450.   get { return Local ?? (Local = new System.Random(unchecked(Environment.TickCount * 31 + Thread.CurrentThread.ManagedThreadId))); }
    451.   }
    452. }
    453. static class MyExtensions
    454. {
    455.   public static void Shuffle<T>(this IList<T> list)
    456.   {
    457.   int n = list.Count;
    458.   while (n > 1)
    459.   {
    460.   n--;
    461.   int k = ThreadSafeRandom.ThisThreadsRandom.Next(n + 1);
    462.   T value = list[k];
    463.   list[k] = list[n];
    464.   list[n] = value;
    465.   }
    466.   }
    467. }
    I make the array, then assign the references to the neighbors for each cell. Later on I call the same position in the array. But I'm still getting the same effect as before, where everything returns to the one type. Any tips? I'll pore through it more thoroughly later on when I have some more time, but I wanted to put it out here in case there were any tips or errors people could point out.
     
  19. takatok

    takatok

    Joined:
    Aug 18, 2016
    Posts:
    1,496
    A second thing to note about probability chances. Lets say we have 3 states. A cell is in State 0. It has 3 neighbors of state 1, that gives a 0.5 chance of turning to 1. It has 2 neighbors of state 2, that give a 0.8 chance of turning to state2.

    Depending on how you check states and turn the cells, you could be giving a higher chance than needed to some states. Say you check the 0.5 and its true, then you check 0.8 and its true. Is the state set to 2 since that was the last state you checked? If so then the actual chance of state 1 happening is 0.5*.2 = 0.1


    A better approach would be to but the chances in a list
    0->1 = 0.5
    0->2 = 0.8
    0->0 = 0.1 (1-0.5)*(1.-0.8)

    Now clearly these add to > 1.0 So we need to figure out the totalProbability (1.4) and pick a number between 0 and 1.4 then figure out what state we are in from that.

    Given an array of floats above this code will produce the correct index
    Code (CSharp):
    1. private int GetStateFromProbability(float[] probChances)
    2.     {
    3.         float totalProb = 0;
    4.         for (int i = 0; i < probChances.Length; ++i)
    5.             totalProb += probChances[i];
    6.         // this is here so we don't pick the actual last value
    7.         // its a small chance 1 out of however big number the pseudorandom number generator uses
    8.         // but its necessary for absolute toggles of 1.0 probabilities.  Otherwise super rare chance it will be wrong.
    9.         totalProb -= .00001f;
    10.         float[] pickRange = new float[probChances.Length];
    11.      
    12.         pickRange[0] = 0;
    13.         for (int i = 1; i < pickRange.Length; ++i)
    14.             pickRange[i] = pickRange[i - 1] + probChances[i - 1];
    15.         float pick = Random.Range(0, totalProb);
    16.         int index = probChances.Length - 1;
    17.         while (pickRange[index] > pick)
    18.             index--;
    19.         return index;
    20.     }
     
  20. EternalAmbiguity

    EternalAmbiguity

    Joined:
    Dec 27, 2014
    Posts:
    3,144
    That's a good point. I'll have to make an adjustment for that. I'll add it to the list. And by the way, I meant to say it earlier but I definitely want to talk to you later about optimization. And thanks for all the help.

    So for some reason the problem I was having with not being able to return a numerical value (even though I could return a referenced value) disappeared...and appeared elsewhere.

    First, though, I got that part where all cells would return to one type figured out. The new code is here:

    Code (csharp):
    1. using System;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using System.IO;
    5. using System.Linq;
    6. using System.Threading;
    7. using UnityEngine;
    8. using UnityEngine.UI;
    9.  
    10. public class GridMake : MonoBehaviour
    11. {
    12.     GameObject tilePrefab;
    13.     GameObject IterationText;
    14.     GameObject RunText;
    15.     GameObject basePage;
    16.     GameObject probParent;
    17.     GameObject currentInputField;
    18.     public GameObject neighborUp;
    19.     public GameObject neighborDown;
    20.     public GameObject neighborLeft;
    21.     public GameObject neighborRight;
    22.     //GameObject gridCheck;
    23.  
    24.     GameObject[] neighborArray;
    25.     GameObject[] tiles;
    26.     GameObject[,] gridOfCells;
    27.  
    28.     float gridWidth, gridHeight;
    29.     //float time = 0.0f;
    30.     float timeToWait = 0f;
    31.     public int cellType;
    32.     int cellNumber;
    33.     int maxIterations = 0;
    34.     int currentIteration;
    35.     public int arrayCopyRangeStart;
    36.     int result;
    37.     float trueScale;
    38.     float scaleHeight;
    39.     float scaleWidth;
    40.     int maxRuns = 0;
    41.     int currentRun;
    42.     public Toggle saveFileCheck;
    43.     Dropdown neighborhoodType;
    44.     Dictionary<int, Color> dict;
    45.     Dictionary<Color, int> inverseDict;
    46.     int[] newColors;
    47.     Dropdown orderselect;
    48.     int[] probs;
    49.     int[] neighborProbs;
    50.     int[] cellTypeTransitions;
    51.  
    52.     int xTilesCreated, yTilesCreated;
    53.  
    54.  
    55.     int intGridHeight;
    56.     int intGridWidth;
    57.     public int currentCellNumber;
    58.  
    59.     Color currentColor;
    60.  
    61.     public string inputFieldText;
    62.     public int testNumber1;
    63.     public int testNumber2 = 0;
    64.     public int testNumber3;
    65.     public int testNumber4;
    66.     public int testNumber5;
    67.     public GameObject testGameObject;
    68.     public Color neighborUpColor;
    69.     public Color neighborDownColor;
    70.     public Color neighborLeftColor;
    71.     public Color neighborRightColor;
    72.     public GameObject NewneighborUp;
    73.     public GameObject NewneighborDown;
    74.     public GameObject NewneighborLeft;
    75.     public GameObject NewneighborRight;
    76.     public Color upColor;
    77.     public Color downColor;
    78.     public Color leftColor;
    79.     public Color rightColor;
    80.     public string tempTransition;
    81.     public string tempTransition2;
    82.  
    83.     public float tileDistance = 0.0f; //Spacing between tiles
    84.     void Start()
    85.     {
    86.  
    87.     }
    88.  
    89.     void Update()
    90.     {
    91.      
    92.     }
    93.  
    94.     public IEnumerator CreateTiles()
    95.     {
    96.         orderselect = GameObject.FindGameObjectWithTag("OrderDropDown").GetComponent<Dropdown>();
    97.         cellNumber = int.Parse(GameObject.FindGameObjectWithTag("numbercelltype").GetComponent<Text>().text);
    98.         maxIterations = int.Parse(GameObject.Find("IterationCount").GetComponent<Text>().text);
    99.         maxRuns = int.Parse(GameObject.Find("RunCount").GetComponent<Text>().text);
    100.         timeToWait = float.Parse(GameObject.Find("WaitTimeCount").GetComponent<Text>().text);
    101.         IterationText = GameObject.Find("IterationText");
    102.         RunText = GameObject.Find("RunText");
    103.         newColors = new int[cellNumber];
    104.         probs = new int[(cellNumber) * (cellNumber - 1)];
    105.         int[] cellNumberCounts = new int[cellNumber];
    106.         Array[] startingCellAmountGroups = new Array[cellNumber];
    107.         neighborhoodType = GameObject.FindGameObjectWithTag("NeighborhoodDropdown").GetComponent<Dropdown>();
    108.  
    109.         if (orderselect.value == 0)
    110.         {
    111.             cellTypeTransitions = new int[(cellNumber) * (cellNumber - 1)];
    112.             for (int i = 0; i < cellNumber; ++i)
    113.             {
    114.                 string numstring = (i + 1).ToString();
    115.                 newColors[i] = GameObject.Find("NMP" + (orderselect.value + 1) + numstring).transform.Find("ColorDropdown").GetComponent<Dropdown>().value;
    116.                 GameObject basePage = GameObject.Find("NMP" + (orderselect.value + 1) + numstring);
    117.                 startingCellAmountGroups[i] = new GameObject[int.Parse(GameObject.Find("NMP" + (orderselect.value + 1) + numstring).transform.Find("StartNumberInputField").transform.Find("Text").GetComponent<Text>().text)];
    118.  
    119.                 for (int j = 1; j < cellNumber; ++j)
    120.                 {
    121.                     string secondNumstring = (j).ToString();
    122.                     GameObject prob = basePage.transform.Find("ProbabilityScrollView").transform.Find("1stProbContent").gameObject;
    123.  
    124.                     probs[((j + ((cellNumber - 1) * i)) - 1)] = Mathf.RoundToInt((float.Parse(prob.transform.Find("ProbInputField1." + secondNumstring).Find("ProbTransValue").GetComponent<Text>().text)) * 1000000000) * (int.MaxValue / 1000000000);
    125.  
    126.                     string tempTransition = prob.transform.Find("ProbInputField1." + secondNumstring).transform.Find("ProbTransText").GetComponent<Text>().text.Remove(0, 33);
    127.                     string tempTransition2 = tempTransition.Remove(1, 1);
    128.                     int transitionNumber = int.Parse(tempTransition2);
    129.                     cellTypeTransitions[((j + ((cellNumber - 1) * i)) - 1)] = transitionNumber;
    130.                 }
    131.             }
    132.             //NOTE!!! The above sets a minimum probability value of 0.000000001. Changing to using floats might solve the problem.
    133.         }
    134.  
    135.         if (orderselect.value == 1)
    136.         {
    137.             if (neighborhoodType.value == 0)
    138.             {
    139.                 cellTypeTransitions = new int[((cellNumber - 1) * 5) * cellNumber];
    140.                 neighborProbs = new int[((cellNumber - 1) * 5) * cellNumber];
    141.                 for (int i = 0; i < cellNumber; ++i)
    142.                 {
    143.                     string numstring = (i + 1).ToString();
    144.                     basePage = GameObject.Find("NMP2" + numstring);
    145.                     probParent = basePage.transform.Find("ProbabilityScrollView").transform.Find("1stProbContent").gameObject;
    146.                     startingCellAmountGroups[i] = new GameObject[int.Parse(GameObject.Find("NMP" + (orderselect.value + 1) + numstring).transform.Find("StartNumberInputField").transform.Find("Text").GetComponent<Text>().text)];
    147.                     newColors[i] = GameObject.Find("NMP2" + numstring).transform.Find("ColorDropdown").GetComponent<Dropdown>().value;
    148.                     for (int j = 0; j < (cellNumber - 1); ++j)
    149.                     {
    150.                         for (int k = 0; k < 5; ++k)
    151.                         {
    152.                             string secondNumString = (j + 1).ToString();
    153.                             currentInputField = probParent.transform.Find("ProbInputField2." + secondNumString + "." + k.ToString()).gameObject;
    154.                             inputFieldText = currentInputField.transform.Find("ProbTransValue").GetComponent<Text>().text;
    155.                             neighborProbs[(i * ((cellNumber - 1) * 5)) + (j * 5) + k] = Mathf.RoundToInt((float.Parse(probParent.transform.Find("ProbInputField2." + secondNumString + "." + k.ToString()).transform.Find("ProbTransValue").GetComponent<Text>().text)) * 1000000000) * (int.MaxValue / 1000000000);
    156.                             tempTransition = probParent.transform.Find("ProbInputField2." + secondNumString + "." + k).transform.Find("ProbTransText").GetComponent<Text>().text.Remove(0, 61);
    157.                             tempTransition2 = tempTransition.Remove(1, 8);
    158.                             int transitionNumber = int.Parse(tempTransition2);
    159.                             cellTypeTransitions[(i * ((cellNumber - 1) * 5)) + (j * 5) + k] = transitionNumber;
    160.                         }
    161.                     }
    162.                 }
    163.             }
    164.         }
    165.  
    166.         //This dictionary only uses the Unity-named colors because it's easier to pick these in a drop-down than to have a color selector.
    167.         //Future updates might adjust the UI page to allow for direct color selection, which could then be passed to this script and avoid the artificial limitations here.
    168.         //After all, this can only show up to 9 cell types. Any number is possible, but with only 9 colors you can only differentiate between nine types.
    169.         dict = new Dictionary<int, Color>();
    170.         dict.Add(0, Color.black);
    171.         dict.Add(1, Color.blue);
    172.         dict.Add(2, Color.cyan);
    173.         dict.Add(3, Color.gray);
    174.         dict.Add(4, Color.green);
    175.         dict.Add(5, Color.magenta);
    176.         dict.Add(6, Color.red);
    177.         dict.Add(7, Color.white);
    178.         dict.Add(8, Color.yellow);
    179.  
    180.         inverseDict = new Dictionary<Color, int>();
    181.         inverseDict.Add(Color.black, 0);
    182.         inverseDict.Add(Color.blue, 1);
    183.         inverseDict.Add(Color.cyan, 2);
    184.         inverseDict.Add(Color.gray, 3);
    185.         inverseDict.Add(Color.green, 4);
    186.         inverseDict.Add(Color.magenta, 5);
    187.         inverseDict.Add(Color.red, 6);
    188.         inverseDict.Add(Color.white, 7);
    189.         inverseDict.Add(Color.yellow, 8);
    190.  
    191.  
    192.         //UI stuff and grid formation
    193.         {
    194.             tilePrefab = GameObject.FindGameObjectWithTag("tilePrefab");
    195.             gridWidth = int.Parse(GameObject.Find("Horigrid").GetComponent<Text>().text);
    196.             gridHeight = int.Parse(GameObject.Find("Vertgrid").GetComponent<Text>().text);
    197.             intGridWidth = Convert.ToInt32(Math.Ceiling(gridWidth));
    198.             intGridHeight = Convert.ToInt32(Math.Ceiling(gridHeight));
    199.  
    200.             float xOffset = 0.0f, yOffset = 0.0f;
    201.             scaleHeight = ((Screen.height / (Screen.height / 10)) / gridHeight);
    202.             scaleWidth = ((Screen.width / (Screen.width / 10)) / gridWidth);
    203.             trueScale = Mathf.Min(scaleHeight, scaleWidth);
    204.             tilePrefab.transform.localScale = new Vector3(trueScale, trueScale, trueScale);
    205.  
    206.             gridOfCells = new GameObject[intGridWidth, intGridHeight];
    207.  
    208.             for (xTilesCreated = 0; xTilesCreated < gridWidth; xTilesCreated += 1)
    209.             {
    210.                 for (yTilesCreated = 0; yTilesCreated < gridHeight; yTilesCreated += 1)
    211.                 {
    212.                     Vector3 position = new Vector3(transform.position.x + xOffset + (trueScale / 2), transform.position.y + yOffset + (trueScale / 2), transform.position.z);
    213.  
    214.                     GameObject tile = Instantiate(tilePrefab, position, Quaternion.identity) as GameObject;
    215.                     yOffset += trueScale + tileDistance;
    216.                     tile.name = ("Cell" + ((xTilesCreated * gridWidth) + yTilesCreated));
    217.                     tile.tag = ("Cells");
    218.                     gridOfCells[xTilesCreated, yTilesCreated] = tile;
    219.                 }
    220.                 xOffset += trueScale + tileDistance;
    221.                 yOffset = 0.0f;
    222.             }
    223.             tilePrefab.GetComponent<Renderer>().enabled = false;
    224.             tiles = GameObject.FindGameObjectsWithTag("Cells");
    225.             for (xTilesCreated = 0; xTilesCreated < gridWidth; xTilesCreated += 1)
    226.             {
    227.                 for (yTilesCreated = 0; yTilesCreated < gridHeight; yTilesCreated += 1)
    228.                 {
    229.                     SetNeighbor();
    230.                     yield return new WaitForSeconds(0.5f);
    231.                 }
    232.             }
    233.  
    234.             using (StreamWriter wt = File.AppendText(Application.persistentDataPath + "Run " + currentRun + ".txt"))
    235.             {
    236.                 wt.Write("Iteration: " + (currentIteration + 1));
    237.                 for (int i = 0; i < neighborArray.Length; ++i)
    238.                 {
    239.                     try
    240.                     {
    241.                         wt.Write(neighborArray[i].name);
    242.                     }
    243.                     catch (System.NullReferenceException)
    244.                     {
    245.                         wt.Write("Empty");
    246.                     }
    247.                  
    248.                     wt.WriteLine();
    249.                 }
    250.              
    251.                 wt.Close();
    252.             }
    253.  
    254.             foreach (GameObject tile in tiles)
    255.             {
    256.                 tile.transform.localPosition = new Vector3((tile.transform.position.x - (gridWidth / 2) * trueScale), (tile.transform.position.y - ((gridHeight / 2)) * trueScale), tile.transform.position.z);
    257.                 tile.GetComponent<Renderer>().enabled = true;
    258.             }
    259.          
    260.         }
    261.  
    262.         //gridCheck = gridOfCells[1, 0];
    263.  
    264.         //This sets up the probability array to have the appropriate values for probability ranges
    265.         if (orderselect.value == 0)
    266.         {
    267.             for (int i = 0; i < (cellNumber * (cellNumber - 1)); i += (cellNumber - 1))
    268.             {
    269.                 for (int j = (i + (cellNumber - 2)); j > (i - 1); --j)
    270.                 {
    271.                     for (int k = (j - 1); k > (i - 1); --k)
    272.                     {
    273.                         probs[j] = probs[j] + probs[k];
    274.                     }
    275.                 }
    276.  
    277.             }
    278.         }
    279.  
    280.         //This is the actual iteration loop
    281.         for (currentRun = 1; currentRun < (maxRuns + 1); ++currentRun)
    282.         {
    283.             //This first shuffles the group of objects, then changes the colors of the objects based on cell type starting amounts
    284.             tiles.Shuffle();
    285.             for (int i = 0; i < cellNumber; ++i)
    286.             {
    287.                 arrayCopyRangeStart = 0;
    288.                 int j = 0;
    289.                 for (int k = (i - 1); k > (-1); --k)
    290.                 {
    291.                     arrayCopyRangeStart = j + startingCellAmountGroups[k].Length;
    292.                     j = arrayCopyRangeStart;
    293.                 }
    294.                 Array.Copy(tiles, arrayCopyRangeStart, startingCellAmountGroups[i], 0, startingCellAmountGroups[i].Length);
    295.                 foreach (GameObject tile in startingCellAmountGroups[i])
    296.                 {
    297.                     tile.GetComponent<Renderer>().material.color = dict[newColors[i]];
    298.                     cellType = (i + 1);
    299.                 }
    300.             }
    301.  
    302.             for (currentIteration = 0; currentIteration < (maxIterations); currentIteration++)
    303.             {
    304.                 tiles.Shuffle();
    305.                 for (int k = 0; k < cellNumber; ++k) cellNumberCounts[k] = 0;
    306.                 foreach (GameObject tile in tiles)
    307.                 {
    308.                     if (orderselect.value == 1)
    309.                     {
    310.                         cellType = (Array.IndexOf(newColors, inverseDict[tile.GetComponent<Renderer>().material.color])) + 1;
    311.                         currentCellNumber = int.Parse(tile.name.Remove(0, 4));
    312.                         CheckNeighbors();
    313.                         organizeProbs();
    314.                     }
    315.                  
    316.                     result = UnityEngine.Random.Range(0, int.MaxValue);
    317.                     testNumber1 = result;
    318.                         int i = ((cellType - 1) * (cellNumber - 1));
    319.                     if (orderselect.value == 1)
    320.                         {
    321.                             for (int j = (i + (cellNumber - 2)); j > i; --j)
    322.                             {
    323.                                 testNumber2 = probs[j];
    324.                                 testNumber4 = probs[i];
    325.                                 if (result < probs[j] && result >= probs[j - 1])
    326.                                 {
    327.                                     tile.GetComponent<Renderer>().material.color = dict[newColors[(cellTypeTransitions[((i * 5) + ((j - i) * 5))] - 1)]];
    328.                                     cellType = cellTypeTransitions[((i * 5) + ((j - i) * 5))];
    329.                                     goto Label;
    330.                                 }
    331.                             }
    332.                             if (result < probs[i])
    333.                             {
    334.                                 tile.GetComponent<Renderer>().material.color = dict[newColors[(cellTypeTransitions[(i * 5)] - 1)]];
    335.                                 cellType = cellTypeTransitions[(i * 5)];
    336.                                 goto Label;
    337.                             }
    338.                         }
    339.                     if (orderselect.value == 0)
    340.                             {
    341.                                 for (int j = (i + (cellNumber - 2)); j > i; --j)
    342.                                 {
    343.                                     testNumber2 = probs[j];
    344.                                     testNumber4 = probs[i];
    345.                                     if (result < probs[j] && result >= probs[j - 1])
    346.                                     {
    347.                                         tile.GetComponent<Renderer>().material.color = dict[newColors[(cellTypeTransitions[j] - 1)]];
    348.                                         cellType = cellTypeTransitions[j];
    349.                                         goto Label;
    350.                                     }
    351.                                 }
    352.                                 if (result < probs[i])
    353.                                 {
    354.                                     tile.GetComponent<Renderer>().material.color = dict[newColors[(cellTypeTransitions[i] - 1)]];
    355.                                     cellType = cellTypeTransitions[i];
    356.                                     goto Label;
    357.                                 }
    358.                             }
    359.                 Label:
    360.                     ;
    361.  
    362.                     cellNumberCounts[(cellType - 1)] += 1;
    363.                 }
    364.              
    365.                 if (saveFileCheck.isOn == true)
    366.                 {
    367.                     using (StreamWriter wt = File.AppendText(Application.persistentDataPath + "Run " + currentRun + ".txt"))
    368.                     {
    369.                         wt.Write("Iteration: " + (currentIteration + 1));
    370.                         for (int i = 0; i < cellNumber; ++i)
    371.                         {
    372.                             string cellTypeString = (i + 1).ToString();
    373.                             wt.Write(" Cell Type " + cellTypeString + ": " + cellNumberCounts[i]);
    374.                         }
    375.                         wt.WriteLine();
    376.                         wt.Close();
    377.                     }
    378.                 }
    379.                     IterationText.GetComponent<Text>().text = "Iteration: " + (currentIteration + 1).ToString();
    380.                     RunText.GetComponent<Text>().text = "Run: " + currentRun.ToString();
    381.                     yield return new WaitForSeconds(timeToWait);
    382.                 }
    383.         }
    384.     }
    385.  
    386.     void organizeProbs()
    387.     {
    388.         for (int i = 0; i < (cellNumber * (cellNumber - 1)); i += (cellNumber - 1))
    389.         {
    390.             for (int j = (i + (cellNumber - 2)); j > (i - 1); --j)
    391.             {
    392.                 for (int k = (j - 1); k > (i - 1); --k)
    393.                 {
    394.                     probs[j] = probs[j] + probs[k];
    395.                 }
    396.             }
    397.  
    398.         }
    399.     }
    400.  
    401.     void SetNeighbor()
    402.     {
    403.         if (orderselect.value != 0)
    404.         {
    405.             // if (gridType.value == 0)
    406.             if (neighborhoodType.value == 0)
    407.             {
    408.                 neighborArray = new GameObject[(intGridHeight * intGridWidth) * 4];
    409.                 try
    410.                 {
    411.                     neighborUp = gridOfCells[(xTilesCreated), (yTilesCreated + 1)];
    412.                 }
    413.                 catch (System.IndexOutOfRangeException)
    414.                 {
    415.                     neighborUp = null;
    416.                 }
    417.                 try
    418.                 {
    419.                     neighborDown = gridOfCells[(xTilesCreated), (yTilesCreated - 1)];
    420.                 }
    421.                 catch (System.IndexOutOfRangeException)
    422.                 {
    423.                     neighborDown = null;
    424.                 }
    425.                 try
    426.                 {
    427.                     neighborLeft = gridOfCells[(xTilesCreated - 1), (yTilesCreated)];
    428.                 }
    429.                 catch (System.IndexOutOfRangeException)
    430.                 {
    431.                     neighborLeft = null;
    432.                 }
    433.                 try
    434.                 {
    435.                     neighborRight = gridOfCells[(xTilesCreated + 1), (yTilesCreated)];
    436.                 }
    437.                 catch (System.IndexOutOfRangeException)
    438.                 {
    439.                     neighborRight = null;
    440.                 }
    441.                 neighborArray[((((xTilesCreated * intGridWidth) + yTilesCreated) * 4) + 0)] = neighborUp;
    442.                 neighborArray[((((xTilesCreated * intGridWidth) + yTilesCreated) * 4) + 1)] = neighborDown;
    443.                 neighborArray[((((xTilesCreated * intGridWidth) + yTilesCreated) * 4) + 2)] = neighborLeft;
    444.                 neighborArray[((((xTilesCreated * intGridWidth) + yTilesCreated) * 4) + 3)] = neighborRight;
    445.                 testGameObject = neighborArray[((((xTilesCreated * intGridWidth) + yTilesCreated) * 4) + 0)];
    446.  
    447.             }
    448.         }
    449.         else
    450.         {
    451.         }
    452.     }
    453.  
    454.     void CheckNeighbors()
    455.     {
    456.         upColor = Color.clear;
    457.         downColor = Color.clear;
    458.         leftColor = Color.clear;
    459.         rightColor = Color.clear;
    460.         testNumber5 = (currentCellNumber * 4);
    461.         try
    462.         {
    463.             upColor = neighborArray[((currentCellNumber * 4) + 0)].GetComponent<Renderer>().material.color;
    464.             NewneighborUp = neighborArray[((currentCellNumber * 4) + 0)];
    465.             neighborUpColor = upColor;
    466.         }
    467.         catch (System.NullReferenceException)
    468.         {
    469.         }
    470.         try
    471.         {
    472.             downColor = neighborArray[((currentCellNumber * 4) + 1)].GetComponent<Renderer>().material.color;
    473.             NewneighborDown = neighborArray[((currentCellNumber * 4) + 1)];
    474.             neighborDownColor = downColor;
    475.         }
    476.         catch (System.NullReferenceException)
    477.         {
    478.         }
    479.         try
    480.         {
    481.             leftColor = neighborArray[((currentCellNumber * 4) + 2)].GetComponent<Renderer>().material.color;
    482.             NewneighborLeft = neighborArray[((currentCellNumber * 4) + 2)];
    483.             neighborLeftColor = leftColor;
    484.         }
    485.         catch (System.NullReferenceException)
    486.         {
    487.         }
    488.         try
    489.         {
    490.             rightColor = neighborArray[((currentCellNumber * 4) + 3)].GetComponent<Renderer>().material.color;
    491.             NewneighborRight = neighborArray[((currentCellNumber * 4) + 3)];
    492.             neighborRightColor = rightColor;
    493.         }
    494.         catch (System.NullReferenceException)
    495.         {
    496.         }
    497.         Color[] vonNeumannGroup = new Color[4] { upColor, downColor, leftColor, rightColor };
    498.         List<Color> filteredVonNeumann = new List<Color>();
    499.         for (int currentCheck = 0; currentCheck < cellNumber; ++currentCheck)
    500.         {
    501.             int tempValue = 0;
    502.             if (currentCheck == (cellType - 1)) continue;
    503.             if (currentCheck < (cellType - 1))
    504.             {
    505.                 for (int j = 0; j < 4; ++j)
    506.                 {
    507.  
    508.                     if (vonNeumannGroup[j] == dict[newColors[currentCheck]])
    509.                     {
    510.                         filteredVonNeumann.Add(vonNeumannGroup[j]);
    511.                     }
    512.                 }
    513.                 testNumber3 = filteredVonNeumann.Count;
    514.                 if (filteredVonNeumann.Count == 4)
    515.                 {
    516.                     tempValue = neighborProbs[(((cellType - 1) * ((cellNumber - 1) * 5)) + (currentCheck * 5)) + 4];
    517.                 }
    518.                 if (filteredVonNeumann.Count == 3)
    519.                 {
    520.                     tempValue = neighborProbs[(((cellType - 1) * ((cellNumber - 1) * 5)) + (currentCheck * 5)) + 3];
    521.                 }
    522.                 if (filteredVonNeumann.Count == 2)
    523.                 {
    524.                     tempValue = neighborProbs[(((cellType - 1) * ((cellNumber - 1) * 5)) + (currentCheck * 5)) + 2];
    525.                 }
    526.                 if (filteredVonNeumann.Count == 1)
    527.                 {
    528.                     tempValue = neighborProbs[(((cellType - 1) * ((cellNumber - 1) * 5)) + (currentCheck * 5)) + 1];
    529.                 }
    530.                 if (filteredVonNeumann.Count == 0)
    531.                 {
    532.                     tempValue = neighborProbs[(((cellType - 1) * ((cellNumber - 1) * 5)) + (currentCheck * 5))];
    533.                 }
    534.                 probs[((cellType - 1) * (cellNumber - 1)) + currentCheck] = tempValue;
    535.             }
    536.             else if (currentCheck > (cellType - 1))
    537.             {
    538.                 for (int j = 0; j < 4; ++j)
    539.                 {
    540.  
    541.                     if (vonNeumannGroup[j] == dict[newColors[currentCheck]])
    542.                     {
    543.                         filteredVonNeumann.Add(vonNeumannGroup[j]);
    544.                     }
    545.                 }
    546.                 if (filteredVonNeumann.Count == 4)
    547.                 {
    548.                     tempValue = neighborProbs[(((cellType - 1) * ((cellNumber - 1) * 5)) + ((currentCheck - 1) * 5)) + 4];
    549.                 }
    550.                 if (filteredVonNeumann.Count == 3)
    551.                 {
    552.                     tempValue = neighborProbs[(((cellType - 1) * ((cellNumber - 1) * 5)) + ((currentCheck - 1) * 5)) + 3];
    553.                 }
    554.                 if (filteredVonNeumann.Count == 2)
    555.                 {
    556.                     tempValue = neighborProbs[(((cellType - 1) * ((cellNumber - 1) * 5)) + ((currentCheck - 1) * 5)) + 2];
    557.                 }
    558.                 if (filteredVonNeumann.Count == 1)
    559.                 {
    560.                     tempValue = neighborProbs[(((cellType - 1) * ((cellNumber - 1) * 5)) + ((currentCheck - 1) * 5)) + 1];
    561.                 }
    562.                 if (filteredVonNeumann.Count == 0)
    563.                 {
    564.                     tempValue = neighborProbs[(((cellType - 1) * ((cellNumber - 1) * 5)) + ((currentCheck - 1) * 5))];
    565.                 }
    566.                 probs[((cellType - 1) * (cellNumber - 1)) + (currentCheck - 1)] = tempValue;
    567.             }
    568.          
    569.         }
    570.     }
    571. }
    572.  
    573. public static class ThreadSafeRandom
    574. {
    575.     [ThreadStatic]
    576.     private static System.Random Local;
    577.  
    578.     public static System.Random ThisThreadsRandom
    579.     {
    580.         get { return Local ?? (Local = new System.Random(unchecked(Environment.TickCount * 31 + Thread.CurrentThread.ManagedThreadId))); }
    581.     }
    582. }
    583.  
    584. static class MyExtensions
    585. {
    586.     public static void Shuffle<T>(this IList<T> list)
    587.     {
    588.         int n = list.Count;
    589.         while (n > 1)
    590.         {
    591.             n--;
    592.             int k = ThreadSafeRandom.ThisThreadsRandom.Next(n + 1);
    593.             T value = list[k];
    594.             list[k] = list[n];
    595.             list[n] = value;
    596.         }
    597.     }
    598. }
    Everything is working thus far except for the actual neighbor effects themselves--the probabilities don't increase like they should because the cell isn't detecting its neighbors properly. It isn't detecting its neighbors properly because the array which should have the references is basically all empty for some reason I don't understand.

    So I'm having the problem where the numerical value doesn't get the reference properly, or perhaps more accurately the reference in an array isn't being stored. The key part for this is the SetNeighbors function, line 401. The values in neighborArray should be updated to include the different neighbors, and while the script is running they can be "seen" to be correct (in the editor, through a public variable), but later on they don't seem to stay there. I write the neighborArray to text (line 234), but almost every value is empty.

    Any tips?
     
  21. takatok

    takatok

    Joined:
    Aug 18, 2016
    Posts:
    1,496
    I think its going to be hard to debug this since you've mixed your model(data) with your view(how to display the data).

    I whipped up some code that is purely the CA independent of displaying it, its 4 script files none of which need to be on a GameObject. They just sit in your scripts folder.

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class Point
    6. {
    7.     private int x;
    8.     private int y;
    9.  
    10.     public int X
    11.     {
    12.         get { return x; }
    13.         set { x=value; }
    14.     }
    15.  
    16.     public int Y
    17.     {
    18.         get { return y; }
    19.         set { y = value; }
    20.     }
    21.  
    22.     public Point()
    23.         :this(0,0)
    24.     {}
    25.  
    26.     public Point(int x, int y)
    27.     {
    28.         X = x;
    29.         Y = y;
    30.     }
    31.  
    32.     public bool onGrid(int width, int height)
    33.     {
    34.         if (x < 0 || y < 0)
    35.             return false;
    36.         if (x >= width || y >= height)
    37.             return false;
    38.         return true;
    39.     }
    40.  
    41.     public static Point operator +(Point a, Point b)
    42.     {
    43.         return new Point(a.x + b.x, a.y + b.y);
    44.     }
    45.  
    46. }
    47.  

    Code (CSharp):
    1. using System;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using UnityEngine;
    5.  
    6. public class Neighborhood
    7. {
    8.     static Point[] MooreOffsets = new Point[8]
    9.     {
    10.         new Point(0,1),
    11.         new Point(1,1),
    12.         new Point(1,0),
    13.         new Point(1,-1),
    14.         new Point(0,-1),
    15.         new Point(-1,-1),
    16.         new Point(-1,0),
    17.         new Point(-1,1)
    18.     };
    19.  
    20.     static Point[] vonNeumannOffsets = new Point[4]
    21.     {
    22.         new Point(0,1),
    23.         new Point(1,0),
    24.         new Point(0,-1),
    25.         new Point(-1,0)
    26.     };
    27.  
    28.     private NType neighborType;
    29.  
    30.     public NType NeighborType
    31.     {
    32.         get { return neighborType; }
    33.         set { neighborType = value; }
    34.     }
    35.  
    36.     public Neighborhood(NType type)
    37.     {
    38.         neighborType = type;
    39.     }
    40.  
    41.     public List<Point> GetNeighbors(int x, int y)
    42.     {
    43.         return GetNeighbors(new Point(x, y));
    44.     }
    45.     public List<Point> GetNeighbors(Point cell)
    46.     {
    47.         switch (neighborType)
    48.         {
    49.             case NType.Moore:
    50.                 return GetMooreNeighbors(cell);
    51.             case NType.VonNeumann:
    52.                 return GetVonNeumannNeighbors(cell);
    53.             default:
    54.                 throw new ArgumentException("Unkown Neighborhood type in GetNeighbors");
    55.         }
    56.     }
    57.  
    58.     private List<Point> GetMooreNeighbors(Point cell)
    59.     {
    60.         List<Point> neighbors = new List<Point>();
    61.         foreach(Point p in MooreOffsets)
    62.             neighbors.Add(cell + p);
    63.         return neighbors;
    64.     }
    65.  
    66.     private List<Point> GetVonNeumannNeighbors(Point cell)
    67.     {
    68.         List<Point> neighbors = new List<Point>();
    69.         foreach (Point p in vonNeumannOffsets)
    70.             neighbors.Add(cell + p);
    71.         return neighbors;
    72.     }
    73.  
    74.     public  int GetNeighborSize()
    75.     {
    76.         switch (neighborType)
    77.         {
    78.             case NType.Moore:
    79.                 return 8;
    80.             case NType.VonNeumann:
    81.                 return 4;
    82.             default:
    83.                 throw new ArgumentException("Unkown NeighborHood type in GetNeighborSize");
    84.         }
    85.     }
    86.  
    87. }
    88.  
    89. public enum NType
    90. {
    91.     VonNeumann,
    92.     Moore
    93. }
    94.  

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class CellState
    6. {
    7.     // prob of becoming state X , with Y neighbors of X
    8.     float[,] prob;
    9.  
    10.     public CellState( int totalStates,int neighborSize)
    11.     {
    12.         prob = new float[totalStates, neighborSize + 1];
    13.     }
    14.  
    15.     public void SetProbablity(int state, int numNeighbors, float val)
    16.     {
    17.         prob[state, numNeighbors] = val;
    18.     }
    19.     public float GetProbablity(int state, int numNeighbors)
    20.     {
    21.         return prob[state, numNeighbors];
    22.     }
    23. }
    24.  

    Code (CSharp):
    1. using System;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using UnityEngine;
    5. using Random = UnityEngine.Random;
    6.  
    7. public class CA
    8. {
    9.     private CellState[] states;
    10.     private Neighborhood neighborhood;
    11.     private int[,] grid;
    12.     private int[,] backup;
    13.     private int gridWidth;
    14.     private int gridHeight;
    15.     private int numStates;
    16.  
    17.  
    18.     public CA(int width, int height, int numStates, NType type)
    19.     {
    20.         gridWidth = width;
    21.         gridHeight = height;
    22.         this.numStates = numStates;
    23.         grid = new int[width, height];
    24.         backup = new int[width, height];
    25.         neighborhood = new Neighborhood(type);
    26.         states = new CellState[numStates];
    27.         for (int i = 0; i < numStates; ++i)
    28.             states[i] = new CellState(numStates, neighborhood.GetNeighborSize());
    29.  
    30.  
    31.     }
    32.  
    33.     public void SetStateInfo(int startState, int endState,int numNeighbors, float prob)
    34.     {
    35.         states[startState].SetProbablity(endState, numNeighbors, prob);
    36.     }
    37.  
    38.     public void SetCellState(int x, int y, int state)
    39.     {
    40.         grid[x, y] = state;
    41.     }
    42.  
    43.     public int GetCellState(int x, int y)
    44.     {
    45.         return grid[x, y];
    46.     }
    47.  
    48.     public void OneIteration()
    49.     {
    50.  
    51.         Array.Copy(grid, backup, gridWidth * gridHeight);
    52.         for (int x=0;x<gridWidth; ++x)
    53.         {
    54.             for (int y=0;y<gridHeight;++y)
    55.             {
    56.                 int currentState = grid[x, y];
    57.                 List<int> neighborStateCount = GetNeighborCount(x,y);
    58.                 float[] probChances  = GetProbChances(currentState,neighborStateCount);
    59.                 grid[x, y] = GetStateFromProbability(probChances);
    60.             }
    61.         }
    62.     }
    63.  
    64.     private List<int> GetNeighborCount(int x, int y)
    65.     {
    66.         List<int> neighborCount = new List<int>();
    67.         for (int i = 0; i < numStates; ++i)
    68.             neighborCount.Add(0);
    69.         List<Point> neighbors = neighborhood.GetNeighbors(x, y);
    70.  
    71.         // Get a count of each state in our neighborhood
    72.         foreach (Point p in neighbors)
    73.         {
    74.             if (!p.onGrid(gridWidth, gridHeight))
    75.                 continue;
    76.             neighborCount[backup[p.X, p.Y]]++;
    77.         }
    78.         return neighborCount;
    79.     }
    80.  
    81.     private float[] GetProbChances(int currentState,List<int> neighborStateCount)
    82.     {
    83.         float[] probChances = new float[numStates];
    84.         float noChangeProb = 1;
    85.         for (int p = 0; p < numStates; ++p)
    86.         {
    87.             // skip if we are on the state of the current cell
    88.             // we'll figure it out after we find out all the other probs
    89.             if (p == currentState)
    90.                 continue;
    91.             float prob = states[currentState].GetProbablity(p, neighborStateCount[p]);
    92.             probChances[p] = prob;
    93.             // the chance of us not changing is the product of all the other states not happening
    94.             noChangeProb *= (1 - prob);
    95.         }
    96.         probChances[currentState] = noChangeProb;
    97.         return probChances;
    98.     }
    99.  
    100.     private int GetStateFromProbability(float[] probChances)
    101.     {
    102.         float totalProb = 0;
    103.         for (int i = 0; i < probChances.Length; ++i)
    104.             totalProb += probChances[i];
    105.         // this is here so we don't pick the actual last value
    106.         // its a small chance 1 out of however big number the pseudorandom number generator uses
    107.         // but its necessary for absolute toggles of 1.0 probabilities.  Otherwise super rare chance it will be wrong.
    108.         totalProb -= .00001f;
    109.         float[] pickRange = new float[probChances.Length];
    110.  
    111.         pickRange[0] = 0;
    112.         for (int i = 1; i < pickRange.Length; ++i)
    113.             pickRange[i] = pickRange[i - 1] + probChances[i - 1];
    114.         float pick = Random.Range(0, totalProb);
    115.         int index = probChances.Length - 1;
    116.         while (pickRange[index] > pick)
    117.             index--;
    118.         return index;
    119.     }
    120. }
    121.  

    Now these 4 classes are split to handle a particular job. Each method also does one thing. Note when reading OneIteration of the CA class you can easily get a feel for what its doing. If there is a bug, you can first check out how GetNeighbors work, make sure its doing what it should. Then check GetProbChances and so on.

    I changed how your design worked slightly be getting rid of the idea of order 1.0 and order 1.5. Every CA is just a probablistic chance of going from A->B based on how many B neighbors A has. You can implement determinstic CA like Conway's Life by just using 100% probabilities.

    Here is a very primitive View implementation of Conway's Life
    Just attach this script to an empty GameObject, Press play and then S to toggle through one iteration.
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class LifeTest : MonoBehaviour
    6. {
    7.     CA myCA;
    8.     Texture2D tex;
    9.     Sprite board;
    10.     SpriteRenderer sr;
    11.  
    12.     private void Awake()
    13.     {
    14.         myCA = new CA(50, 50, 2, NType.Moore);
    15.  
    16.  
    17.         for (int i = 0; i <= 8; ++i)
    18.         {
    19.             // 0s never become 1s
    20.             myCA.SetStateInfo(0, 1, i, 0);
    21.             // 1s always become 0s
    22.             myCA.SetStateInfo(1, 0, i, 1);
    23.         }
    24.         // only 3 neighbors can turn a 0 into 1
    25.         myCA.SetStateInfo(0, 1, 3, 1);
    26.         // 2 or 3 neighbors stop 1s from being 0s
    27.         // 6 empty = 2 live,  5 empty = 3 live
    28.         myCA.SetStateInfo(1, 0, 6, 0);
    29.         myCA.SetStateInfo(1, 0, 5, 0);
    30.  
    31.           for (int i = 0; i < 50; ++i)
    32.           {
    33.               for (int j = 0; j < 50; ++j)
    34.               {
    35.                   if (Random.Range(0, 10) == 0)
    36.                       myCA.SetCellState(i, j, 1);
    37.               }
    38.           }
    39.     }
    40.  
    41.     private void Start()
    42.     {
    43.         tex = new Texture2D(500, 500);
    44.         board = Sprite.Create(tex, new Rect(Vector2.zero, new Vector2(500, 500)), Vector2.zero, 100);
    45.         sr = gameObject.AddComponent<SpriteRenderer>();
    46.         sr.sprite = board;
    47.         UpdateBoard();
    48.     }
    49.  
    50.     private void Update()
    51.     {
    52.         if (Input.GetKeyDown(KeyCode.S))
    53.         {
    54.             myCA.OneIteration();
    55.             UpdateBoard();
    56.         }
    57.     }
    58.  
    59.     private void UpdateBoard()
    60.     {
    61.         Color tileColor;
    62.         for (int i = 0; i < 50; ++i)
    63.         {
    64.             for (int j = 0; j < 50; ++j)
    65.             {
    66.                 if (myCA.GetCellState(i, j) == 0)
    67.                     tileColor = Color.black;
    68.                 else
    69.                     tileColor = Color.yellow;
    70.                 for (int a = i * 10; a < i * 10 + 10; ++a)
    71.                     for (int b = j * 10; b < j * 10 + 10; ++b)
    72.                         tex.SetPixel(a, b, tileColor);
    73.             }
    74.         }
    75.         tex.Apply();
    76.     }
    77.  
    78. }

    Here is a another implementation of a 3 State CA with a 0.5 probability of changing from A->B regardless of neighbors. So effectively each iteration A->B = 0.5 A->C = 0.5 and A->A will be .25 (1-0.5)*(1-0.5). So each step a cell will stay the same .25/1.25, and turn into one of the other two at 0.5/1.25

    Again just add this to an empty GameObject and press Play.. Hit S to step. (Note you should disable or remove the Conway Life object )
    Code (CSharp):
    1.  
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using UnityEngine;
    5.  
    6.  
    7. public class ProbabilityTest : MonoBehaviour {
    8.  
    9.     CA myCA;
    10.     Texture2D tex;
    11.     Sprite board;
    12.     SpriteRenderer sr;
    13.  
    14.     private void Awake()
    15.     {
    16.         myCA = new CA(50, 50, 3, NType.VonNeumann);
    17.  
    18.         for (int startState =0;startState < 3; ++startState)
    19.         {
    20.             for (int endState = 0;endState < 3; ++endState)
    21.             {
    22.                 if (endState == startState)
    23.                     continue;
    24.                 for (int n = 0; n <= 4; ++n)
    25.                     myCA.SetStateInfo(startState, endState, n, 0.5f);
    26.             }
    27.         }
    28.  
    29.  
    30.         for (int i = 0; i < 50; ++i)
    31.         {
    32.             for (int j = 0; j < 50; ++j)
    33.             {
    34.                 myCA.SetCellState(i, j, Random.Range(0,3));
    35.             }
    36.         }
    37.     }
    38.  
    39.     private void Start()
    40.     {
    41.         tex = new Texture2D(500, 500);
    42.         board = Sprite.Create(tex, new Rect(Vector2.zero, new Vector2(500, 500)), Vector2.zero, 100);
    43.         sr = gameObject.AddComponent<SpriteRenderer>();
    44.         sr.sprite = board;
    45.         UpdateBoard();
    46.     }
    47.  
    48.     private void Update()
    49.     {
    50.         if (Input.GetKeyDown(KeyCode.S))
    51.         {
    52.             myCA.OneIteration();
    53.             UpdateBoard();
    54.         }
    55.     }
    56.  
    57.     private void UpdateBoard()
    58.     {
    59.         Color tileColor;
    60.         for (int i = 0; i < 50; ++i)
    61.         {
    62.             for (int j = 0; j < 50; ++j)
    63.             {
    64.                 switch (myCA.GetCellState(i, j))
    65.                 {
    66.                     case 0:
    67.                         tileColor = Color.black;
    68.                         break;
    69.                     case 1:
    70.                         tileColor = Color.green;
    71.                         break;
    72.                     case 2:
    73.                         tileColor = Color.red;
    74.                         break;
    75.                     default:
    76.                         tileColor = Color.blue;
    77.                         break;
    78.                 }
    79.          
    80.                 for (int a = i * 10; a < i * 10 + 10; ++a)
    81.                     for (int b = j * 10; b < j * 10 + 10; ++b)
    82.                         tex.SetPixel(a, b, tileColor);
    83.             }
    84.         }
    85.         tex.Apply();
    86.     }
    87. }
    88.  

    You can easily use the top 4 files I posted as a basis for you CA, and build whatever view you want on top of them. Your existing code is all usable just strip out anything that to do with the data. And just get information from the InputFields and pass it to CA. Then whenever the user hits a button or presses a key (whatever UI you decide to implement) call CA.OneIteration() to update the data. Then you call CA.GetCellState on each cell to update your view

    Edit: Send me a message if you need help figuring any of it out. Especially CellState class might be confusing on what its doing.

    Really Late Edit:
    Try changing the all 0.5 probabilites to this:

    Code (CSharp):
    1. for (int n = 0; n <= 4; ++n)
    2.                     myCA.SetStateInfo(startState, endState, n, (float)n/4.0f);
    Makes the cells change to a color more likely the more neighbors it has. It slowly clumps up and they 3 colors sort of fight each other and it morphs around.

    Final Last Edit: :)
    Added this so D key can run it continously:
    Code (CSharp):
    1. bool continous = false;
    2.     float lastIteration;
    3.     float stepTime = 0.2f;
    4.  
    5. private void Update()
    6.     {
    7.         if (continous == true)
    8.         {
    9.             lastIteration += Time.deltaTime;
    10.             if (lastIteration >= stepTime)
    11.             {
    12.                 myCA.OneIteration();
    13.                 UpdateBoard();
    14.                 lastIteration = 0;
    15.             }
    16.             if (Input.GetKeyDown(KeyCode.D))
    17.                 continous = false;
    18.         }
    19.         else
    20.         {
    21.             if (Input.GetKeyDown(KeyCode.S))
    22.             {
    23.                 myCA.OneIteration();
    24.                 UpdateBoard();
    25.             }
    26.             if (Input.GetKeyDown(KeyCode.D))
    27.             {
    28.                 continous = true;
    29.                 lastIteration = 0;
    30.             }
    31.         }
    32.     }
     
    Last edited: Feb 11, 2017
    EternalAmbiguity likes this.
  22. EternalAmbiguity

    EternalAmbiguity

    Joined:
    Dec 27, 2014
    Posts:
    3,144
    Thanks a ton for all of the help.

    Yeah, that was probably a bad idea to mix the two all together. I figured it would be easier (because my programming skill is primitive at best), but I guess that was only initially. Now it's hard to separate things out.

    I'll try to work with what you have to get what I need.
     
  23. EternalAmbiguity

    EternalAmbiguity

    Joined:
    Dec 27, 2014
    Posts:
    3,144
    Let me first say thanks a ton for all of this. I typed it all out (at least the first four) and tried out both the Life variant and the probabilistic variant. Both worked great.

    One thing I noticed about the probabilistic variant: it looks like you pick a blanket probability for all cell types (though with the edit, you do make it change for increasing numbers of neighbors of a certain type) and do that all at once. How would I separate that out like I did for my stuff, where each cell type has its own set of probabilities? Would I put a bunch of if (startState = x)statements within the for (startState) loop?

    Additionally, about the part where individual probabilities get averaged to make a final probability (0.5 and 0.5 become 0.75) concerns me, because it's not obvious when putting in a value. In that case, how does one truly get a 50% chance of A going to B, and a 50% chance of A going to C at the same time (thus there's NO chance if it staying A)? I personally feel in this case it would be okay to point out to the user that the probabilities are all combined, and their maximum cannot be greater than 1.

    Also, does this "shuffle" or randomize the cells before each iteration, so it isn't following the same path through them each time? I don't see that anywhere.
     
    Last edited: Feb 16, 2017
  24. takatok

    takatok

    Joined:
    Aug 18, 2016
    Posts:
    1,496
    well my implementation of the view (displaying the data) was very primitive. The idea of separating the CA from the view was the main point. All of the concerns (except the probability combination) would be handled by your view.

    So generally in a MVC design (Model-View-Controller) you have your model (data) which is the 4 files i posted. You also have the view (how to display the data) which would be either a texture that your drawing on, or individual sprites all lined up, or some 3D crazy way to display the data. Then you also have a controller. This guys job is to talk to the view to get info to pass to the model. As well as tell the model to update data and give it back to pass to the view. The idea being you should never have the model and the view communicate with each other. That makes it easy to swap in and out view's. I sort of combined the View and Controller aspects in my slapped together life and probability scripts. The controller would also be the guy in charge of talking to the player, so he would handle all the UI stuff.

    This controller script would have the CA on it, as well as the public GameObject links to the Canvas that is displaying the initial screen of your project. The Controller script would be in charge of making the new pages for each state of the CA, and it would be keeping track of references to all those pages. Now when the user hits done when he's entered in all the data (you could add in sanity checks here to make sure all the data is valid and pop up an error), the controller script would just go to the inital page and get all the data for setting up the CA. Then it would go through all the input fields and tell the CA the data it needs with the SetStateInfo calls.

    So to answer your questions:
    You could do something like this in your controller script(the one the created all the user pages).
    Code (CSharp):
    1. public class CAController: MonoBehaviour
    2. {
    3.          public Canvas mainCanvas;
    4.          private CA myCA;
    5.  
    6.  
    7.          public void InitCA()
    8.          {
    9.                // Code here to read from the main page's InputFields
    10.                //Get all the width,height, neighborhood,cellStates info
    11.                myCA = new CA(.........);
    12.      
    13.                for (int i=0;i<NumberOfUIPagesYouMade;++i)
    14.                {
    15.                       for (int j=0;j<cellStates;++j)
    16.                       {
    17.                             for (k=0;k<=neighborHoodSize;++k)
    18.                             {
    19.                                      float val = ValueFromInputField(j*neighborSize+k);
    20.                                      myCA.SetStateInfo(i,j,val);
    21.                               }
    22.                    }
    23.           }
    24. }

    The idea behind the above code is when you created the pages you kept links to them in some sort of List<> Then you can just iterate through each page (Each page being the start state in the probability matrix). Then each page should have J sets of K entries. where J is the number of cellStates and K is the neighborhoodsize+1. This way we get a set of values going from every state to every state. (including A->A).

    Now the line here:
    Code (CSharp):
    1. float val = ValueFromInputField(j*neighborSize+k);
    would actually involve getting the Panel from the List<> of Panels you made on the fly. Then running through that panel and getting components of each input field to get the actual value. IT will be very dependant on how you create these UI pages on the fly.

    I wasn't sure how you wanted your probability CA to work. The problem with getting them to all add to 1 is quite complicated though. Lets Say I'm in state A in a VonNeumann neighborhood. I have 0 A neighbors, 2 B neighbors and 2 C neighbors. That means you need to make sure that the value of A-A with 0 neighbors + A->B 2 neighbors and A->C with 2 neighbors all add to 1. Same goes for any combination of neighbors and states. That could be a huge burden on the user to get a pretty exact set of StateChange probabilities. Right now you have input for A->B in all cases and A->C in all cases. I think if you just add A->A for all cases, then if the user wanted 0% chance for A to stay A he can just put 0 in all values for any number of A neighbors. Then you can use my same probabliity code to squish the probablities into a normalized [0,1] range. You'd modify (and actually make it simpler) by taking out the code that is calculating the A->A chance on the fly, and explicitly using the lookup data. It would still squish the probalities to fit into [0,1] but I don't see an easy way around it.

    Another example:
    A->A, 0 = 0% -------- A->A 0 = 0% ------ A->A,1 = 30%
    A->B, 2 = 50% -------- A->B 3 = 80% ---- A->B, 2 = 50%
    A->C = 2 =50% ------- A->C 1 = 20% ---- A->C, 1 = 20%

    All three of those example above work out nicely and at to 100% but now we have this state
    A->A 1 = 30% (from 3)
    A->B 3 = 80% (from 2)
    A->C 0 = 0% (not set yet)

    So we can't always make it add to 1, well we can but it would be really complicated. Better to just let the user define all the states including A->A (stays the same) chances and we just normalize to [0,1]. Then if the user didn't want this normalization to happen he can input a set of data that would always add to 1 no matter what neighbors came up.. but you make it so he doesn't have to. I haven't thought about it much, but I think given a set of probabilites you could use something like a Markov Chain to normalize them all ahead of time, and display it for the user. Maybe a button could be clicked to do this.

    The CA starts in an uninitialized state (basically everything is set to state 0, and all probabliites are 0). it expects the controller to initialize it. For example my simple life controller script initialized here randomly to be 10% "On" (state 1)
    Code (CSharp):
    1.  for (int i = 0; i < 50; ++i)
    2.           {
    3.               for (int j = 0; j < 50; ++j)
    4.               {
    5.                   if (Random.Range(0, 10) == 0)
    6.                       myCA.SetCellState(i, j, 1);
    7.               }
    8.           }
    Your code can easily initialize it however you need to. To make sure exact percentages are reached you can use a ShuffleBag technique, where you put all the x,y co-ords in a list and pick one out at random and set that to a state
    something like this:
    Code (CSharp):
    1. float stateAPerc = .30f;
    2. float stateBPerc =. 30f;
    3. float stateCPerc = .40f;
    4. int[] stateCellNums = new intStateCellNums[3];
    5. stateCellNums[0] = (int)(stateAPerc*width*height);
    6. stateCellNums[1] = (int)(stateBPerc*width*height);
    7. stateCellNums[2] = (int)(stateCPerc*width*height);
    8.  
    9. //First put all the points in a ShuffleBag
    10. List<Point> allPoints = new List<Point>();
    11. for (int x=0;x<width;++x){
    12.       for (int y=0;y<height;++y)
    13.              allPoints.Add(new Point(x,y));
    14.  
    15. // Go through all X states
    16. for (int i=0;i<cellStates;++i)
    17. {
    18.         // Get how many cells we need of this state
    19.         int cellNum = stateCellNums[i];
    20.         // And pick a random Point, set it, then remove that Point from the list
    21.         while (cellNum >0)
    22.         {
    23.                  int index = Random.Range(0,allPoints.Count);
    24.                  Point p = allPoints[index];
    25.                  myCA.SetCellState(p.X, p.Y,i);
    26.                  allPoints.RemoveAt(index);
    27.                  cellNum--;
    28.          }
    29. }
    30.  

    Note: The CA has lots of potential to be expanded upon.

    For example it doesn't worry about bounary conditions at all. This means it explicitly assumes the entire grid is surrounded by cells that are always in a state that isn't any state. So the edge/corner cells will always turn out to have less neighbors than inner cells. You can figure out some method of handling the edge explicitly(its always considered to be in a state that your checking for example), or figure out a way to make the grid bigger to handle overflow (though you still might only display the original width height). (and you'd have to have a setting for what initial state this overflow area is set to).

    It doesn't keep track of how long a state has been in that state. You could add that in. So if State A is red it starts out bright red and slowly goes darker as it stays red for longer periods of time.
     
    Last edited: Feb 16, 2017
    EternalAmbiguity likes this.
  25. EternalAmbiguity

    EternalAmbiguity

    Joined:
    Dec 27, 2014
    Posts:
    3,144
    Okay, I'm trying to work through taking the ProbabilityTest script and making it generic. Starting at the top, I'm not completely sure what you mean by endState and startState--more specifically, why you have lines 37 or so (the website seems to remove blank lines from code) in my slightly modified script below.

    Code (csharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class GenericTest : MonoBehaviour
    6. {
    7.     int cellTypeNumber;
    8.     int gridWidth;
    9.     int gridHeight;
    10.     CA myCA;
    11.     Texture2D tex;
    12.     Sprite board;
    13.     SpriteRenderer sr;
    14.     private void Awake()
    15.     {
    16.        
    17.     }
    18.     private void Start()
    19.     {
    20.        
    21.     }
    22.     private void Update()
    23.     {
    24.         if (Input.GetKeyDown(KeyCode.S))
    25.         {
    26.             myCA.OneIteration();
    27.             UpdateBoard();
    28.         }
    29.     }
    30.     public void CreateCA()
    31.     {
    32.         myCA = new CA(gridWidth, gridHeight, cellTypeNumber, NType.VonNeumann);
    33.         for (int startState = 0; startState < cellTypeNumber; ++startState)
    34.         {
    35.             for (int endState = 0; endState < cellTypeNumber; ++endState)
    36.             {
    37.                 if (endState == startState)
    38.                     continue;
    39.                 for (int n = 0; n <= 4; ++n)
    40.                     float val = ;
    41.                     myCA.SetStateInfo(startState, endState, n, (float)n / 4.0f);
    42.             }
    43.         }
    44.  
    45.         for (int i = 0; i < 50; ++i)
    46.         {
    47.             for (int j = 0; j < 50; ++j)
    48.             {
    49.                 myCA.SetCellState(i, j, Random.Range(0, 3));
    50.             }
    51.         }
    52.     }
    53.     public void StartCA()
    54.     {
    55.         tex = new Texture2D(500, 500);
    56.         board = Sprite.Create(tex, new Rect(Vector2.zero, new Vector2(500, 500)), Vector2.zero, 100);
    57.         sr = gameObject.AddComponent<SpriteRenderer>();
    58.         sr.sprite = board;
    59.         UpdateBoard();
    60.     }
    61.     private void UpdateBoard()
    62.     {
    63.         Color tileColor;
    64.         for (int i = 0; i < 50; ++i)
    65.         {
    66.             for (int j = 0; j < 50; ++j)
    67.             {
    68.                 switch (myCA.GetCellState(i, j))
    69.                 {
    70.                     case 0:
    71.                         tileColor = Color.black;
    72.                         break;
    73.                     case 1:
    74.                         tileColor = Color.green;
    75.                         break;
    76.                     case 2:
    77.                         tileColor = Color.red;
    78.                         break;
    79.                     default:
    80.                         tileColor = Color.blue;
    81.                         break;
    82.                 }
    83.                 for (int a = i * 10; a < i * 10 + 10; ++a)
    84.                     for (int b = j * 10; b < j * 10 + 10; ++b)
    85.                         tex.SetPixel(a, b, tileColor);
    86.             }
    87.         }
    88.         tex.Apply();
    89.     }
    90. }
    Is that simply supposed to be for skipping the A-->A step? Just trying to understand what you have here.

    Outside of that, I think my first step is to redo the UI stuff so it uses just one type of instantiated page regardless of the CA "order" and all that. Probably make a dropdown Neighbor Effects with choices None, von Neumann, and Moore.

    After I do that I can continue line 47 and move on.

    Thanks again for all of your help. You've been instrumental to what I'm doing here.
     
    Last edited: Feb 17, 2017
  26. takatok

    takatok

    Joined:
    Aug 18, 2016
    Posts:
    1,496
    yes. because your original UI for State A only had neighbor VAlues for turning into state B and state C. Thats also why I wrote the probability code for staying in the same State the way I did. becuase you didn't have any explicit fields for declaring A->A with X neighbors.

    So the SetStateCode takes startSTate, endState, neighbors, prob perc. That just means the probability of StartState turning into EndState if it has X EndStateneighbors.

    So SetStateInfo(startState, endState, n, (float)n / 4.0f); means startState , that has 0 endState neighbors will turn into endstate 0/4 (0%) . startSTate, that has 4 endState neighbors will turn into endState 100% of the time (4/4)

    By the way the Arrays to handle A->A percentages already have the room and the ability I was just skpping them and levaing them empty. You can Easily do this;
    SetStateInfo(0,0,3,.4) which would mean STate 0 would stay State 0, 40% of the time if it has 3 State0 neighbors.

    And you should change GetProbChances to this:
    Code (CSharp):
    1.  private float[] GetProbChances(int currentState,List<int> neighborStateCount)
    2.     {
    3.         float[] probChances = new float[numStates];
    4.        
    5.         for (int p = 0; p < numStates; ++p)
    6.         {
    7.          
    8.             float prob = states[currentState].GetProbablity(p, neighborStateCount[p]);
    9.             probChances[p] = prob;
    10.          
    11.         }
    12.         return probChances;
    13.     }
    its much simpler now since it will just lookup the chance of B->B instead of figuring it out on the fly.
     
    EternalAmbiguity likes this.
  27. EternalAmbiguity

    EternalAmbiguity

    Joined:
    Dec 27, 2014
    Posts:
    3,144
    To return to the topic of the A-->A probability, I personally wouldn't really expect a user to go in each time and change that manually. Like, say I'm setting up a three-state CA with the von Neumann neighborhood. Starting on one state, with the chance of going to either other state is like so:

    0 neighbors: 0.000001
    1 neighbor: 0.00001
    2 neighbors: 0.0001
    3 neighbors: 0.001
    4 neighbors: 0.01

    And then with each of those other states have 0 probability of changing, so they stay as the state they are.

    In that situation, I really wouldn't want (or want the user to) to have to go through the calculation needed to determine what A-->A needs to be.

    See, I'm working with situations where 0.1 would be like the absolute max a probability would ever be. So there's very little chance of going over 1.

    So I feel if this were to require the user input it, it would have to calculate it automatically somehow. I might just be obsessing over UX though; I did for my work with other programs.
     
  28. takatok

    takatok

    Joined:
    Aug 18, 2016
    Posts:
    1,496
    Then you can revert my other code to something like it was. Instead of multiplying the chance that the others didn't happen you just add up all the other probabilities and A->A would be 1-those
     
    EternalAmbiguity likes this.
  29. EternalAmbiguity

    EternalAmbiguity

    Joined:
    Dec 27, 2014
    Posts:
    3,144
    Okay, I've made a modified "controller" script here based off of your ProbabilityTest script. I've shown it below.

    Code (csharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.UI;
    5. public class vNGenericTest : MonoBehaviour
    6. {
    7.     int amountOfCellTypes;
    8.     int gridWidth;
    9.     int gridHeight;
    10.     CA myCA;
    11.     Texture2D tex;
    12.     Sprite board;
    13.     SpriteRenderer sr;
    14.     private void Awake()
    15.     {
    16.     }
    17.     private void Start()
    18.     {
    19.     }
    20.     private void Update()
    21.     {
    22.         if (Input.GetKeyDown(KeyCode.S))
    23.         {
    24.             myCA.OneIteration();
    25.             UpdateBoard();
    26.         }
    27.     }
    28.     public void CreateCA()
    29.     {
    30.         amountOfCellTypes = int.Parse(GameObject.FindGameObjectWithTag("numbercelltype").GetComponent<Text>().text);
    31.         gridWidth = int.Parse(GameObject.Find("Horigrid").GetComponent<Text>().text);
    32.         gridHeight = int.Parse(GameObject.Find("Vertgrid").GetComponent<Text>().text);
    33.         myCA = new CA(gridWidth, gridHeight, amountOfCellTypes, NType.VonNeumann);
    34.         for (int i = 0; i < amountOfCellTypes; ++i)
    35.         {
    36.             string numstring = (i + 1).ToString();
    37.             GameObject basePage = GameObject.Find("NMP1" + numstring);
    38.             for (int j = 1; j < amountOfCellTypes; ++j)
    39.             {
    40.                 GameObject probGrandparent = basePage.transform.Find("ProbabilityScrollView").transform.Find("1stProbContent").gameObject;
    41.                 for (int n = 0; n <= 4; ++n)
    42.                 {
    43.                     GameObject probParent = probGrandparent.transform.Find("ProbInputField1." + j + "." + n).gameObject;
    44.                     float val = float.Parse(probParent.transform.Find("ProbTransValue").GetComponent<Text>().text);
    45.                     int startState = int.Parse(probParent.transform.Find("ProbTransText").GetComponent<Text>().text.Remove(0, 28).Remove(1, 41));
    46.                     int endState = int.Parse(probParent.transform.Find("ProbTransText").GetComponent<Text>().text.Remove(0, 33).Remove(1, 36));
    47.                     myCA.SetStateInfo(startState, endState, n, val);
    48.                 }
    49.             }
    50.         }
    51.  
    52.         for (int i = 0; i < gridWidth; ++i)
    53.         {
    54.             for (int j = 0; j < gridHeight; ++j)
    55.             {
    56.                 myCA.SetCellState(i, j, Random.Range(0, 3));
    57.             }
    58.         }
    59.     }
    60.     public void StartCA()
    61.     {
    62.         tex = new Texture2D(gridWidth, gridHeight);
    63.         board = Sprite.Create(tex, new Rect(Vector2.zero, new Vector2(gridWidth, gridHeight)), Vector2.zero, 1);
    64.         sr = gameObject.AddComponent<SpriteRenderer>();
    65.         sr.sprite = board;
    66.         UpdateBoard();
    67.     }
    68.     private void UpdateBoard()
    69.     {
    70.         Color tileColor;
    71.         for (int i = 0; i < gridWidth; ++i)
    72.         {
    73.             for (int j = 0; j < gridHeight; ++j)
    74.             {
    75.                 switch (myCA.GetCellState(i, j))
    76.                 {
    77.                     case 0:
    78.                         tileColor = Color.black;
    79.                         break;
    80.                     case 1:
    81.                         tileColor = Color.green;
    82.                         break;
    83.                     case 2:
    84.                         tileColor = Color.red;
    85.                         break;
    86.                     default:
    87.                         tileColor = Color.blue;
    88.                         break;
    89.                 }
    90.                 for (int a = i * 10; a < i * 10 + 10; ++a)
    91.                     for (int b = j * 10; b < j * 10 + 10; ++b)
    92.                         tex.SetPixel(a, b, tileColor);
    93.             }
    94.         }
    95.         tex.Apply();
    96.     }
    97. }
    As you can see, in this one I have references to UI elements. This IS the controller script...but if you think I'm doing it poorly just point it out to me. Or if there are other mistakes please let me know.

    I changed where SetStateInfo gets the startState and endState from because of the way my UI's set up. It's set up to automatically skip over situations where the start and end state will be the same, so I just grabbed the appropriate startState and endState vales from my UI text. Technically it won't work for situations where there're ten different cell types, but neither will the colors currently so I'm not worried about that right now (and that's an extremely unlikely use case for me).

    I will need to change the SetCellState part so that the user can select the amounts they want. I will also need to do that with the color, but before that I'm having an issue with the code.


    When I attach the above (or attached) to a gameObject, and set CreateCA to run at the appropriate time, I'm getting an IndexOutOfRangeException...in the CellState script (which is unchanged from how you had it), on line 17. Going to that script and looking at it, at first I thought it was because you never declare a value for totalStates a few lines up (and I still don't know how you're using an array that isn't fixed, because I thought arrays in C# had to be a fixed length). Now, however, I'm really not sure why it's giving me that.

    One thing to point out: the number of values will not be totalStates x neighborhoodSize + 1. Because if you think about it, that's not giving probabilities FROM each individual type TO the other types (with varying amounts of neighbors).

    So say there are three cell types. There needs to be a probability for type 1 to go to type 2 (with the varying amounts of neighbors), and type 1 to go to type 3 (with the varying amounts of neighbors). So 2 x 5 (or totalStates - 1 x neighborhoodSize + 1) just for one type.

    For all of them, it would be each cell type times that ^ , or (totalStates x (totalStates - 1)) x (neighborhoodSize + 1).

    But basically I'm not sure how to get it working correctly.

    I've also attached the project as it stands now so you can try it out and see for yourself if you wish. Just set the neighbor effects to von Neumann, go through the process of inputting all the information, and then trying to run the CA.
     

    Attached Files:

  30. takatok

    takatok

    Joined:
    Aug 18, 2016
    Posts:
    1,496
    Well first i'll Explain CellState class. Its meant to represent all the changes from one State to any other state. So for example it will contain all the changes from A->A, A->B and A->C, etc. But NOT B->A or C->B. Just A->something. Its size (totalStatesxNeighborSize+1). Its not using (totalStates-1) because its still making room for A->A. For 2 reasons 1) it just makes the code easier to write if I don't have to figure out which states I'm skipping over and indexing. state A->C will always be lookup endstate 2 (in a 0-based array). I don't have to remember I skipped 0 (A->A) so C is really 1. That possible wasted space is neglibible.
    2) its there so if you do decide to have the user add A->A transistions no code rewriting is necessary. So to emphasize that.. one CellState class is only totalStatesxNeighborsize+1 big.

    So the class CA itself has an Array of CellStates. This array is numStates big. so effectively the total space is (totalStatesxtotalStatesxNeighbors+1) Just like you thought it should be. IF you look at SetStateInfo in CA class it takes the startState as an index into an array of CellStates.. and then calls that CellState with endState,neighbors and value.

    To answer you question about arrays being fixed length. They are.. As soon as you declare them they are pretty much stuck at that size. Your just thinking you have to declare their size right away, which you don't. You can wait and get passed in variables about how big it should be. However once you actually create the array of CellStates,and they create their arrays of totalStatesxneighbors+1 they are stuck at that size for the whole program (Technically you can resize them but effectively your just making a new array at the new size and sticking it back into the old name).

    Finally your bug is because arrays are 0-based and your pages are 1-based. When you parse the cellState from the title of the page your getting cellStats 1-3 instead of 0-2. just subtract 1 from the number you get for start and End state.

    Also there is another bug that hasn't shown up yet. Your i is looping from 0 to cellstates-1 which is good. Your j is looping from 1 to cellstates-1. I assume because you want to skip startState =0, endState = 0. However, this is ok for for startState=0. But when i=1 (startState = 1) J will still go from 1.. then 2. So skipping startSTate =1 and endState=0 the second time through the loop which is bad.

    What you want is this :
    Code (CSharp):
    1.  for (int i = 0; i < amountOfCellTypes; ++i)
    2.         {
    3.             string numstring = (i + 1).ToString();
    4.             GameObject basePage = GameObject.Find("NMP1" + numstring);
    5.             for (int j = 0; j < amountOfCellTypes; ++j)
    6.             {
    7.                 if (i==j)
    8.                      conintue;
    9.                  string cellString = (j+1).ToString();
    10.                 GameObject probGrandparent = basePage.transform.Find("ProbabilityScrollView").transform.Find("1stProbContent").gameObject;
    11.                 for (int n = 0; n <= 4; ++n)
    12.                 {
    13.                
    14.                     GameObject probParent = probGrandparent.transform.Find("ProbInputField1." + cellString + "." + n).gameObject;
    Minor thing, but I'm really confused by the Apply button. Why not just make the next button do the Apply button stuff.. then bring up the first page? You can still pop up the error page if something isn't correct At the very least the apply should be down next to the Next button. You can even get fancy later and "Grey Out" Next, until apply has been hit.
     
    Last edited: Feb 21, 2017
    EternalAmbiguity likes this.
  31. EternalAmbiguity

    EternalAmbiguity

    Joined:
    Dec 27, 2014
    Posts:
    3,144
    Thank you for all of this. I'll work on updating things tomorrow (it's 2:25 AM here), but just to explain my use of the Apply button--that was just so the pages for each cell type get created before pressing the next button. If it did them all at once, there would be a longer delay when pressing Next. So I just made it a separate button so it can instantiate those pages and input fields in the background while the player is still inputting information. Then pressing Next goes quickly to the cell type pages.

    Do you think that's a bad idea? Do you think that amount of slowdown matters at all?
     
  32. EternalAmbiguity

    EternalAmbiguity

    Joined:
    Dec 27, 2014
    Posts:
    3,144
    Alright, I understand about CellState. I wondered if that was the case but I wasn't sure.

    I think I understand what you mean about arrays. I understand enough to use them, anyway.

    Thanks for pointing out that first bug.

    As for the second one, I start from 1 rather than 0 because that's the way the input fields (and the probabilities) work. If j = endStates, then there are not three possible endStates in a three-cell-type scenario. There are only two--1 going to 2, and 1 going to 3. My system doesn't account for 1 going to 1. Thus, the input fields all start from 1, and automatically skip over the 1 to 1, or 2 to 2, or 3 to 3, etc.

    Thus i = startStates, and j = endStates FROM a single startState. And it doesn't matter if the numbers overlap--addressing overlap is already taken care of when instantiating the input fields when creating the cellpages.

    So if I have j=0; j<cellTypeNumber then there will be 3 j values. That would correspond to input fields like inputField1.1, inputField 1.2, and inputField 1.3 (ignoring the neighbor stuff for now). But that's one too many: that cell type can only transition to two others, so there is only ever inputField1.1 and inputField1.2.

    I tried using your code and it gave me a NullReferenceException, because it was trying to pull an inputField beyond the maximum number of them. You could see too if you tried it. So I just left that one alone.

    Current code:
    Code (csharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.UI;
    5. public class vNGenericTest : MonoBehaviour
    6. {
    7.     int amountOfCellTypes;
    8.     int gridWidth;
    9.     int gridHeight;
    10.     CA myCA;
    11.     Texture2D tex;
    12.     Sprite board;
    13.     SpriteRenderer sr;
    14.     private void Awake()
    15.     {
    16.     }
    17.     private void Start()
    18.     {
    19.     }
    20.     private void Update()
    21.     {
    22.         if (Input.GetKeyUp(KeyCode.E))
    23.         {
    24.             myCA.OneIteration();
    25.             UpdateBoard();
    26.         }
    27.         if(Input.GetKey(KeyCode.C))
    28.         {
    29.             myCA.OneIteration();
    30.             UpdateBoard();
    31.         }
    32.     }
    33.     public void CreateCA()
    34.     {
    35.         amountOfCellTypes = int.Parse(GameObject.FindGameObjectWithTag("numbercelltype").GetComponent<Text>().text);
    36.         gridWidth = int.Parse(GameObject.Find("Horigrid").GetComponent<Text>().text);
    37.         gridHeight = int.Parse(GameObject.Find("Vertgrid").GetComponent<Text>().text);
    38.         myCA = new CA(gridWidth, gridHeight, amountOfCellTypes, NType.VonNeumann);
    39.         for (int i = 0; i < amountOfCellTypes; ++i)
    40.         {
    41.             string numstring = (i + 1).ToString();
    42.             GameObject basePage = GameObject.Find("NMP1" + numstring);
    43.             for (int j = 1; j < amountOfCellTypes; ++j)
    44.             {
    45.                 GameObject probGrandparent = basePage.transform.Find("ProbabilityScrollView").transform.Find("1stProbContent").gameObject;
    46.                 for (int n = 0; n <= 4; ++n)
    47.                 {
    48.                     GameObject probParent = probGrandparent.transform.Find("ProbInputField1." + j + "." + n).gameObject;
    49.                     float val = float.Parse(probParent.transform.Find("ProbTransValue").GetComponent<Text>().text);
    50.                     int startState = (int.Parse(probParent.transform.Find("ProbTransText").GetComponent<Text>().text.Remove(0, 28).Remove(1, 41))) - 1;
    51.                     int endState = (int.Parse(probParent.transform.Find("ProbTransText").GetComponent<Text>().text.Remove(0, 33).Remove(1, 36))) - 1;
    52.                     myCA.SetStateInfo(startState, endState, n, val);
    53.                 }
    54.             }
    55.         }
    56.  
    57.         for (int i = 0; i < gridWidth; ++i)
    58.         {
    59.             for (int j = 0; j < gridHeight; ++j)
    60.             {
    61.                 myCA.SetCellState(i, j, Random.Range(0, 3));
    62.             }
    63.         }
    64.     }
    65.     public void StartCA()
    66.     {
    67.         tex = new Texture2D((gridWidth*10), (gridHeight*10));
    68.         board = Sprite.Create(tex, new Rect(Vector2.zero, new Vector2((gridWidth * 10), (gridHeight * 10))), Vector2.zero, 10f);
    69.         sr = gameObject.AddComponent<SpriteRenderer>();
    70.         sr.sprite = board;
    71.         UpdateBoard();
    72.     }
    73.     private void UpdateBoard()
    74.     {
    75.         Color tileColor;
    76.         for (int i = 0; i < gridWidth; ++i)
    77.         {
    78.             for (int j = 0; j < gridHeight; ++j)
    79.             {
    80.                 switch (myCA.GetCellState(i, j))
    81.                 {
    82.                     case 0:
    83.                         tileColor = Color.black;
    84.                         break;
    85.                     case 1:
    86.                         tileColor = Color.green;
    87.                         break;
    88.                     case 2:
    89.                         tileColor = Color.red;
    90.                         break;
    91.                     default:
    92.                         tileColor = Color.blue;
    93.                         break;
    94.                 }
    95.                 for (int a = i * 10; a < i * 10 + 10; ++a)
    96.                     for (int b = j * 10; b < j * 10 + 10; ++b)
    97.                         tex.SetPixel(a, b, tileColor);
    98.             }
    99.         }
    100.         tex.Apply();
    101.     }
    102. }
    So anyway, right now I seem to have it working. For some reason it's showing up behind my UI panel so I'm going to have to figure out how to correct that (I've tried changing layers, moving the GridCanvas to World Space...no dice). Additionally, I'm going to need to figure out how to scale it appropriately to fit the screen, rather than simply being a square in the middle of the page. And I still need to do the user color choice and user amounts selection.

    But otherwise it seems okay right now. Thank you very, very much. I'll post further updates here as I move forward.
     
  33. EternalAmbiguity

    EternalAmbiguity

    Joined:
    Dec 27, 2014
    Posts:
    3,144
    Okay, I'm trying to figure out how to do the shuffle. The shuffle needs to basically move randomly through all cells for each iteration. You may have seen that I was able to implement it for my GridMake script, but for this I'm not sure how to do it.

    It looks like I should go into OneIteration and scramble to order of movement through x and y. Is this correct?

    Edit: One thing I don't understand is the very end of the controller script, where it sets pixels to a texture or something:

    Code (csharp):
    1. for (int a = i * 10; a < i * 10 + 10; ++a)
    2.                     for (int b = j * 10; b < j * 10 + 10; ++b)
    3.                         tex.SetPixel(a, b, tileColor);
    Could you explain what exactly this is doing? Should I change it at all to make it generic?

    Edit: Okay, I'm running into a problem. It's the same problem that my professor's program had, actually. Basically at low probabilities, only one cell type (out of two possible) is getting selected. I've attached the latest version so it can be tested. To describe the process:

    Select "New CA Model." Set neighbor effects to von Neumann. Pick whatever grid size (I typically do 100 x 100). Set 3 cell types and Apply. Move to type 1 cell page. Set all probabilities to 0 (so a type 1 cell is trapped in that state). Move to type 3 cell page. Set all probabilities to 0 (so a type 3 cell is trapped in that state). Move back to type 2 cell page. Set probabilities as follows:
    0.00001
    0.0001
    0.001
    0.01
    0.1
    0.00001
    0.0001
    0.001
    0.01
    0.1

    Press Next to get to the end, then confirm Yes. Press Start CA to initialize the CA. Press Un//Pause to view the CA in action.


    Now, I haven't gotten the user-selected amounts at start in here yet. So I've set the grid to initialize with only type 2 cells. But anyway, when I do the above, I get almost all cells converting to just one type--the blue type.

    Both types (1 and 3) have equal probabilities, so there should be roughly, generally the same amounts of both. But again, almost all are the one type. I'm not sure why this is happening.
     

    Attached Files:

    Last edited: Feb 22, 2017
  34. takatok

    takatok

    Joined:
    Aug 18, 2016
    Posts:
    1,496
    The loops in the code your asking about is just iterating over the entire grid and drawing squares inside the texture to represent each cell in the CA. I wasn't writing for extensibility just speed. I hard coded that the entire texture was 500 x500. and made my grid 50x50 so each cell had to be 10x10. Thats why the outer loops i,j go from 0 to 49. So the inner loops a,b are representing the co-ordinates of of each cell in the texture. You definitely want to make all those hard-coded numbers generic.

    This where some decision will have to be made on how you display your CA. Also why we separated out the actual data(the model) from the display. THe CA code will happily calculate its entire grid without caring how you go about displaying it. So if you want to keep displaying the CA based off how I did it with the texture. You'll need to figure out how big you want that texture to be. Then you'll either need to pick a set cell size (say 10 pixels) or figure out how big each cell needs to be based on the grid size. Both have problems associated with them.

    1) If you pick a set cell size: The pros are that it will always look the same, and you can pick reasonable size to begin with that looks good. Additionally you can add a slider control so the user can adjust the cells size. Make each cell bigger or smaller.

    The cons: Its harder to program. You have to take into consideration that the cells might not fill up the entire texture (not a big deal, esp if you draw a solid color on the texture first, then draw your grid on top of that). But what if the cellsize is big enough that it overflows the bounds of the texture. You have to figure out how much you can display, and probably have some system to move around the grid. ScrollBars on the side and bottom, or maybe holding the mouse down and dragging around the grid.


    2) Variable cell size - so the cell size is set so : cell width = textureWidth/gridWidth and cell Height = textureHeight/gridHeight. Pros: The cells will always exactly fill up the grid. Easy to program with simple loops
    Cons: User has no control over the cell size, and it will be really big or really small depending on the ratio of texture size vs Grid size. Also if the grid size gets too big.. cell dimensions might get < 1. That will be a problem.

    You could start programming with the 2nd model. It will be easy and you can use it for simple test cases to make sure everything else is working. Then once you satisfied that the CA is working correctly, you can change it to 1 and code in nice ways to display the CA with lots of user options. Which again is the entire point of separating the data from the view. It will make that transition easy.

    Downloaded the project and looking at it now for the 2nd problem

    Edit: After downloading the project it seems you already implemented 2) for viewing the CA :)
     
    Last edited: Feb 23, 2017
    EternalAmbiguity likes this.
  35. takatok

    takatok

    Joined:
    Aug 18, 2016
    Posts:
    1,496
    Ok the bug is my epsilon for figuring out percentages was too big. It was .00001 which is exaclty what your red percentage was. The epsilon was deleting your red percentage. Change the CA code to this:
    Code (CSharp):
    1. private int gridWidth;
    2. private int gridHeight;
    3. private int numStates;
    4. private float epsilon = .000000001f;  //<---- this line is new
    5.  
    6. // .... lots of other code:
    7.  
    8. private int GetStateFromProbability(float[] probChances)
    9.     {
    10.         float totalProb = 0;
    11.         for (int i = 0; i < probChances.Length; ++i)
    12.             totalProb += probChances[i];
    13.         //this is here so we don't pick the actual last value
    14.         //it' a small chance (1 out of however big number the pseudorandom number generator uses)
    15.         //but it's necessary for absolute toggles of 1.0 probabilities. Otherwise super rare chance it will be wrong.
    16.         totalProb -= epsilon;  //<----- So is this line
    17.         float[] pickRange = new float[probChances.Length];
    18.  

    I also changed the probability code to be the difference between 1 and the other probabilities like we discussed earlier (though that wasn't the bug its still should be changed to how you talked about it).
    It just sets the probability of A->A to 1-(all the other probs) or 0 if those probs are >=1
    Code (CSharp):
    1.  private float[] GetProbChances(int currentState, List<int> neighborStateCount)
    2.     {
    3.         float[] probChances = new float[numStates];
    4.         float totalProb = 0;
    5.         for (int p = 0; p < numStates; ++p)
    6.         {
    7.             //skip if we are on the state of the current cell
    8.             //we'll figure it out after we find out all the other probs
    9.             if (p == currentState)
    10.                 continue;
    11.             float prob = states[currentState].GetProbability(p, neighborStateCount[p]);
    12.             probChances[p] = prob;
    13.             // the chance of us not changing is the product of all the other states not happening
    14.             totalProb += prob;
    15.         }
    16.         float noChange = 1 - totalProb;
    17.         probChances[currentState] = noChange > 0 ? noChange : 0;
    18.         return probChances;
    19.     }

    Another thing you might want to think about, is if you have code that is repeated a lot that represents an action you should wrapper it in a function. For example when the user presses E, or if continuous mode is on.. we want to run one iteration of the CA, and we do this by calling myCA.OneIteration(). You have this line in numerous places. Now there is a chance the concept of "Run One Iteration" might involve more than just calling the CA. We might want to update some iteration counter, or do other things. So you when you have multiple lines of the same code you should do this:

    Code (CSharp):
    1.  private void Update()
    2.     {
    3.         if (Input.GetKeyUp(KeyCode.E))
    4.         {
    5.             OneIteration();
    6.             UpdateBoard();
    7.         }
    8.         if(Input.GetKey(KeyCode.C))
    9.         {
    10.             OneIteration();
    11.             UpdateBoard();
    12.         }
    13.  
    14.         if(buttonscript.continueCA == true)
    15.         {
    16.             OneIteration();
    17.             UpdateBoard();
    18.         }
    19.     }
    20.  
    21.     public void OneIteration()
    22.     {
    23.         myCA.OneIteration();
    24.     }

    This won't change anything at all in your code. But lets say later on we want to update a TextBox with the current Iterations we could just add it in one place:
    Code (CSharp):
    1. public void OneIteration()
    2.     {
    3.         myCA.OneIteration();
    4.         iterations++;
    5.         iterText.text = iterations.ToString();
    6.     }
    Then we know we've changed it everywhere since everyone who wants to do OneIteration calls our OneIterations function. we don't have to hunt down every spot we typed myCA.OneIteration()

    I implemented all the above changes i mentioned and here's a few things that might make testing easier:
    I hooked up the IterationTextBox to your vNGenericTest like this;
    Code (CSharp):
    1.  private int iterations = 0;
    2. public Text iterText;
    Drag IterationText from your Hierarchy onto that spot in the inspector
    Then the changes I showed above in the OneIteration method will show us our current iteration.

    Also I modified your Pause/Unpause button so it was more understandable.
    Change its initial text to Paused
    Add this line at the top of the Buttons Script
    Code (CSharp):
    1. public Text pauseButtonText;
    Drag the Text component of the PauseButton (push the down arrow to see it) onto that spot in the inspector
    Then change the PauseButton method to this:
    Code (CSharp):
    1. public void PauseButton()
    2.     {
    3.         continueCA = !continueCA;
    4.         pauseButtonText.text = continueCA ? "UnPaused" : "Paused";
    5.  
    6.     }
    It will now toggle between Paused and UnPaused so you know what state its in.
     
    EternalAmbiguity likes this.
  36. EternalAmbiguity

    EternalAmbiguity

    Joined:
    Dec 27, 2014
    Posts:
    3,144
    Okay, I worked through all of that. It's looking great now! Thanks so, so much for all of the help.

    However, I'm still not completely sure about the shuffling. For both the initialization and for during the running CA, I'm not really sure how one would take a for loop and convert it into a random selection, especially for a two-dimensional set of values. Something like this, perhaps? http://stackoverflow.com/questions/15884285/get-a-random-value-from-a-two-dimensional-array
     
  37. takatok

    takatok

    Joined:
    Aug 18, 2016
    Posts:
    1,496
    Add this method to the CA class
    Code (CSharp):
    1.  
    2. using System.Linq;
    3.    public void InitializeGrid(List<float> ratios)
    4.     {
    5.         // make sure ratios is as big as our numStates
    6.         while (ratios.Count < numStates)
    7.             ratios.Add(0f);
    8.         while (ratios.Count > numStates)
    9.             ratios.Remove(ratios.Last());
    10.  
    11.         //Convert our list of float ratios to [0,1] percentages
    12.         float total = ratios.Sum();
    13.         for (int i = 0; i < ratios.Count; ++i)
    14.             ratios[i] /= total;
    15.  
    16.         //Get a list of how many cells of each time we need
    17.         List<int> cellCount = new List<int>();
    18.         int totalCells = gridWidth * gridHeight;
    19.         for (int i = 0; i < numStates; ++i)
    20.             cellCount.Add((int)(ratios[i] * totalCells));
    21.      
    22.         // Because of rounding we might be a few from our total
    23.         // we'll just add them to state 0. It will not be noticeable
    24.         cellCount[0] += totalCells - cellCount.Sum();
    25.  
    26.         // this is a list of which states still needed to be added
    27.         List<int> stateIndex = new List<int>();
    28.         for (int i = 0; i < numStates; ++i)
    29.             stateIndex.Add(i);
    30.  
    31.         for (int i = 0; i < gridWidth; ++i)
    32.         {
    33.             for (int j = 0; j < gridHeight; ++j)
    34.             {
    35.                 // Get a random number for the states left to do and set it to the grid
    36.                 // we can't just use the number directly becuase stateIndex
    37.                 // could be 0,3,4  if we've finished setting all the 1s and 2s
    38.                 int index = Random.Range(0, stateIndex.Count);
    39.                 int state = stateIndex[index];
    40.                 grid[i, j] = state;
    41.  
    42.                 // Subtract that state from its cellcount
    43.                 // And if it goes to zero remove it from both our lists
    44.                 cellCount[index]--;
    45.                 if (cellCount[index] == 0)
    46.                 {
    47.                     cellCount.RemoveAt(index);
    48.                     stateIndex.RemoveAt(index);
    49.                 }
    50.  
    51.             }
    52.         }
    53.  
    54.     }

    To use it you need to make a List<float> from the numbers each cell should start with. Say the user put 30,30,40 for the starting values of each state you just turn those into floats and add them to a list and call the CA with it. They don't even have to add to 100.. the method treats them as ratios. So 10,10,20 would fill 25% state 1, 25% state 2 and 50% state 3

    The idea of the algorithm is this:
    • Convert all the ratios to percentages
    • Multiply those percentages by the total GridSize for each state
    • Stick those answer int a List called CellCount
    • Create a second List that has all the states initially (0,1,2,...)
    • Run through every cell in the grid and randomly assign state X from the states in the state list
    • subtract 1 from cellCount[x]
    • if cellCount[x] is 0, remove X from both the cellCount list and the stateList
    I used some Linq functions so you need using System.Linq at the top
     
    EternalAmbiguity likes this.
  38. EternalAmbiguity

    EternalAmbiguity

    Joined:
    Dec 27, 2014
    Posts:
    3,144
    Great, thanks! I'll take a look at this. One thing I'm not sure about is where I would set where to get the ratios or initial amounts from. I have the input fields in my UI, but obviously we don't want a reference to the UI in this CA class...

    Additionally, I still need to do the thing where it updates all cells in a random order...well...the more I think about it, is this necessary if it keeps an old copy of the grid and uses that for the full iteration? It seems like the random movement thing was more a version of what you're doing here with the old copy of the grid...
     
  39. takatok

    takatok

    Joined:
    Aug 18, 2016
    Posts:
    1,496
    You do it the same way you are setting the states. You absolutely use the Input fields you already have. You get all the values from the input fields stick them in a List<float> then call myCA.InitializeGrid(List<float>) This is still separation of the model and the view. When you are setting the StateInfo for the probabilites, your controller script vNWhateverItWas is using its panels it made to tell the CA the initial conditions. The would be true using the Input fields on each Cell State page. Its the Controller telling the CA how to initialize.

    The controller is also asking the CA what is the state of Cell[i,j] then telling the texture to color this spot this color. the view and the model are never talking to each other. Which makes it easier to change the CA (the model) or change the view (the texture) independent of each other.

    Why would it matter if it did it in a random order or not. I'm assuming you don't want to influence the current grid with current grid. That is iteration 12.. Should only look at iteration 11's values. If you were not making a backup of iteration 11, and overwriting it as you went along I could see using a random order. But with a backup grid the result will be deterministic (within realms of random chance). So if you seed the same random seed on a setup it will always come out the same. Or if you doing something deterministic like Life it will always come out correct.

    If you wanted it to randomly update a cell then use that cell's current value for its neighbors you'll have to totally change one Iteration (though I can't see that being useful).
     
    Last edited: Feb 25, 2017
  40. EternalAmbiguity

    EternalAmbiguity

    Joined:
    Dec 27, 2014
    Posts:
    3,144
    Well, the random thing was something my professor insisted upon. When I made my previous GridMake version, he asked me multiple times how I was making it iterate randomly through the grid.

    I'm going to have to explain to him that having it store a static grid and then iterating through all the cells accomplishes the same goal.

    After I get something set up for the input fields and controller script I'll post it here.
     
  41. EternalAmbiguity

    EternalAmbiguity

    Joined:
    Dec 27, 2014
    Posts:
    3,144
    Okay, so I added in the code to grab the values from the input field and add them sequentially to the list. Basically just

    List<float> ratios = new List<float>();

    before Awake(), then

    ratios.Add(float.Parse(basePage.transform.Find("StartNumberInputField").transform.Find("Text").GetComponent<Text>().text));

    within the first for loop (in CreateCA()) of vNGenericTest directly below the basePage line. So, the initialization technically works. However...see the attached images.

    Initialization Bug.png Initialization Bug2.png

    As you can see, there's an artifact of some type. I'm not exactly sure why it's happening. The minor-ly artifacted image has "ratio" values of 33, 34, and 33. The majorly artifacted image has values of 20, 60, and 20 (it probably goes without saying, but in both cases the one with the "most" is the black). In addition to the more obvious artifact of the black color, there also is some for the blue and red--in both cases, one of them's right edge is two columns off from the other one's right edge.

    Additionally, I'm trying to figure out how to do the user-selected color thing. What's throwing me most about this is your switch statement, in particular the switch expression, which I don't have a whole lot of experience with. I need the number of possible cases to be variable (based on the number of cell types), and I need a way to pull the current color from another place (I've made another List) based on the current "case" or key or whatever being evaluated.

    One thing I'm thinking of is a simple for loop like so:

    Code (csharp):
    1. List<string> colors = new List<string>();
    2. ...
    3. colors.Add("Color." + basePage.transform.Find("ColorDropdown").transform.Find("Label").gameObject.GetComponent<Text>().text.ToLower());
    4. ...
    5. for (int i = 0; i < amountOfCellTypes; ++i)
    6. {
    7. tileColor = colors[i]
    8. }
    The ellipses indicate gaps. First part is before Awake(). Second part is in CreateCA(). Third part would be where the switch is now in UpdateBoard().

    The only problem with the above is, I'm not sure what would be lost by removing any reference to myCA.GetCellState(i, j). I've looked at the documentation but I'm not sure what function GetCellState is having in the statement.


    Edit: actually, that last part is wrong now that I think about it. You're not iterating through all of those values. You're picking a value from a group of possible cases. So in reality that last section in the code should be something like:

    Code (csharp):
    1. private void UpdateBoard()
    2.     {
    3.         System.Drawing.Color tileColor;
    4.         for (int i = 0; i < gridWidth; ++i)
    5.         {
    6.             for (int j = 0; j < gridHeight; ++j)
    7.             {
    8.                 tileColor = System.Drawing.Color.FromName(colors[myCA.GetCellState(i, j)]);
    9.  
    10.                 for (int a = i * 10; a < i * 10 + 10; ++a)
    11.                     for (int b = j * 10; b < j * 10 + 10; ++b)
    12.                         tex.SetPixel(a, b, tileColor);
    13.             }
    14.         }
    15.         tex.Apply();
    16.     }
    with no for loop at all. Is that correct? Realizing this makes me realize that GetCellState is returning an int, which is why you have it in your switch statement.

    One problem with the above code: it says I can't convert between System.Drawing.Color and UnityEngine.Color (I added System.Drawing to the Assets folder, then added "using System.Drawing" to the start) at SetPixel. Any tips for this part?

    I used System.Drawing because it lets me get the color from a string of the name. Initially I was just using Unity's colors, but I can't convert from the string to an actual color. If I have to I'll just use a dictionary for this part like I did for the GridMake script, but I would prefer something simpler like this if it's possible (plus, this has way more color choices than Unity's colors).
     
    Last edited: Feb 27, 2017
  42. takatok

    takatok

    Joined:
    Aug 18, 2016
    Posts:
    1,496
    a switch statement is just a compact way of writing lots of if .. else if... else if
    The following 2 blocks of code are the same.
    Code (CSharp):
    1. int myInt = random.Range(0,3);
    2. switch(myInt)
    3. {
    4.    case 0:
    5.       Debug.Log("Its zero");
    6.       break;
    7.    case 1:
    8.       Debug.Log("Its one");
    9.       break;
    10.    case 2:
    11.       Debug.Log("Its two");
    12.       break;
    13.     default:
    14.        Debug.Log("Error MyInt is bigger than 2");
    15.        break;
    16. }    
    17.      
    Code (CSharp):
    1. int myInt = Random.Range(0,3);
    2. if (myInt == 0)
    3.    Debug.Log("Its zero");
    4. else if (myInt == 1)
    5.    Debug.Log("Its one");
    6. else if (myInt == 2)
    7.   Debug.Log("Its two");
    8. else
    9.    Debug.Log("Error myInt is bigger than 2");
     
  43. EternalAmbiguity

    EternalAmbiguity

    Joined:
    Dec 27, 2014
    Posts:
    3,144
    Yeah I was totally misunderstanding the switch statement. Please see my edit when you get the chance.

    Edit: I'm probably going to have to just use a dictionary. I can't see any simple way to convert between the two. I was thinking about converting the System.Drawing.Color to an RGBA, then use that for Unity, but it uses ARGB, and it doesn't even use the same number scale or so it seems. So it would be a bigger mess than it's worth. But that would only change that small part of the final part. I'll post what I come up with for that in a short while.
     
    Last edited: Feb 27, 2017
  44. EternalAmbiguity

    EternalAmbiguity

    Joined:
    Dec 27, 2014
    Posts:
    3,144
    Okay, here's the latest code with a dictionary:

    Code (csharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.UI;
    5.  
    6. public class vNGenericTest : MonoBehaviour
    7. {
    8.     int amountOfCellTypes;
    9.     int gridWidth;
    10.     int gridHeight;
    11.     Buttons buttonscript;
    12.     List<float> ratios = new List<float>();
    13.     List<int> colors = new List<int>();
    14.     Dictionary<int, Color> dict;
    15.  
    16.     CA myCA;
    17.     Texture2D tex;
    18.     Sprite board;
    19.     SpriteRenderer sr;
    20.     private int iterations = 0;
    21.     public Text iterText;
    22.  
    23.     private void Awake()
    24.     {
    25.     }
    26.  
    27.     private void Start()
    28.     {
    29.         buttonscript = GameObject.Find("ButtonScript").GetComponent<Buttons>();
    30.  
    31.         //This dictionary only uses the Unity-named colors because it's easier to pick these in a drop-down than to have a color selector.
    32.         //Future updates might adjust the UI page to allow for direct color selection, which could then be passed to this script and avoid the artificial limitations here.
    33.         //After all, this can only show up to 9 cell types. Any number is possible, but with only 9 colors you can only differentiate between nine types.
    34.         dict = new Dictionary<int, Color>();
    35.         dict.Add(0, Color.black);
    36.         dict.Add(1, Color.blue);
    37.         dict.Add(2, Color.cyan);
    38.         dict.Add(3, Color.gray);
    39.         dict.Add(4, Color.green);
    40.         dict.Add(5, Color.magenta);
    41.         dict.Add(6, Color.red);
    42.         dict.Add(7, Color.white);
    43.         dict.Add(8, Color.yellow);
    44.     }
    45.  
    46.     private void Update()
    47.     {
    48.         if (Input.GetKeyUp(KeyCode.E))
    49.         {
    50.             OneIteration();
    51.             UpdateBoard();
    52.         }
    53.         if(Input.GetKey(KeyCode.C))
    54.         {
    55.             OneIteration();
    56.             UpdateBoard();
    57.         }
    58.  
    59.         if(buttonscript.continueCA == true)
    60.         {
    61.             OneIteration();
    62.             UpdateBoard();
    63.         }
    64.     }
    65.  
    66.     public void OneIteration()
    67.     {
    68.         myCA.OneIteration();
    69.         iterations++;
    70.         iterText.text = "Iteration: " + iterations.ToString();
    71.     }
    72.  
    73.     public void CreateCA()
    74.     {
    75.         amountOfCellTypes = int.Parse(GameObject.FindGameObjectWithTag("numbercelltype").GetComponent<Text>().text);
    76.         gridWidth = int.Parse(GameObject.Find("Horigrid").GetComponent<Text>().text);
    77.         gridHeight = int.Parse(GameObject.Find("Vertgrid").GetComponent<Text>().text);
    78.  
    79.         myCA = new CA(gridWidth, gridHeight, amountOfCellTypes, NType.VonNeumann);
    80.  
    81.         for (int i = 0; i < amountOfCellTypes; ++i)
    82.         {
    83.             string numstring = (i + 1).ToString();
    84.             GameObject basePage = GameObject.Find("NMP1" + numstring);
    85.             ratios.Add(float.Parse(basePage.transform.Find("StartNumberInputField").transform.Find("Text").GetComponent<Text>().text));
    86.             colors.Add(basePage.transform.Find("ColorDropdown").GetComponent<Dropdown>().value);
    87.             for (int j = 1; j < amountOfCellTypes; ++j)
    88.             {
    89.                 GameObject probGrandparent = basePage.transform.Find("ProbabilityScrollView").transform.Find("1stProbContent").gameObject;
    90.                 for (int n = 0; n <= 4; ++n)
    91.                 {
    92.                     GameObject probParent = probGrandparent.transform.Find("ProbInputField1." + j + "." + n).gameObject;
    93.                     float val = float.Parse(probParent.transform.Find("ProbTransValue").GetComponent<Text>().text);
    94.                     int startState = (int.Parse(probParent.transform.Find("ProbTransText").GetComponent<Text>().text.Remove(0, 28).Remove(1, 41))) - 1;
    95.                     int endState = (int.Parse(probParent.transform.Find("ProbTransText").GetComponent<Text>().text.Remove(0, 33).Remove(1, 36))) - 1;
    96.                     myCA.SetStateInfo(startState, endState, n, val);
    97.                 }
    98.             }
    99.         }
    100.  
    101.         myCA.InitializeGrid(ratios);
    102.     }
    103.  
    104.     public void StartCA()
    105.     {
    106.         tex = new Texture2D((gridWidth*10), (gridHeight*10));
    107.         board = Sprite.Create(tex, new Rect(Vector2.zero, new Vector2((gridWidth * 10), (gridHeight * 10))), Vector2.zero, 1f);
    108.         sr = gameObject.AddComponent<SpriteRenderer>();
    109.         sr.transform.localScale = new Vector2((45f / gridWidth), (45f / gridHeight));
    110.         sr.transform.localPosition = new Vector2((sr.transform.position.x - (((gridWidth * 10) / 2) * sr.transform.localScale.x)), (sr.transform.position.y - (((gridHeight * 10) / 2) * sr.transform.localScale.y)));
    111.         sr.GetComponent<SpriteRenderer>().sortingLayerName = "Foreground";
    112.         sr.sprite = board;
    113.         UpdateBoard();
    114.     }
    115.  
    116.     private void UpdateBoard()
    117.     {
    118.         Color tileColor;
    119.         for (int i = 0; i < gridWidth; ++i)
    120.         {
    121.             for (int j = 0; j < gridHeight; ++j)
    122.             {
    123.                 tileColor = dict[colors[myCA.GetCellState(i, j)]];
    124.  
    125.                 for (int a = i * 10; a < i * 10 + 10; ++a)
    126.                     for (int b = j * 10; b < j * 10 + 10; ++b)
    127.                         tex.SetPixel(a, b, tileColor);
    128.             }
    129.         }
    130.         tex.Apply();
    131.     }
    132. }
    And it works:

    Initialization Bug3.png

    It still has the strange glitch, but it only happens to the one type this time. BTW it's cyan = 33, grey = 34, green = 33.

    So the two main things left are to solve that ^ and then re-implement the writing-to-a-file. It would be nice to have the user able to select where such a file would be stored, but I'll cross that bridge when I get to it.

    Edit: Woooooah, hang on a second. I just tried creating a grid with ONLY one cell type and it still gave me a mix. The first time I tried 0, 100, and 0. The next time I tried 0, 10000, and 0 (both times I used a 100 x 100 size grid). Same result: something like that up above, though it did not have the weird side artifact. Very, very strange.
     
    Last edited: Feb 28, 2017
  45. takatok

    takatok

    Joined:
    Aug 18, 2016
    Posts:
    1,496
    its because my initialize grid code had some logical errors. I fixed this an optimized the function to randomly pick a float from a range. Tried multiple cases including 0,100,0 and it seems to work correctly now without those weird artifact on the right side.
    Code (CSharp):
    1. using System;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using UnityEngine;
    5. using System.Linq;
    6. using Random = UnityEngine.Random;
    7. using SysRand = System.Random;
    8.  
    9. public class CA
    10. {
    11.     private CellState[] states;
    12.     private Neighborhood neighborhood;
    13.     private int[,] grid;
    14.     private int[,] backup;
    15.     private int gridWidth;
    16.     private int gridHeight;
    17.     private int numStates;
    18.     SysRand myRand;
    19.  
    20.  
    21.  
    22.     public CA(int width, int height, int numStates, NType type)
    23.     {
    24.         gridWidth = width;
    25.         gridHeight = height;
    26.         this.numStates = numStates;
    27.         grid = new int[width, height];
    28.         backup = new int[width, height];
    29.         neighborhood = new Neighborhood(type);
    30.         states = new CellState[numStates];
    31.         for (int i = 0; i < numStates; ++i)
    32.             states[i] = new CellState(numStates, neighborhood.GetNeighborSize());
    33.         myRand = new SysRand();
    34.  
    35.  
    36.     }
    37.  
    38.     public void InitializeGrid(List<float> ratios)
    39.     {
    40.         // make sure ratios is as big as our numStates
    41.         while (ratios.Count < numStates)
    42.             ratios.Add(0f);
    43.         while (ratios.Count > numStates)
    44.             ratios.Remove(ratios.Last());
    45.  
    46.         //Convert our list of float ratios to [0,1] percentages
    47.         float total = ratios.Sum();
    48.         for (int i = 0; i < ratios.Count; ++i)
    49.             ratios[i] /= total;
    50.  
    51.         //Get a list of how many cells of each time we need
    52.         List<int> cellCount = new List<int>();
    53.         int totalCells = gridWidth * gridHeight;
    54.         for (int i = 0; i < numStates; ++i)
    55.             cellCount.Add((int)(ratios[i] * totalCells));
    56.      
    57.         // Because of rounding we might be a few from our total
    58.         // we'll just add them to state 0. It will not be noticeable
    59.         cellCount[0] += totalCells - cellCount.Sum();
    60.  
    61.         // this is a list of which states still needed to be added
    62.         List<int> stateIndex = new List<int>();
    63.         for (int i = 0; i < numStates; ++i)
    64.             stateIndex.Add(i);
    65.  
    66.         float[] pickRange = GetPickRange(ratios);
    67.  
    68.         for (int k = 0; k < 5; ++k)
    69.         {
    70.             for (int i = 0; i < gridWidth; ++i)
    71.             {
    72.                 for (int j = k; j < gridHeight; j+=5)
    73.                 {
    74.                     // Get a random number for the states left to do and set it to the grid
    75.                     // we can't just use the number directly becuase stateIndex
    76.                     // could be 0,3,4  if we've finished setting all the 1s and 2s
    77.                     int index = GetIndexFromRange(pickRange);
    78.                     int state = stateIndex[index];
    79.                     grid[i, j] = state;
    80.  
    81.                     // Subtract that state from its cellcount
    82.                     // And if it goes to zero remove it from both our lists
    83.                     cellCount[index]--;
    84.                     if (cellCount[index] == 0)
    85.                     {
    86.                         cellCount.RemoveAt(index);
    87.                         stateIndex.RemoveAt(index);
    88.                         ratios.RemoveAt(index);
    89.                         pickRange = GetPickRange(ratios);
    90.  
    91.                     }
    92.  
    93.                 }
    94.             }
    95.         }
    96.     }
    97.  
    98.     public void SetStateInfo(int startState, int endState, int numNeighbors, float prob)
    99.     {
    100.         states[startState].SetProbability(endState, numNeighbors, prob);
    101.      
    102.     }
    103.  
    104.     public void SetCellState(int x, int y, int state)
    105.     {
    106.         grid[x, y] = state;
    107.     }
    108.  
    109.     public int GetCellState(int x, int y)
    110.     {
    111.         return grid[x, y];
    112.     }
    113.  
    114.     public void OneIteration()
    115.     {
    116.         Array.Copy(grid, backup, gridWidth * gridHeight);
    117.         for (int x = 0; x < gridWidth; ++x)
    118.         {
    119.             for (int y = 0; y < gridHeight; ++y)
    120.             {
    121.                 int currentState = grid[x, y];
    122.                 List<int> neighborStateCount = GetNeighborCount(x, y);
    123.                 float[] probChances = GetProbChances(currentState, neighborStateCount);
    124.                 grid[x, y] = GetStateFromProbability(probChances);
    125.             }
    126.         }
    127.     }
    128.  
    129.     private List<int> GetNeighborCount(int x, int y)
    130.     {
    131.         List<int> neighborCount = new List<int>();
    132.         for (int i = 0; i < numStates; ++i)
    133.             neighborCount.Add(0);
    134.         List<Point> neighbors = neighborhood.GetNeighbors(x, y);
    135.  
    136.         //Get a count of each state in our neighborhood
    137.         foreach (Point p in neighbors)
    138.         {
    139.             if (!p.onGrid(gridWidth, gridHeight))
    140.                 continue;
    141.             neighborCount[backup[p.X, p.Y]]++;
    142.         }
    143.         return neighborCount;
    144.     }
    145.  
    146.     private float[] GetProbChances(int currentState, List<int> neighborStateCount)
    147.     {
    148.         float[] probChances = new float[numStates];
    149.         float totalProb = 0;
    150.         for (int p = 0; p < numStates; ++p)
    151.         {
    152.             //skip if we are on the state of the current cell
    153.             //we'll figure it out after we find out all the other probs
    154.             if (p == currentState)
    155.                 continue;
    156.             float prob = states[currentState].GetProbability(p, neighborStateCount[p]);
    157.             probChances[p] = prob;
    158.             // the chance of us not changing is the product of all the other states not happening
    159.             totalProb += prob;
    160.         }
    161.         float noChange = 1 - totalProb;
    162.         probChances[currentState] = noChange > 0 ? noChange : 0;
    163.         return probChances;
    164.     }
    165.  
    166.     private int GetStateFromProbability(float[] probChances)
    167.     {
    168.         float[] pickRange = GetPickRange(probChances.ToList());
    169.         return GetIndexFromRange(pickRange);
    170.     }
    171.  
    172.     private int GetIndexFromRange(float[] pickRange)
    173.     {
    174.         float range = pickRange[pickRange.Length - 1];
    175.         float pick = (float)myRand.NextDouble() * range;
    176.         return Array.IndexOf(pickRange, pickRange.Where(x => x >= pick).First());
    177.     }
    178.  
    179.     private float[] GetPickRange(List<float> prob)
    180.     {
    181.         float[] pickRange = new float[prob.Count];
    182.         float total = 0;
    183.         for (int i=0;i<prob.Count;++i)
    184.         {
    185.             total += prob[i];
    186.             pickRange[i] = total;
    187.         }
    188.         return pickRange;
    189.     }
    190. }

    Just a quick note that might be silly. But I noticed a few times you've mentioned typing all the new code in. I assume thats just a turn of phrase, but you know you drag you mouse over the above code, hit ctrl+C. Then in your editor in the CA.cs file hit Ctrl+A (highlight all) and then hit Ctrl+V and it will overwrite the entire file with this file.
     
    EternalAmbiguity likes this.
  46. EternalAmbiguity

    EternalAmbiguity

    Joined:
    Dec 27, 2014
    Posts:
    3,144
    I've mentioned it a few times so I'm not sure which time you're referring to :p

    However, I've tried to avoid just copy-pasting your code because I saw somewhere (I don't remember where) that just copy-pasting stuff is a terrible way to learn. By typing it in myself, I'm forced to see every method and function--to be exposed to it at the very least, so hopefully I can start to understand it. Rather than just pasting it in there and not even being exposed to it.

    I will however, just paste this in :p Because I don't want to miss some minor detail you've added in or something.

    Anyway, just tried it and it works. Thanks a ton!

    Question for you I just thought of: can I save the sprite after each (variable time)? I intend to save the amount of each cell type after each iteration--can I set this up to save the sprite as an image or something every X iterations?

    Edit: and while I'm thinking about it, how would I do the other--save the amount of each cell type? Is that something that should be kept in CA or in my controller script?
     
    Last edited: Feb 28, 2017
    takatok likes this.
  47. takatok

    takatok

    Joined:
    Aug 18, 2016
    Posts:
    1,496
    Rewriting the code by hand does make sense from a learning point of view.

    You can certainly save this texture out to a file.
    https://docs.unity3d.com/ScriptReference/Texture2D.EncodeToPNG.html

    You just get the texture on the sprite and use that function to create an array of bytes. You can write these bytes out to a file using a StreamWriter.

    As far as keeping track of state numbers thats a pretty easy function to write, and you would write it in the CA. Its the one keeping all the hard data. Just create a List<int> stateCount and add 0 for every numState. Then iterate over the grid and do something like stateCount[grid[i,j]]++; (grid[i,j] returns the state at that point. and use that state as an index into your list and just add 1 to it. When your done your list will have the numbers of each state. Your method can return that list.
     
    EternalAmbiguity likes this.
  48. EternalAmbiguity

    EternalAmbiguity

    Joined:
    Dec 27, 2014
    Posts:
    3,144
    Okay, so to save the image I did this in vNGenericTest:

    Code (csharp):
    1. public void SaveImage()
    2.     {
    3.         byte[] bytes = tex.EncodeToPNG();
    4.         Directory.CreateDirectory(Application.persistentDataPath + "/CA Image Captures");
    5.         File.WriteAllBytes(Application.persistentDataPath + "/CA Image Captures/" + System.DateTime.Now.ToString("yyyy-MM-dd HH-mm-ss") + " Iteration " + iterations + ".png", bytes);
    6.     }
    It works well.

    For the iterative count I've done this so far in CA:

    Code (csharp):
    1. List<int> stateCount = new List<int>();
    2. ...
    3. for (int i = 0; i < numStates; ++i)
    4.         {
    5.             stateCount.Add(0);
    6.             states[i] = new CellState(numStates, neighborhood.GetNeighborSize());
    7.         }
    8. ...
    9. public List<int> cellCount()
    10.     {
    11. for (int i = 0; i < numStates; ++i)
    12.             stateCount[i] = 0;
    13.         for (int x = 0; x < gridWidth; ++x)
    14.         {
    15.             for (int y = 0; y < gridHeight; ++y)
    16.             {
    17.                 int currentState = grid[x, y];
    18.                 stateCount[currentState] += 1;
    19.             }
    20.         }
    21.         return stateCount;
    22.     }
    First part is at the beginning of the class. Second part is a small modification of your for loop inside the method CA. The last part is a method I put at the end of the script. This is all I have so far, but I'll need to make a new method in vNGenericTest which uses streamwriter to write the current value of the list to a file.

    Just thought I'd post this in case I'm going about this wrong or if there's some optimization I'm missing. Later I'll write the method and test it out.
     
    takatok likes this.
  49. EternalAmbiguity

    EternalAmbiguity

    Joined:
    Dec 27, 2014
    Posts:
    3,144
    Okay, I'm kind of stuck. Relevant parts of the CA script:

    Code (csharp):
    1. public static List<int> stateCount = new List<int>();
    2. ...
    3. for (int i = 0; i < numStates; ++i)
    4.         {
    5.             stateCount.Add(0);
    6.             states[i] = new CellState(numStates, neighborhood.GetNeighborSize());
    7.         }
    8. ...
    9. public void OneIteration()
    10.     {
    11.         Array.Copy(grid, backup, gridWidth * gridHeight);
    12.         for (int i = 0; i < numStates; ++i)
    13.             stateCount[i] = 0;
    14.         for (int x = 0; x < gridWidth; ++x)
    15.         {
    16.             for (int y = 0; y < gridHeight; ++y)
    17.             {
    18.                 int currentState = grid[x, y];
    19.                 List<int> neighborStateCount = GetNeighborCount(x, y);
    20.                 float[] probChances = GetProbChances(currentState, neighborStateCount);
    21.                 grid[x, y] = GetStateFromProbability(probChances);
    22.                 stateCount[currentState] += 1;
    23.             }
    24.         }
    25.     }
    I got rid of my separate method. Didn't think it was necessary, though if separating out this into a new method is better, by all means tell me. I also made the List static, because I couldn't pass it over to vNGenericTest without doing so (I'm not really sure why).

    Then the relevant parts of vNGenericTest:

    Code (csharp):
    1. List<List<int>> fullCount = new List<List<int>>();
    2. ...
    3. public void OneIteration()
    4.     {
    5.         myCA.OneIteration();
    6.         iterations++;
    7.         iterText.text = "Iteration: " + iterations.ToString();
    8.         fullCount.Add(CA.stateCount);
    9.     }
    10. ...
    11. public void SaveIterationCount()
    12.     {
    13.         using (StreamWriter wt = File.AppendText(Application.persistentDataPath + System.DateTime.Now.ToString("yyyy-MM-dd HH-mm-ss") + " CA Run.txt"))
    14.         {
    15.              
    16.             for (int i = 0; i < fullCount.Count; ++i)
    17.             {
    18.                 wt.Write("Iteration: " + i);
    19.                 for (int j = 0; j < amountOfCellTypes; ++j)
    20.                 {
    21.                     string cellTypeString = (j + 1).ToString();
    22.                     wt.Write(" Cell Type " + cellTypeString + ": " + fullCount[i][j]);
    23.                 }
    24.                 wt.WriteLine();
    25.             }
    26.             wt.Close();
    27.         }
    28.     }
    The first part is in the initialization area. Second part is just a small modification to the iterative script, which will add a "line" to a list per iteration, where each "line" is the list with cell counts. The third part is a script to print that out. I have a button linked to that third part, so the user can output the file at any time during the run.

    Unfortunately, the text stays at the final result for every line (except for the iteration part). It actually outputs the correct number of lines (I stopped at iteration 62 and saved it, and it had 62 lines), but each one has the same text ("Iteration: 883 Cell Type 1: 3605 Cell Type 2: 2223 Cell Type 3: 4172" in one case).

    I'm not sure why this is happening. One possibility I see is that when I do "fullCount.Add(CA.stateCount), it overwrites the previous lines...but I don't see how that could even happen. Any suggestions?

    I've attached the project.
     

    Attached Files:

  50. takatok

    takatok

    Joined:
    Aug 18, 2016
    Posts:
    1,496
    Its because lists are reference types not value types. When you make a List<List<int>> you have a lists of lists. Now each list is just a reference out to the full list. So lets assume that CA.statecount is sitting in location 1000 in memory. When you do this line:
    Code (CSharp):
    1.  fullCount.Add(CA.stateCount);
    fullcount[0] = memory location 1000 (the reference to CA.statuecount). Now we can index into that memory with fullCount[0][0] to get the first int there. fullcount[0][1] will get us the 2nd int and so on.

    Now you go to the next iteration, and CA.StateCount has all the NEW iteration information but its still sitting at memory location 1000. fullcount[0] is still pointing to memory location 1000. Now you add CA.stateCount again so now
    fullcount[0] = 1000 (memory location)
    fullcount[1] = 1000 (memory location)

    Your just adding the a reference to the same static list over and over. So your entire fullcount will all be pointing to CA.Statecount and it will only have the whatever data was in it the last iteration. To fix this we need to make a new list and copy statecount into it:
    Code (CSharp):
    1. public void OneIteration()
    2.     {
    3.         myCA.OneIteration();
    4.         iterations++;
    5.         iterText.text = "Iteration: " + iterations.ToString();
    6.         List<int> currentCellCount = new List<int>();
    7.         // Add range copies the contents of one list to another
    8.         currentCellCount.AddRange(CA.stateCount);
    9.         fullCount.Add(currentCellCount);
    10.     }
    Note if you just did this:
    Code (CSharp):
    1. currentCellCount = CA.stateCount;
    we would again just be copying the reference (memory location) of CA.stateCount and that wouldn't help us at all.
     
    EternalAmbiguity likes this.