Ségrégation
A propos du code
Les règles
Définition des paramètres
#define NB_CELLS 30
#define CELL_SIZE (SCREEN_HEIGHT / NB_CELLS)
On aura besoin de la fraction de cases vides dans cette grille
#define EMPTY 0.1 // % of empty cells
La proportion de cases rouges
#define RED_RATIO 0.5 // % of red in the initial state
Et le coefficient de ressemblance.
#define LIKENESS 0.3 // % of wanted neighbors of the same color
La grille sera stockée dans un tableau d'entiers qui pourra contenir 0 pour une case vide, 1 pour un agent rouge ou
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;
Et on va utiliser une palette pour ces 3 valeurs.
// 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
Mise en place de la grille
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);
}
J'ai aussi écrit une fonction pour les rectangles vides, mais on ne va pas l'utiliser.
// 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]]);
}
Télécharger le code source
Télécharger l'exécutable pour Windows
Ca donne ce résultat:
int nbOccupied = NB_CELLS * NB_CELLS * (1 - EMPTY); // number of occupied cells
Maintenant, comment remplit-on les cases occupées en respectant le ratio rouge/vert ?
int nbRed = nbOccupied * RED_RATIO; // number of red cells
Et quand on va remplir les nbOccupied cases, les nbRed premières seront rouges. Le reste sera vert.
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;
}
}
}
Télécharger le code source
Télécharger l'exécutable pour Windows
Ce code produit le même résultat que la première image de cet article.
Séléctionner un agent
// find an empty cell
int xEmpty, yEmpty;
while (true)
{
xEmpty = rand() % NB_CELLS;
yEmpty = rand() % NB_CELLS;
if (grid[xEmpty][yEmpty] == 0)
break;
}
Et une pleine
// find an occupied cell
int xOccupied, yOccupied;
while (true)
{
xOccupied = rand() % NB_CELLS;
yOccupied = rand() % NB_CELLS;
if (grid[xOccupied][yOccupied] != 0)
break;
}
Ensuite, pour décider si l'agent va aller dans la case vide, on a besoin de calculer s'il est "heureux" dans
Compter les voisins
float countNeighbors(int xCell, int yCell, int color)
{
// count neighbors
int nbSame = 0;
int nbOther = 0;
On va boucler de -1 à 1 sur les deux axes et ajouter ces valeurs aux coordonnées de la case pour avoir les
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;
On ne compte pas les cases qui sont en dehors de la grille non plus.
// check if we are not outside of the grid
if (x < 0 || x >= NB_CELLS ||
y < 0 || y >= NB_CELLS)
continue;
Et on ne compte pas les cases vides...
// empty cell ?
if (grid[x][y] == 0)
continue;
Enfin on peut incrémenter les compteurs
// count
if (grid[x][y] == color)
nbSame++;
else
nbOther++;
}
Maintenant on peut retourner le ratio. Mais attention, ce n'est pas le ratio de nbSame sur nbOther, mais le ratio
return (float)nbSame / (float)(nbSame + nbOther);
Mais il y a un autre piège ici. On ne peut pas diviser par zéro, alors on doit tester ça avant de retourner notre
if (nbSame + nbOther == 0)
return 0.0;
Décider de déménager
// current likeness
int color = grid[xOccupied][yOccupied];
float likeOld = countNeighbors(xOccupied, yOccupied, color);
Ensuite on peut tester si il est malheureux
// is unHappy ?
if (likeOld < LIKENESS)
{
Si c'est le cas, on peut calculer la ressemblance qu'il aurait s'il était dans la case vide
// new likeness
float likeNew = countNeighbors(xEmpty, yEmpty, color);
Enfin, s'il serait heureux à cet endroit, on le fait bouger.
// want to move ?
if (likeNew >= LIKENESS)
{
grid[xEmpty][yEmpty] = color;
grid[xOccupied][yOccupied] = 0;
}
}
Télécharger le code source
Télécharger l'exécutable pour Windows
Ce code a tendance à donner le résultat qu'on a décrit au début.
Accélération
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);
De cette facon on saura quand le programme a fini parce que le nombre d'agents malheureux va tendre vers 0.
// find an unhappy cell
int xOccupied, yOccupied;
while (true)
{
xOccupied = rand() % NB_CELLS;
yOccupied = rand() % NB_CELLS;
if (happy[xOccupied][yOccupied] == false)
break;
}
Et le processus de décision sera alors simplifié:
// 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;
}
Télécharger le code source
Télécharger l'exécutable pour Windows
Ce code a l'air plus rapide même si j'ai agrandi la grille à 40*40 et que j'ai ajouté un sys.wait(50) dans la
Avec 3 couleurs
palette[3] = Color( 20, 20, 255); // blue
Ensuite on aura besoin du ratio qu'on va utiliser pour remplir la grille au départ:
#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;
}
}
}
Télécharger le code source
Télécharger l'exécutable pour Windows
Et c'est tout.