1 /*
2  * Modification History
3  *
4  * 2006-September-28   Jason Rohrer
5  * Created.
6  *
7  * 2006-October-4   Jason Rohrer
8  * Fixed rapid-fire gift bug.
9  * Changed to stop pursuit of desired position after completing a task.
10  *
11  * 2006-October-10   Jason Rohrer
12  * Added a limit on maximum plot size.
13  * Updated task_abandonPlot to work with new getLeastLikedGardener behavior.
14  *
15  * 2006-November-2   Jason Rohrer
16  * Increased following distance for non-parent.
17  */
18 
19 
20 
21 #include "GardenerAI2.h"
22 #include "gameFunctions.h"
23 
24 
25 #include <math.h>
26 #include <float.h>
27 
28 
29 #include "minorGems/util/random/StdRandomSource.h"
30 
31 
32 
33 
34 
35 extern StdRandomSource globalRandomSource;
36 
37 extern double globalMaxPlotDimension;
38 
39 
40 
41 /**
42  * Gets the maximum dimension of a plot.
43  */
getMaximumDimension(Vector3D * inCornerA,Vector3D * inCornerB)44 double getMaximumDimension( Vector3D *inCornerA, Vector3D *inCornerB ) {
45     double xDimension = fabs( inCornerB->mX - inCornerA->mX );
46     double yDimension = fabs( inCornerB->mY - inCornerA->mY );
47 
48     if( xDimension > yDimension ) {
49         return xDimension;
50         }
51     else {
52         return yDimension;
53         }
54     }
55 
56 
57 
GardenerAI(Gardener * inGardener,World * inWorld)58 GardenerAI::GardenerAI( Gardener *inGardener, World *inWorld )
59     : mGardener( inGardener ), mWorld( inWorld ),
60       mNextPlantingLocation( NULL ),
61       mNextPlotLocation( NULL ),
62       mSecondsSinceLastGift( 0 ),
63       mCurrentRestTime( 0 ),
64       // three seconds
65       mMaxRestTime( 3 ),
66       mCurrentTask( task_none ) {
67 
68     }
69 
70 
71 
~GardenerAI()72 GardenerAI::~GardenerAI() {
73     if( mNextPlantingLocation != NULL ) {
74         delete mNextPlantingLocation;
75         mNextPlantingLocation = NULL;
76         }
77     if( mNextPlotLocation != NULL ) {
78         delete mNextPlotLocation;
79         mNextPlotLocation = NULL;
80         }
81     }
82 
83 
84 // an array of gene locators for behavior modifiers
85 // makes it easy to loop over gene locators below
86 
87 GardenerGeneLocator taskModifierGeneLocators[ NUM_GARDENER_TASKS ] =
88     { task_none_modifier,
89       task_water_modifier,
90       task_harvest_modifier,
91       task_eat_modifier,
92       task_createPlot_modifier,
93       task_abandonPlot_modifier,
94       task_plant_modifier,
95       task_expandPlot_modifier,
96       task_capturePlant_modifier,
97       task_poisonPlant_modifier,
98       task_giveGift_modifier,
99       task_mate_modifier,
100       task_rest_modifier };
101 
102 
103 
passTime(double inTimeDeltaInSeconds)104 void GardenerAI::passTime( double inTimeDeltaInSeconds ) {
105 
106     // before doing anything else, check if we are still following parent
107     Gardener *parent = mGardener->getParentToFollow();
108 
109     // or following another leader
110     Gardener *leader = parent;
111 
112     double followDistance = 10;
113 
114     if( parent == NULL ||
115         parent->isDead() ) {
116 
117         leader = mGardener->getLeader();
118 
119         // a bit longer if leader not parent
120         followDistance = 15;
121         }
122 
123 
124     if( leader != NULL &&
125         ! leader->isDead() ) {
126 
127         // follow it if we get too far away
128         Vector3D *destination;
129 
130         if( mWorld->getGardenerDistance( mGardener, leader )
131             >
132             followDistance ) {
133 
134             // move closer
135             destination = mWorld->getGardenerPosition( leader );
136             }
137         else {
138             // stay where we are
139             destination = mWorld->getGardenerPosition( mGardener );
140             }
141 
142         mGardener->setDesiredPosition( destination );
143 
144         delete destination;
145 
146         return;
147         }
148 
149 
150 
151     // if we got here, we're not following a parent
152     mSecondsSinceLastGift += inTimeDeltaInSeconds;
153 
154 
155     SimpleVector<Plant*> *plants = mWorld->getPlotPlants( mGardener );
156 
157     int numPlants = plants->size();
158 
159 
160     // if we don't have a plot, we must always switch to that task
161     Vector3D *plotCenter = mWorld->getPlotCenter( mGardener );
162 
163     if( plotCenter == NULL ) {
164         mCurrentTask = task_createPlot;
165         }
166     delete plotCenter;
167 
168 
169     if( mCurrentTask == task_none ) {
170         // need to pick a task
171 
172         // we first compute weights for each task
173 
174         // The general idea is to compute a [0,1] weight for each.
175         // In some cases, this is binary, simply indicating whether the task
176         // is sensible or not (example:  no sense in eating if we aren't
177         // hungry).
178         // In other cases, this weight is continuous and represents urgency.
179 
180         // However, these [0,1] weights are deterministic and the same
181         // for all individuals.
182 
183         // Individual differences stem from the application of genetic
184         // modifiers that adjust these [0,1] weights.
185         // After the application of modifiers, weights may exceed 1.
186 
187 
188 
189         double weights[ NUM_GARDENER_TASKS ];
190         GardenerTask taskLabels[ NUM_GARDENER_TASKS ];
191 
192         // used to normalize weights
193         double weightSum = 0;
194 
195         int t;
196         for( t=0; t<NUM_GARDENER_TASKS; t++ ) {
197             // hack:  enum type is actually a series of integers
198             taskLabels[t] = (GardenerTask) t;
199 
200             switch( taskLabels[t] ) {
201                 case task_none: {
202                     // never intentionally pick no task
203                     weights[t] = 0;
204                     break;
205                     }
206                 case task_water: {
207                     Plant *driestPlant = getDriestPlant();
208                     if( driestPlant == NULL ) {
209                         weights[t] = 0;
210                         }
211                     else {
212                         double driestWaterStatus =
213                             driestPlant->getWaterStatus();
214 
215                         // approaches 1 as driest plant approaches no water
216                         weights[t] = ( 0.25 - driestWaterStatus ) / 0.25;
217                         }
218 
219                     break;
220                     }
221                 case task_harvest: {
222 
223                     Plant *ripePlant = NULL;
224 
225                     int i;
226 
227                     for( i=0; i<numPlants && ripePlant == NULL; i++ ) {
228                         Plant *thisPlant = *( plants->getElement( i ) );
229 
230                         if( thisPlant->isRipe() ) {
231                             ripePlant = thisPlant;
232                             }
233                         }
234 
235                     if( ripePlant != NULL ) {
236                         weights[t] = 1;
237                         }
238                     else {
239                         weights[t] = 0;
240                         }
241                     break;
242                     }
243                 case task_eat: {
244                     weights[t] = 0;
245 
246                     // first check if hungry
247                     double lowestLevel = 0.25;
248                     int lowIndex = -1;
249                     for( int i=0; i<3; i++ ) {
250                         double level = mGardener->getNutrientLevel(i);
251                         if( level < lowestLevel ) {
252                             lowIndex = i;
253                             lowestLevel = level;
254                             }
255                         }
256 
257                     if( lowIndex != -1 ) {
258                         // make sure we have fruit to eat
259                         int index =
260                             mGardener->getIndexOfFruitHighInNutrient(
261                                 lowIndex );
262 
263                         if( index != -1 ) {
264                             // grows closer to 1 as level approaches zero
265                             weights[t] = (0.25 - lowestLevel) / 0.25;
266                             }
267                         }
268                     break;
269                     }
270                 case task_createPlot: {
271                     // we only create a plot if we don't have one above
272                     // we never select this task probabalistically
273                     weights[t] = 0;
274                     break;
275                     }
276                 case task_abandonPlot: {
277 
278                     weights[t] = 0;
279 
280                     double ourPlotArea =
281                         mWorld->getPlotArea( mGardener );
282 
283                     if( ourPlotArea > 0 ) {
284                         // sum overlaps with other gardners
285                         double overlapAreaSum = 0;
286 
287                         int numGardeners;
288                         Gardener **gardeners =
289                             mWorld->getAllGardeners( &numGardeners );
290 
291                         for( int i=0; i<numGardeners; i++ ) {
292 
293                             Gardener *g = gardeners[i];
294 
295                             // ignore self
296                             if( g != mGardener ) {
297 
298                                 // only consider gardeners for whom we are
299                                 // the least liked gardener.
300                                 // We're making them angry, should we abandon
301                                 // our plot and seek land elsewhere?
302 
303                                 // Compare their like metric for us to their
304                                 // metric for the gardener that they like
305                                 // the least.
306                                 // Thus, we can tell if we are on their
307                                 // "least liked" list.
308 
309                                 double theirLikeMetricForUs =
310                                     g->getLikeMetric( mGardener );
311 
312                                 Gardener *theirLeastLiked =
313                                     g->getLeastLikedGardener();
314 
315                                 double theirLowestLikeMetric =
316                                     g->getLikeMetric( theirLeastLiked );
317 
318 
319                                 if( theirLikeMetricForUs <=
320                                     theirLowestLikeMetric ) {
321 
322                                     // they hate us
323 
324                                     // add in their overlap
325                                     overlapAreaSum +=
326                                         mWorld->getPlotIntersectionArea(
327                                             mGardener,
328                                             g );
329                                     }
330                                 }
331                             }
332                         delete [] gardeners;
333 
334                         double overlapFraction = overlapAreaSum / ourPlotArea;
335                         if( overlapFraction > 1 ) {
336                             overlapFraction = 1;
337                             }
338 
339                         double threshold = mGardener->mGenetics.getParameter(
340                             overlapTolerance );
341 
342                         if( overlapFraction >= threshold ) {
343                             // our sum of overlap with gardeners that hate us
344                             // is greater than our threshold
345 
346 
347                             // weight depends on how much overlap we have
348                             // above our threshold
349                             double range = 1 - threshold;
350 
351                             if( range > 0 ) {
352                                 weights[t] =
353                                     ( overlapFraction - threshold ) / range;
354                                 }
355                             else {
356                                 weights[t] = 1;
357                                 }
358                             }
359                         }
360                     break;
361                     }
362                 case task_plant: {
363                     int targetPlantCount =
364                         (int)( mGardener->mGenetics.getParameter(
365                             desiredPlantCount ) );
366 
367                     int numPlants = plants->size();
368 
369                     SimpleVector<Seeds*> *seedsVector =
370                         mGardener->getAllSeeds();
371 
372                     int numSeeds = seedsVector->size();
373                     delete seedsVector;
374 
375 
376                     if( numSeeds > 0 && numPlants < targetPlantCount ) {
377                         // weight shrinks as we near our target count
378                         weights[t] =
379                             (double)(targetPlantCount - numPlants) /
380                             targetPlantCount;
381                         }
382                     else {
383                         weights[t] = 0;
384                         }
385                     break;
386                     }
387                 case task_expandPlot: {
388                     // we only try to expand the plot if our planting
389                     // task fails, so we never select this task
390                     // probabalistically
391                     weights[t] = 0;
392                     break;
393                     }
394                 case task_capturePlant: {
395                     weights[t] = 0;
396 
397                     Gardener *leastLiked =
398                         mGardener->getLeastLikedGardener();
399 
400                     if( leastLiked != NULL ) {
401 
402                         Plant *plantToTake =
403                             getClosestPlantInGardenerPlot( leastLiked );
404 
405                         if( plantToTake != NULL ) {
406 
407                             double likeMetric =
408                                 mGardener->getLikeMetric( leastLiked );
409 
410                             // weight depending on our like metric
411                             weights[t] = 1 - likeMetric;
412                             }
413                         }
414                     break;
415                     }
416                 case task_poisonPlant: {
417                     weights[t] = 0;
418 
419                     Gardener *leastLiked =
420                         mGardener->getLeastLikedGardener();
421 
422                     if( leastLiked != NULL ) {
423 
424                         Plant *plantToPoison =
425                             mWorld->getTendedPlant( leastLiked );
426 
427                         if( plantToPoison != NULL ) {
428 
429                             double likeMetric =
430                                 mGardener->getLikeMetric( leastLiked );
431 
432                             // don't consider poisoning unless
433                             // we really don't like them
434                             if( likeMetric < 0.25 ) {
435                                 // weight depending on our like metric
436                                 weights[t] = (0.25 - likeMetric) / 0.25;
437                                 }
438                             }
439                         }
440                     break;
441                     }
442                 case task_giveGift: {
443                     weights[t] = 0;
444 
445                     if( mSecondsSinceLastGift > 5 ) {
446                         Gardener *mostLiked =
447                             mGardener->getMostLikedGardener();
448 
449                         if( mostLiked != NULL ) {
450 
451                             // only give if we have 2+ more fruits than them
452                             // (to avoid back and forth giving when there is an
453                             //  odd number of fruits between the two of us)
454                             if( mGardener->getStoredFruitCount() >
455                                 mostLiked->getStoredFruitCount() + 1 ) {
456 
457                                 double likeMetric =
458                                     mGardener->getLikeMetric( mostLiked );
459 
460                                 // weight based on like metric
461                                 weights[t] = likeMetric;
462                                 }
463                             }
464                         }
465                     break;
466                     }
467                 case task_mate: {
468 
469                     weights[t] = 0;
470 
471                     // if not pregnant
472                     // and not target of another pregancy
473                     // hand have enough fruit to feel secure
474                     if( ! mGardener->isPregnant() &&
475                         ! mWorld->isTargetOfPregnancy( mGardener ) &&
476                         mGardener->getStoredFruitCount() >=
477                         mGardener->mGenetics.getParameter(
478                             storedFruitsBeforeMating ) ) {
479 
480                         // we are not pregnant already
481 
482                         // we have enough fruit stored
483                         // consider mating
484 
485                         double ourThreshold =
486                             mGardener->mGenetics.getParameter(
487                                 matingThreshold );
488 
489 
490                         Gardener *mostLiked =
491                             mGardener->getMostLikedGardener();
492 
493                         if( mostLiked != NULL ) {
494 
495                             double mostLikedMatingThreshold =
496                                 mostLiked->mGenetics.getParameter(
497                                     matingThreshold );
498 
499                             double likeMetric =
500                                 mGardener->getLikeMetric( mostLiked );
501 
502                             if( likeMetric >= ourThreshold
503                                 &&
504                                 mostLiked->getLikeMetric( mGardener ) >=
505                                 mostLikedMatingThreshold
506                                 &&
507                                 ! mostLiked->isPregnant()
508                                 &&
509                                 ! mWorld->isTargetOfPregnancy( mostLiked ) ) {
510 
511                                 // we like them enough to mate
512                                 // and
513                                 // they like us enough to mate
514                                 // and
515                                 // they are not already pregnant
516                                 // and
517                                 // they are not already target of
518                                 // another pregnancy
519 
520 
521                                 // weight depends on like metric
522 
523                                 // how much over our mating threshold?
524                                 double range = 1 - ourThreshold;
525 
526                                 if( range > 0 ) {
527 
528                                     weights[t] =
529                                         ( likeMetric - ourThreshold ) / range;
530                                     }
531                                 else {
532                                     weights[t] = 1;
533                                     }
534                                 }
535                             }
536                         }
537                     break;
538                     }
539                 case task_rest: {
540                     // never pick resting probabalistically
541 
542                     // instead, select it as default when all other tasks have
543                     // no weight, below
544                     weights[t] = 0;
545                     break;
546                     }
547                 }
548 
549             // apply genetic modifiers (skip task_none)
550             if( t > 0 ) {
551                 weights[t] *=
552                     mGardener->mGenetics.getParameter(
553                         taskModifierGeneLocators[t] );
554                 }
555 
556             weightSum += weights[t];
557             }
558 
559         // now we have a weight for each task
560 
561         // normalize
562 
563         if( weightSum == 0 ) {
564             // avoid divide by zero
565             weightSum = 1;
566             }
567 
568         for( t=0; t<NUM_GARDENER_TASKS; t++ ) {
569             weights[t] /= weightSum;
570             }
571 
572         // now weights are a probability distribution
573 
574         // pick a random variable to sample from the probability distribution
575         double randomVariable = globalRandomSource.getRandomDouble();
576 
577         // to sample from a probability distribution using a [0,1] random
578         // variable, walk through weights, summing them, until weight
579         // sum passes random variable value
580 
581         char hit = false;
582         double weightAccumulation = 0;
583 
584         for( t=0; t<NUM_GARDENER_TASKS && !hit; t++ ) {
585 
586             weightAccumulation += weights[t];
587 
588             if( weightAccumulation >= randomVariable ) {
589                 // found the task hit by our random variable
590                 hit = true;
591 
592                 mCurrentTask = taskLabels[t];
593                 }
594             }
595 
596         if( mCurrentTask == task_none ) {
597             // this means that all task weights were zero, or that our
598             // random variable was zero
599 
600             // default to resting
601             mCurrentTask = task_rest;
602             }
603         }
604 
605 
606     // we have a task
607     // execute it
608 
609     switch( mCurrentTask ) {
610         case task_none: {
611             printf( "Error:  task_none selected by GardenerAI2.\n" );
612             break;
613             }
614         case task_water: {
615             Plant *driestPlant = getDriestPlant();
616 
617             if( driestPlant != NULL ) {
618                 if( mGardener->getCarryingWater() ) {
619                     // already carrying water
620 
621                     // move to the driest plant
622 
623 
624                     Vector3D *plantPosition =
625                         mWorld->getPlantPosition( driestPlant );
626 
627                     Vector3D *gardenerPosition =
628                         mWorld->getGardenerPosition( mGardener );
629 
630                     if( ! gardenerPosition->equals( plantPosition ) ) {
631                         // move to plant
632                         mGardener->setDesiredPosition( plantPosition );
633                         }
634                     else {
635                         // already at plant
636 
637                         // dump water
638                         mGardener->setCarryingWater( false );
639 
640                         mWorld->dumpWater( mGardener );
641 
642                         // finished
643                         mCurrentTask = task_none;
644                         }
645 
646                     delete gardenerPosition;
647                     delete plantPosition;
648                     }
649                 else {
650                     // there is a dry plant, and we're not carrying water
651                     // fetch water
652 
653                     if( ! mWorld->isInWater( mGardener ) ) {
654 
655                         Vector3D *waterPoint =
656                             mWorld->getClosestWater( mGardener );
657 
658                         mGardener->setDesiredPosition( waterPoint );
659 
660                         delete waterPoint;
661                         }
662                     else {
663                         // grab water
664                         mGardener->setCarryingWater( true );
665                         }
666                     }
667                 }
668             else {
669                 // no driest plant
670 
671                 // make sure we're not still carrying water after
672                 // a plant finished growing
673                 mGardener->setCarryingWater( false );
674 
675                 // finished
676                 mCurrentTask = task_none;
677                 }
678 
679             break;
680             }
681         case task_harvest: {
682             Plant *ripePlant = NULL;
683 
684             int i;
685 
686             for( i=0; i<numPlants && ripePlant == NULL; i++ ) {
687                 Plant *thisPlant = *( plants->getElement( i ) );
688 
689                 if( thisPlant->isRipe() ) {
690                     ripePlant = thisPlant;
691                     }
692                 }
693 
694             if( ripePlant != NULL ) {
695 
696 
697                 // move toward it
698 
699                 Vector3D *plantPosition =
700                     mWorld->getPlantPosition( ripePlant );
701 
702                 Vector3D *gardenerPosition =
703                     mWorld->getGardenerPosition( mGardener );
704 
705                 if( ! gardenerPosition->equals( plantPosition ) ) {
706                     // move to plant
707                     mGardener->setDesiredPosition( plantPosition );
708                     }
709                 else {
710                     // already at plant
711 
712                     // harvest it
713 
714                     mWorld->harvestPlant( mGardener, ripePlant );
715 
716                     // finished
717                     mCurrentTask = task_none;
718                     }
719 
720 
721                 delete gardenerPosition;
722                 delete plantPosition;
723                 }
724             else {
725                 // nothing to harvest
726 
727                 // finished
728                 mCurrentTask = task_none;
729                 }
730 
731             break;
732             }
733         case task_eat: {
734             // first find what nutrient we are lowest in
735             double lowestLevel = 0.25;
736             int lowIndex = -1;
737             for( int i=0; i<3; i++ ) {
738                 double level = mGardener->getNutrientLevel(i);
739                 if( level < lowestLevel ) {
740                     lowIndex = i;
741                     lowestLevel = level;
742                     }
743                 }
744 
745             if( lowIndex != -1 ) {
746                 // low in at least one nutrient
747 
748                 // try to find a fruit high in that nutrient
749                 int index =
750                     mGardener->getIndexOfFruitHighInNutrient( lowIndex );
751 
752                 if( index != -1 ) {
753                     mGardener->setSelectedObjectIndex( index );
754                     // eat selected fruit
755                     mGardener->eat();
756                     }
757                 }
758 
759             // always finish, since this is an instant action
760             mCurrentTask = task_none;
761 
762             break;
763             }
764         case task_createPlot: {
765             // need to pick a new plot
766 
767             // first pick a random land point in the world
768             if( mNextPlotLocation == NULL ) {
769                 mNextPlotLocation = mWorld->getRandomLandPoint();
770                 }
771 
772             Vector3D *gardenerPosition =
773                 mWorld->getGardenerPosition( mGardener );
774 
775             if( ! gardenerPosition->equals( mNextPlotLocation ) ) {
776                 // move to next plot location
777                 mGardener->setDesiredPosition( mNextPlotLocation );
778                 }
779             else {
780                 // there already
781 
782 
783                 // once there, create a plot
784 
785                 Vector3D a( mNextPlotLocation );
786 
787                 Vector3D b( mNextPlotLocation );
788 
789                 // 20 x 20
790 
791                 a.mX -= 10;
792                 a.mY -= 10;
793 
794                 b.mX += 10;
795                 b.mY += 10;
796 
797 
798                 delete mNextPlotLocation;
799                 mNextPlotLocation = NULL;
800 
801                 // don't check if plot meets our criteria
802 
803                 // we will do that in the behavior selection (since
804                 // we might decide to abandon our plot)
805 
806                 mWorld->setGardenerPlot( mGardener, &a, &b );
807 
808                 // finished
809                 mCurrentTask = task_none;
810                 }
811             delete gardenerPosition;
812             break;
813             }
814         case task_abandonPlot: {
815             mWorld->setGardenerPlot( mGardener, NULL, NULL );
816             break;
817             }
818         case task_plant: {
819             if( mNextPlantingLocation == NULL ) {
820                 // haven't picked a spot yet
821 
822                 char foundPlantable = false;
823                 int numTries = 0;
824                 int maxNumTries = 100;
825 
826                 Vector3D *cornerA, *cornerB;
827 
828                 mWorld->getGardenerPlot( mGardener, &cornerA, &cornerB );
829 
830 
831                 while( !foundPlantable && numTries < maxNumTries ) {
832 
833 
834                     double x = globalRandomSource.getRandomBoundedDouble(
835                         cornerA->mX, cornerB->mX );
836 
837                     double y = globalRandomSource.getRandomBoundedDouble(
838                         cornerA->mY, cornerB->mY );
839 
840 
841                     mNextPlantingLocation = new Vector3D( x, y, 0 );
842 
843                     if( mWorld->canPlant( mNextPlantingLocation ) ) {
844                         foundPlantable = true;
845                         }
846                     else {
847                         // try again
848                         delete mNextPlantingLocation;
849                         mNextPlantingLocation = NULL;
850                         }
851                     numTries++;
852                     }
853 
854                 delete cornerA;
855                 delete cornerB;
856                 }
857 
858 
859             if( mNextPlantingLocation != NULL ) {
860 
861                 Vector3D *gardenerPosition =
862                     mWorld->getGardenerPosition( mGardener );
863 
864                 if( ! gardenerPosition->equals( mNextPlantingLocation ) ) {
865                     // move to next plant location
866                     mGardener->setDesiredPosition( mNextPlantingLocation );
867                     }
868                 else {
869                     // at next location:
870 
871                     // make sure we can still plant
872                     // else pick another location at next time step
873                     if( mWorld->canPlant( mNextPlantingLocation ) ) {
874                         // plant here
875 
876                         double soilCondition =
877                             mWorld->getSoilCondition( mNextPlantingLocation );
878 
879 
880                         SimpleVector<Seeds*> *seedsVector =
881                             mGardener->getAllSeeds();
882 
883                         // find best for this soil
884                         Seeds *best = NULL;
885                         double minSoilDistance = 2;
886 
887                         for( int i=0; i<seedsVector->size(); i++ ) {
888                             Seeds *seeds = *( seedsVector->getElement( i ) );
889 
890                             double distance =
891                                 fabs( seeds->mIdealSoilType - soilCondition );
892 
893                             if( distance < minSoilDistance ) {
894                                 minSoilDistance = distance;
895                                 best = seeds;
896                                 }
897                             }
898 
899                         delete seedsVector;
900 
901 
902                         if( best != NULL ) {
903                             mWorld->addPlant( mGardener,
904                                               new Plant( soilCondition, best ),
905                                               mNextPlantingLocation );
906 
907                             mGardener->removeSeeds( best );
908                             }
909 
910                         // finished
911                         mCurrentTask = task_none;
912                         }
913 
914                     delete mNextPlantingLocation;
915                     mNextPlantingLocation = NULL;
916                     }
917 
918                 delete gardenerPosition;
919                 }
920             else {
921                 // tried to pick a plantable location, but failed
922 
923                 // switch to expand task
924                 mCurrentTask = task_expandPlot;
925                 }
926             break;
927             }
928         case task_expandPlot: {
929             Vector3D *a, *b;
930 
931             mWorld->getGardenerPlot( mGardener, &a, &b );
932 
933             // compute a vector stretching from b to a
934             Vector3D b_to_a( a );
935 
936             b_to_a.subtract( b );
937 
938             // expand plot by 10% in each direction
939 
940             b_to_a.scale( 0.10 );
941 
942             // push a away from b
943             a->add( &b_to_a );
944 
945 
946             // also push b away from a
947             // opposite direction
948             b_to_a.scale( -1 );
949 
950             b->add( &b_to_a );
951 
952 
953             // make sure it's not too big
954 
955             double maxDimension = getMaximumDimension( a, b );
956 
957             if( maxDimension <= globalMaxPlotDimension ) {
958 
959                 mWorld->setGardenerPlot( mGardener, a, b );
960 
961                 // finished
962                 mCurrentTask = task_none;
963                 }
964             else {
965                 // can't expand, though we need to
966 
967                 // abandon plot
968                 mCurrentTask = task_abandonPlot;
969                 }
970 
971             delete a;
972             delete b;
973 
974 
975             break;
976             }
977         case task_capturePlant: {
978             Gardener *leastLiked = mGardener->getLeastLikedGardener();
979 
980             if( leastLiked != NULL ) {
981 
982                 Plant *plantToTake =
983                     getClosestPlantInGardenerPlot( leastLiked );
984 
985                 if( plantToTake != NULL ) {
986                     char expanded = expandOurPlotToContainPlant( plantToTake );
987 
988                     if( expanded ) {
989                         // every time we successfully take revenge, our anger
990                         // toward this gardener lessens a bit
991                         mGardener->getFriendly( leastLiked );
992                         }
993                     // else our plot cannot get any bigger
994 
995                     }
996                 }
997 
998             // always finish (this task executes in one timestep)
999             mCurrentTask = task_none;
1000             break;
1001             }
1002         case task_poisonPlant: {
1003             Gardener *leastLiked = mGardener->getLeastLikedGardener();
1004 
1005             if( leastLiked != NULL ) {
1006 
1007                 Plant *plantToPoison =
1008                     mWorld->getTendedPlant( leastLiked );
1009 
1010                 if( plantToPoison != NULL ) {
1011 
1012                     // found candidate
1013 
1014                     // walk to it
1015 
1016                     Vector3D *plantPosition =
1017                         mWorld->getPlantPosition( plantToPoison );
1018 
1019                     Vector3D *gardenerPosition =
1020                         mWorld->getGardenerPosition( mGardener );
1021 
1022                     if( ! gardenerPosition->equals( plantPosition ) ) {
1023                         // move to plant
1024                         mGardener->setDesiredPosition( plantPosition );
1025                         }
1026                     else {
1027                         // already at plant
1028                         mWorld->dumpPoison( mGardener );
1029 
1030                         // every time we take revenge, our anger
1031                         // toward this gardener lessens a bit
1032                         mGardener->getFriendly( leastLiked );
1033 
1034                         // finished
1035                         mCurrentTask = task_none;
1036                         }
1037 
1038                     delete gardenerPosition;
1039                     delete plantPosition;
1040                     }
1041                 else {
1042                     // nothing to poison
1043                     mCurrentTask = task_none;
1044                     }
1045                 }
1046             else {
1047                 // nothing to poison
1048                 mCurrentTask = task_none;
1049                 }
1050             break;
1051             }
1052         case task_giveGift: {
1053             Gardener *mostLiked = mGardener->getMostLikedGardener();
1054 
1055             if( mostLiked != NULL ) {
1056 
1057                 // only give if we have 2+ more fruits than them
1058                 // (to avoid back and forth giving when there is an
1059                 //  odd number of fruits between the two of us)
1060                 if( mGardener->getStoredFruitCount() >
1061                     mostLiked->getStoredFruitCount() + 1 ) {
1062 
1063                     Vector3D *ourPosition =
1064                         mWorld->getGardenerPosition( mGardener );
1065                     Vector3D *otherPosition =
1066                         mWorld->getGardenerPosition( mostLiked );
1067 
1068                     double distance =
1069                         ourPosition->getDistance( otherPosition );
1070 
1071                     if( distance < getMaxDistanceForTransactions() ) {
1072                         // close enough to give
1073 
1074                         // find out which nutrient they are low in
1075                         int lowIndex = -1;
1076                         double lowValue = 2;
1077                         for( int i=0; i<3; i++ ) {
1078                             double value = mostLiked->getNutrientLevel(i);
1079                             if( value < lowValue  ) {
1080                                 lowIndex = i;
1081                                 lowValue = value;
1082                                 }
1083                             }
1084 
1085                         // try to find a fruit high in that nutrient
1086                         int index = mGardener->getIndexOfFruitHighInNutrient(
1087                             lowIndex );
1088 
1089                         // we will always get a valid index here, because
1090                         // we have checked that we have stored fruit above
1091                         mGardener->setSelectedObjectIndex( index );
1092 
1093                         mWorld->giveFruit(
1094                             mGardener, mostLiked,
1095                             // don't save seeds, but get any fruit, even
1096                             // if fruit not selected
1097                             mGardener->getSelectedFruit( false, true ) );
1098 
1099                         // reset our gift timer so that we don't send
1100                         // rapid-fire gifts before our first one arrives
1101 
1102                         // the reason we need a gift timer, and not
1103                         // a revenge timer or any other kind of timer,
1104                         // is because our decision to give a gift is
1105                         // based on the state of the other gardener, and not
1106                         // just on our own state.  Their state changes
1107                         // in response to our gift only after they receive
1108                         // it, so we must wait until they receive it
1109                         // before we decide to give again.
1110 
1111                         // for revenge actions, and other actions (like eating,
1112                         // watering, etc.), our decision is based only
1113                         // on our internal state, which changes as soon
1114                         // as we execute the action.
1115 
1116                         mSecondsSinceLastGift = 0;
1117 
1118 
1119                         // every time we give a gift,
1120                         // our friendliness toward this gardener is depleted
1121                         // a bit
1122 
1123                         // Actually, don't do this for now, since for mating
1124                         // purposes, we want to retain our friendliness
1125 
1126                         // mGardener->getAngry( mostLiked );
1127 
1128                         // finished
1129                         mCurrentTask = task_none;
1130                         }
1131                     else {
1132                         // move toward friend
1133                         mGardener->setDesiredPosition( otherPosition );
1134                         }
1135 
1136                     delete ourPosition;
1137                     delete otherPosition;
1138                     }
1139                 else {
1140                     // they have enough fruit now, cancel
1141                     mCurrentTask = task_none;
1142                     }
1143                 }
1144             else {
1145                 // we no longer have a most liked
1146                 mCurrentTask = task_none;
1147                 }
1148 
1149             break;
1150             }
1151         case task_mate: {
1152             // if not pregnant
1153             // and not target of another pregancy
1154             // don't check fruit supply (we've decided to mate, so let's do it)
1155             if( ! mGardener->isPregnant() &&
1156                 ! mWorld->isTargetOfPregnancy( mGardener ) ) {
1157 
1158                 // we are not pregnant already
1159 
1160                 // we have enough fruit stored
1161                 // consider mating
1162 
1163                 double ourThreshold =
1164                     mGardener->mGenetics.getParameter( matingThreshold );
1165 
1166 
1167                 Gardener *mostLiked = mGardener->getMostLikedGardener();
1168 
1169                 if( mostLiked != NULL ) {
1170 
1171                     double mostLikedMatingThreshold =
1172                         mostLiked->mGenetics.getParameter( matingThreshold );
1173 
1174 
1175                     if( mGardener->getLikeMetric( mostLiked ) >=
1176                         ourThreshold
1177                         &&
1178                         mostLiked->getLikeMetric( mGardener ) >=
1179                         mostLikedMatingThreshold
1180                         &&
1181                         ! mostLiked->isPregnant()
1182                         &&
1183                         ! mWorld->isTargetOfPregnancy( mostLiked ) ) {
1184 
1185                         // we like them enough to mate
1186                         // and
1187                         // they like us enough to mate
1188                         // and
1189                         // they are not already pregnant
1190                         // and
1191                         // they are not already target of another pregnancy
1192 
1193 
1194                         Vector3D *ourPosition =
1195                             mWorld->getGardenerPosition( mGardener );
1196                         Vector3D *otherPosition =
1197                             mWorld->getGardenerPosition( mostLiked );
1198 
1199                         double distance =
1200                             ourPosition->getDistance( otherPosition );
1201 
1202                         if( distance < getMaxDistanceForTransactions() ) {
1203 
1204                             mWorld->mateGardeners( mGardener, mostLiked );
1205 
1206                             // finished
1207                             mCurrentTask = task_none;
1208                             }
1209                         else {
1210                             // move toward friend
1211                             mGardener->setDesiredPosition( otherPosition );
1212                             }
1213 
1214                         delete ourPosition;
1215                         delete otherPosition;
1216                         }
1217                     else {
1218                         // something changed since we decided to mate
1219                         mCurrentTask = task_none;
1220                         }
1221                     }
1222                 else {
1223                     // we no longer have a most liked
1224                     mCurrentTask = task_none;
1225                     }
1226                 }
1227             else {
1228                 // we are already pregnant or target of another pregnancy
1229                 mCurrentTask = task_none;
1230                 }
1231             break;
1232             }
1233         case task_rest: {
1234             mCurrentRestTime += inTimeDeltaInSeconds;
1235             if( mCurrentRestTime >= mMaxRestTime ) {
1236                 // finished
1237                 mCurrentRestTime = 0;
1238                 mCurrentTask = task_none;
1239                 }
1240             break;
1241             }
1242         }
1243 
1244 
1245 
1246     if( mCurrentTask == task_none ) {
1247 
1248         // just finished a task
1249 
1250         // Make sure we stop moving and don't continue chasing down
1251         // the desired position we set as part of our last task.
1252 
1253         // For example, when giving a gift, we set our desired position
1254         // at the gardener, but we can stop moving closer to the gardener
1255         // once we have gotten close enough and given the gift.
1256 
1257         Vector3D *gardenerPosition =
1258             mWorld->getGardenerPosition( mGardener );
1259 
1260         mGardener->setDesiredPosition( gardenerPosition );
1261 
1262         delete gardenerPosition;
1263         }
1264 
1265 
1266 
1267     delete plants;
1268     }
1269 
1270 
1271 
getClosestPlantInGardenerPlot(Gardener * inGardener)1272 Plant *GardenerAI::getClosestPlantInGardenerPlot( Gardener *inGardener ) {
1273     SimpleVector<Plant *> *ourPlants = mWorld->getPlotPlants( mGardener );
1274     SimpleVector<Plant *> *otherPlants = mWorld->getPlotPlants( inGardener );
1275 
1276     if( otherPlants == NULL ) {
1277 
1278         if( ourPlants != NULL ) {
1279             delete ourPlants;
1280             }
1281 
1282         return NULL;
1283         }
1284 
1285     // first, filter otherPlants to remove overlaps with ourPlants
1286 
1287     int i=0;
1288     while( i < otherPlants->size() ) {
1289 
1290         Plant *plant = *( otherPlants->getElement( i ) );
1291 
1292         if( ourPlants != NULL &&
1293             ourPlants->getElementIndex( plant ) != -1 ) {
1294             // overlap
1295             otherPlants->deleteElement( i );
1296             }
1297         else {
1298             // no overlap
1299             i++;
1300             }
1301         }
1302 
1303     if( ourPlants != NULL ) {
1304         delete ourPlants;
1305         }
1306 
1307 
1308     Plant *returnPlant = NULL;
1309 
1310 
1311     int numPlants = otherPlants->size();
1312 
1313     if( numPlants > 0 ) {
1314 
1315         // look for closest to us
1316 
1317         Vector3D *ourPostion = mWorld->getGardenerPosition( mGardener );
1318 
1319         double closestDistance = DBL_MAX;
1320 
1321         for( i=0; i<numPlants; i++ ) {
1322             Plant *plant = *( otherPlants->getElement( i ) );
1323 
1324             Vector3D *plantPosition = mWorld->getPlantPosition( plant );
1325 
1326             double distance = plantPosition->getDistance( ourPostion );
1327 
1328             delete plantPosition;
1329 
1330             if( distance < closestDistance ) {
1331                 closestDistance = distance;
1332                 returnPlant = plant;
1333                 }
1334             }
1335 
1336         delete ourPostion;
1337         }
1338 
1339 
1340     delete otherPlants;
1341 
1342     return returnPlant;
1343     }
1344 
1345 
1346 
getDriestPlant()1347 Plant *GardenerAI::getDriestPlant() {
1348 
1349     SimpleVector<Plant*> *plants = mWorld->getPlotPlants( mGardener );
1350 
1351     int numPlants = plants->size();
1352 
1353 
1354     Plant *driestPlant = NULL;
1355 
1356     // ignore plants that have at least 1/4 water
1357     double driestWaterStatus = 0.25;
1358 
1359 
1360     for( int i=0; i<numPlants; i++ ) {
1361         Plant *thisPlant = *( plants->getElement( i ) );
1362 
1363         double waterStatus = thisPlant->getWaterStatus();
1364         if( waterStatus < driestWaterStatus ) {
1365             driestPlant = thisPlant;
1366             driestWaterStatus = waterStatus;
1367             }
1368         }
1369 
1370     delete plants;
1371 
1372     return driestPlant;
1373     }
1374 
1375 
1376 
expandOurPlotToContainPlant(Plant * inPlant)1377 char GardenerAI::expandOurPlotToContainPlant( Plant *inPlant ) {
1378 
1379     Vector3D *cornerA, *cornerB;
1380 
1381     mWorld->getGardenerPlot( mGardener, &cornerA, &cornerB );
1382 
1383     Vector3D *plantPosition = mWorld->getPlantPosition( inPlant );
1384 
1385     Vector3D *closestCornerX;
1386     double xDistance;
1387     Vector3D *closestCornerY;
1388     double yDistance;
1389 
1390     double cornerAXDistance = plantPosition->mX - cornerA->mX;
1391     double cornerBXDistance = plantPosition->mX - cornerB->mX;
1392 
1393     double cornerAYDistance = plantPosition->mY - cornerA->mY;
1394     double cornerBYDistance = plantPosition->mY - cornerB->mY;
1395 
1396     if( fabs( cornerAXDistance ) < fabs( cornerBXDistance ) ) {
1397         closestCornerX = cornerA;
1398         xDistance = cornerAXDistance;
1399         }
1400     else {
1401         closestCornerX = cornerB;
1402         xDistance = cornerBXDistance;
1403         }
1404 
1405 
1406     if( fabs( cornerAYDistance ) < fabs( cornerBYDistance ) ) {
1407         closestCornerY = cornerA;
1408         yDistance = cornerAYDistance;
1409         }
1410     else {
1411         closestCornerY = cornerB;
1412         yDistance = cornerBYDistance;
1413         }
1414 
1415     // make distances a little bit larger (1 unit) to contain plant
1416     double extra = 1;
1417     if( xDistance < 0 ) {
1418         xDistance -= extra;
1419         }
1420     if( xDistance > 0 ) {
1421         xDistance += extra;
1422         }
1423     if( yDistance < 0 ) {
1424         yDistance -= extra;
1425         }
1426     if( yDistance > 0 ) {
1427         yDistance += extra;
1428         }
1429 
1430 
1431 
1432     // only want to change corners in way that makes plot bigger
1433     // thus, to contain inPlant, we may need to expand in only one direction
1434     // (in other words, x or y)
1435 
1436     // we can use the plot area to detect plot contraction
1437 
1438     double oldArea =
1439         fabs( cornerA->mX - cornerB->mX ) *
1440         fabs( cornerA->mY - cornerB->mY );
1441 
1442 
1443     closestCornerX->mX += xDistance;
1444 
1445     double newArea =
1446         fabs( cornerA->mX - cornerB->mX ) *
1447         fabs( cornerA->mY - cornerB->mY );
1448 
1449     if( newArea < oldArea ) {
1450         // contraction
1451 
1452         // reverse it
1453         closestCornerX->mX -= xDistance;
1454         }
1455 
1456 
1457     oldArea =
1458         fabs( cornerA->mX - cornerB->mX ) *
1459         fabs( cornerA->mY - cornerB->mY );
1460 
1461 
1462     closestCornerY->mY += yDistance;
1463 
1464     newArea =
1465         fabs( cornerA->mX - cornerB->mX ) *
1466         fabs( cornerA->mY - cornerB->mY );
1467 
1468     if( newArea < oldArea ) {
1469         // contraction
1470 
1471         // reverse it
1472         closestCornerY->mY -= yDistance;
1473         }
1474 
1475     char returnValue;
1476 
1477     if( getMaximumDimension( cornerA, cornerB ) <= globalMaxPlotDimension ) {
1478 
1479         mWorld->setGardenerPlot( mGardener, cornerA, cornerB );
1480 
1481         returnValue = true;
1482         }
1483     else {
1484         // new plot exceeds limit
1485         returnValue = false;
1486         }
1487 
1488 
1489 
1490     delete plantPosition;
1491 
1492     delete cornerA;
1493     delete cornerB;
1494 
1495     return returnValue;
1496     }
1497