need advice implementing AI in game.

Member
Posts: 157
Joined: 2002.12
Post: #1
Hello again

I've been trying to get some AI working. I would like to know if you guys have any advice on where to go or things to avoid in the process.

I've though about stuff like "Objectives, task, actions, etc"
Any advice would be great .
thanks
Quote this message in a reply
cloke
Unregistered
 
Post: #2
The Mac Game Programming book has a lot of good AI info in it. Basic logic stuff, and a good deal on path finding (a little broken). The page below has a ton of good info also.

http://www.gameai.com/
Quote this message in a reply
Moderator
Posts: 916
Joined: 2002.10
Post: #3
NYGhost Wrote:Hello again

I've been trying to get some AI working. I would like to know if you guys have any advice on where to go or things to avoid in the process.

I've though about stuff like "Objectives, task, actions, etc"
Any advice would be great .
thanks

well, do you want learning? no learning?
simple Finite State Machine or real time agent?

I'm currently taking an AI class right now at college. I really should post all my programs and homeworks as they come
Quote this message in a reply
Member
Posts: 28
Joined: 2003.10
Post: #4
For the ship and station AI in Oolite, I used an Objective-C class to implement simple finite state machines for decision making, but hardcoded simple behaviours, so that for example, a pirate's AI determines who and what to attack, but the direct flight control for the attack is handled by the code itself.

This can be thought of in terms of a simple animal analogy: the AI provides higher brain functions ('I'll eat that gazelle'), the coded behaviours the instincts ('legs: run, paws:snatch, jaws:bite').

The AI class is fairly simple, a page or two of code, which uses Apple property lists to store the state machine descriptions and leverages Objective-C's ability to turn strings into method selectors. I wrote it after reading, understanding and disliking the implementation of FSM's in Game Programming Gems (1).

If you like, I could post it here, it'd be easire than downloading all 30Mb of Oolite's source.

-- Giles Williams
Oolite - retro space-sim agogo
Quote this message in a reply
Member
Posts: 157
Joined: 2002.12
Post: #5
That may be helpful, post code please Smile
Quote this message in a reply
Member
Posts: 157
Joined: 2002.12
Post: #6
On this one I would like flexible approach I've read a bit about state machine.
but know nothing about "real time agent".
Quote this message in a reply
Member
Posts: 157
Joined: 2002.12
Post: #7
thank you!
Quote this message in a reply
Moderator
Posts: 916
Joined: 2002.10
Post: #8
Quote this message in a reply
Member
Posts: 28
Joined: 2003.10
Post: #9
There's a fair amount of debugging code in there, but here ya go. AI.h AI.m and a typical AI property list for Oolite.

AI.h
Code:
//
//  AI.h
/*
*
*  Oolite
*
*  Created by Giles Williams on Sat Apr 03 2004.
*  Copyright (c) 2004 for aegidian.org. All rights reserved.
*

Copyright (c) 2004, Giles C Williams
All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

ï   Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.

ï   Redistributions in binary form must reproduce the above copyright notice, this
list of conditions and the following disclaimer in the documentation and/or other
materials provided with the distribution.

ï   Neither the name of aegidian.org nor the names of its contributors may be used
to endorse or promote products derived from this software without specific prior
written permission.

ï   Neither this product nor its source code nor any products derived from them may
be offered for sale.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
DAMAGE.

*/

#import <Foundation/Foundation.h>

#import "entities.h"

#define AI_THINK_INTERVAL                    0.125

@interface AI : NSObject {

    ShipEntity        *owner;                        // the object this is the AI for
    NSString        *owner_desc;                // describes the object this is the AI for

    NSDictionary    *stateMachine;
    NSString        *currentState;
    NSMutableArray  *pendingMessages;
    
    NSMutableArray  *ai_stack;
    
    NSLock            *aiLock;
    
    double            nextThinkTime;
    double            thinkTimeInterval;
    
}

- (id) initWithStateMachine:(NSString *) smName andState:(NSString *) stateName;

- (void) setOwner:(ShipEntity *)ship;

- (void) preserveCurrentStateMachine;

- (void) restorePreviousStateMachine;

- (void) exitStateMachine;

- (void) setStateMachine:(NSString *) smName;

- (int) ai_stack_depth;

- (void) setState:(NSString *) stateName;

- (void) reactToMessage:(NSString *) message;

- (void) takeAction:(NSString *) action;

- (void) think;

- (void) message:(NSString *) ms;

- (void) setNextThinkTime:(double) ntt;

- (double) nextThinkTime;

- (void) setThinkTimeInterval:(double) tti;

- (double) thinkTimeInterval;

@end
AI.m
Code:
//
//  AI.m
/*
*
*  Oolite
*
*  Created by Giles Williams on Sat Apr 03 2004.
*  Copyright (c) 2004 for aegidian.org. All rights reserved.
*

Copyright (c) 2004, Giles C Williams
All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

ï   Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.

ï   Redistributions in binary form must reproduce the above copyright notice, this
list of conditions and the following disclaimer in the documentation and/or other
materials provided with the distribution.

ï   Neither the name of aegidian.org nor the names of its contributors may be used
to endorse or promote products derived from this software without specific prior
written permission.

ï   Neither this product nor its source code nor any products derived from them may
be offered for sale.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
DAMAGE.

*/

#import "AI.h"
#import "entities.h"
#import "ResourceManager.h"


@implementation AI

- (id) init
{    
    self = [super init];
    //
    ai_stack = [[NSMutableArray alloc] initWithCapacity:8];
    //
    pendingMessages = [[NSMutableArray alloc] initWithCapacity:16]; // alloc retains
    //
    nextThinkTime = 0.0;
    //
    aiLock = [[NSLock alloc] init];
    //
    thinkTimeInterval = AI_THINK_INTERVAL;
    //
    return self;
}

- (void) dealloc
{
    if (owner_desc)            [owner_desc release];
    if (ai_stack)            [ai_stack release];
    if (stateMachine)        [stateMachine release];
    if (currentState)        [currentState release];
    if (pendingMessages)    [pendingMessages release];
    if (aiLock)                [aiLock release];
    [super dealloc];
}

- (id) initWithStateMachine:(NSString *) smName andState:(NSString *) stateName
{    
    self = [super init];
    //
    pendingMessages = [[NSMutableArray alloc] initWithCapacity:16]; // alloc retains
    //
    aiLock = [[NSLock alloc] init];
    //
    [self setStateMachine:smName];
    //
    currentState = [stateName retain];
    //
    return self;
}

- (void) setOwner:(ShipEntity *)ship
{
    owner = ship;   // now we assume this is retained elsewhere!
    if (owner_desc)            [owner_desc release];
    owner_desc = [[NSString stringWithFormat:@"%@ %d", [owner name], [owner universal_id]] retain];
}

- (void) preserveCurrentStateMachine
{
    NSMutableDictionary *pickledMachine = [NSMutableDictionary dictionaryWithCapacity:3];
    [pickledMachine setObject:stateMachine forKey:@"stateMachine"];
    [pickledMachine setObject:currentState forKey:@"currentState"];
    [pickledMachine setObject:pendingMessages forKey:@"pendingMessages"];
    
    if (!ai_stack)
        ai_stack = [[NSMutableArray alloc] initWithCapacity:8];
    
    [ai_stack insertObject:pickledMachine atIndex:0];    //  PUSH
}

- (void) restorePreviousStateMachine
{
    if (!ai_stack)
        return;
    if ([ai_stack count] < 1)
        return;
    NSMutableDictionary *pickledMachine = [ai_stack objectAtIndex:0];
    
    //debug
    //NSLog(@"restoring pickled ai :\n%@",[pickledMachine description]);
    
    [aiLock lock];
    if (stateMachine)   [stateMachine release];
    stateMachine = [[NSDictionary dictionaryWithDictionary:(NSDictionary *)[pickledMachine objectForKey:@"stateMachine"]] retain];
    if (currentState)   [currentState release];
    currentState = [[NSString stringWithString:(NSString *)[pickledMachine objectForKey:@"currentState"]] retain];
    if (pendingMessages)   [pendingMessages release];
    pendingMessages = [[NSMutableArray arrayWithArray:(NSArray *)[pickledMachine objectForKey:@"pendingMessages"]] retain];  // restore a MUTABLE array
    //NSLog(@"debug restorePreviousStateMachine");
    [aiLock unlock];
    
    [ai_stack removeObjectAtIndex:0];   //  POP
}

- (void) exitStateMachine
{
    if ([ai_stack count] > 0)
    {
        if ((owner)&&([owner reportAImessages]))   NSLog(@"Popping previous state machine for %@",self);
        [self restorePreviousStateMachine];
        [self reactToMessage:@"RESTARTED"];
    }
}

- (void) setStateMachine:(NSString *) smName
{
    //NSString *filepath;
    
    //
    //filepath = [[[[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:@"Contents"] stringByAppendingPathComponent:@"Resources"] stringByAppendingPathComponent:smName];
    //
    //NSLog(@"Loading AI data from %@",filepath);
    //
    [aiLock lock];
    //
    if (stateMachine)
    {
        [self preserveCurrentStateMachine];
        [stateMachine release];
    }
    stateMachine = [[ResourceManager dictionaryFromFilesNamed:smName inFolder:@"AIs" andMerge:NO] retain];
    //stateMachine = [[NSDictionary alloc] initWithContentsOfFile:filepath]; // alloc retains
    //
    [aiLock unlock];
    //
    [self setState:@"GLOBAL"];
    //
    //NSLog(@"AI Loaded:\n%@",[stateMachine description]);
    //
    
    //  refresh name
    //
    if (owner_desc)            [owner_desc release];
    owner_desc = [[NSString stringWithFormat:@"%@ %d", [owner name], [owner universal_id]] retain];
}

- (int) ai_stack_depth
{
    return [ai_stack count];
}


- (void) setState:(NSString *) stateName
{
    if ([stateMachine objectForKey:stateName])
    {
        //if ((owner)&&([owner universal_id])&&([owner reportAImessages])) NSLog(@"AI for %@ enters state %@", owner_desc, stateName);
        //
        [self reactToMessage:@"EXIT"];
        if (currentState)        [currentState release];
        currentState = [stateName retain];
        [self reactToMessage:@"ENTER"];
    }
}

- (void) reactToMessage:(NSString *) message
{
    int i;
    NSArray* actions;
    NSDictionary* messagesForState;
    
    if ([owner universal_id] == NO_TARGET)  // don't think until launched
        return;
    //

    [aiLock lock];
    //
    messagesForState = [NSDictionary dictionaryWithDictionary:[stateMachine objectForKey:currentState]];
    //
    if ((currentState)&&(![message isEqual:@"UPDATE"])&&((owner)&&([owner reportAImessages])))
        NSLog(@"AI for %@ in state '%@' receives message '%@'", owner_desc, currentState, message);
    //
    actions = [NSArray arrayWithArray:[messagesForState objectForKey:message]];
    //
    [aiLock unlock];

    if ((actions)&&([actions count] > 0))
    {
        //
        for (i = 0; i < [actions count]; i++)
            [self takeAction:[actions objectAtIndex:i]];
        //
    }
    else
    {
        if (currentState)
        {
            SEL _interpretAIMessageSel = @selector(interpretAIMessage:);
            //if ((owner)&&([owner reportAImessages])&&(![message isEqual:@"UPDATE"]))
            //   NSLog(@"AI for %@ has no response to '%@' in state '%@'", owner_desc, message, currentState);
            //if ([owner respondsToSelector:NSSelectorFromString(@"interpretAIMessage:")])
            if ([owner respondsToSelector:_interpretAIMessageSel])
                [owner performSelector:_interpretAIMessageSel withObject:message];
        }
        return;
    }
}

- (void) takeAction:(NSString *) action
{
    NSArray*    tokens = [action componentsSeparatedByString:@" "];
    NSString*    dataString = nil;
    NSString*   my_selector;
    SEL            _selector;
    
    if ((owner)&&([owner reportAImessages]))   NSLog(@"%@ to take action %@", owner_desc, action);
    
    if ([tokens count] < 1)
    {
        if ([owner reportAImessages])   NSLog(@"No action '%@'",action);
        return;
    }
    
    my_selector = (NSString *)[tokens objectAtIndex:0];
    
    if ([tokens count] > 1)
    {
        dataString = (NSString *)[tokens objectAtIndex:1];
    }
    
    _selector = NSSelectorFromString(my_selector);
    
    if (![owner respondsToSelector:_selector])
    {
        if ([my_selector isEqual:@"setStateTo:"])
            [self setState:dataString];
        else
            NSLog(@"***** %@ does not respond to %@",owner_desc, my_selector);
    }
    else
    {
        if (dataString)
            [owner performSelector:_selector withObject:dataString];
        else
            [owner performSelector:_selector];
    }
}

- (void) think
{
    
    NSArray *ms_list = nil;
    
    if ([owner universal_id] == NO_TARGET)  // don't think until launched
        return;
    //
    
    [self reactToMessage:@"UPDATE"];

    [aiLock lock];
    if (pendingMessages)
    {
        //NSLog(@"debug1");
        ms_list = [NSArray arrayWithArray:pendingMessages];
        //NSLog(@"debug2");
        [pendingMessages removeAllObjects];
    }
    [aiLock unlock];
    
    if (ms_list)
    {
        int i;
        for (i = 0; i < [ms_list count]; i++)
            [self reactToMessage:(NSString *)[ms_list objectAtIndex:i]];
    }
}

- (void) message:(NSString *) ms
{
    if ([owner universal_id] == NO_TARGET)  // don't think until launched
        return;
    //

    [pendingMessages addObject:ms];
    //[self think];
}

- (void) setNextThinkTime:(double) ntt
{
    nextThinkTime = ntt;
}

- (double) nextThinkTime
{
    return nextThinkTime;
}

- (void) setThinkTimeInterval:(double) tti
{
    thinkTimeInterval = tti;
}

- (double) thinkTimeInterval
{
    return thinkTimeInterval;
}

@end
missileAI.plist
Code:
{
    "ATTACK_SHIP" = {
        "DESIRED_RANGE_ACHIEVED" = ("setStateTo: DETONATE");
        ENTER = ("setDesiredRangeTo: 25.0", performIntercept);
        EXIT = ();
        "TARGET_DESTROYED" = ("setStateTo: EXPLODE");
        "TARGET_LOST" = ("setStateTo: EXPLODE");
        "ECM" = ("setStateTo: EXPLODE");
        "GONE_BEYOND_RANGE" = ("setStateTo: EXPLODE");
        UPDATE = ("setDesiredRangeTo: 30000.0", checkDistanceTravelled, "setDesiredRangeTo: 25.0", "pauseAI: 5.0");
    };
    DETONATE = {
        ENTER = ("setDesiredRangeTo: 250.0", dealEnergyDamageWithinDesiredRange, becomeExplosion);
        EXIT = ();
        UPDATE = ();
    };
    EXPLODE = {
        ENTER = (becomeExplosion);
        EXIT = ();
        UPDATE = ();
    };
    GLOBAL = {
        ENTER = ("setSpeedFactorTo: 1.0", "setStateTo: ATTACK_SHIP");
        EXIT = ();
        UPDATE = ();
    };
}

-- Giles Williams
Oolite - retro space-sim agogo
Quote this message in a reply
Member
Posts: 157
Joined: 2002.12
Post: #10
that was good skyhawk! :o
it really helps map out thet things I may need, although it may be more than what I can implement on my own.
Quote this message in a reply
Post Reply 

Possibly Related Threads...
Thread: Author Replies: Views: Last Post
  Advice regarding Views &amp; Touches for iPhone game Elphaba 5 3,119 Jul 17, 2009 01:54 PM
Last Post: Frank C.
  Implementing audio in games sammyitch 3 2,411 Dec 21, 2005 06:27 AM
Last Post: unknown
  Implementing sound Muffinking 25 8,279 Nov 4, 2003 05:22 AM
Last Post: MattDiamond