//=========================================================================== // FLOCKING: LOVE MEETS SOCIAL PSYCHOLOGY // A HCS 110 Course Project // Amy Huang //=========================================================================== // The Leonardo image is 612 by 612 pixels. // The Voyager images are 100 by 44 pixels. //--------------------------------------------------------------------------- #include #include #pragma hdrstop #include "Unit1.h" #include "stdlib.h" #include //--------------------------------------------------------------------------- #pragma package(smart_init) #pragma resource "*.dfm" TForm1 *Form1; //--------------------------------------------------------------------------- __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { // Double buffering eliminates some screen flicker but takes 30% longer. DoubleBuffered = true; } //--------------------------------------------------------------------------- //=========================================================================== // GLOBAL VARIABLES //=========================================================================== // ------------------------------------------------- your program begins here // this sets up graphics functions to handle the import of a bitmap image Graphics::TBitmap* b = new Graphics::TBitmap(); RGBTRIPLE* t; // 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[612][612]; int bmpGreen[612][612]; int bmpBlue[612][612]; // this sets up the MIDI functionality int midiport = 0; HMIDIOUT device; union { public: unsigned long word; unsigned char data[4]; } message; int soundType = 0; int instrument; int note; TImage *image[24] = {0}; // sets up an array of images TShape *shape[24] = {0}; int states[27][3]; class anAgent { public: double velocity; double direction; double newDirection; double newDistance; double lastDistance; float x; float y; bool sex; bool flocking; int lastNN; int mostEncounteredN; int myMaxLinks; int interest; int loveStyle; int attachmentStyle; int intimacy; int passion; int commitment; bool inRelationship; int sigOther; int exPartner; } agent[24]; // array of links [from][to] which is directional int links[24][24] = {0}; // used when we want to connect to agents who are not already linked int agentLinked[24] = {0}; int maxLinks; double aveDir, aveVelo, sumXs, sumYs; // average directions & velocities int sumVisibleNeighbors; // visible neighbors int stepCount = 0; int datingCount = 0; int increment = 5; double x, y, z; bool stop = true; bool wasStopped = false; bool showNNs = false; bool showSOs = false; bool collisions = false; int iterations; int i; int myNeighbor; int dist; int radius = 30; int chosenAgent; int imageDownX, imageDownY; bool agentWasJustChosen = false; //=========================================================================== // FUNCTIONS //=========================================================================== //-------------------------------------------------- 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 < 612; y++) { t = (RGBTRIPLE*)bm->ScanLine[y]; for (int x = 0; x < 612; x++) { bmpRed[x][y] = t->rgbtRed; // used for preference 0-4 bmpGreen[x][y] = t->rgbtGreen; // used for distance 0-3 bmpBlue[x][y] = t->rgbtBlue; // used for neighborhood 0-2 t++; } } } //------------------------------------------------------------- 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 has been acquired. void renderBMP(Graphics::TBitmap* bm) { for (int y = 0; y < 612; y++) { t = (RGBTRIPLE*)bm->ScanLine[y]; for (int x = 0; x < 612; x++) { Form1->Canvas->Pixels[x][y]= static_cast(RGB(t->rgbtRed, t->rgbtGreen, t->rgbtBlue)); t++; } } } //---------------------------------------------------------------- color ramp int colorRamp(int range, int value) { int pixelDistanceAlongPath = (value * 1792) / range; int red, green, blue; // Which edge of the color cube are we on? if (pixelDistanceAlongPath < 256) { // Edge 1 from BLACK to BLUE red=0; green=0; blue=pixelDistanceAlongPath; } else if (pixelDistanceAlongPath < 512) { // Edge 2 from BLUE to CYAN red =0; green=pixelDistanceAlongPath-256; blue=255; } else if (pixelDistanceAlongPath < 768) { // Edge 3 from CYAN to GREEN red =0; green =255; blue= 255-(pixelDistanceAlongPath-512); } else if (pixelDistanceAlongPath < 1024) { // Edge 4 from GREEN to YELLOW red= (pixelDistanceAlongPath-768); green =255; blue =0; } else if (pixelDistanceAlongPath <1280) { // Edge 5 from YELLOW to RED red =255; green=255-(pixelDistanceAlongPath-1024); blue =0; } else if (pixelDistanceAlongPath < 1536) { // Edge 6 from RED to MAGENTA red =255; green=0; blue=pixelDistanceAlongPath -1280; } else { // Edge 7 from MAGENTA to WHITE red =255; green=pixelDistanceAlongPath-1536; blue =255; } return (RGB(red, green, blue)); } //------------------------------------------------------------- initializeStates void initializeStates (void) { // The states grid is constant. It provides an index for each state so that // colorRamp may be used accordingly. for (int row = 0; row < 27; row++) { switch (row / 9) { // sets of 9 case 0: states[row][0] = -1; break; case 1: states[row][0] = 0; break; case 2: states[row][0] = 1; break; } switch (row / 3) { //sets of 3 case 0: case 3: case 6: states[row][1] = -1; break; case 1: case 4: case 7: states[row][1] = 0; break; case 2: case 5: case 8: states[row][1] = 1; break; } switch (row % 3) { //repeating -1, 0, 1 pattern in 3rd col case 0: states[row][2] = -1; break; case 1: states[row][2] = 0; break; case 2: states[row][2] = 1; break; } } } //------------------------------------------------------------- get agent state int getAgentStateIndex (int me) { int agentInti = agent[me].intimacy; int agentPas = agent[me].passion; int agentCom = agent[me].commitment; for (int i = 0; i <= 27; i++) { if (agentInti == states[i][0]) { if (agentPas == states[i][1]) { if (agentCom == states[i][2]) { return i; } } } } } //------------------------------------------------------get new image for agent void getNewImage (int me) { int asi = getAgentStateIndex(me); if (asi == 26) image[me]->Picture = Form1->Image111->Picture; else if (asi == 25 || asi == 24) image[me]->Picture = Form1->Image110->Picture; else if (asi == 23 || asi == 20) image[me]->Picture = Form1->Image101->Picture; else if (asi == 17 || asi == 8) image[me]->Picture = Form1->Image011->Picture; else if (asi == 22 || asi == 16 || asi == 14) image[me]->Picture = Form1->ImageHappy->Picture; //1, 0, 0 in any combo else if (asi == 6 || asi == 7 || asi == 15) image[me]->Picture = Form1->ImagePas1->Picture; //pas is 1 else if (asi == 2 || asi == 5 || asi == 6 || asi == 11 || asi == 18 || asi == 19 || asi == 21) image[me]->Picture = Form1->Image1xx->Picture; //1, x, x in any combo else image[me]->Picture = Form1->ImageNoLove->Picture; } //--------------------------------------------------------- get new bgcolor void getNewBgColor (int me) { //add 2 and have the max be 29 to eliminate the dark colors shape[me]->Brush->Color = colorRamp(29, getAgentStateIndex(me) + 2); //have some differentiation of color for love styles only if //passion love behavior is selected if (agent[me].inRelationship && agent[me].passion == 1 && Form1->RadioGroupFlockingBehavior->ItemIndex == 12) { //add 1 and have the max be 7 to eliminate the dark colors shape[me]->Brush->Color = colorRamp(7, agent[me].loveStyle + 1); } } //--------------------------------------------------------- get love style text AnsiString getLoveStyle (int me) { int myLoveStyle = agent[me].loveStyle; switch (myLoveStyle) { case 0: return "Eros"; break; case 1: return "Mania"; break; case 2: return "Ludus"; break; case 3: return "Pragma"; break; case 4: return "Storge"; break; case 5: return "Agape"; break; } } //---------------------------------------------------- get attachment style text AnsiString getAttachment (int me) { int myAttachment = agent[me].attachmentStyle; switch (myAttachment) { case 0: return "Secure"; break; case 1: return "Dismissive-Avoidant"; break; case 2: return "Anxious-Preoccupied"; break; case 3: return "Fearful-Avoidant"; break; } } //------------------------------------------------------------------- shuffle void shuffle (void) { for (int i = 0; i < 24; i++) { agent[i].x = random(560) + 30; image[i]->Left = agent[i].x - image[i]->Width / 2; agent[i].y = random(510) + 50; image[i]->Top = agent[i].y - image[i]->Height / 2; image[i]->Picture = Form1->ImageNoLove->Picture; shape[i]->Left = agent[i].x - shape[i]->Width / 2; shape[i]->Top = agent[i].y - shape[i]->Height / 2; shape[i]->Brush->Color = clBlack; agent[i].direction = (float(random(1000)) / 500) * M_PI; agent[i].velocity = float(random(1000)) / 1500 + .1; agent[i].flocking = false; agent[i].sex = false; //everyone will be male for now agent[i].interest = random(100); //It makes sense that no one knows each other, so all stats -1. agent[i].intimacy = -1; agent[i].passion = -1; agent[i].commitment = -1; agent[i].sigOther = -1; agent[i].loveStyle = random(6); //One of 6 love styles if (random(10) < 5) //50% chance that they will be secure agent[i].attachmentStyle = 0; else { //50% chance that it will be one of 3 random styles switch (random(3)) { case 0: agent[i].attachmentStyle = 1; break; case 1: agent[i].attachmentStyle = 2; break; case 2: agent[i].attachmentStyle = 3; break; } } } } //--------------------------------------------------------------------- reset void reset (void) { Form1->Refresh(); iterations = 0; Form1->EditIterations->Text = iterations; shuffle(); } //-------------------------------------------------------------------- circle void circle (void) { Form1->Refresh(); for (int i = 0; i < 24; i++) { agent[i].x = 274 + 250 * cos(i * (2 * M_PI / 24)) + 30; agent[i].y = 274 + 250 * sin(i * (2 * M_PI / 24)) + 30; image[i]->Left = agent[i].x - image[i]->Width / 2; image[i]->Top = agent[i].y - image[i]->Height / 2; shape[i]->Left = agent[i].x - shape[i]->Width / 2; shape[i]->Top = agent[i].y - shape[i]->Height / 2; } } //----------------------------------- returns a direction to a specific agent double directionFromTo (int from, int to) { x = agent[to].x - agent[from].x; y = agent[to].y - agent[from].y; return atan(y / x); } //---------------------- finds average Velo and Dir of neighbor with a radius int averageOfNeighbors (int me) { aveDir = 0; // directions aveVelo = 0; // velocities sumXs = 0; // x component sumYs = 0; // y component sumVisibleNeighbors = 0; // number of visible neighbors for (int i = 0; i < 24; i++) { if (i == me) continue; x = agent[i].x - agent[me].x; y = agent[i].y - agent[me].y; z = sqrt(x * x + y * y); if (z < radius) { // average all visible neighbors sumVisibleNeighbors++; sumYs += agent[i].velocity * sin(agent[i].direction); sumXs += agent[i].velocity * cos(agent[i].direction); } } if ((sumYs != 0) && (sumXs != 0) && (sumVisibleNeighbors > 0)) { aveDir = atan(sumYs / sumXs); aveVelo = sqrt(sumXs * sumXs + sumYs * sumYs) / sumVisibleNeighbors; } } //----------------------------------------------- returns my nearest neighbor int nearestNeighbor (int me) { myNeighbor = 0; // my nearest neighbor dist = 10000; // shortest distance for (int i = 0; i < 24; i++) { if (i == me) continue; x = agent[i].x - agent[me].x; y = agent[i].y - agent[me].y; z = sqrt(x * x + y * y); if (z < dist) { // select nearest neighbor dist = z; myNeighbor = i; } } return myNeighbor; } //-------------------------------------------------- show my nearest neighbor void showNearestNeighbor (int me) { Form1->Canvas->Pen->Color = clBlue; Form1->Canvas->Pen->Width = 1; Form1->Canvas->MoveTo(agent[me].x, agent[me].y); Form1->Canvas->LineTo(agent[nearestNeighbor(me)].x, agent[nearestNeighbor(me)].y); } //------------------------------------------------ returns my Significant Other int mySigOther (int me) { return agent[me].sigOther; } //-------------------------------------------------- show my Significant Other void showSigOther (int me) { if (mySigOther(me) != -1) { //must have a significant other Form1->Canvas->Pen->Color = clPurple; if (agent[me].inRelationship) Form1->Canvas->Pen->Color = clRed; Form1->Canvas->Pen->Width = 5; Form1->Canvas->MoveTo(agent[me].x, agent[me].y); Form1->Canvas->LineTo(agent[mySigOther(me)].x, agent[mySigOther(me)].y); } } //---------------------------------------------------- show perceptual circle void showPerceptualCircle (int me) { Form1->Canvas->Brush->Style = bsClear; Form1->Canvas->Ellipse(agent[me].x - radius, agent[me].y - radius, agent[me].x + radius, agent[me].y + radius); } //---------------------------------------------------- proximity: get max links void getMaxLinks (void) { int max = 0; for (int i = 0; i < 24; i++) { for (int j = 0; j < 24; j++) { if (links[i][j] > max) { max = links[i][j]; } } } maxLinks = max; } //--------------------------------------------- proximity: get max links for me void getMaxLinksForMe (int from) { int max = 0; for (int i = 0; i < 24; i++) { if (links[from][i] > max) { max = links[from][i]; agent[from].mostEncounteredN = i; } } agent[from].myMaxLinks = max; } //--------------------------------------------------------- judge Compatibility void judgeCompatibility (int a1, int a2, bool wavering) { /*judge the compatibility of the agents. take a different route depending on whether they are already in a relationship (and therefore are "wavering" = true), or are possibly going to enter (wavering = false). */ if (!wavering) { //Either has no S.O., or is "considering"/"dating" [has an SO but is //NOT in a relationship]. if ((agent[a1].sigOther == -1 && agent[a2].sigOther == -1) || (agent[a1].sigOther == a2 && agent[a2].sigOther == a1)) { //they have to have the exact same level of at least one stat. if (agent[a1].intimacy == agent[a2].intimacy || agent[a1].passion == agent[a2].passion || agent[a1].commitment == agent[a2].commitment) { //use selected flocking behaviors to determine whether they are //right for each other -- kludgy way to test if floats are ==. if (*(int*)&agent[a1].direction == *(int*)&agent[a2].direction && *(int*)&agent[a1].velocity == *(int*)&agent[a2].velocity) { agent[a1].sigOther = a2; agent[a2].sigOther = a1; //if 5 rounds have passed this way, enter into relationship. if (datingCount == 5) { agent[a1].inRelationship = true; agent[a2].inRelationship = true; datingCount = 0; } else datingCount++; } } } } //Else--they are in a relationship and wavering; therefore, //test their scale on the ramp. /* If they're "generally unhappy," which is simulated by their state index being less than 13 [which is not really accurate], then: commitment = 1 for either party? chance of breaking up is 1/30. otherwise, chance of breaking up is 1/15. */ else if (getAgentStateIndex(a1) < 13) { if (((agent[a1].commitment == 1 || agent[a2].commitment == 1) && random(30) == 0) || random(15) == 0) { agent[a1].inRelationship = false; agent[a2].inRelationship = false; agent[a1].sigOther = -1; agent[a2].sigOther = -1; agent[a1].exPartner = a2; agent[a2].exPartner = a1; //then send one in the opposite direction with random velocity, //and just set the other's velocity to random. agent[a1].direction += M_PI; if (agent[a1].direction > 2 * M_PI) agent[a1].direction = agent[a1].direction - 2 * M_PI; agent[a1].velocity = float(random(1000)) / 1500 + .1; agent[a2].velocity = float(random(1000)) / 1500 + .1; } } } //--------------------------------------------------------- bump and tumble void bumpAndTumble (int me) { /* For each stat, if it is not 1, it has a 1/10 chance of going either up or down. If it is 1, it has a smaller, 1/20 chance of going down. */ if (agent[me].commitment != 1 && random(10) == 0) { if (agent[me].commitment > -1 && random(2) == 0) agent[me].commitment--; else agent[me].commitment++; } else if (random(20) == 0) agent[me].commitment--; if (agent[me].passion != 1 && random(10) == 0) { if (agent[me].passion > -1 && random(2) == 0) agent[me].passion--; else agent[me].passion++; } else if (random(20) == 0) agent[me].passion--; if (agent[me].intimacy != 1 && random(10) == 0) { if (agent[me].intimacy > -1 && random(2) == 0) agent[me].intimacy--; else agent[me].intimacy++; } else if (random(20) == 0) agent[me].intimacy--; } //---------------------------------------------------------------------- step void step (void) { //Form1->Refresh(); iterations++; Form1->EditIterations->Text = iterations; increment = Form1->TrackBarIncrement->Position; for (i = 0; i < 24; i++) { // calculate agent's new position based on velocity and direction agent[i].x += agent[i].velocity * cos(agent[i].direction) * increment; agent[i].y += agent[i].velocity * sin(agent[i].direction) * increment; // move the visualization on the screen accordingly image[i]->Left = agent[i].x - image[i]->Width / 2; image[i]->Top = agent[i].y - image[i]->Height / 2; shape[i]->Left = agent[i].x - shape[i]->Width / 2; shape[i]->Top = agent[i].y - shape[i]->Height / 2; /////////////////////////////////////////////////////////////////////// ///////////////////// FLOCKING BEHAVIORS BELOW //////////////////////// /////////////////////////////////////////////////////////////////////// // agents ignore each other if (Form1->RadioGroupFlockingBehavior->ItemIndex == 0 && agent[i].lastNN != nearestNeighbor(i)) { links[i][myNeighbor]++; agent[i].lastNN = myNeighbor; } // adopt nearest neighbor's direction (problematic with BOUNCE) if (Form1->RadioGroupFlockingBehavior->ItemIndex == 1) { agent[i].direction = agent[nearestNeighbor(i)].direction; } // increment direction by 1/50 your nearest neighbor's if (Form1->RadioGroupFlockingBehavior->ItemIndex == 2) { agent[i].direction += agent[nearestNeighbor(i)].direction / 30; if (agent[i].direction > 2 * M_PI) { // modulo divide agent[i].direction = agent[i].direction - 2 * M_PI; } } // decrement direction by 1/100 your nearest neighbor's if (Form1->RadioGroupFlockingBehavior->ItemIndex == 3) { agent[i].direction -= agent[nearestNeighbor(i)].direction / 100; if (agent[i].direction > 2 * M_PI) { // modulo divide agent[i].direction = agent[i].direction - 2 * M_PI; } } // adopt nearest neighbor's velocity if (Form1->RadioGroupFlockingBehavior->ItemIndex == 4) { agent[i].velocity = agent[nearestNeighbor(i)].velocity; } // increment by half nearest neighbor's velocity if (Form1->RadioGroupFlockingBehavior->ItemIndex == 5) { agent[i].velocity += agent[nearestNeighbor(i)].velocity / 50; } // adopt nearest neighbor's direction and speed if (Form1->RadioGroupFlockingBehavior->ItemIndex == 6) { agent[i].velocity = agent[nearestNeighbor(i)].velocity; agent[i].direction = agent[nearestNeighbor(i)].direction; } // STRAIGHT COUPLES // if same sex: both reverse direction and take random velocities // if opposite sex: both take average direction and NN velocity if (Form1->RadioGroupFlockingBehavior->ItemIndex == 7) { nearestNeighbor(i); if ((dist < radius) && (agent[i].lastNN != myNeighbor)) { if (agent[i].sex == agent[myNeighbor].sex) { // reverse agent[i].direction += M_PI; if (agent[i].direction > 2 * M_PI) { agent[i].direction = agent[i].direction - 2 * M_PI; } agent[myNeighbor].direction += M_PI; if (agent[myNeighbor].direction > 2 * M_PI) { agent[myNeighbor].direction = agent[myNeighbor].direction - 2 * M_PI; } agent[i].velocity = float(random(1000)) / 1500 + .1; agent[myNeighbor].velocity = float(random(1000)) / 1500 + .1; } else { // adopt aveDir = (agent[i].direction + agent[myNeighbor].direction) / 2; agent[i].direction = aveDir; agent[myNeighbor].direction = aveDir; agent[i].velocity = agent[myNeighbor].velocity; } agent[i].lastNN = myNeighbor; } } // GAY/LES COUPLES // if opposite sex: both reverse direction and take random velocities // if same sex: both take average direction and NN velocity if (Form1->RadioGroupFlockingBehavior->ItemIndex == 8) { nearestNeighbor(i); if ((dist < radius) && (agent[i].lastNN != myNeighbor)) { if (agent[i].sex != agent[myNeighbor].sex) { // reverse agent[i].direction += M_PI; if (agent[i].direction > 2 * M_PI) { agent[i].direction = agent[i].direction - 2 * M_PI; } agent[myNeighbor].direction += M_PI; if (agent[myNeighbor].direction > 2 * M_PI) { agent[myNeighbor].direction = agent[myNeighbor].direction - 2 * M_PI; } agent[i].velocity = float(random(1000)) / 1500 + .1; agent[myNeighbor].velocity = float(random(1000)) / 1500 + .1; } else { // adopt aveDir = (agent[i].direction + agent[myNeighbor].direction) / 2; agent[i].direction = aveDir; agent[myNeighbor].direction = aveDir; agent[i].velocity = agent[myNeighbor].velocity; } agent[i].lastNN = myNeighbor; } } // Your direction and velocity are the average of those // you can see within a given radius... if (Form1->RadioGroupFlockingBehavior->ItemIndex == 9) { averageOfNeighbors(i); if (aveVelo != 0 && aveDir != 0 && sumVisibleNeighbors > 1) { agent[i].velocity = aveVelo; agent[i].direction = aveDir; agent[i].flocking = true; } else if (agent[i].flocking == TRUE) { agent[i].direction = (float(random(1000)) / 500) * M_PI; agent[i].velocity = float(random(1000)) / 1500 + .1; agent[i].flocking = false; } } // INTIMACY behavior: PROXIMITY // Adopt most often encountered neighbor's velocity and direction, // if most encountered neighbor == nearestNeighbor. if (Form1->RadioGroupFlockingBehavior->ItemIndex == 10 || Form1->RadioGroupFlockingBehavior->ItemIndex == 14) { if (agent[i].lastNN != nearestNeighbor(i)) { links[i][myNeighbor]++; links[myNeighbor][i]++; //two-way encounter agent[i].lastNN = myNeighbor; } getMaxLinksForMe(i); getMaxLinks(); // It has to be in the top 10% of encounter counts. if (agent[i].mostEncounteredN == myNeighbor && agent[i].myMaxLinks >= .9 * maxLinks) { agent[i].velocity = agent[myNeighbor].velocity; agent[i].direction = agent[myNeighbor].direction; //This has a 1 in 2 chance of setting agent[i]'s intimacy for myNeighbor //to +1. Also, 1 in 5 chance of setting passion to +1. if (random(2) == 0 && agent[i].intimacy < 1) agent[i].intimacy++; if (random(5) == 0 && agent[i].passion < 1) agent[i].passion++; } } // INTIMACY behavior: COMMONALITY // Each agent has a "hobby number" from 0-99. A quantification of their // interests. Adopt NN's speed if similarity of interest is within 10 // (5 each way). Adopt NN's direction if similarity of interest is within // 6 (3 each way). if (Form1->RadioGroupFlockingBehavior->ItemIndex == 11 || Form1->RadioGroupFlockingBehavior->ItemIndex == 14) { if (agent[i].lastNN != nearestNeighbor(i)) { links[i][myNeighbor]++; links[myNeighbor][i]++; agent[i].lastNN = myNeighbor; } int commonInterest = abs(agent[i].interest - agent[myNeighbor].interest); if (commonInterest <= 5) { agent[i].velocity = agent[myNeighbor].velocity; } if (commonInterest <= 3) { agent[i].direction = agent[myNeighbor].direction; //this has a 1 in 2 chance of setting agent[i]'s intimacy for myNeighbor //to +1. also, 1 in 3 chance of setting commitment to +1. code follows { if (random(2) == 0 && agent[i].intimacy < 1) agent[i].intimacy++; if (random(3) == 0 && agent[i].commitment < 1) agent[i].commitment++; } } //PASSION behavior: LOVE STYLE //Each agent has a randomly assigned "love style" from 0-5. //If their love styles are linked-styles (are within 1 of each other), //adopt same velocity; if their love styles match, then adopt same dir. if (Form1->RadioGroupFlockingBehavior->ItemIndex == 12 || Form1->RadioGroupFlockingBehavior->ItemIndex == 14) { if (agent[i].lastNN != nearestNeighbor(i)) { links[i][myNeighbor]++; links[myNeighbor][i]++; agent[i].lastNN = myNeighbor; } int loveStyleDiff = abs(agent[i].loveStyle - agent[myNeighbor].loveStyle); if (loveStyleDiff <= 1 || loveStyleDiff == 5) { //5 is wraparound agent[i].velocity = agent[myNeighbor].velocity; //if love styles match, set passion to +1, otherwise if they are "linked" //(within 1 in each direction) 1 in 2 chance of setting passion to +1. if (random(2) == 0 && agent[i].passion < 1) agent[i].passion++; } if (loveStyleDiff == 0) { agent[i].direction = agent[myNeighbor].direction; if (agent[i].passion < 1) agent[i].passion++; } } //COMMITMENT behavior: ATTACHMENT STYLE //Each agent has one "attachment style" out of the following: secure, //anxious, dismissive, fearful. If myNeighbor is also mostEncounteredN, //and attachment styles also match, then affect commitment and //have a 30% chance of flocking. // Commitment is affected even for non-S.O., even though we aren't // coding family, because we reflect the general idea of valuing some // friends/potential dates over others. if (Form1->RadioGroupFlockingBehavior->ItemIndex == 13 || Form1->RadioGroupFlockingBehavior->ItemIndex == 14) { if (agent[i].lastNN != nearestNeighbor(i)) { links[i][myNeighbor]++; links[myNeighbor][i]++; agent[i].lastNN = myNeighbor; } int myAttachment = agent[i].attachmentStyle; getMaxLinksForMe(i); if (agent[i].mostEncounteredN == myNeighbor) { //Same attachment style --> 4 cases if (myAttachment == agent[myNeighbor].attachmentStyle) { switch (myAttachment) { case 0: //secure //commitment = +1 to both parties agent[i].commitment = 1; agent[myNeighbor].commitment = 1; break; case 1: //anxious //your commitment might go higher, his might go lower (30%) if (random(10) < 3 && agent[myNeighbor].commitment < 1) agent[i].commitment++; else if (random(10) < 3 && agent[myNeighbor].commitment > -1) agent[myNeighbor].commitment--; break; case 2: //dismissive //his commitment might go higher, yours might go lower (30%) if (random(10) < 3 && agent[myNeighbor].commitment < 1) agent[myNeighbor].commitment++; else if (random(10) < 3 && agent[myNeighbor].commitment > -1) agent[i].commitment--; break; case 3: //fearful agent[i].commitment = -1; agent[myNeighbor].commitment = -1; break; } if (random(10) < 3) { //30% chance of flocking agent[i].velocity = agent[myNeighbor].velocity; agent[i].direction = agent[myNeighbor].direction; } } } //WHAT AM I DOING?! //If one of love behaviors is chosen, if agent has been flocking with NN, // but NN is in a relationship or NN is their ex, then send agent in //reverse direction from NN with random velocity. if (Form1->RadioGroupFlockingBehavior->ItemIndex == 10 || Form1->RadioGroupFlockingBehavior->ItemIndex == 11 || Form1->RadioGroupFlockingBehavior->ItemIndex == 12 || Form1->RadioGroupFlockingBehavior->ItemIndex == 13 || Form1->RadioGroupFlockingBehavior->ItemIndex == 14) { if (*(int*)&agent[i].direction == *(int*)&agent[myNeighbor].direction && *(int*)&agent[i].velocity == *(int*)&agent[myNeighbor].velocity && ((agent[myNeighbor].inRelationship && agent[myNeighbor].sigOther != i) || agent[i].exPartner == myNeighbor)) { agent[i].direction += M_PI; if (agent[i].direction > 2 * M_PI) { agent[i].direction = agent[i].direction - 2 * M_PI; } agent[i].velocity = float(random(1000)) / 1500 + .1; } } } /////////////////////////////////////////////////////////////////////// ///////////////////// FLOCKING BEHAVIORS ABOVE /////////////////////// /////////////////////////////////////////////////////////////////////// /*##########################################################################*/ // THE MEAT OF THE SIMULATION. // Every 10th step, if you are in a relationship, your feelings (stats) // will change a little bit [take a "bump and tumble"]. At this point, // judge to see if you're still compatible with your partner. // If you're not in a relationship, look to see if you're compatible // with your NN based on whether you guys would flock together. if (stepCount == 10) { if (agent[i].inRelationship) { bumpAndTumble(i); bumpAndTumble(agent[i].sigOther); judgeCompatibility(i, agent[i].sigOther, true); } //If I'm not in a relationship, and I hope to enter //a relationship with my NN, then NN must not be in a relationship //with someone else. (have no sig. other) else if (nearestNeighbor(i) != agent[i].exPartner && !agent[myNeighbor].inRelationship) { judgeCompatibility(i, myNeighbor, false); } stepCount = 0; } else stepCount++; // New image if in relationship - otherwise, default image with black bg. if (agent[i].inRelationship) { getNewImage(i); getNewBgColor(i); } else { image[i]->Picture = Form1->ImageNoLove->Picture; shape[i]->Brush->Color = clBlack; } /*##########################################################################*/ //////////////////////////////////////////////////////////////// BOUNCE if (Form1->RadioGroupBoundary->ItemIndex == 0) { if (agent[i].x > 560) { agent[i].direction = M_PI - agent[i].direction; if (soundType == 1) Beep(196, 50); if (soundType == 2) { PlaySound ("C:\\WINDOWS\\Media\\Windows XP Balloon.wav", "", SND_ASYNC); } if (soundType == 3) { PlaySound ("C:\\Program Files\\Windows NT\\Pinball\\SOUND181.wav", "", SND_ASYNC); } if (soundType == 4) { message.data[0] = 0x90; message.data[1] = 55; message.data[2] = 100; message.data[3] = 0; midiOutShortMsg(device, message.word); } } if (agent[i].y > 560) { agent[i].direction = 2 * M_PI - agent[i].direction; if (soundType == 1) Beep(261, 50); if (soundType == 2) { PlaySound ("C:\\WINDOWS\\Media\\Windows XP pop-up blocked.wav", "", SND_ASYNC); } if (soundType == 3) { PlaySound ("C:\\Program Files\\Windows NT\\Pinball\\SOUND243.wav", "", SND_ASYNC); } if (soundType == 4) { message.data[0] = 0x90; message.data[1] = 60; message.data[2] = 100; message.data[3] = 0; midiOutShortMsg(device, message.word); } } if (agent[i].x < 30) { agent[i].direction = M_PI - agent[i].direction; if (soundType == 1) Beep(330, 50); if (soundType == 2) { PlaySound ("C:\\WINDOWS\\Media\\Windows XP Balloon.wav", "", SND_ASYNC); } if (soundType == 3) { PlaySound ("C:\\Program Files\\Windows NT\\Pinball\\SOUND713.wav", "", SND_ASYNC); } if (soundType == 4) { message.data[0] = 0x90; message.data[1] = 64; message.data[2] = 100; message.data[3] = 0; midiOutShortMsg(device, message.word); } } if (agent[i].y < 50) { agent[i].direction = 2 * M_PI - agent[i].direction; if (soundType == 1) Beep(392, 50); if (soundType == 2) { PlaySound ("C:\\WINDOWS\\Media\\Windows XP pop-up blocked.wav", "", SND_ASYNC); } if (soundType == 3) { PlaySound ("C:\\Program Files\\Windows NT\\Pinball\\SOUND735.wav", "", SND_ASYNC); } if (soundType == 4) { message.data[0] = 0x90; message.data[1] = 67; message.data[2] = 100; message.data[3] = 0; midiOutShortMsg(device, message.word); } } // modular divide direction by one rotation if (agent[i].direction > 2 * M_PI) { agent[i].direction = agent[i].direction - 2 * M_PI; } if (agent[i].direction < 0) { agent[i].direction = agent[i].direction + 2 * M_PI; } } ///////////////////////////////////////////////////////////////// WRAP else { if (agent[i].x > 590) { agent[i].x = 30; image[i]->Left = agent[i].x - image[i]->Width / 2; shape[i]->Left = agent[i].x - shape[i]->Width / 2; if (soundType == 1) Beep(196, 50); if (soundType == 2) { PlaySound ("C:\\WINDOWS\\Media\\Windows XP Balloon.wav", "", SND_ASYNC); } if (soundType == 3) { PlaySound ("C:\\Program Files\\Windows NT\\Pinball\\SOUND181.wav", "", SND_ASYNC); } if (soundType == 4) { message.data[0] = 0x90; message.data[1] = 55; message.data[2] = 100; message.data[3] = 0; midiOutShortMsg(device, message.word); } } if (agent[i].y > 560) { agent[i].y = 50; image[i]->Top = agent[i].y - image[i]->Height / 2; shape[i]->Top = agent[i].y - shape[i]->Height / 2; if (soundType == 1) Beep(330, 50); if (soundType == 2) { PlaySound ("C:\\WINDOWS\\Media\\Windows XP Balloon.wav", "", SND_ASYNC); } if (soundType == 3) { PlaySound ("C:\\Program Files\\Windows NT\\Pinball\\SOUND713.wav", "", SND_ASYNC); } if (soundType == 4) { message.data[0] = 0x90; message.data[1] = 64; message.data[2] = 100; message.data[3] = 0; midiOutShortMsg(device, message.word); } } if (agent[i].x < 30) { agent[i].x = 590; image[i]->Left = agent[i].x - image[i]->Width / 2; shape[i]->Left = agent[i].x - shape[i]->Width / 2; if (soundType == 1) Beep(261, 50); if (soundType == 2) { PlaySound ("C:\\WINDOWS\\Media\\Windows XP pop-up blocked.wav", "", SND_ASYNC); } if (soundType == 3) { PlaySound ("C:\\Program Files\\Windows NT\\Pinball\\SOUND243.wav", "", SND_ASYNC); } if (soundType == 4) { message.data[0] = 0x90; message.data[1] = 60; message.data[2] = 100; message.data[3] = 0; midiOutShortMsg(device, message.word); } } if (agent[i].y < 50) { agent[i].y = 560; image[i]->Top = agent[i].y - image[i]->Height / 2; shape[i]->Top = agent[i].y - shape[i]->Height / 2; if (soundType == 1) Beep(392, 50); if (soundType == 2) { PlaySound ("C:\\WINDOWS\\Media\\Windows XP pop-up blocked.wav", "", SND_ASYNC); } if (soundType == 3) { PlaySound ("C:\\Program Files\\Windows NT\\Pinball\\SOUND735.wav", "", SND_ASYNC); } if (soundType == 4) { message.data[0] = 0x90; message.data[1] = 67; message.data[2] = 100; message.data[3] = 0; midiOutShortMsg(device, message.word); } } } } } //---------------------------------------------------------------------- Run void run (void) { Form1->Refresh(); stop = false; message.data[0] = 0xC0; message.data[1] = 6; message.data[2] = 100; message.data[3] = 0; midiOutShortMsg(device, message.word); while (stop == false) { step(); Application->ProcessMessages(); } if (showNNs) { for (int i = 0; i < 24; i++) { showNearestNeighbor(i); } } if (showSOs) { for (int i = 0; i < 24; i++) { showSigOther(i); } } showNNs = false; showSOs = false; } //=========================================================================== // EVENT HANDLERS //=========================================================================== //------------------------------------------------------------ On Form Create void __fastcall TForm1::FormCreate(TObject *Sender) { midiOutOpen(&device, midiport, 0, 0, CALLBACK_NULL); randomize(); initializeStates(); // defines an array of shapes with properties - initialize as black circles for (int i = 0; i < 24; i++) { shape[i] = new TShape(this); shape[i]->Brush->Color = clBlack; shape[i]->Parent = Form1; shape[i]->Visible = true; shape[i]->Height = 65; shape[i]->Width = 65; shape[i]->Left = 0; shape[i]->Top = 0; shape[i]->Tag = i; shape[i]->Shape = stCircle; } // defines an array of images with properties and events for (int i = 0; i < 24; i++) { image[i] = new TImage(this); image[i]->Parent = Form1; image[i]->Visible = true; image[i]->OnMouseDown = ImageMouseDown; image[i]->OnMouseUp = ImageMouseUp; image[i]->OnMouseMove = ImageMouseMove; image[i]->AutoSize= true; image[i]->Left = 0; image[i]->Top = 0; image[i]->Picture = Form1->ImageNoLove->Picture; image[i]->Transparent = true; image[i]->Tag = i; } shuffle(); } //------------------------------------------------------- On image Mouse Down // An exception to the rule which says "Let Borland write event handlers." // This one you must type in yourself in addition to a reference to it // in Unit1.h (look at the source code in that unit). void __fastcall TForm1::ImageMouseDown(TObject *Sender, TMouseButton Button, TShiftState Shift, int X, int Y) { TImage *image = dynamic_cast(Sender); // The following will be used for mouseMove and mouseUp events... // Captures the index number of the image that was clicked chosenAgent = image->Tag; // remembers is we were stopped or running wasStopped = stop; stop = true; if (Button == 0) { // Begin drag object // Remembers that an image was chosen for mouseMove and mouseUp agentWasJustChosen = true; // Remembers where on the image the mouse was downed imageDownX = X; imageDownY = Y; } else { // Show nearest neighbor showNearestNeighbor(chosenAgent); showPerceptualCircle(chosenAgent); } } //------------------------------------------------------- On image Mouse Move // An exception to the rule which says "Let Borland write event handlers." // This one you must type in yourself in addition to a reference to it // in Unit1.h (look at the source code in that unit). void __fastcall TForm1::ImageMouseMove(TObject *Sender, TShiftState Shift, int X, int Y) { TImage *image = dynamic_cast(Sender); // TShape *shape = dynamic_cast(Sender); chosenAgent = image->Tag; if (agentWasJustChosen) { // this drags the image along... image->Left = image->Left + X - imageDownX; image->Top = image->Top + Y - imageDownY; // shape->Left = image->Left + X - imageDownX; // shape->Top = image->Top + Y - imageDownY; agent[chosenAgent].x = image->Left + image->Width / 2;; agent[chosenAgent].y = image->Top + image->Height / 2;; } // When the mouse moves over an image its values are displayed EditAgent->Text = image->Tag; EditX->Text = int(agent[chosenAgent].x); EditY->Text = int(agent[chosenAgent].y); EditDirection->Text = agent[image->Tag].direction; EditVelocity->Text = agent[image->Tag].velocity; EditLoveStyle->Text = getLoveStyle(agent[image->Tag].loveStyle); EditAttachment->Text = getAttachment(agent[image->Tag].attachmentStyle); EditInterest->Text = agent[image->Tag].interest; if (agent[image->Tag].sex) { EditSex->Text= "F"; } else EditSex->Text= "M"; EditNN->Text = nearestNeighbor(image->Tag); } //--------------------------------------------------------- On image Mouse Up // An exception to the rule which says "Let Borland write event handlers." // This one you must type in yourself in addition to a reference to it // in Unit1.h (look at the source code in that unit). void __fastcall TForm1::ImageMouseUp(TObject *Sender, TMouseButton Button, TShiftState Shift, int X, int Y) { TImage *image = dynamic_cast(Sender); // this is the end of the drag... agentWasJustChosen = false; EditAgent->Text = image->Tag; // When the mouse moves over an image its values are displayed EditX->Text = int(agent[chosenAgent].x); EditY->Text = int(agent[chosenAgent].y); EditDirection->Text = agent[image->Tag].direction; EditVelocity->Text = agent[image->Tag].velocity; EditLoveStyle->Text = getLoveStyle(agent[image->Tag].loveStyle); EditAttachment->Text = getAttachment(agent[image->Tag].attachmentStyle); EditInterest->Text = agent[image->Tag].interest; // if we were running before the drag, then run... Form1->Refresh(); if (!wasStopped) { run(); } } //---------------------------------------------------------------- run button void __fastcall TForm1::ButtonRunClick(TObject *Sender) { run(); } //--------------------------------------------------------------- step button void __fastcall TForm1::ButtonStepClick(TObject *Sender) { stop = true; step(); } //--------------------------------------------------------------- stop button void __fastcall TForm1::ButtonStopClick(TObject *Sender) { stop = true; } //---------------------------------------------------------- randomize button void __fastcall TForm1::ButtonResetClick(TObject *Sender) { reset(); } //------------------------------------------------------------- circle button void __fastcall TForm1::ButtonCircleClick(TObject *Sender) { circle(); } //------------------------------------------------------ sound type RadioGroup void __fastcall TForm1::RadioGroupSonificationClick(TObject *Sender) { soundType = RadioGroupSonification->ItemIndex; } //------------------------------------------------------- show nearest neighbors void __fastcall TForm1::ButtonNNsClick(TObject *Sender) { if (stop) { for (int i = 0; i < 24; i++) { showNearestNeighbor(i); } } else { showNNs = true; stop = true; } } //------------------------------------------------------ show significant others void __fastcall TForm1::ButtonSOsClick(TObject *Sender) { if (stop) { for (int i = 0; i < 24; i++) { showSigOther(i); } } else { showSOs = true; stop = true; } } //--------------------------------------------------------------------------- void __fastcall TForm1::TrackBarRadiusChange(TObject *Sender) { Form1->Refresh(); radius = TrackBarRadius->Position; Form1->EditRadius->Text = radius; if (stop == true) { for (int i = 0; i < 24; i++) { showPerceptualCircle(i); } } } //--------------------------------------------------------------------------- void __fastcall TForm1::ButtonOpenBMPClick(TObject *Sender) { if (OpenBitMapDialog->Execute() ) b->LoadFromFile(OpenBitMapDialog->FileName); bmpToArray(b); renderBMP(b); } //---------------------------------------------------------------------------