Saturday, January 21, 2017

On The Road to a Decision

hey all,

Jan. 20th marked the end and the beginning of an era, in more ways than one. On the world stage, Mr. Trump has entered office as the 45th POTUS, ending 8 years of Mr. Obama. Whatever you think of either man's politics, Mr. Obama presided with dignity and respect, and delivered his speeches with gravitas and eloquence. Nobody knows exactly what Mr. Trump will do, but we all know it will be done in an outrageous and probably offensive manner.

On my own small personal stage, Jan. 20th marked my last full time day as an English teacher. Starting Monday, I will be devoting my mornings to programming, research on game design and development, and job hunting for positions in the game industry.

...

I've been watching The Matrix series and blasting Rage Against the Machine's eponymous album for the last week. The themes of CHOICE and DECISION seem to be haunting me, perhaps partly because that has been what I'm struggling to implement in my The Sims-inspired needs system. I've written code that goes through all the needs, and compares how meeting each need would affect the mood of the NPC.


public float SimulateMoodChange(NeedType nt, float potency)
 {
  float curmood = CurrentMood;
  float deltamood = 0;
  //float curmood = CurrentMood- nee.MoodResponse((int)nt)[potency];
  float nttwo = 0;
  switch (nt)
  {
   case NeedType.Food:
    nttwo = nee.Hunger;
    break;
   case NeedType.Hydration:
    nttwo = nee.Thirst;
    break;
   case NeedType.Rest:
    nttwo = nee.Tiredness;
    break;
   case NeedType.Sex:
    nttwo = nee.Lust;
    break;
   case NeedType.Comfort:
    nttwo = nee.Comfort;
    break;
   case NeedType.Cleanliness:
    nttwo = nee.Cleanliness;
    break;
   case NeedType.Shelter:
    nttwo = nee.Shelter;
    break;
   case NeedType.Intimacy:
    nttwo = nee.Intimacy;
    break;
   case NeedType.Fun:
    nttwo = nee.Fun;
    break;
   case NeedType.Safety:
    nttwo = nee.Safety;
    break;
   case NeedType.Respect:
    nttwo = nee.Respect;
    break;
   case NeedType.MAX:
    break;
   default:
    break;
  }
  Debug.Log("Simulate mood change" + " need is " + nt);
  //Debug.Log("potency ")
  for (int i = 0; i < nee.NumberOfNeeds; i++)
  {
   ResponseCurveFloat mr = nee.MoodResponse(i);
   switch ((NeedType)i)
   {
    case NeedType.Food:
     if ((NeedType)i != nt)
     {
      deltamood = Mathf.Clamp(deltamood + mr[nee.Hunger], -100, 100);
     }
     else
     {
      deltamood = Mathf.Clamp(deltamood + nee.MoodResponse((int)nt)[nttwo - (100f - potency)], -100, 100);
     }
     break;
    case NeedType.Hydration:
     if ((NeedType)i != nt)
     {
      deltamood = Mathf.Clamp(deltamood + mr[nee.Thirst], -100, 100);
     }
     else
     {
      deltamood = Mathf.Clamp(deltamood + nee.MoodResponse((int)nt)[nttwo - (100f - potency)], -100, 100);
     }
     break;
    case NeedType.Rest:
     if ((NeedType)i != nt)
     {
      deltamood = Mathf.Clamp(deltamood + mr[nee.Tiredness], -100, 100);
     }
     else
     {
      deltamood = Mathf.Clamp(deltamood + nee.MoodResponse((int)nt)[nttwo - (100f - potency)], -100, 100);
     }
     break;
    case NeedType.Sex:
     if ((NeedType)i != nt)
     {
      deltamood = Mathf.Clamp(deltamood + mr[nee.Lust], -100, 100);
     }
     else
     {
      deltamood = Mathf.Clamp(deltamood + nee.MoodResponse((int)nt)[nttwo - (100f - potency)], -100, 100);
     }
     break;
    case NeedType.Comfort:
     if ((NeedType)i != nt)
     {
      deltamood = Mathf.Clamp(deltamood + mr[nee.Comfort], -100, 100);
     }
     else
     {
      deltamood = Mathf.Clamp(deltamood + nee.MoodResponse((int)nt)[nttwo - (100f - potency)], -100, 100);
     }
     break;
    case NeedType.Cleanliness:
     if ((NeedType)i != nt)
     {
      deltamood = Mathf.Clamp(deltamood + mr[nee.Cleanliness], -100, 100);
     }
     else
     {
      deltamood = Mathf.Clamp(deltamood + nee.MoodResponse((int)nt)[nttwo - (100f - potency)], -100, 100);
     }
     break;
    case NeedType.Shelter:
     if ((NeedType)i != nt)
     {
      deltamood = Mathf.Clamp(deltamood + mr[nee.Shelter], -100, 100);
     }
     else
     {
      deltamood = Mathf.Clamp(deltamood + nee.MoodResponse((int)nt)[nttwo - (100f - potency)], -100, 100);
     }
     break;
    case NeedType.Intimacy:
     if ((NeedType)i != nt)
     {
      deltamood = Mathf.Clamp(deltamood + mr[nee.Intimacy], -100, 100);
     }
     else
     {
      deltamood = Mathf.Clamp(deltamood + nee.MoodResponse((int)nt)[nttwo - (100f - potency)], -100, 100);
     }
     break;
    case NeedType.Fun:
     if ((NeedType)i != nt)
     {
      deltamood = Mathf.Clamp(deltamood + mr[nee.Fun], -100, 100);
     }
     else
     {
      deltamood = Mathf.Clamp(deltamood + nee.MoodResponse((int)nt)[nttwo - (100f - potency)], -100, 100);
     }
     break;
    case NeedType.Safety:
     if ((NeedType)i != nt)
     {
      deltamood = Mathf.Clamp(deltamood + mr[nee.Safety], -100, 100);
     }
     else
     {
      deltamood = Mathf.Clamp(deltamood + nee.MoodResponse((int)nt)[nttwo - (100f - potency)], -100, 100);
     }
     break;
    case NeedType.Respect:
     if ((NeedType)i != nt)
     {
      deltamood = Mathf.Clamp(deltamood + mr[nee.Respect], -100, 100);
     }
     else
     {
      deltamood = Mathf.Clamp(deltamood + nee.MoodResponse((int)nt)[nttwo - (100f - potency)], -100, 100);
     }
     break;
    case NeedType.MAX:
     break;
    default:
     break;
   }
  }
  //float delta = nee.MoodResponse((int)nt)[nttwo - (100f-potency)];
  float delta = deltamood - CurrentMood;

  //curmood = Mathf.Clamp(curmood + nee.MoodResponse((int)nt)[nttwo - (potency*nttwo)], -100, 100);
  return delta;
 }

Following the lecture notes to Richard Evans's GDC talk about The Sims 3, I am converting the delta value into a probability. Since his equation is a little bit mystical to me still,


I am just normalizing the value of each delta value by dividing it by the total amount of mood change, which gives a percentage between 0 (never going to happen) and 1 (an inevitability, Mr. Anderson).


void EvaluateMood()
 {
  float runningTotal = 0f;
  float needlevel = 0;

  List < NeedProb > needProbabilities = new List < NeedProb > ();
  foreach (GameObject go in adObjects)
  {
   switch (go.GetComponent < advertisingobject > ().MyNeedType)
   {
    case NeedType.Food:
     needlevel = myNeeds.Hunger;
     break;
    case NeedType.Hydration:
     needlevel = myNeeds.Thirst;
     break;
    case NeedType.Rest:
     needlevel = myNeeds.Tiredness;
     break;
    case NeedType.Sex:
     needlevel = myNeeds.Lust;
     break;
    case NeedType.Comfort:
     needlevel = myNeeds.Comfort;
     break;
    case NeedType.Cleanliness:
     needlevel = myNeeds.Cleanliness;
     break;
    case NeedType.Shelter:
     needlevel = myNeeds.Shelter;
     break;
    case NeedType.Intimacy:
     needlevel = myNeeds.Intimacy;
     break;
    case NeedType.Fun:
     needlevel = myNeeds.Fun;
     break;
    case NeedType.Safety:
     needlevel = myNeeds.Safety;
     break;
    case NeedType.Respect:
     needlevel = myNeeds.Respect;
     break;
    case NeedType.MAX:
     break;
    default:
     break;
   }
   needProbabilities.Add(new NeedProb(go.GetComponent < advertisingobject > ().MyNeedType, myMood.SimulateMoodChange(go.GetComponent < advertisingobject >().MyNeedType,
    go.GetComponent < advertisingobject > ().MyMeetNeed.potency)));
   runningTotal += myMood.SimulateMoodChange(go.GetComponent < advertisingobject > ().MyNeedType, 
    go.GetComponent < advertisingobject > ().MyMeetNeed.potency);
  }

  for (int i = 0; i < needProbabilities.Count; i++)
  {
   Debug.Log(needProbabilities[i].nt + " probability is " + needProbabilities[i].probabilty);

  }

  for (int i = 0; i < needProbabilities.Count; i++)
  {
   if (runningTotal > 0)
   {
    needProbabilities[i].probabilty = needProbabilities[i].probabilty / runningTotal;
   }
  }

  for (int i = 0; i < needProbabilities.Count; i++)
  {
   Debug.Log(needProbabilities[i].nt + " normalized probability is " + needProbabilities[i].probabilty);

  }

 }

I've also set up another function that just evaluates the needs, and ran into the following problem. If I want to implement one of the common methods of picking an option, I create a simple array of length 100, and assign a NeedType to each, I need a way of rounding the floating probabilities to integers such that they will add up to 100. For example, if Food is 45.65934% probable, Hydration is 23.763%, etc. I need a way to round those fractional values in such a way that they get assigned to appropriate needs.

Luckily for me, one of America's Founding Fathers, Alexander Hamilton, had already figured out a way to deal with this problem. Basically, you take the integer part of each percentage and sum them. You find the difference between the sum and the target number, then you take integers that had the largest remainders and add 1 to them until there no places available.

How great is it that this method was developed by a man who was the first Secretary of Treasury of the US, and, given his desire for a strong executive branch and military, and support for industrialization, would most likely have had the support of our current POTUS.


int integalPart = 0;
  List < NeedProb > remainder = new List < NeedProb > ();
  int extra = 0;
  for (int i = 0; i < needProbabilities.Count; i++)
  {
   integalPart += (int)(needProbabilities[i].probabilty * 100);
   remainder.Add(new NeedProb(needProbabilities[i].nt,(needProbabilities[i].probabilty * 100)- (int)(needProbabilities[i].probabilty * 100)));
  }

  //for (int i = 0; i < needProbabilities.Count; i++)
  //{
  // Debug.Log(needProbabilities[i].nt + " " + needProbabilities[i].probabilty);
  //}


  for (int i = 0; i < needProbabilities.Count; i++)
  {
   needProbabilities[i].probabilty = (int)(needProbabilities[i].probabilty * 100);
  }

  //needProbabilities.Sort();
  //for (int i = 0; i < needProbabilities.Count; i++)
  //{
  // Debug.Log(needProbabilities[i].nt + " at time " + Time.time + " is " + needProbabilities[i].probabilty);
  //}

  extra = 100 - integalPart;


  remainder.Sort();
  remainder.Reverse();
  for (int i = 0; i < extra; i++)
  {
   needProbabilities[(int)remainder[i].nt].probabilty++;

  }

  int cur = 0;
  int cur2 = 0;
  for (int i = 0; i < needProbabilities.Count; i++)
  {
   cur = cur2;
   for (int j = cur; j < cur + (int)(needProbabilities[i].probabilty); j++)
   {
    prob[j] = needProbabilities[i].nt;
    cur2++;
   }
  }

So anyway, using these probabilities, I can generate a random number from between 0 - 99, and access my probability array, prob, to pick a need to satisfy. The next step is to start moving towards it using my Steering Behaviors.

Cheers,

No comments:

Post a Comment