Implementing flexible logic?

Blitz3D Forums/Blitz3D Programming/Implementing flexible logic?

hockings(Posted 2009) [#1]
All,
I'm after some suggestions as to how you'd tackle the following problem. I've simplified my idea and written code in pseudocode to hopefully make my problem easier to understand.

Lets say you have 2 random choices in your game logic.

eg.
50% chance enemy attacks
50% chance enemy waits

Coding that is easy.

Choice = random(100). 
If Choice < 50 then attacks
else if choice > 50 then waits.



But then you want to add some dynamic modifiers. eg. If game has gone on for too long then you want to increase the chance of attack.

You could code this like
Choice = random(100)
if game time = too long then
     If Choice < 80 then attacks
     else if choice > 20 then waits.
else
     If Choice < 50 then attacks
     else if choice > 50 then waits.
end if


but if you start having lots of modifiers, coding the whole tree would get difficult. What I'm thinking will hopefully be an easier solution, but I'm not sure how to code it nicely. What I'm thinking is

Chance_of_attack=50
Chance_of_wait=50

if gametime=too long then Chance_Of_Attack += 30

Choice = rand (Chance_of_Attack + Chance_of_wait)
if Choice < Chance of Attack then attack
else
Wait
[code]

Easy enough, but if you then want to add more modifiers based on conditions (eg. chance_of_randomly_exploding, chance_of_turning_blue) you'll end up with
[code]
Choice = rand (Chance_of_Attack + Chance_of_wait + ...)
...
if Choice > (Chance of Attack + chance_of_wait + chance_of_blue) &&  Choice < (Chance of Attack + chance_of_wait + chance_of_blue + Chance_of_randomly_exploding) then ...


which will get VERY messy! Any ideas how to make this cleaner? Sorry if my explaination is confusing!!!


Mahan(Posted 2009) [#2]
I'll do some pseudo code myself:


;send param with percent chance for something to be true
function percentChance(pc%)
  if random(100) <= pc% then return true
  return false
end function


;in another part of the code:
Const ATTACK_BASE_CHANCE = 50

if percentChance(ATTACK_BASE_CHANCE + chanceFromTimePased() + chanceFromAbilities())
  Attack()
else
  Wait()
endif




Now you just write all the conditions into their respective functions chanceFromTimePased() and chanceFromAbilities().


Example the function chanceFromTimePased() should probably return 0 (zero) if no time has passed, and then maybe add another percent chance for each 10 seconds (or so depending on your game). For it to do this you'll have to save the start time in a global variable or something like that and calculate against the present time.

Likewize the chanceFromAbilities() can have a base 0 (zero) percent chance, but if your enemy, as an example, has significantly higher firepower than you, the chance might get higher that he attacks, etc.

Dunno if this helps but otherwise just ask some more =)

edit: fixed bugged pseudocode :)


hockings(Posted 2009) [#3]
Mahan, thanks for the reply. The problem with that that I can see is that in my game there are probably about 10 factors that will determine the actions (it's not a space invaders type game, I'm using that as it's easier to explain :D

If I add all those into a percentChance function there would be two potential problem
1) I'll be doing lots of maths and "random" (function) calls for multiple enemies which would end up very inefficient
ie
for each of up to 100 enemies
  if percentChance(ATTACK_BASE_CHANCE + chanceFromTimePased() + chanceFromAbilities())
    Attack()
  else
    Wait()
  endif
next


and there's also multiple actions (probably about 20) the enemies could take so I'd end up with more like the following which gets even more inefficient (when called for 100 enemies * 60 times a second)


for each of up to 100 enemies
  if percentChance(ATTACK_BASE_CHANCE + chanceFromTimePased() + chanceFromAbilities())
    Attack()
  else if percentChance(FLY_IN_A_CIRCLE + Other_Modifiers + chanceFromAbilities())
    FlyInACircle()
  else if percentChance(FLY_TO_THE_RIGHT + Other_Modifiers + chanceFromAbilities())
    FlyToTheRight()
  
  ...
  
  else
    Wait()
  endif
next


I've also got the suspicion (though I'm not 100% sure on this, I'll have to think about it some more) that if I did the code above you'd end up with the wrong probabilities.

ie. If you had 4 options and you wanted them to all have an equal chance, the following code would give them each a 25% chance
R=rand(100)
if R<=25 then option1
if R>25 && R<=50 then option2
if R>50 && R<75 then option3
else option 4


if I used multiple percentChance functions to do the same thing I think you'd end up with a 25% * 25% * 25% chance of the third option happening which is more like 1 in 200 or something. I might be totally wrong though, I'm seriously sleep deprived and maths isn't my strong suit! :D


Ross C(Posted 2009) [#4]
To be honest, the amount of code your talking about is nothing, speed wise. If you can't find a simplier/more efficient way of writing it out, do it the long way. Theirs no shame in that, and it gets the job done :o) You can sometimes spent way too long trying to make code neater and more efficient.

Personally, I would use an assessment variable. The situation is taken into consideration.

How much health does the player have?
How much health does the enemy have? What is the chance of the enemy winning?

Based your entire process on the situation. It would be helpful to know what the type of game is though, as it may require a different approach.


Mahan(Posted 2009) [#5]
I agree with Ross C here completely. Always follow this pattern when writing a program:

1) Write the program as clear and easy understandable as possible
2) Check performance
3) Localize bottleneck (through logging, profiling)
4) (if plausible:) Optimize bottlenecks

Never write the program optimized from an early assumption about what will be slow in the program. You'll end up with messy code.

Anecdote: I once came across a function that was ~2000 lines long, where i had to find a bug. When I asked other devs why this function wasn't splitted into several shorter functions I got the answer that it was because it was "optimized" and it was to cut the overhead from function-calls. After I split this function into ~20 smaller functions and I optimized the real bottleneck (optimized the database querys by making more specialized indexes and fixed so the function didn't ask db multiple times for the same info) the function was both more readable and 100-800% faster than the previous "optimized" version.

^.^ This is why one should write good code first and optimize just if needed afterwards, because then it will be easier to see what is taking the time.

Lastly in the optimization area I'd like to add that finding a good algorithm is often much more important, and can give extreme effectivity benefits compared to general optimization (like loop unrolling, common subexpression packing, function inlining etc).


hockings(Posted 2009) [#6]
Thanks for the replies guys.
To put it in a bit more perspective, the game I'm trying to write is similar to "The Sims" - at each check the NPCs will have to work out what they want to do next based on their needs and what is around them. Based on whether the player has put certain things into the environment (eg. they'll be more likely to play if there's lots of games in the room, but the room just as easily might only have a bed in it) the logic's going to get rather complicated.
I'm not TOO concerned about the efficiency of the code, but this piece of logic is the crux of the game and will be called frequently for lots of NPCs, so getting it reasonably correct (at least the structure) first time is important. I don't fancy writing what will end up both a huge chunk of code and the focus of the game, multiple times if I've taken the wrong approach.
That being said, I do code like Mahan suggested - I typically compartmentalise everything into small discrete functions for readability (ie. later debugging :D ) purposes and haven't found a sensible reason yet to have huge functions in my code.

The end result I'm going to go for is to use state based logic. Give the NPC a state (eg. hungry) then do an if-then (or select) tree based on what may be in their local environment. The if-then tree will get rather big as there are a lot of things that could potentially be in their environment, but I don't see any way around that.

Thanks all!


Warner(Posted 2009) [#7]
You could maybe give each character parameters that indicate their mood, in this case something like 'agression'. The agression field would then be a percentage in the range 0..100, that can be fed into the attack routine.



Axel Wheeler(Posted 2009) [#8]
As Warner says. Here are my suggestions:

Separate the various parameters into two layers; an influences layer and a behaviors layer. The behaviors are things like speed, etc. Influences are things like agression, hunger, other characters behaviors, etc.

Each influence can affect multiple behaviors and Each behavior can be affected by multiple influences.

All the scalar behaviors and scalar influences should be expressed the same way, either as percents or coefficients (0.0 - 1.0) which I prefer.

(WAIT: See last paragraph first...)

Since 1.0 is the maximum, select an average or normal value for each influencing parameter (make it always .5 for simplicity's sake).

Now it gets a bit weird, but it addresses your .25 * .25 problem.

If the influencing parameter is below normal (which is .5) then we are decreasing the behavior paramenter. Simply double the influencing parameter and multiply.

Thus, a .40 energy level influencing a .64 speed would result in a speed of .512, a slight reduction.

On the other hand, if the influencing parameter is more than .5, it is increasing the behavior parameter. You can't just double and multiply because that could put the new value over the top (1.0) and cause a bug.

Instead, first flip both the influencing and behavior parameters (i.e. 1.0 - parameter) and do the same as above. Then flip the result.

So, if a .70 energy level is influencing a .64 speed:
- Flip .70 to .30 (1.0 - .70)
- Double it to .60
- Flip .64 to .36 (1.0 - .64)
- Multiply .60 * .36 = .216
- Flip .216 to .784 (1.0 - .216)

Thus, a somewhat higher than average energy level (.70) turned a .64 speed to a .784 speed.

The advantages are the great flexibility of being to take any number of factors into consideration, and the fact that the result will never go outside the range 0.0 - 1.0. And the fact that you only need to do all this when a change happens so it shouldn't be much of a performance hit.

I used this system for determining the influence of things like altitude and temperature on the color of a terrain pixel, but I could see that it would work for behaviors as well.

I just realized another way would be to have an average of 1.0 with no maximum. This way you would just multiply as needed. At the moment it sounds to me like a far simpler system than the one I described above as long as you aren't bound by a fixed maximum as I was in my terrain program.

Anyway, good luck.


hockings(Posted 2009) [#9]
Axel Wheeler
That might end up a good solution for influencing behaviours that the NPC is currently undertaking.

I'm not sure how I'd work that into the choice of what activity the NPC chooses to undertake but I'll think about it and see if I can see a way for it to work. Unless I'm misunderstanding what you're suggesting of course!


Warner
I like the split out of the functions. Thanks.


RifRaf(Posted 2009) [#10]
sorry i didnt read all of the above.. but couldnt every entity+item in this game have its own effect_behavior list.

For example a bed has a rest_Effect on a player by x%, you could add that to a fatigue of the player and determine sleep.

Same goes if you can pre determine all player states then every item can have a state change variable with its own conditions of effect.

Maybe thats not any less complicated, but one other way to go.


hockings(Posted 2009) [#11]
RifRaf - Thanks for the reply, but the two issues I can see with that solution :-

1) If you had 100 items/affecting factors in the game for example, an effect_behaviour list with every option combined with every other option would be a VERY long and unmanageable piece of code. Easily implemented when there's only a handful of options possible, but unfortunately there's a lot more with what I'm thinking of doing.

2) If you work things as percentages, with a lot of external factors acting on the code's decision, you can easily hit a point where the chances of something happening are well over 100% and this doesn't allow for any randomness in the game.

Yes as a rule if a player's character is eg. 100% tired the game should make them sleep, but they could be 100% tired and no bed may be available for example which then breaks the conditional logic. That's one of the ideas I've been trying to (badly) get across, when there's lots of contributing factors I think you really need a logic system that allows for the range to fluctuate from the usual 0-100% range.