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