Fuzzy Inference System

Started by
10 comments, last by IADaveMark 11 years, 7 months ago
Hi, I have a piece of coursework set to make an implementation of a fuzzy inference system in C++ to control a "car" that moves in the direction towards a "line" along one axis.

I feel I have a fairly good grasp of the whole purpose and reason for fuzzy logic, particularly for game AI, but I get a bit confused when it comes down to actually implementing it.

Firstly, here is a screenshot of my fuzzy inference system in matlab:
screen.png

As you can see, I have 2 inputs (each with 2 membership functions), 2 rules and 1 output.
I understand the inputs well, but I don't quite see how the output values properly tie in via the rules to give a sensible output. My system should assess the current motion of the car and the distance between the car and the line to output a new car motion (change along the single axis in units per frame).

I don't think my output is doing anything useful, but I don't understand what an output actually 'does' based on the rules. Considering rules, what I thought I should be looking for was a way to assess greater or less than, but the only valid options are equal to or not equal to an input's membership function.

My application follows this structure:

//define membership functions for inputs (sigmf curves for rate of change of car and distance from line target)

//called every frame, 30 frames per second
update(){
//evaluate input membership function results
//evaluate rules based on input results
//set new rate of change of car based on output membership function
}


The part I don't understand is how an output membership function has any application towards evaluating a rate of change.
My output has 2 membership functions, and my rules should dictate which one is applied. But what are the inputs to my output membership functions? I can't seem to find a good explanation online or in my course notes, I am probably very close to getting this to work nicely but my misunderstanding is probably making it seem more complicated than it should be.

Here is the full source (it isn't pluggable yet, while I test it):
[source lang=cpp]
#include <iostream>
#include <math.h>

//for keyboard input
#include "conio.h"
//for Sleep()
#include "Windows.h"

using namespace std;

//global for Euler's/Napier's constant
double e = 2.718281828459045235360287471352;

//sigmodally shaped membership function
double sigmf(double x, double a, double c){
return 1 / (1 + pow(e,-a * (x - c)));
}

//class to encapsulate line movement and basic kinematics
class Line{
float x;
float xMax;
float xSpeed;
float xSpeedMax;
float friction;
float accel;

public:
Line(float sX, float sXMax, float sFriction, float sXSpeedMax, float sAccel){
x = sX;
xMax = sXMax;
friction = sFriction;
xSpeedMax = sXSpeedMax;
accel = sAccel;

xSpeed = 0;
}

void update(){
//if a key has been pressed
if(_kbhit() != 0){
switch(_getch()){
// A or a
case 97:
case 65:
xSpeed -= accel; //apply motion down the axis
if(xSpeed < -xSpeedMax) xSpeed = -xSpeedMax; //cap the speed
break;

// D or d
case 100:
case 68:
xSpeed += accel; //apply motion up the axis
if(xSpeed > xSpeedMax) xSpeed = xSpeedMax; //cap the speed
break;
}
}

if(xSpeed > 0){ //if moving
xSpeed -= friction; //apply friction
if(xSpeed > 0){ //if still moving
x += xSpeed; //move
if(x > xMax){ //don't go out of bounds
x = xMax;
xSpeed = 0;
}
} else {
xSpeed = 0; //don't let friction make it move the other way
return;
}
} else if(xSpeed < 0){ //if moving
xSpeed += friction; //apply friction
if(xSpeed < 0){ //if still moving
x += xSpeed; //move
if(x < 0){ //don't go out of bounds
x = 0;
xSpeed = 0;
}
} else {
xSpeed = 0; //don't let friction make it move the other way
return;
}
}
}

void draw(){
int i;

//pad with spaces until max x marker co-ord
for(i = 0;i < xMax;i ++){
cout << " ";
}
cout << "V\n"; //draw the max x co-ord marker

//pad with spaces until x co-ord
for(i = 0;i < x;i ++){
cout << " ";
}
cout << "|\n"; //draw the line
}

float getX(){
return x;
}

float getXMax(){
return xMax;
}
};

//semi-pluggable fuzzy inference system class
class FuzzySystem{
double distRange, currentSteerRange;

//distance input evaluation
double distInput(double dist){
double sigmfResult = sigmf(dist,-0.19,-28.23) * distRange;
if(dist > 0) return sigmfResult;
return -sigmfResult;
}
double distInput(float dist){
return distInput(static_cast<double>(dist));
}

//rate of change input evaluation
double currentSteerInput(double currentSteer){
double sigmfResult = sigmf(currentSteer,-1.02,-5.228) * currentSteerRange;
if(currentSteer > 0) return sigmfResult;
return -sigmfResult;
}
double currentSteerInput(float dist){
return currentSteerInput(static_cast<double>(dist));
}

//calculates the output from the system based on input results
float output(double oDistResult, double oCurrentSteerResult){
//if dist and current steer are "right" (rule)
if(oDistResult < 0){
//find a new output
double newSteer = sigmf(oDistResult,1.02,5.228)*10;
//cout << "newSteer: " << newSteer << endl;
printf("'right'\nnewSteer: %.4f\n",newSteer);
return static_cast<float>(newSteer);
//if dist and current steer are "left" (rule)
} else if(oDistResult > 0){
//find a new output
double newSteer = sigmf(oDistResult,1.02,5.228)*10;
//cout << "newSteer: " << newSteer << endl;
printf("'left'\nnewSteer: %.4f\n",newSteer);
return static_cast<float>(-newSteer);
} else return 0.0f; //no speed, close enough to the line
}

public:
//constructor, sets up the range for inputs
FuzzySystem(double sDistRange = 60, double sCurrentSteerRange = 10){
distRange = sDistRange;
currentSteerRange = sCurrentSteerRange;
}

//returns a new speed for the car based on the outcome of fuzzy logic
float resolve(double dist, double currentSteer){
cout << dist << endl;
cout << currentSteer << "\n-------------------\n";
double distResult = distInput(dist);
double currentSteerResult = currentSteerInput(currentSteer);
cout << "distResult: " << distResult << endl <<
"currentSteerResult: " << currentSteerResult << "\n-------------------\n";

float out = output(distResult, currentSteerResult);
return out;
}
float resolve(float dist, float currentSteer){
return resolve(static_cast<double>(dist),static_cast<double>(currentSteer));
}
};

//class to encapsulate the car and it's associated AI
class CarAI{
Line* line; //maintain a pointer to the line
FuzzySystem* fuzzy;
float x, xLast, xSpeed;

//returns the distance to the line
float distToLine(){
return x - line->getX();
}

//returns the rate of change of the car's x co-ordinate
float xChange(){
return x - xLast;
}
public:
CarAI(Line* sLine){
fuzzy = new FuzzySystem;
line = sLine;
x = line->getX() + 2.327f;
xSpeed = 0;
}

//called each frame to assess car AI and apply motion
void update(){
//have the fuzzy inference system control the x speed
xSpeed = fuzzy->resolve(distToLine(),xChange());
//cap the speed
if(xSpeed < -10) xSpeed = -10;
if(xSpeed > 10) xSpeed = 10;
//remember previous x co-ord
xLast = x;
//apply motion
x += xSpeed;
//clamp into range
if(x < 0) x = 0;
if(x > line->getXMax()) x = line->getXMax();
}

void draw(){
int i;

//pad with spaces until x co-ord
for(i = 0;i < x;i ++){
cout << " ";
}
cout << "#\n"; //draw the car
}
};

int main(){
//introduction message
int dots = 1;
//loop while there is no keyboard input
while(_kbhit() == 0){
if(dots == 1){
//introduction message
cout << "AI For Games Development - coursework\n";
cout << "0801109 - Philip Robinson\n";
cout << "Racing Line FIS test application\n\n";

cout << "A and D allow the racing line (|) to be moved left and right.\n";
cout << "V marks the maximum x co-ordinate of the racing line and the "
"car.\n";
cout << "The car is denoted by a # character.\n\n";

cout << "Press any key to continue";
}
cout << ".";
dots ++;

Sleep(600);
if(dots > 3){
dots = 1;
//clear the screen
system("cls");
}
}

//make the Line object
Line line(11,60,0.16f,3.0f,0.32f);
//make the CarAI object
CarAI car(&line);

//is the update loop to keep running or not
bool run = true;
//sleep time in ms each frame
int sleepTime = 30;
//frame counter
long frame = 0;

//enter the main loop
while(run){
//clear the screen
system("cls");

//increment the frame counter
frame ++;

//update the racing line
line.update();
//update the car
car.update();

//show the frame count
cout << "frame: " << frame << endl;
cout << "line x: " << line.getX() << endl;

//draw the line
line.draw();
//draw the car
car.draw();

//limit the frame rate
Sleep(sleepTime);
}

//clear the screen
system("cls");
//write closing message
cout << "closing app\n";
//sleep
Sleep(1000);
//end the process
return 0;
}

[/source]

Can anybody direct me to further reading on the topic, or clarify how it all goes together for me? I understand that this post is a bit ambiguous, but that only reflects my own confusion on this topic; feel free to probe me for anything else you need to know to help me. I want to get this completed soon.

Thanks in advance for any help.
Advertisement
First of all, you don't need greater than or less than. Fuzzy logic works this way. Let's say I have a rule that says:
If (A) then (B)

This means that if the truth value of A is 1, then B will be 1. If the truth value of A is 0.5, though, then B will be 0.5.

The AND operator takes the smallest of the two parameters, and the OR operator takes the largest.
So, say A=0.75 and B=0.25.

If (A and B) then C
If (A or B) then D

As a result of these rules, C == 0.25 and D == 0.75.

NOT(a) simply ( 1 - truth(a) )
Given that, and given that I'm understanding what the variables in your problem correspond to (your car is supposed to follow a line, right?), I don't think your rules make sense.

Your rules are saying something like:
"If I am to the right of the line and I'm steering right, then steer right"
"If I am to the left of the line and I'm steering left, then steer left"

To analyze your graph you need to understand what each axis is saying. Take the corner closest to the viewer. That corner is where distance is minimum, meaning you are all the way to the right of the line. It is also where currentSteer is minimum, meaning you are already steering all the way to the right. The output value steer at that corner is minimum, meaning you are saying that in that situation you should steer the car hard to the right. Does that make sense logically? You should analyze other points on the graph to see if they make sense (they don't).

First of all, assuming the output variable steer is the rate of change in the angle of your steering (rather than the value angle itself)

if (distance is right) and NOT(currentSteer is left) then (steer is left)
if (distance is left) and NOT(currentSteer is right) then (steer is right)



These rules say basically:
"If I am to the right of the line and I'm not steering left, then steer left... steer more if I'm farther to the right or if I'm not steering very much to the left"
"if I am to the left of the line and I'm not steering right, then steer right... steer more if I'm farther to the left or if I'm not steering very much to the right"

Try that out, and if that gives you a graph that seems to make sense at each point, then you're good.
Sigh.

I wish I understood the fascination with fuzzy logic when a simple set of response curves can do wonders. Adding in the fuzzy sets is a whole unnecessary layer.

Dave Mark - President and Lead Designer of Intrinsic Algorithm LLC
Professional consultant on game AI, mathematical modeling, simulation modeling
Co-founder and 10 year advisor of the GDC AI Summit
Author of the book, Behavioral Mathematics for Game AI
Blogs I write:
IA News - What's happening at IA | IA on AI - AI news and notes | Post-Play'em - Observations on AI of games I play

"Reducing the world to mathematical equations!"


Sigh.

I wish I understood the fascination with fuzzy logic when a simple set of response curves can do wonders. Adding in the fuzzy sets is a whole unnecessary layer.


I agree. Especially after wrestling with this, trying to get it to work. FIS could be useful on large projects however, where different people are providing rules to be used within somebody else's application and the level of control is required, but it doesn't really help if the programmed FIS layer isn't pluggable - which mine isn't. It seems a bit too "catch-all" to be effective.
I have something that works now:
[source lang="cpp"]
#include <iostream>
#include <math.h>

//for keyboard input
#include "conio.h"
//for Sleep()
#include "Windows.h"

using namespace std;

//global for Euler's/Napier's constant
double e = 2.718281828459045235360287471352;

//sigmodally shaped membership function
double sigmf(double x, double a, double c){
return 1 / (1 + pow(e,-a * (x - c)));
}

//class to encapsulate line movement and basic kinematics
class Line{
float x;
float xMax;
float xSpeed;
float xSpeedMax;
float friction;
float accel;

public:
Line(float sX, float sXMax, float sFriction, float sXSpeedMax, float sAccel){
x = sX;
xMax = sXMax;
friction = sFriction;
xSpeedMax = sXSpeedMax;
accel = sAccel;

xSpeed = 0;
}

void update(){
//if a key has been pressed
if(_kbhit() != 0){
switch(_getch()){
// A or a
case 97:
case 65:
xSpeed -= accel; //apply motion down the axis
if(xSpeed < -xSpeedMax) xSpeed = -xSpeedMax; //cap the speed
break;

// D or d
case 100:
case 68:
xSpeed += accel; //apply motion up the axis
if(xSpeed > xSpeedMax) xSpeed = xSpeedMax; //cap the speed
break;
}
}

if(xSpeed > 0){ //if moving
xSpeed -= friction; //apply friction
if(xSpeed > 0){ //if still moving
x += xSpeed; //move
if(x > xMax){ //don't go out of bounds
x = xMax;
xSpeed = 0;
}
} else {
xSpeed = 0; //don't let friction make it move the other way
return;
}
} else if(xSpeed < 0){ //if moving
xSpeed += friction; //apply friction
if(xSpeed < 0){ //if still moving
x += xSpeed; //move
if(x < 0){ //don't go out of bounds
x = 0;
xSpeed = 0;
}
} else {
xSpeed = 0; //don't let friction make it move the other way
return;
}
}
}

void draw(){
int i;

//pad with spaces until max x marker co-ord
for(i = 0;i < xMax;i ++){
cout << " ";
}
cout << "V\n"; //draw the max x co-ord marker

//pad with spaces until x co-ord
for(i = 0;i < x;i ++){
cout << " ";
}
cout << "|\n"; //draw the line
}

float getX(){
return x;
}

float getXMax(){
return xMax;
}
};

//fuzzy inference system class
class FuzzySystem{
//ranges of membership functions
double distRange, currentSteerRange, outRange,

//results of membership functions
distMfLeft, distMfRight,
steerMfLeft, steerMfRight;

//distance input membership functions
double distInputMf_Left(double dist){
double sigmfResult = sigmf(dist,-0.19,-18.23);
return sigmfResult;
}
double distInputMf_Right(double dist){
double sigmfResult = sigmf(dist,0.19,18.23);
return sigmfResult;
}

//rate of change input membership functions
double currentSteerInputMf_Left(double currentSteer){
double sigmfResult = sigmf(currentSteer,1.02,5.228);
return sigmfResult;
}
double currentSteerInputMf_Right(double currentSteer){
double sigmfResult = sigmf(currentSteer,-1.02,-5.228);
return sigmfResult;
}

//rate of change output membership functions
double steerOutputMf(double steerOutput){
return steerOutput * 10;
double sigmfResult = sigmf(steerOutput,-0.1,0.028);
return sigmfResult;
}
//calculates the results of input membership functions
void computeInputs(double dist, double steer){
//evaluate distance input membership functions
distMfLeft = distInputMf_Left(dist);
printf("distMf_Left value: %.4f\n",distMfLeft);
distMfRight = distInputMf_Right(dist);
printf("distMf_Right value: %.4f\n",distMfRight);

//evaluate currentSteer input membership functions
steerMfLeft = currentSteerInputMf_Left(steer);
printf("steerMfLeft value: %.4f\n",steerMfLeft);
steerMfRight = currentSteerInputMf_Right(steer);
printf("steerMfRight value: %.4f\n",steerMfRight);
}

//calculates the output from the system based on input results
float output(){
/*
//if dist and current steer are "right" (rule)
if(oDistResult < 0.01){
//find a new output
double newSteer = sigmf(oDistResult,1.02,5.228);
printf("rule: 'right'\nFIS output (newSteer): %.4f\n", newSteer);
return static_cast<float>(newSteer);
//if dist and current steer are "left" (rule)
} else if(oDistResult > 0.01){
//find a new output
double newSteer = sigmf(oDistResult,1.02,5.228);
printf("rule: 'left'\nFIS output (newSteer): %.4f\n", newSteer);
return static_cast<float>(-newSteer);
} else {
printf("rule: no rule applies\n\n");
return 0.0f; //no speed, close enough to the line
}
*/
//rules
/*
//rule 1
//distance is right and currentSteer is right
//take the largest
double rule1Result;
rule1Result = (distMfRight > steerMfRight)? distMfRight: steerMfRight;
printf("rule 1 outcome: %.4f\n",rule1Result);

//rule 2
//distance is left and currentSteer is left
//take the largest
double rule2Result;
rule2Result = (distMfLeft > steerMfLeft)? distMfLeft: steerMfLeft;
printf("rule 2 outcome: %.4f\n",rule2Result);

double steerResult;
//which rule was more prominent
if(rule1Result > rule2Result){
steerResult = rule1Result*10;
} else {
steerResult = -rule2Result*10;
}
*/
//printf("new speed: %.4f\n",steerResult);

//return steerResult;
return 0;
}

public:
//constructor, sets up the range for inputs and outputs
FuzzySystem(double sDistRange = 60, double sCurrentSteerRange = 10,
double sOutRange = 10){
outRange = sOutRange;
distRange = sDistRange;
currentSteerRange = sCurrentSteerRange;
}

//returns a new speed for the car based on the outcome of fuzzy logic
float resolve(double dist, double currentSteer){
computeInputs(dist, currentSteer);

//holds the input results based on the applied rules
//double inputResultDistance, inputResultCurrentSteer;

/*
if (distance is right) and NOT(currentSteer is left) then (steer is left)

if (distance is left) and NOT(currentSteer is right) then (steer is right)
*/

double steerLeft, steerRight;
//if (distance is right) and NOT(currentSteer is left) then (steer is left)
steerLeft = (distMfRight > 1-steerMfLeft)? steerMfLeft: distMfRight;

//if (distance is left) and NOT(currentSteer is right) then (steer is right)
steerRight = (distMfLeft > 1-steerMfRight)? steerMfRight: distMfLeft;

printf("steerLeft: %.4f\n" "steerRight: %.4f\n", steerLeft, steerRight);

printf("steerOutputMf(steerLeft): %.4f\n",steerOutputMf(steerLeft));
printf("steerOutputMf(steerRight): %.4f\n",steerOutputMf(steerRight));

//output value
double out;
//which member function has the greatest magnitude
if(fabs(steerLeft) > fabs(steerRight)){
out = -steerOutputMf(steerLeft);
} else {
out = steerOutputMf(steerRight);
}

printf("out: %.4f\n",out);

//float steerOut =




// float out = output(dist, currentSteer);
return out;
//return 0;
}
float resolve(float dist, float currentSteer){
return resolve(static_cast<double>(dist),static_cast<double>(currentSteer));
}
};

//class to encapsulate the car and it's associated AI
class CarAI{
Line* line; //maintain a pointer to the line
FuzzySystem* fuzzy; //pointer to a fuzzy inference system
float x, xLast, xSpeed;

//returns the distance to the line
float distToLine(){
return x - line->getX();
}

//returns the rate of change of the car's x co-ordinate
float xChange(){
return x - xLast;
}
public:
CarAI(Line* sLine){
fuzzy = new FuzzySystem;
line = sLine;
x = 30.0f;
xSpeed = 0;
}

//called each frame to assess car AI and apply motion
void update(){
//have the fuzzy inference system control the x speed
xSpeed = fuzzy->resolve(distToLine(),xChange());
//cap the speed
/*
if(xSpeed < -10) xSpeed = -10;
if(xSpeed > 10) xSpeed = 10;
*/
//remember previous x co-ord
xLast = x;
//apply motion
x += xSpeed;
//clamp into range
if(x < 0) x = 0;
if(x > line->getXMax()) x = line->getXMax();
}

void draw(){
int i;

//pad with spaces until x co-ord
for(i = 0;i < x;i ++){
cout << " ";
}
cout << "#\n"; //draw the car
}

float getX(){
return x;
}
};

int main(){
//introduction message
int dots = 1;
//loop while there is no keyboard input
while(_kbhit() == 0){
if(dots == 1){
//introduction message
cout << "AI For Games Development - coursework\n";
cout << "0801109 - Philip Robinson\n";
cout << "Racing Line FIS test application\n\n";

cout << "A and D allow the racing line (|) to be moved left and right.\n";
cout << "V marks the maximum x co-ordinate of the racing line and the "
"car.\n";
cout << "The car is denoted by a # character.\n\n";

cout << "Press any key to continue";
}
cout << ".";
dots ++;

Sleep(600);
if(dots > 3){
dots = 1;
//clear the screen
system("cls");
}
}

//make the Line object
Line line(11,60,0.16f,3.0f,0.32f);
//make the CarAI object
CarAI car(&line);

//is the update loop to keep running or not
bool run = true;
//sleep time in ms each frame
int sleepTime = 30;
//frame counter
long frame = 0;

//enter the main loop
while(run){
//clear the screen
system("cls");

//increment the frame counter
frame ++;

//update the racing line
line.update();
//update the car
car.update();

//show the frame count
cout << "frame: " << frame << endl;
cout << "line x: " << line.getX() << endl;
cout << "car x: " << car.getX() << endl;

//draw the line
line.draw();
//draw the car
car.draw();

//limit the frame rate
Sleep(sleepTime);
}

//clear the screen
system("cls");
//write closing message
cout << "closing app\n";
//sleep
Sleep(1000);
//end the process
return 0;
}

[/source]

Only problem is it jitters back and forth when it gets very close to the line, but I can edit my membership functions to fix that.

This part confused me:
[source lang="cpp"]
//if (distance is right) and NOT(currentSteer is left) then (steer is left)
steerLeft = (distMfRight > 1-steerMfLeft)? steerMfLeft: distMfRight;

//if (distance is left) and NOT(currentSteer is right) then (steer is right)
steerRight = (distMfLeft > 1-steerMfRight)? steerMfRight: distMfLeft;
[/source]

[quote name='IADaveMark' timestamp='1306461245' post='4816270']
Sigh.

I wish I understood the fascination with fuzzy logic when a simple set of response curves can do wonders. Adding in the fuzzy sets is a whole unnecessary layer.


I agree. Especially after wrestling with this, trying to get it to work. FIS could be useful on large projects however, where different people are providing rules to be used within somebody else's application and the level of control is required, but it doesn't really help if the programmed FIS layer isn't pluggable - which mine isn't. It seems a bit too "catch-all" to be effective.
[/quote]

I doubt very much that the usefulness of fuzzy logic would improve with project size.

Utility maximization is often a much better way to describe decision-making problems. In one case I built such an optimizer using a modular frame where people could add utility terms of their own, plus a tool to design curves using splines, which people used to describe their utility terms and to tweak them without touching the code. Several people have added terms an tweaked existing ones, and it works great.

Utility maximization is often a much better way to describe decision-making problems.

Can you recommend a good book on how to model utility response curves? rolleyes.gif


Dave Mark - President and Lead Designer of Intrinsic Algorithm LLC
Professional consultant on game AI, mathematical modeling, simulation modeling
Co-founder and 10 year advisor of the GDC AI Summit
Author of the book, Behavioral Mathematics for Game AI
Blogs I write:
IA News - What's happening at IA | IA on AI - AI news and notes | Post-Play'em - Observations on AI of games I play

"Reducing the world to mathematical equations!"


[quote name='alvaro' timestamp='1306505713' post='4816432']
Utility maximization is often a much better way to describe decision-making problems.

Can you recommend a good book on how to model utility response curves? rolleyes.gif
[/quote]

Yes, your book does cover that.

But really, some common sense and experimentation will go a long long way. Just make sure your functions don't have any funny features like discontinuities or spurious local minima: C^1 cubic splines work relly well, and most of the time you want to make sure that the second derivative is positive.
Do I need any defuzzification for my system? The magnitude of the output from the rules seems to closely represent a good new rate of change of car as it is (then I apply a sign based on which has the largest magnitude), so the whole output MF isn't really required in my system.

If I am to use defuzzification, there seems to be no concrete way to select a method based on the purpose of the FIS.

Bearing in mind that this is probably the last time I will use fuzzy inference systems...

Do I need any defuzzification for my system? The magnitude of the output from the rules seems to closely represent a good new rate of change of car as it is (then I apply a sign based on which has the largest magnitude), so the whole output MF isn't really required in my system.

If I am to use defuzzification, there seems to be no concrete way to select a method based on the purpose of the FIS.

If the control input that you are looking to use has continuous variable input -- e.g. a steering wheel that is not simply, "straight," "turn left," and "turn right" but rather can be turned to any angle -- then defuzzification is useless. Just simply convert the input of amount of turn needed into the amount of deflection. Same with braking force... normalize the input of how much braking you need and then map a response curve over onto how much (normalized) braking to apply.


Dave Mark - President and Lead Designer of Intrinsic Algorithm LLC
Professional consultant on game AI, mathematical modeling, simulation modeling
Co-founder and 10 year advisor of the GDC AI Summit
Author of the book, Behavioral Mathematics for Game AI
Blogs I write:
IA News - What's happening at IA | IA on AI - AI news and notes | Post-Play'em - Observations on AI of games I play

"Reducing the world to mathematical equations!"

This topic is closed to new replies.

Advertisement