personality/emotional based NPC decisions

Hey guys,

For my project I needed the NPC (computer players) to make decisions based off their personality, specifically based off five basic emotions, anger, disgust, fear, happiness, and sadness.

I built a simple script “logicHandler.py” which will do all of this, and make random decisions based off the personality of these.

You can of course, use other emotions or categories as you wish, by using a call to setWeight(name, weightValue) as you wish.

Without further ado, here you go:

import random
#- A logicHandler makes emotional decisions, based off the emotional traites of the logicHandler
#- You may assign 'weights' to the logicHandler, for basic emotions such as:
#- Anger, disgust, fear, happiness, and sadness
#- Then the logicHandler can make choices from a dict, that defines choices for each possible emotion
#- For instance {'anger': ['beAngry], 'disgust': ['beDisgusted', 'beDisgustedTwo']} and so forth

def weightedChoice(inputList):
	n = random.uniform(0, 1)
	for possibleChoice, weight in inputList:
		if n < weight:
			break
		n = n - weight
	return possibleChoice


class logicHandler:
	def __init__(self, angerWeight = None, disgustWeight = None, fearWeight = None, happinessWeight = None, sadnessWeight = None):
		self.__weights = {}
		self.setAngerWeight(angerWeight)
		self.setDisgustWeight(disgustWeight)
		self.setFearWeight(fearWeight)
		self.setHappinessWeight(happinessWeight)
		self.setSadnessWeight(sadnessWeight)

	def choice(self, choiceDict):
		if len(choiceDict.keys()) == 0:
			return

		validWeights = []
		for weightName, weight in self.__weights.items():
			if weight:
				validWeights.append((weightName, weight))

		percentWeights = []
		totalWeight = 0
		for weightName, weight in validWeights:
			totalWeight += weight

		for weightName, weight in validWeights:
			percentWeight = weight / totalWeight
			percentWeights.append((weightName, percentWeight))

		
		#- Make weighted choice
		choiceList = []
		for weightName, weight in percentWeights:
			if weightName in choiceDict:
				choiceList.append((choiceDict[weightName], weight))

		picked = weightedChoice(choiceList)
		if len(picked) == 0:
			return
		picked = random.choice(picked)
		return picked

	def __checkWeight(self, weight):
		if weight == None:
			return
		assert weight >= 0.0
		assert weight <= 1.0
		return float(weight)

	''' set/get weight value '''
	def setWeight(self, weight, value):
		self.__weights[weight] = self.__checkWeight(value)

	def getWeight(self, weight):
		return self.__weights[weight]

	def hasWeight(self, weight):
		return weight in self.__weights

	def getWeights(self):
		return self.__weights

	''' set/get anger weight'''
	def setAngerWeight(self, weight):
		self.setWeight('anger', weight)

	def getAngerWeight(self):
		return self.getWeight('anger')


	''' set/get disgust weight'''
	def setDisgustWeight(self, weight):
		self.setWeight('disgust', weight)

	def getDisgustWeight(self):
		return self.getWeight('disgust')


	''' set/get fear weight'''
	def setFearWeight(self, weight):
		self.setWeight('fear', weight)

	def getFearWeight(self):
		return self.getWeight('fear')


	''' set/get happiness weight'''
	def setHappinessWeight(self, weight):
		self.setWeight('happiness', weight)

	def getHappinessWeight(self):
		return self.getWeight('happiness')


	''' set/get sadness weight'''
	def setSadnessWeight(self, weight):
		self.setWeight('sadness', weight)

	def getSadnessWeight(self):
		return self.getWeight('sadness')

Put that in a “logicHandler.py” file, or whatever
And you can use this code like this:

from logicHandler import *
l = logicHandler()
l.setAngerWeight(1.0) #- Very angry
l.setFearWeight(0.5) #- Midly afraid

choiceDict = {
	'anger': ['I am angry!', 'I am angry 2!'],
	'fear': ['I am afraid!', 'I am afraid 2!'],
}

print l.choice(choiceDict)

Thanks I can use this in my project.

I started extending …

Affection
Anger
Angst
Anguish
Annoyance
Anxiety
Apathy
Arousal
Awe
Boldness
Boredom
Contempt
Contentment
Curiosity
Depression
Desire
Despair
Disappointment
Disgust
Dread
Ecstasy
Embarrassment
Envy
Euphoria
Excitement
Fear
Fearlessness
Frustration
Gratitude
Grief
Guilt
Happiness
Hatred
Hope
Horror
Hostility
Hurt
Hysteria
Indifference
Interest
Jealousy
Joy
Loathing
Loneliness
Love
Lust
Misery
Panic
Passion
Pity
Pleasure
Pride
Rage
Regret
Remorse
Sadness
Satisfaction
Shame
Shock
Shyness
Sorrow
Suffering
Surprise
Terror
Wonder
Worry
Zeal
Zest

Hi Zester,

I never anticipated someone would use so many :laughing:
I’ve used your amazing list of emotions, I hope that’s okay.

I know you’ve probably got your own version of the script, so this is just for anyone else.

I’ve added much better support for any emotion, and added a brief explanation of how you could use it for things outside just emotions.

Here is the updated script (with a few bugs fixed that I later found):

import random 
#- A logicHandler makes weighted decisions based off the logicHandlers personal weights
#-
#- A perfect example is making choices based off current emotions, however do note that
#- you could use other keys in place of 'emotions'. Anything, even 'desireToRunAway' could be used.
#-
#- First create an instance of the logicHandler, and (optionally) pass in any
#- predefined keys and weights as a dict.
#-
#- This will use the default list of emotions and weights
#Code: l = logicHandler()
#-
#- This will allow you to define the emotions and weights at creation
#Code: l = logicHandler({'anger': 1.0, 'fear': 0.5, 'desireToRunAway': 1.0})
#-
#- After creation you can change the weight of a key (in this case emotion), using setWeight(key, weight)
#Code: l.setWeight('fear', 1.0)
#Code: l.setWeight('anger', 0.0)
#-
#- Then you may use the logicHandler to make choices, from a list of possible choices for each key (in this case emotion)
#Code: possibleChoices = {'fear': ['screamInFear', 'cryInFear'], 'anger': ['yellAtSomeone', 'breakSomething']}
#Code: print 'The choice he made was', l.choice(possibleChoices)
#-
#- Notice in the possibleChoices dict above, we left out any option for 'desireToRunAway', as such, there is no choice
#- for the 'desireToRunAway', and it simply will never be used (unless we gave it an option in the possibleChoices dict)


def weightedChoice(inputList):
	if len(inputList) == 0:
		return
	n = random.uniform(0, 1)
	for possibleChoice, weight in inputList:
		if n < weight:
			break
		n = n - weight
	return possibleChoice

class logicHandler:
	defaultWeights = {
		'affection': 1.0,
		'anger': 1.0,
		'angst': 1.0,
		'anguish': 1.0,
		'annoyance': 1.0,
		'anxiety': 1.0,
		'apathy': 1.0,
		'arousal': 1.0,
		'awe': 1.0,
		'boldness': 1.0,
		'boredom': 1.0,
		'contempt': 1.0,
		'contentment': 1.0,
		'curiosity': 1.0,
		'depression': 1.0,
		'desire': 1.0,
		'despair': 1.0,
		'dissapointment': 1.0,
		'disgust': 1.0,
		'dread': 1.0,
		'ecstasy': 1.0,
		'embarrassment': 1.0,
		'envy': 1.0,
		'euphoria': 1.0,
		'excitement': 1.0,
		'fear': 1.0,
		'fearlessness': 1.0,
		'frustration': 1.0,
		'gratitude': 1.0,
		'grief': 1.0,
		'guilt': 1.0,
		'happiness': 1.0,
		'hatred': 1.0,
		'hope': 1.0,
		'horror': 1.0,
		'hostility': 1.0,
		'hurt': 1.0,
		'hysteria': 1.0,
		'indifference': 1.0,
		'interest': 1.0,
		'jealousy': 1.0,
		'joy': 1.0,
		'loathing': 1.0,
		'loneliness': 1.0,
		'love': 1.0,
		'lust': 1.0,
		'misery': 1.0,
		'panic': 1.0,
		'passion': 1.0,
		'pity': 1.0,
		'pleasure': 1.0,
		'pride': 1.0,
		'rage': 1.0,
		'regret': 1.0,
		'remorse': 1.0,
		'sadness': 1.0,
		'satisfaction': 1.0,
		'shame': 1.0,
		'shock': 1.0,
		'shyness': 1.0,
		'sorrow': 1.0,
		'suffering': 1.0,
		'suprise': 1.0,
		'terror': 1.0,
		'wonder': 1.0,
		'worry': 1.0,
		'zeal': 1.0,
		'zest': 1.0,
	}
	def __init__(self, emotionWeights = None):
		if emotionWeights == None:
			emotionWeights = self.defaultWeights
		self.__weights = emotionWeights

	def choice(self, choiceDict):
		if len(choiceDict.keys()) == 0:
			return

		validWeights = []
		for weightName, weight in self.__weights.items():
			if weight:
				if weightName in choiceDict:
					validWeights.append((weightName, weight))

		percentWeights = []
		totalWeight = 0
		for weightName, weight in validWeights:
			totalWeight += weight

		for weightName, weight in validWeights:
			percentWeight = weight / totalWeight
			percentWeights.append((weightName, percentWeight))

		#- Make weighted choice
		choiceList = []
		for weightName, weight in percentWeights:
			if weightName in choiceDict:
				choiceList.append((choiceDict[weightName], weight))

		if len(choiceList) == 0:
			return

		picked = weightedChoice(choiceList)
		if len(picked) == 0:
			return
		picked = random.choice(picked)
		return picked

	def __validateWeight(self, weight):
		if weight == None:
			return
		assert weight >= 0.0
		assert weight <= 1.0
		return float(weight)

	def setWeight(self, weight, value):
		assert self.hasWeight(weight)
		self.__weights[weight] = self.__validateWeight(value)

	def getWeight(self, weight):
		assert self.hasWeight(weight)
		return self.__weights[weight]

	def hasWeight(self, weight):
		return weight in self.__weights

	def getWeights(self):
		return self.__weights


if __name__ == '__main__':
	l = logicHandler()
	l.setWeight('anger', 1.0) #- Very angry
	l.setWeight('fear', 0.5) #- Mildly afraid

	choiceDict = {
		'anger': ['I am angry!', 'I am angry 2!'],
		'fear': ['I am afraid!', 'I am afraid 2!'],
	}

	print 'choice 1', l.choice(choiceDict)
	print 'choice 2', l.choice(choiceDict)
	print 'choice 3', l.choice(choiceDict)

Well…I got the list of emotions from wikipedia :slight_smile: and I had only added a couple. Before I got side tracked by something else hence why I didn’t post what modifications I made. Good thing to because you did a much better job than I would have.

But I am glad to see that you added in those extra emotions, thats kinda why I posted them I was hoping you would take your code to the next level.

Is see potential in your code tied with PandaAi and maybe instead of creating Hero’s or Serial Killers there just made randomly. Maybe you bump into the wrong character on the street, and that send him tumbling emotionally down a deep dark path that will come back to haunt you at a latter time.

Great Work :slight_smile: