//=========================================================================== // SHELLING'S SEGREGATION MODEL // VERSION 3 // 1 February 2006 // // There are 3 kinds of individuals in this world: Cyan, Magenta and Yellow. // Agents will move to an empty space depending upon a set of rules. // Agent scheduling is completely random. //=========================================================================== #include #pragma hdrstop #include "Unit1.h" #include "stdlib.h" // add this to randomize random number generator #include "time.h" // add this to randomize random number generator //--------------------------------------------------------------------------- #pragma package(smart_init) #pragma resource "*.dfm" TForm1 *Form1; //--------------------------------------------------------------------------- __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { } //=========================================================================== // DEFINES //=========================================================================== #define BLACK 0 #define CYAN 1 #define MAGENTA 2 #define YELLOW 3 #define dONLY 0 #define COMBOBOX 0 #define TRACKBAR 1 #define MOORE 0 #define TONG 1 #define VONNEUMANN 2 //=========================================================================== // VARIABLES //=========================================================================== // this sets up graphics functions to handle the import of a bitmap image Graphics::TBitmap* b = new Graphics::TBitmap(); RGBTRIPLE* t; // this is the main array for the world int world[200][200]; int myType; // these arrays capture the Red, Green and Blue values of a bitmap image // the .bmp image is used as a geographic map of additional rules int bmpRed[200][200]; int bmpGreen[200][200]; int bmpBlue[200][200]; // these are locations in the world int e, s; // east and south int hE, hS; // old east and old south (the agent's home) int dE, dS; // new east and new south (the agent's destination) // these are neighbor counters int hNeighbors[4]; // neighbors of each type at home int dNeighbors[4]; // neighbors of each type at destination int neighborhood = MOORE; // these are user choices int numberLikeMe; // preference for ones own kind int ruleChoice; // choice of rule int distanceChoice; // choice of distance int rulesApplyTo; // choice of neighborhoods to check int popFiller = 0; int cyanPop, magentaPop, yellowPop, emptyPop; // these assist in computing wrap-around neighbors int row, column, wrapRow, wrapColumn; // this a single counter int i; int runTo = 1000; // this is "a throw of the dice" int dice; int chance; // this counts frames, defined as one opportunity for each agent to move // a frame is 40,000 opportunities (200 x 200) int frame; // this counts opportunities for a single agent // to move from an old location to a new location int opportunity; int iterations; // this records whether "STOP" has been pressed bool stop = true; // this records whether rendering has been turned off bool renderOff = false; //=========================================================================== // FUNCTIONS //=========================================================================== //---------------------------------------------------------------- FILL WORLD // Fills the world array with the random values 0, 1, 2 and 3. // These are interpreted as Empty or Red, Blue or Green agents. // You might change the proportion of agents and empty cells. void fillWorld(void) { randomize(); iterations = 0; if (popFiller == COMBOBOX) { Form1->LabelError->Visible = false; for (e=0; e<200; e++) { for (s=0; s<200; s++) { switch (Form1->ComboBoxFill->ItemIndex) { case 0: { // 25% Empty, 25% Cyan, 25% Magenta, 25% Yellow Form1->TrackBarCyanPop->Position = 25; Form1->TrackBarMagentaPop->Position = 25; Form1->TrackBarYellowPop->Position = 25; Form1->TrackBarEmptyPop->Position = 25; world[e][s]=random(4); break; } case 1: { // 17% Empty, 50% Cyan, 17% Magenta, 17% Yellow Form1->TrackBarCyanPop->Position = 50; Form1->TrackBarMagentaPop->Position = 17; Form1->TrackBarYellowPop->Position = 17; Form1->TrackBarEmptyPop->Position = 17; if (random(2) == 0) { world[e][s] = CYAN; } else { int thirds = random(3); if (thirds == 0) world[e][s] = BLACK; if (thirds == 1) world[e][s] = MAGENTA; if (thirds == 2) world[e][s] = YELLOW; } break; } case 2: { // 30% Empty, 10% Cyan, 20% Magenta, 40% Yellow Form1->TrackBarCyanPop->Position = 10; Form1->TrackBarMagentaPop->Position = 20; Form1->TrackBarYellowPop->Position = 40; Form1->TrackBarEmptyPop->Position = 30; dice = random(10); if (dice >= 0 && dice <= 2) world[e][s] = BLACK; if (dice == 3) world[e][s] = CYAN; if (dice == 4 || dice == 5) world[e][s] = MAGENTA; if (dice >=6 && dice <=9) world[e][s] = YELLOW; break; } } } } popFiller = COMBOBOX; } if (popFiller == TRACKBAR) { Form1->LabelError->Visible = false; cyanPop = Form1->TrackBarCyanPop->Position; magentaPop = Form1->TrackBarMagentaPop->Position; yellowPop = Form1->TrackBarYellowPop->Position; if (cyanPop + magentaPop + yellowPop > 100) { // too many Form1->LabelError->Visible = true; Form1->TrackBarCyanPop->Position = 0; Form1->TrackBarMagentaPop->Position = 0; Form1->TrackBarYellowPop->Position = 0; Form1->TrackBarEmptyPop->Position = 0; goto exit; } else { // not too many emptyPop = 100 - cyanPop - magentaPop - yellowPop; Form1->TrackBarEmptyPop->Position = emptyPop; for (e=0; e<200; e++) { for (s=0; s<200; s++) { chance = random(40000); if (chance < (400 * cyanPop)) world[e][s] = CYAN; else if (chance < 400 * (cyanPop + magentaPop)) world[e][s] = MAGENTA; else if (chance < 400 * (cyanPop + magentaPop + yellowPop)) world[e][s] = YELLOW; else world[e][s] = BLACK; } } } } exit: } //-------------------------------------------------------------- RENDER WORLD void renderWorld(void) { Form1->Canvas->Pen->Color = clBlack; Form1->Canvas->MoveTo(Form1->PaintBox1->Left-1, Form1->PaintBox1->Top-1); Form1->Canvas->LineTo(Form1->PaintBox1->Left + Form1->PaintBox1->Width+1, Form1->PaintBox1->Top-1); Form1->Canvas->LineTo(Form1->PaintBox1->Left + Form1->PaintBox1->Width+1, Form1->PaintBox1->Top-1 + Form1->PaintBox1->Height + 1); Form1->Canvas->LineTo(Form1->PaintBox1->Left-1, Form1->PaintBox1->Top-1 + Form1->PaintBox1->Height + 1); Form1->Canvas->LineTo(Form1->PaintBox1->Left-1, Form1->PaintBox1->Top-1); for (e=0; e<200; e++) { for (s=0; s<200; s++) { switch (world[e][s]) { case BLACK: { Form1->PaintBox1->Canvas->Brush->Color = clBlack; Form1->PaintBox1->Canvas->Pen->Color = clBlack; Form1->PaintBox1->Canvas->Rectangle(e*2, s*2, e*2+2, s*2+2); ///// break; } case CYAN: { Form1->PaintBox1->Canvas->Brush->Color = clAqua; Form1->PaintBox1->Canvas->Pen->Color = clAqua; Form1->PaintBox1->Canvas->Rectangle(e*2, s*2, e*2+2, s*2+2); ///// break; } case MAGENTA: { Form1->PaintBox1->Canvas->Brush->Color = clFuchsia; Form1->PaintBox1->Canvas->Pen->Color = clFuchsia; Form1->PaintBox1->Canvas->Rectangle(e*2, s*2, e*2+2, s*2+2); ///// break; } case YELLOW: { Form1->PaintBox1->Canvas->Brush->Color = clYellow; Form1->PaintBox1->Canvas->Pen->Color = clYellow; Form1->PaintBox1->Canvas->Rectangle(e*2, s*2, e*2+2, s*2+2); ///// break; } } } } } //------------------------------------------------------------- RENDER BITMAP // This renders the imported bitmap to the screen. // It does not change the values in any of the arrays. // All it does is confirm that the image is available. void renderBMP(Graphics::TBitmap* bm) { for (int y=0; y<200; y++) { t = (RGBTRIPLE*)bm->ScanLine[y]; for (int x=0; x<200; x++) { Form1->PaintBox1->Canvas->Pixels[x][y]= static_cast(RGB(t->rgbtRed, t->rgbtGreen, t->rgbtBlue)); t++; } } } //-------------------------------------------------- FILL ARRAYS FROM BITMAP // This fills the Red, Green and Blue arrays from the bitmap. // It converts the color values (0-255) to preferences (0-7). // The conversion divides the color values by 35. // With a gray-scale image the Red, Green and Blue values are identical. void bmpToArray(Graphics::TBitmap* bm) { for (int y=0; y<200; y++) { t = (RGBTRIPLE*)bm->ScanLine[y]; for (int x=0; x<200; x++) { bmpRed[x][y]=t->rgbtRed / 52; // used for preference 0-4 bmpGreen[x][y]=t->rgbtGreen / 64; // used for distance 0-3 bmpBlue[x][y]=t->rgbtBlue / 86; // used for neighborhood 0-2 t++; } } } // ---------------------------------------------------------- COUNT NEIGHBORS // Counts the neighbors of a cell at coordinates e and s. // Each agent type is counted separately. // ...Neighbors[0] is the number of Empty neighbors. // ...Neighbors[1] is the number of Cyan neighbors. // ...Neighbors[2] is the number of Magenta neighbors. // ...Neighbors[3] is the number of Yellow neighbors. void countNeighbors(void){ // always count DESTINATION neighbors for (i=0; i<=3; i++) { // set counters to zero dNeighbors[i]=0; } for (int column = (dE-1); column <= (dE+1); column++) { // count if (column < 0) wrapColumn = 199; else if (column > 199) wrapColumn = 0; else wrapColumn = column; for (int row = (dS-1); row <= (dS+1); row++) { if ((dE == column) && (dS == row)) continue; if (neighborhood == TONG) { if ((dE - 1 == column) && (dS == row)) goto endLoopD; if ((dE + 1 == column) && (dS == row)) goto endLoopD; if ((dE == column) && (dS - 1 == row)) goto endLoopD; if ((dE == column) && (dS + 1 == row)) goto endLoopD; } if (neighborhood == VONNEUMANN) { if ((dE - 1 == column) && (dS - 1 == row)) goto endLoopD; if ((dE + 1 == column) && (dS + 1 == row)) goto endLoopD; if ((dE - 1 == column) && (dS + 1 == row)) goto endLoopD; if ((dE + 1 == column) && (dS - 1 == row)) goto endLoopD; } if (row < 0) wrapRow = 199; else if (row > 199) wrapRow = 0; else wrapRow = row; dNeighbors[world[wrapColumn][wrapRow]]++; endLoopD: } } // count HOME neighbors if required if (Form1->RadioGroupApplyTo->ItemIndex == 1) { for (i=0; i<=3; i++) { // set counters to zero hNeighbors[i]=0; } for (int column = (hE-1); column <= (hE+1); column++) { // count if (column < 0) wrapColumn = 199; else if (column > 199) wrapColumn = 0; else wrapColumn = column; for (int row = (hS-1); row <= (hS+1); row++) { if ((hE == column) && (hS == row)) continue; if (neighborhood == TONG) { if ((hE - 1 == column) && (hS == row)) goto endLoopH; if ((hE + 1 == column) && (hS == row)) goto endLoopH; if ((hE == column) && (hS - 1 == row)) goto endLoopH; if ((hE == column) && (hS + 1 == row)) goto endLoopH; } if (neighborhood == VONNEUMANN) { if ((hE - 1 == column) && (hS - 1 == row)) goto endLoopH; if ((hE + 1 == column) && (hS + 1 == row)) goto endLoopH; if ((hE - 1 == column) && (hS + 1 == row)) goto endLoopH; if ((hE + 1 == column) && (hS - 1 == row)) goto endLoopH; } if (row < 0) wrapRow = 199; else if (row > 199) wrapRow = 0; else wrapRow = row; hNeighbors[world[wrapColumn][wrapRow]]++; endLoopH: } } } } //---------------------------------------------------------- MOVE AND RENDER void moveAndRender (void) { // put me in my new home... world[dE][dS] = world[hE][hS]; // remove me from my old home... world[hE][hS] = 0; // if "render each change" is checked if (Form1->CheckBoxRenderEveryMove->Checked) { // move me to my new home (render me in color) switch (world[dE][dS]) { case BLACK: { Form1->PaintBox1->Canvas->Brush->Color = clBlack; Form1->PaintBox1->Canvas->Pen->Color = clBlack; Form1->PaintBox1->Canvas-> Rectangle(dE*2, dS*2, dE*2+2, dS*2+2); break; } case CYAN: { Form1->PaintBox1->Canvas->Brush->Color = clAqua; Form1->PaintBox1->Canvas->Pen->Color = clAqua; Form1->PaintBox1->Canvas-> Rectangle(dE*2, dS*2, dE*2+2, dS*2+2); break; } case MAGENTA: { Form1->PaintBox1->Canvas->Brush->Color = clFuchsia; Form1->PaintBox1->Canvas->Pen->Color = clFuchsia; Form1->PaintBox1->Canvas-> Rectangle(dE*2, dS*2, dE*2+2, dS*2+2); break; } case YELLOW: { Form1->PaintBox1->Canvas->Brush->Color = clYellow; Form1->PaintBox1->Canvas->Pen->Color = clYellow; Form1->PaintBox1->Canvas-> Rectangle(dE*2, dS*2, dE*2+2, dS*2+2); break; } } // remove me from my old home (render me Black) Form1->PaintBox1->Canvas->Brush->Color = clBlack; Form1->PaintBox1->Canvas->Pen->Color = clBlack; Form1->PaintBox1->Canvas-> Rectangle(hE*2, hS*2, hE*2+2, hS*2+2); } } //---------------------------------------------------------------------- RUN void run (void) { stop = false; renderWorld(); // for each FRAME for (frame = 0; frame < runTo; frame++) { if (stop) break; Form1->EditFrame->Text = frame; // grab user settings // grab the preference from the trackbar numberLikeMe=Form1->TrackBarDestinationNeighbors->Position; Form1->EditDestinationNeighbors->Text = numberLikeMe; // grab the rule choice that was selected ruleChoice = Form1->ComboBoxRules->ItemIndex; // grab the distance that was selected distanceChoice = Form1->RadioGroup1->ItemIndex; // grab the neighborhood to be counted choice rulesApplyTo = Form1->RadioGroupApplyTo->ItemIndex; // for each OPPORTUNITY in one frame.................................. for (opportunity = 0; opportunity < 40000; opportunity++) { hE = random(200); // choose a home position hS = random(200); myType = world[hE][hS]; // grab the home agent's type // set distance and neighborhood per BitMap if (ruleChoice == 1) { distanceChoice = bmpGreen[dE][dS]; switch (bmpBlue[dE][dS]) { case 0: { neighborhood = MOORE; break; } case 1: { neighborhood = TONG; break; } case 2: { neighborhood = VONNEUMANN; break; } } } // choose a destination position switch (distanceChoice) { case 0: { // distance of 200 dE = random(200); dS = random(200); break; } case 1: { // distance of 100 dE = hE + random(200) - 100; dS = hS + random(200) - 100; break; } case 2: { // distance of 50 dE = hE + random(100) - 50; dS = hS + random(100) - 50; break; } case 3: { // distance of 5 dE = hE + random(11) - 5; dS = hS + random(11) - 5; break; } } if (dE < 0) dE = 200 + dE; // wrap around edges if (dE > 199) dE = dE - 200; if (dS < 0) dS = 200 + dS; if (dS > 199) dS = dS - 200; //########################################################################## //################################## RULES BELOW ########################### //########################################################################## // if someone is home and destination is vacant.................... if ((world[hE][hS] != 0) && (world[dE][dS] == 0)) { if (ruleChoice == 4) { // if neighborhoods differ if (myType == CYAN) neighborhood = MOORE; if (myType == MAGENTA) neighborhood = TONG; if (myType == YELLOW) neighborhood = VONNEUMANN; } countNeighbors(); // count neighbors // switch to chosen rules....................................... switch (ruleChoice) { case 0: { // use TrackBar if (dNeighbors[myType] >= numberLikeMe) { if (rulesApplyTo == dONLY) moveAndRender(); else if (dNeighbors[myType] > hNeighbors[myType]) moveAndRender(); } break; } case 1: { // use BitMap numberLikeMe = bmpRed[dE][dS]; if (dNeighbors[myType] >= numberLikeMe) { if (rulesApplyTo == dONLY) moveAndRender(); else if (dNeighbors[myType] > hNeighbors[myType]) moveAndRender(); } break; } case 2: { // different preferences for same type if (myType == CYAN) numberLikeMe = 5; if (myType == MAGENTA) numberLikeMe = 3; if (myType == YELLOW) numberLikeMe = 0; if (dNeighbors[myType] >= numberLikeMe) { if (rulesApplyTo == dONLY) moveAndRender(); else if (dNeighbors[myType] >= hNeighbors[myType]) moveAndRender(); } break; } case 3: { // diff preferences for diff types if (myType == CYAN && dNeighbors[CYAN] >= 4) { if (rulesApplyTo == dONLY) moveAndRender(); else if (dNeighbors[CYAN] > hNeighbors[CYAN]) moveAndRender(); } if (myType == MAGENTA && dNeighbors[CYAN] >= 2) { if (rulesApplyTo == dONLY) moveAndRender(); else if (dNeighbors[CYAN] > hNeighbors[CYAN]) moveAndRender(); } if (myType == YELLOW && dNeighbors[YELLOW] > 1) { if (rulesApplyTo == dONLY) moveAndRender(); else if (dNeighbors[YELLOW] > hNeighbors[YELLOW]) moveAndRender(); } break; } case 4: { // different neighborhoods if (dNeighbors[myType] >= numberLikeMe) { if (rulesApplyTo == dONLY) moveAndRender(); else if (dNeighbors[myType] > hNeighbors[myType]) moveAndRender(); } break; } } // end switch to chosen rules................................ //########################################################################## //################################## RULES ABOVE ########################### //########################################################################## } // end if someone home and destination is vacant................ } // end of one frame................................................ Application->ProcessMessages(); } if (Form1->CheckBoxRenderEveryMove->Checked == false) renderWorld(); Form1->CheckBoxRenderEveryMove->Checked = true; Application->ProcessMessages(); } //=========================================================================== // EVENT HANDLERS //=========================================================================== //---------------------------------------------------------------- RUN BUTTON void __fastcall TForm1::ButtonRunRandomCellClick(TObject *Sender) { run(); } //--------------------------------------------------------------- STOP BUTTON void __fastcall TForm1::ButtonStopClick(TObject *Sender) { stop = true; } //------------------------------------------------ DESIRED NEIGHBORS TRACKBAR void __fastcall TForm1::TrackBarDestinationNeighborsChange(TObject *Sender) { Form1->EditDestinationNeighbors->Text= Form1->TrackBarDestinationNeighbors->Position; } //--------------------------------------------------------------- FILL BUTTON void __fastcall TForm1::ButtonFillWorldClick(TObject *Sender) { fillWorld(); renderWorld(); } //------------------------------------------------------------- RENDER BUTTON void __fastcall TForm1::ButtonRenderWorldClick(TObject *Sender) { renderWorld(); } //------------------------------------------------------ IMPORT BITMAP BUTTON void __fastcall TForm1::ButtonImportBitmapClick(TObject *Sender) { if (OpenPictureDialog1->Execute() ) b->LoadFromFile(OpenPictureDialog1->FileName); bmpToArray(b); renderBMP(b); } //------------------------------------------------------ RENDER BITMAP BUTTON void __fastcall TForm1::ButtonRenderBitmapClick(TObject *Sender) { renderBMP(b); } //---------------------------------------------------------------- FORM PAINT void __fastcall TForm1::FormPaint(TObject *Sender) { renderWorld(); } //-------------------------------------------------------- RADIO GROUP RUN TO void __fastcall TForm1::RadioGroupRunToClick(TObject *Sender) { switch (RadioGroupRunTo->ItemIndex) { case 0: { runTo = 1000; break; } case 1: { runTo = 3000; break; } case 2: { runTo = 5000; break; } case 3: { runTo = 10000; break; } } } //--------------------------------------------------- COMBO BOX RULES CHANGE- void __fastcall TForm1::ComboBoxRulesChange(TObject *Sender) { if (ComboBoxRules->ItemIndex == 0) { Form1->LabelPreference->Visible = true; Form1->TrackBarDestinationNeighbors->Visible = true; Form1->EditDestinationNeighbors->Visible = true; Form1->GroupBoxBitmap->Visible = false; } if (ComboBoxRules->ItemIndex == 1) { Form1->LabelPreference->Visible = false; Form1->TrackBarDestinationNeighbors->Visible = false; Form1->EditDestinationNeighbors->Visible = false; Form1->GroupBoxBitmap->Visible = true; } } //----------------------------------------------- ComboBox Fill World Change void __fastcall TForm1::ComboBoxFillChange(TObject *Sender) { popFiller = COMBOBOX; } //------------------------------------------ TrackBar Cyan Population Change void __fastcall TForm1::TrackBarCyanPopChange(TObject *Sender) { popFiller = TRACKBAR; } //--------------------------------------- TrackBar Magenta Population Change void __fastcall TForm1::TrackBarMagentaPopChange(TObject *Sender) { popFiller = TRACKBAR; } //---------------------------------------- TrackBar Yellow Population Change void __fastcall TForm1::TrackBarYellowPopChange(TObject *Sender) { popFiller = TRACKBAR; } //---------------------------------------- TrackBar Empty Population Change void __fastcall TForm1::TrackBarEmptyPopChange(TObject *Sender) { popFiller = TRACKBAR; } //-------------------------------------------------------- Moore SpeedButton void __fastcall TForm1::SpeedButtonMooreClick(TObject *Sender) { neighborhood = MOORE; Form1->EditNeighborhood->Text = neighborhood; } //--------------------------------------------------------- Tong SpeedButton void __fastcall TForm1::SpeedButtonTongClick(TObject *Sender) { neighborhood = TONG; Form1->EditNeighborhood->Text = neighborhood; } //--------------------------------------------------- VonNeumann SpeedButton void __fastcall TForm1::SpeedButtonVonNeumannClick(TObject *Sender) { neighborhood = VONNEUMANN; Form1->EditNeighborhood->Text = neighborhood; } //---------------------------------------------------------------- Form Show void __fastcall TForm1::FormShow(TObject *Sender) { fillWorld(); renderWorld(); } //------------------------------------------------------- Whew! That's All...