//=========================================================================== // FLOCKING - June 2007 - Final Proj // Evan Shulman // Exploration of Emotional Interactions //=========================================================================== // The Brain image is 590 by 590 pixels. // The Face images are 69 by 69 pixels. //--------------------------------------------------------------------------- #include #include #pragma hdrstop #include "Unit1.h" #include "stdlib.h" #include // emotions - to be used with the emotional levels of the agents #define HAPPY 0 #define SAD 1 #define ANGRY 2 #define FRIGHT 3 #define DISGUST 4 #define SURPRISE 5 //--------------------------------------------------------------------------- #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 class anAgent { public: // position and movement double velocity; double direction; double lastDirection; double newDistance; double lastDistance; int followCount; float x; float y; int othersKindness[24]; // personal characteristics int type; // you can use this to define different agents with preset vals // ex.) dog vs. cat type can have diff imgs, velocities, and behaviors // gives agents some monetary value they carry double money; // the change (delta) in my money double dmoney; // gives agents health int health; // the change (delta) in my health int dhealth; // stores emotional levels of the agent 0-5 int emo[6]; // the face that is shown based on the emotional level and rules int faceEmo; bool sex; bool flocking; int lastNN; } agent[24]; double aveDir, aveVelo, sumXs, sumYs; // average directions & velocities int sumVisibleNeighbors; // visible neighbors int increment = 5; double x, y, z; bool stop = true; bool wasStopped = false; bool showNNs = false; bool collisions = false; int iterations; int i; int myNeighbor; int dist; int radius = 30; int chosenAgent; int imageDownX, imageDownY; int emoVal; int hVal; double mVal; 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)); } //-------------------------------------------------------------- calculate face void calcFace(int &i) { int maxVal; int faceVal; // SIMPLE EMOTION ALGORITHM // Finds the highest value emotion at the time maxVal = agent[i].emo[0]; faceVal = 0; for(int j = 0; j < 6; j++) { if(agent[i].emo[j] > maxVal) { maxVal = agent[i].emo[j]; faceVal = j; } } agent[i].faceEmo = faceVal; // MORE COMPLEX EMOTIONAL ALGORITHMS TO COME... // Add yours here! } //------------------------------------------------------------------- shuffle void shuffle (void) { // TO BE USED FOR ADVANCED EMOTIONAL COMPUTATION... // To be developed... // max emotional resources = 30 // 0-10 levels for each = 11 levels // 5 is middle level for each // 5 * 6 emotions = 30 // int emoLevel = 30; // stores level map that adds up to 30 // int emoKey[5]; // int sum; 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; agent[i].direction = (float(random(1000)) / 500) * M_PI; agent[i].velocity = float(random(1000)) / 1500 + .1; agent[i].flocking = false; agent[i].health = random(100) + 1; agent[i].money = random(100) + 1; if (i % 2 == 0) { agent[i].sex = true; } else agent[i].sex = false; // randomly set emotions // ADVANCED TEST /*while(sum != emoLevel) { for(int j = 0; j < 5; j++) { emoKey[0] = random(11); sum += emoKey[j]; } } sum = 0;*/ // BASIC VERSION - ignores emoLevel for (int j = 0; j < 6; j++) { agent[i].emo[j] = random(11); } } } //--------------------------------------------------------------------- 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; } } //----------------------------------- 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 = clRed; Form1->Canvas->MoveTo(agent[me].x, agent[me].y); Form1->Canvas->LineTo(agent[nearestNeighbor(me)].x, agent[nearestNeighbor(me)].y); /* if (agent[me].sex == true) { // from F Form1->Canvas->MoveTo(agent[me].x - 7, agent[me].y - 17); } else { Form1->Canvas->MoveTo(agent[me].x, // from M agent[me].y - 22); } if (agent[nearestNeighbor(me)].sex == true) { // to F Form1->Canvas->LineTo(agent[nearestNeighbor(me)].x - 7, agent[nearestNeighbor(me)].y - 17); } else { // to M Form1->Canvas->LineTo(agent[nearestNeighbor(me)].x, agent[nearestNeighbor(me)].y - 22); } */ } //---------------------------------------------------- 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); } //---------------------------------------------------------------------- step void step (void) { // used for random angry behavior int tempHealth; double tempMoney; // used for random sad behavior int tempSadChoice; // used for random surprise behavior int tempSurp; //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; //----------------------------------------------------------------------------- /////////////////////////////////////////////////////////////////////// ///////////////// EMOTION AND FACE CALCULATIONS BELOW ///////////////// /////////////////////////////////////////////////////////////////////// // FACE AND EMOTIONAL CALCULATIONS // Emotional rules // Happy if((agent[i].health > 75) || (agent[i].money > 75) || (agent[nearestNeighbor(i)].faceEmo != ANGRY) || (agent[nearestNeighbor(i)].faceEmo == HAPPY)) { // i'm more happy! agent[i].emo[HAPPY] += 2; // i'm less sad! agent[i].emo[SAD] -= 2; // i'm less angry! agent[i].emo[ANGRY] -= 2; // i'm less disgusted! agent[i].emo[DISGUST] -= 2; // i'm less frightened! agent[i].emo[FRIGHT] -= 2; // i'm less surprised! agent[i].emo[SURPRISE] -= 2; // LIMIT THE CHANGES if(agent[i].emo[HAPPY] > 10) agent[i].emo[HAPPY] = 10; if(agent[i].emo[SAD] < 0) agent[i].emo[SAD] = 0; if(agent[i].emo[ANGRY] < 0) agent[i].emo[ANGRY] = 0; if(agent[i].emo[FRIGHT] < 0) agent[i].emo[FRIGHT] = 0; if(agent[i].emo[DISGUST] < 0) agent[i].emo[DISGUST] = 0; if(agent[i].emo[SURPRISE] < 0) agent[i].emo[SURPRISE] = 0; } // Sad if((agent[i].dhealth < 0) || (agent[i].dmoney < 0) || ((agent[nearestNeighbor(i)].faceEmo == HAPPY) && (agent[i].faceEmo == SAD)) /*|| break-up of a relationship or fight with someone || No one is reacting to my emotions*/) /* loss of friend/family/agent close to you */ { // if it's a big loss, make me more sad, and less happy if(agent[i].dhealth < -10) { agent[i].emo[SAD] += 3; agent[i].emo[HAPPY] -= 3; } if(agent[i].dmoney < -10) { agent[i].emo[SAD] += 3; agent[i].emo[HAPPY] -= 3; } // otherwise only make me a little bit sadder, and less happy else { agent[i].emo[SAD]++; agent[i].emo[HAPPY]--; } // LIMIT THE CHANGES if(agent[i].emo[SAD] > 10) agent[i].emo[SAD] = 10; if(agent[i].emo[HAPPY] < 0) agent[i].emo[HAPPY] = 0; } // Angry if((agent[i].health < 75) || (agent[nearestNeighbor(i)].faceEmo == ANGRY) || (agent[i].dhealth < 0) || (agent[i].dmoney < 0) /* || tired/attacked/NOT GETTING PARTNER = ITERATIONS SINGLE*/) { if(agent[i].health < 25) { agent[i].emo[ANGRY] += 3; agent[i].emo[HAPPY] -= 3; } else if(agent[i].health < 50) { agent[i].emo[ANGRY] += 2; agent[i].emo[HAPPY] -= 2; } // otherwise if health < 75 or i lost health or money else { agent[i].emo[ANGRY]++; agent[i].emo[HAPPY]--; } // LIMIT THE CHANGES if(agent[i].emo[ANGRY] > 10) agent[i].emo[ANGRY] = 10; if(agent[i].emo[HAPPY] < 0) agent[i].emo[HAPPY] = 0; } // Fright if(((agent[nearestNeighbor(i)].faceEmo == ANGRY) /* && next distance will be smaller than the current one = moving towards danger*/) || (agent[i].dhealth < 0) || (agent[i].dmoney < 0) || (agent[nearestNeighbor(i)].health < 50)) { agent[i].emo[FRIGHT]++; // LIMIT THE CHANGES if(agent[i].emo[FRIGHT] > 10) agent[i].emo[FRIGHT] = 10; } // Disgust if((agent[nearestNeighbor(i)].faceEmo == ANGRY) || (agent[nearestNeighbor(i)].health < 50)) { if(agent[nearestNeighbor(i)].health < 13) agent[i].emo[DISGUST] += 3; else if(agent[nearestNeighbor(i)].health < 36) agent[i].emo[DISGUST] += 2; else agent[i].emo[DISGUST]++; agent[i].emo[HAPPY]--; // LIMIT THE CHANGES if(agent[i].emo[DISGUST] > 10) agent[i].emo[DISGUST] = 10; if(agent[i].emo[HAPPY] < 0) agent[i].emo[HAPPY] = 0; } // Surprise if((agent[i].lastNN == nearestNeighbor(i)) && (agent[nearestNeighbor(i)].lastDirection == agent[nearestNeighbor(i)].direction)) { agent[i].emo[SURPRISE] += 3; } // Calculate Face calcFace(i); /////////////////////////////////////////////////////////////////////// ///////////////// EMOTION AND FACE CALCULATIONS ABOVE ///////////////// /////////////////////////////////////////////////////////////////////// //----------------------------------------------------------------------------- /////////////////////////////////////////////////////////////////////// //////////////////// EMOTIONAL BEHAVIORS BELOW //////////////////////// /////////////////////////////////////////////////////////////////////// // RULES FOR HAPPY ACTION // no change behavior, currently happy with what i'm doing! // but make me move if i was stopped if(agent[i].faceEmo == 0) { image[i]->Picture = Form1->happy->Picture; if(agent[i].velocity == 0) agent[i].velocity = float(random(1000)) / 1500 + .1; } // RULES FOR SAD ACTION // move slower or come to a complete stop (movement and actions) else if(agent[i].faceEmo == 1) { image[i]->Picture = Form1->sad->Picture; agent[i].velocity -= 0.05; // LIMIT CHANGE if(agent[i].velocity < 0) agent[i].velocity = 0; // Randomly choose a flocking strategy for one step tempSadChoice = Form1->RadioGroupFlockingBehavior->ItemIndex; Form1->RadioGroupFlockingBehavior->ItemIndex = random(10); } // RULES FOR ANGRY ACTION // fight = approach, use strength to attack health of other, or to take money else if(agent[i].faceEmo == 2) { image[i]->Picture = Form1->angry->Picture; agent[i].lastDirection = agent[i].direction; // approach and harass agent[i].direction = agent[nearestNeighbor(i)].direction; // pick rob or harm if(random(2) == 0) // rob { tempMoney = (random(100.00) + 1); // stolen agent[nearestNeighbor(i)].dmoney = tempMoney * -1; agent[nearestNeighbor(i)].money -= tempMoney; // added agent[i].dmoney = tempMoney; agent[i].money += agent[i].dmoney; } else // harm { tempHealth = (random(100) + 1); // hurt agent[nearestNeighbor(i)].dhealth = tempHealth * -1; agent[nearestNeighbor(i)].health -= tempHealth; } if(agent[i].velocity == 0) agent[i].velocity = float(random(1000)) / 1500 + .1; } // RULES FOR FRIGHT ACTION // try to escape (move away from angry) or find a happy and follow their movements // increase anger over each iteration if the threat is still there else if(agent[i].faceEmo == 3) { image[i]->Picture = Form1->fright->Picture; agent[i].lastDirection = agent[i].direction; if(agent[i].lastNN == nearestNeighbor(i)) { // pick a new direction if(agent[i].direction == agent[nearestNeighbor(i)].direction) agent[i].direction = (float(random(1000)) / 500) * M_PI; // pick it based off of nearest neighbors emotion if(agent[nearestNeighbor(i)].faceEmo == ANGRY) { agent[i].followCount++; // opposite direction agent[i].direction = M_PI + (agent[nearestNeighbor(i)].direction); // for each 5 iterations i've been followed by a threat (ANGRY) // make me more ANGRY if(agent[i].followCount % 5 == 0) agent[i].emo[ANGRY]++; } } else agent[i].followCount = 0; // speedily try to avoid the situation agent[i].velocity = 0.51; if(agent[nearestNeighbor(i)].faceEmo == HAPPY) agent[i].direction = agent[nearestNeighbor(i)].direction; if(agent[i].velocity == 0) agent[i].velocity = float(random(1000)) / 1500 + .1; } // RULES FOR DISGUST ACTION else if(agent[i].faceEmo == 4) { image[i]->Picture = Form1->disgust->Picture; // reverse direction agent[i].lastDirection = agent[i].direction; agent[i].direction = M_PI + agent[i].direction; if(agent[i].velocity == 0) agent[i].velocity = float(random(1000)) / 1500 + .1; } // RULES FOR SURPRISE ACTION // stop // pick an emotion: fear or happy - to inc. and therefore display next else if(agent[i].faceEmo == 5) { image[i]->Picture = Form1->surprise->Picture; // stop agent[i].velocity = 0; // take the surprise value and give some to either FEAR or HAPPINESS tempSurp = random(10); agent[i].emo[SURPRISE] -= tempSurp; // Primitive // unsure about threat, so it either makes me more // fearful or happy if(random(2) == 0) agent[i].emo[FRIGHT] += tempSurp; else agent[i].emo[HAPPY] += tempSurp; if(agent[i].velocity == 0) agent[i].velocity = float(random(1000)) / 1500 + .1; // LIMIT CHANGES if(agent[i].emo[SURPRISE] < 0) agent[i].emo[SURPRISE] = 0; if(agent[i].emo[FRIGHT] > 10) agent[i].emo[FRIGHT] = 10; if(agent[i].emo[HAPPY] > 10) agent[i].emo[HAPPY] = 10; } /////////////////////////////////////////////////////////////////////// //////////////////// EMOTIONAL BEHAVIORS ABOVE //////////////////////// /////////////////////////////////////////////////////////////////////// //----------------------------------------------------------------------------- /////////////////////////////////////////////////////////////////////// ///////////////////// FLOCKING BEHAVIORS BELOW //////////////////////// /////////////////////////////////////////////////////////////////////// // adopt nearest neighbor's direction (problematic with BOUNCE) if (Form1->RadioGroupFlockingBehavior->ItemIndex == 1) { agent[i].lastDirection = agent[i].direction; agent[i].direction = agent[nearestNeighbor(i)].direction; } // increment direction by 1/30 your nearest neighbor's if (Form1->RadioGroupFlockingBehavior->ItemIndex == 2) { agent[i].lastDirection = agent[i].direction; 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].lastDirection = agent[i].direction; 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].lastDirection = agent[i].direction; agent[i].velocity = agent[nearestNeighbor(i)].velocity; } // increment by half nearest neighbor's velocity if (Form1->RadioGroupFlockingBehavior->ItemIndex == 5) { agent[i].lastDirection = agent[i].direction; agent[i].velocity += agent[nearestNeighbor(i)].velocity / 50; } // adopt nearest neighbor's direction and speed if (Form1->RadioGroupFlockingBehavior->ItemIndex == 6) { agent[i].lastDirection = agent[i].direction; 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) { agent[i].lastDirection = agent[i].direction; 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) { agent[i].lastDirection = agent[i].direction; 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) { agent[i].lastDirection = agent[i].direction; 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; } } /////////////////////////////////////////////////////////////////////// ///////////////////// FLOCKING BEHAVIORS ABOVE /////////////////////// /////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////// BOUNCE if (Form1->RadioGroupBoundary->ItemIndex == 0) { if (agent[i].x > 560) { agent[i].lastDirection = agent[i].direction; 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].lastDirection = agent[i].direction; 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].lastDirection = agent[i].direction; 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].lastDirection = agent[i].direction; 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].lastDirection = agent[i].direction; agent[i].direction = agent[i].direction - 2 * M_PI; } if (agent[i].direction < 0) { agent[i].lastDirection = agent[i].direction; 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; 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; 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; 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; 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); } } } /////////////////////////////////////////////////////////////////////// ///////////////////////// WORLD RULES BELOW /////////////////////////// /////////////////////////////////////////////////////////////////////// // give/take-away health and money during each step // money assignment based on health (weaker = less able to do job) // health assignment based on health (healthier = heal quicker) if(agent[i].health < 25) { agent[i].dmoney -= 1.00; // pay to get better agent[i].money += agent[i].dmoney; // LIMIT MONEY if(agent[i].money < 0) agent[i].money = 0; if(agent[i].money != 0) { agent[i].dhealth += 1; agent[i].health += agent[i].dhealth; } else { agent[i].dhealth -= 1; agent[i].health += agent[i].dhealth; } } else if(agent[i].health < 50) { agent[i].dmoney += 2.50; agent[i].money += agent[i].dmoney; agent[i].dhealth += 2; agent[i].health += agent[i].dhealth; } else if(agent[i].health < 75) { agent[i].dmoney += 5.00; agent[i].money += agent[i].dmoney; agent[i].dhealth += 4; agent[i].health += agent[i].dhealth; } else if(agent[i].health < 100) { agent[i].dmoney += 10.00; agent[i].money += agent[i].dmoney; agent[i].dhealth += 6; agent[i].health += agent[i].dhealth; } // LIMIT HEALTH CHANGE 0-100 if(agent[i].health < 0) agent[i].health = 0; if(agent[i].health > 100) agent[i].health = 100; // LIMIT MONEY MAX (100?) if(agent[i].money < 0) agent[i].money = 0; if(agent[i].money > 100) agent[i].money = 100; // RESET DHEALTH AND DMONEY agent[i].dhealth = 0; agent[i].dmoney = 0; // decrease surprise for all if(agent[i].emo[SURPRISE] - 1 >= 0) agent[i].emo[SURPRISE]--; // if it was sad, reset the set flocking behavior if(agent[i].faceEmo == 1) Form1->RadioGroupFlockingBehavior->ItemIndex = tempSadChoice; } } //---------------------------------------------------------------------- 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); } } showNNs = false; } //=========================================================================== // EVENT HANDLERS //=========================================================================== //------------------------------------------------------------ On Form Create void __fastcall TForm1::FormCreate(TObject *Sender) { midiOutOpen(&device, midiport, 0, 0, CALLBACK_NULL); randomize(); // defines an array of images with properties and events // also randomly assigns emotions to agents for (int i = 0; i < 24; i++) { image[i] = new TImage(this); image[i]->Parent = Form1; image[i]->OnMouseDown = ImageMouseDown; image[i]->OnMouseUp = ImageMouseUp; image[i]->OnMouseMove = ImageMouseMove; //image[i]->Height = 100; //image[i]->Width = 50; image[i]->AutoSize= true; image[i]->Left = 0; image[i]->Top = 0; image[i]->Visible = true; image[i]->Transparent = true; image[i]->Tag = i; // checks agent's emotional levels and sets the cooresponding face // Calc & Display Face calcFace(i); if(agent[i].faceEmo == 0) image[i]->Picture = Form1->happy->Picture; else if(agent[i].faceEmo == 1) image[i]->Picture = Form1->sad->Picture; else if(agent[i].faceEmo == 2) image[i]->Picture = Form1->angry->Picture; else if(agent[i].faceEmo == 3) image[i]->Picture = Form1->fright->Picture; else if(agent[i].faceEmo == 4) image[i]->Picture = Form1->disgust->Picture; else if(agent[i].faceEmo == 5) image[i]->Picture = Form1->surprise->Picture; } 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); chosenAgent = image->Tag; if (agentWasJustChosen) { // this drags the image along... image->Left = image->Left + X - imageDownX; image->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; EditHappy->Text = agent[image->Tag].emo[HAPPY]; EditSad->Text= agent[image->Tag].emo[SAD]; EditAngry->Text= agent[image->Tag].emo[ANGRY]; EditFright->Text= agent[image->Tag].emo[FRIGHT]; EditDisgust->Text= agent[image->Tag].emo[DISGUST]; EditSurprise->Text= agent[image->Tag].emo[SURPRISE]; EditHealth->Text = agent[image->Tag].health; EditMoney->Text = agent[image->Tag].money; 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; EditHappy->Text = agent[image->Tag].emo[HAPPY]; EditSad->Text= agent[image->Tag].emo[SAD]; EditAngry->Text= agent[image->Tag].emo[ANGRY]; EditFright->Text= agent[image->Tag].emo[FRIGHT]; EditDisgust->Text= agent[image->Tag].emo[DISGUST]; EditSurprise->Text= agent[image->Tag].emo[SURPRISE]; EditHealth->Text = agent[image->Tag].health; EditMoney->Text = agent[image->Tag].money; // 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; } //--------------------------------------------------------------------------- void __fastcall TForm1::ButtonNNsClick(TObject *Sender) { if (stop) { for (int i = 0; i < 24; i++) { showNearestNeighbor(i); } } else { showNNs = 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); } //--------------------------------------------------------------------------- void __fastcall TForm1::EditHappyChange(TObject *Sender) { emoVal = StrToInt(EditHappy->Text); chosenAgent = StrToInt(Form1->EditAgent->Text); if(emoVal >= 0 || emoVal <= 10) agent[chosenAgent].emo[HAPPY] = emoVal; } //--------------------------------------------------------------------------- void __fastcall TForm1::EditSadChange(TObject *Sender) { emoVal = StrToInt(EditSad->Text); chosenAgent = StrToInt(Form1->EditAgent->Text); if(emoVal >= 0 || emoVal <= 10) agent[chosenAgent].emo[SAD] = emoVal; } //--------------------------------------------------------------------------- void __fastcall TForm1::EditAngryChange(TObject *Sender) { emoVal = StrToInt(EditAngry->Text); chosenAgent = StrToInt(Form1->EditAgent->Text); if(emoVal >= 0 || emoVal <= 10) agent[chosenAgent].emo[ANGRY] = emoVal; } //--------------------------------------------------------------------------- void __fastcall TForm1::EditFrightChange(TObject *Sender) { emoVal = StrToInt(EditFright->Text); chosenAgent = StrToInt(Form1->EditAgent->Text); if(emoVal >= 0 || emoVal <= 10) agent[chosenAgent].emo[FRIGHT] = emoVal; } //--------------------------------------------------------------------------- void __fastcall TForm1::EditDisgustChange(TObject *Sender) { emoVal = StrToInt(EditDisgust->Text); chosenAgent = StrToInt(Form1->EditAgent->Text); if(emoVal >= 0 || emoVal <= 10) agent[chosenAgent].emo[DISGUST] = emoVal; } //--------------------------------------------------------------------------- void __fastcall TForm1::EditSurpriseChange(TObject *Sender) { emoVal = StrToInt(EditSurprise->Text); chosenAgent = StrToInt(Form1->EditAgent->Text); if(emoVal >= 0 || emoVal <= 10) agent[chosenAgent].emo[SURPRISE] = emoVal; } //--------------------------------------------------------------------------- void __fastcall TForm1::EditHealthChange(TObject *Sender) { hVal = StrToInt(EditHealth->Text); chosenAgent = StrToInt(Form1->EditAgent->Text); if(hVal >= 0 || hVal <= 100) { agent[chosenAgent].dhealth += hVal - agent[chosenAgent].health; agent[chosenAgent].health = hVal; } } //--------------------------------------------------------------------------- void __fastcall TForm1::EditMoneyChange(TObject *Sender) { mVal = double(StrToInt(EditMoney->Text)); chosenAgent = StrToInt(Form1->EditAgent->Text); if(mVal >= 0 || mVal <= 100) { agent[chosenAgent].dmoney += mVal - agent[chosenAgent].money; agent[chosenAgent].money = mVal; } } //--------------------------------------------------------------------------- void __fastcall TForm1::EmotionButtonGroup1Items0Click(TObject *Sender) { // go through all agents for (int i = 0; i < 24; i++) { // make HAPPY have max agent[i].emo[HAPPY] = 10; agent[i].emo[SAD] = 0; agent[i].emo[ANGRY] = 0; agent[i].emo[FRIGHT] = 0; agent[i].emo[DISGUST] = 0; agent[i].emo[SURPRISE] = 0; } } //--------------------------------------------------------------------------- void __fastcall TForm1::EmotionButtonGroup1Items1Click(TObject *Sender) { // go through all agents for (int i = 0; i < 24; i++) { // make SAD have max agent[i].emo[HAPPY] = 0; agent[i].emo[SAD] = 10; agent[i].emo[ANGRY] = 0; agent[i].emo[FRIGHT] = 0; agent[i].emo[DISGUST] = 0; agent[i].emo[SURPRISE] = 0; } } //--------------------------------------------------------------------------- void __fastcall TForm1::EmotionButtonGroup1Items2Click(TObject *Sender) { // go through all agents for (int i = 0; i < 24; i++) { // make ANGRY have max agent[i].emo[HAPPY] = 0; agent[i].emo[SAD] = 0; agent[i].emo[ANGRY] = 10; agent[i].emo[FRIGHT] = 0; agent[i].emo[DISGUST] = 0; agent[i].emo[SURPRISE] = 0; } } //--------------------------------------------------------------------------- void __fastcall TForm1::EmotionButtonGroup1Items3Click(TObject *Sender) { // go through all agents for (int i = 0; i < 24; i++) { // make FRIGHT have max agent[i].emo[HAPPY] = 0; agent[i].emo[SAD] = 0; agent[i].emo[ANGRY] = 0; agent[i].emo[FRIGHT] = 10; agent[i].emo[DISGUST] = 0; agent[i].emo[SURPRISE] = 0; } } //--------------------------------------------------------------------------- void __fastcall TForm1::EmotionButtonGroup1Items4Click(TObject *Sender) { // go through all agents for (int i = 0; i < 24; i++) { // make DISGUST have max agent[i].emo[HAPPY] = 0; agent[i].emo[SAD] = 0; agent[i].emo[ANGRY] = 0; agent[i].emo[FRIGHT] = 0; agent[i].emo[DISGUST] = 10; agent[i].emo[SURPRISE] = 0; } } //--------------------------------------------------------------------------- void __fastcall TForm1::EmotionButtonGroup1Items5Click(TObject *Sender) { // go through all agents for (int i = 0; i < 24; i++) { // make SURPRISE have max agent[i].emo[HAPPY] = 0; agent[i].emo[SAD] = 0; agent[i].emo[ANGRY] = 0; agent[i].emo[FRIGHT] = 0; agent[i].emo[DISGUST] = 0; agent[i].emo[SURPRISE] = 10; } } //--------------------------------------------------------------------------- void __fastcall TForm1::EmotionButtonGroup1Items6Click(TObject *Sender) { // go through all agents for (int i = 0; i < 24; i++) { agent[i].dhealth += 100 - agent[i].health; agent[i].health = 100; } } //--------------------------------------------------------------------------- void __fastcall TForm1::EmotionButtonGroup1Items7Click(TObject *Sender) { // go through all agents for (int i = 0; i < 24; i++) { agent[i].dhealth += 0 - agent[i].health; agent[i].health = 0; } } //--------------------------------------------------------------------------- void __fastcall TForm1::EmotionButtonGroup1Items8Click(TObject *Sender) { // go through all agents for (int i = 0; i < 24; i++) { agent[i].dmoney += 0.00 - agent[i].money; agent[i].money = 0.00; } } //--------------------------------------------------------------------------- void __fastcall TForm1::EmotionButtonGroup1Items9Click(TObject *Sender) { // go through all agents for (int i = 0; i < 24; i++) { agent[i].dmoney += 0.00 - agent[i].money; agent[i].money = 0.00; } } //---------------------------------------------------------------------------