Segregation
About the code
The rules
Defining the parameters
#define NB_CELLS 30
#define CELL_SIZE (SCREEN_HEIGHT / NB_CELLS)
We will need the fraction of empty cells in the grid
#define EMPTY 0.1 // % of empty cells
The proportion of red cells
#define RED_RATIO 0.5 // % of red in the initial state
And the likeness coefficient
#define LIKENESS 0.3 // % of wanted neighbors of the same color
The grid will be stored in an int table that can contain 0 for an empty cell, 1 for a red agent or 2 for a green
int grid[NB_CELLS][NB_CELLS];
// initialize the grid
for (int y = 0; y < NB_CELLS; y++)
for (int x = 0; x < NB_CELLS; x++)
grid[x][y] = 0;
And we will use a palette for these 3 values.
// initialize the palette
Color palette[3];
palette[0] = Color( 70, 70, 70); // empty cell
palette[1] = Color(220, 0, 0); // red
palette[2] = Color( 0, 150, 0); // green
Setting up the board
void CGfx::rectFill(int x0, int y0, int x1, int y1, Color c)
{
for (int y = y0; y < y1; y++)
for (int x = x0; x < x1; x++)
gfx.setPixel(x, y, c);
}
I wrote a function for empty rectangles too, but we won't use it.
// draw the grid
for (int y = 0; y < NB_CELLS; y++)
for (int x = 0; x < NB_CELLS; x++)
{
gfx.rectFill( x * CELL_SIZE, y * CELL_SIZE,
(x + 1) * CELL_SIZE - 2, (y + 1) * CELL_SIZE - 2,
palette[grid[x][y]]);
}
Download source code
Download executable for Windows
This gives us this result:
int nbOccupied = NB_CELLS * NB_CELLS * (1 - EMPTY); // number of occupied cells
Now, how do we fill the occupied cells by respecting the red/green ratio.
int nbRed = nbOccupied * RED_RATIO; // number of red cells
And as we will fill nbOccupied cells, the first nbRed ones will be red. The other ones will be green.
for (int i = 0; i < nbOccupied; i++)
{
// find an empty cell
while (true)
{
int x = rand() % NB_CELLS;
int y = rand() % NB_CELLS;
if (grid[x][y] == 0)
{
// fill the cell
if (i < nbRed)
grid[x][y] = 1;
else
grid[x][y] = 2;
break;
}
}
}
Download source code
Download executable for Windows
This code produces the same result as the first image of this article.
Selecting the agent
// find an empty cell
int xEmpty, yEmpty;
while (true)
{
xEmpty = rand() % NB_CELLS;
yEmpty = rand() % NB_CELLS;
if (grid[xEmpty][yEmpty] == 0)
break;
}
And an occupied one
// find an occupied cell
int xOccupied, yOccupied;
while (true)
{
xOccupied = rand() % NB_CELLS;
yOccupied = rand() % NB_CELLS;
if (grid[xOccupied][yOccupied] != 0)
break;
}
Now, to decide if the agent will move to the the empty cell, we will need to compute its "happiness" in the both
Counting the neighbors
float countNeighbors(int xCell, int yCell, int color)
{
// count neighbors
int nbSame = 0;
int nbOther = 0;
We will loop from -1 to 1 on both axes and add these values to the cell coordinates to get the coordinates of the
for (int yi = -1; yi <= 1; yi++)
for (int xi = -1; xi <= 1; xi++)
{
// don't count the center
if (xi == 0 && yi == 0)
continue;
int x = xCell + xi;
int y = yCell + yi;
We won't count cells that are outside the grid too.
// check if we are not outside of the grid
if (x < 0 || x >= NB_CELLS ||
y < 0 || y >= NB_CELLS)
continue;
And we won't count empty cells...
// empty cell ?
if (grid[x][y] == 0)
continue;
Then we can increase the counters
// count
if (grid[x][y] == color)
nbSame++;
else
nbOther++;
}
Now we can return the ratio. But be careful, it's not the ratio of nbSame over nbOther, but the ratio of nbSame
return (float)nbSame / (float)(nbSame + nbOther);
But here there is another trap. We can't divide by zero, so we have to check that before returning:
if (nbSame + nbOther == 0)
return 0.0;
Deciding to move
// current likeness
int color = grid[xOccupied][yOccupied];
float likeOld = countNeighbors(xOccupied, yOccupied, color);
Then we can check if he is unhappy
// is unHappy ?
if (likeOld < LIKENESS)
{
If it's the case, we can compute the likeness he would have if he was in the empty cell
// new likeness
float likeNew = countNeighbors(xEmpty, yEmpty, color);
Then if he would be happy there, make it move.
// want to move ?
if (likeNew >= LIKENESS)
{
grid[xEmpty][yEmpty] = color;
grid[xOccupied][yOccupied] = 0;
}
}
Download source code
Download executable for Windows
This code tends to give the result described at the beginning.
Speeding up
bool happy[NB_CELLS][NB_CELLS];
[...]
// fill happy table
int nbUnhappy = 0;
float meanLikeness = 0.0;
for (int y = 0; y < NB_CELLS; y++)
for (int x = 0; x < NB_CELLS; x++)
{
happy[x][y] = true;
if (grid[x][y] != 0)
{
float like = countNeighbors(x, y, grid[x][y]);
meanLikeness += like;
if (like < LIKENESS)
{
happy[x][y] = false;
nbUnhappy++;
}
}
}
printf("likeness: %f%% unhappy: %f%%\n", meanLikeness * 100.0 / (float)nbOccupied, (float)nbUnhappy * 100.0 / (float)nbOccupied);
This way we will know when the program ends because the number of unhappy agents will get to 0.
// find an unhappy cell
int xOccupied, yOccupied;
while (true)
{
xOccupied = rand() % NB_CELLS;
yOccupied = rand() % NB_CELLS;
if (happy[xOccupied][yOccupied] == false)
break;
}
And the decision process will then be simpler:
// new likeness
int color = grid[xOccupied][yOccupied];
float likeNew = countNeighbors(xEmpty, yEmpty, color);
// want to move ?
if (likeNew >= LIKENESS)
{
grid[xEmpty][yEmpty] = color;
grid[xOccupied][yOccupied] = 0;
}
Download source code
Download executable for Windows
This code looks faster even if I increased the grid to 40*40 and I added a sys.wait(50) in the main loop !
With 3 colors
palette[3] = Color( 20, 20, 255); // blue
Then we need the ratio that we will use to fill the starting grid:
#define RED_RATIO 0.333 // % of red in the initial state
#define BLUE_RATIO 0.333 // % of blue in the initial state
[...]
int nbOccupied = NB_CELLS * NB_CELLS * (1 - EMPTY); // number of occupied cells
int nbRed = nbOccupied * RED_RATIO; // number of red cells
int nbBlue = nbOccupied * BLUE_RATIO; // number of blue cells
for (int i = 0; i < nbOccupied; i++)
{
// find an empty cell
while (true)
{
int x = rand() % NB_CELLS;
int y = rand() % NB_CELLS;
if (grid[x][y] == 0)
{
// fill the cell
if (i < nbRed)
grid[x][y] = 1;
else if (i < nbRed + nbBlue)
grid[x][y] = 3;
else
grid[x][y] = 2;
break;
}
}
}
Download source code
Download executable for Windows
And that's all.