You are on page 1of 6

Máquinas de estados finitos (FSM’s

)
Autor: André Kishimoto Uma máquina de estados finitos (finite state machine, ou FSM) é uma máquina abstrata que possui diversos estados e diversas transições entre esses estados. Em um dado momento, a máquina pode estar somente em um único estado. Através do processamento de uma entrada na máquina, o estado atual define o comportamento da máquina e quais ações devem ser tomadas pela mesma (incluindo se há a necessidade de mudar de estado). Cada transição define de qual estado para qual estado a máquina deve mudar. FSM’s são muito utilizadas em jogos, pois elas são simples e rápidas de serem implementadas, fáceis de depurarem, não necessitam de muito processamento da CPU e são flexíveis (novos estados e transições podem ser adicionados às FSM’s). Vamos imaginar que temos um NPC vivendo em uma casa virtual. Sua principal ação é ficar assistindo TV o dia inteiro. Ele pára de assistir TV somente quando estiver cansado, com fome ou apertado. Quando estiver cansado, ele vai dormir até descansar completamente (depois disso, acorda e volta a assistir TV). Quando estiver com fome, o NPC vai até a cozinha comer algo. E, se depois de comer muito, ficar apertado, ele vai até o banheiro fazer suas necessidades. Depois de aliviado, volta a assistir TV. A Figura 1 contém a representação dos estados e transições da FSM desse exemplo.

Figura 1: Exemplo de FSM.

Note que as transições de estado foram feitas apenas entre “Assistir TV” e as demais. Nada impediria, no entanto, de que tivéssemos transições entre outros estados (por exemplo, enquanto dorme, o NPC pode sentir fome e ir para a cozinha comer algo, assim como, enquanto está comendo, o NPC pode se sentir apertado e precisar ir ao banheiro). Aliás, essa FSM ficaria muito melhor (e mais real) com o acréscimo de transições entre todos os estados, desde que plausíveis.

HORA DA AULA
PDJzine – Revista Eletrônica de Distribuição Livre da Programadores e Desenvolvedores de Jogos Implementação de FSM’s A FSM do exemplo descrito acima pode ser implementada através de uma série de if’s ou dentro de um switch, como mostra a Listagem 1. Note que a implementação foi feita parcialmente e as funções e manipulações de dados foram omitidas, dando ênfase na transição de estados.
// Enumera os estados e define estado atual como “Assistir TV” enum FSM_Estados { AssistirTV, Comer, Dormir, IrAoBanheiro }; int estadoAtual = AssistirTV; // Verifica o estado atual da FSM e faz transições de acordo com as // situações apresentadas switch(estadoAtual) { case AssistirTV: { // A cada ciclo de máquina, incrementa fome, cansaço, etc... if(comFome()) { estadoAtual = Comer; } if(cansado()) { estadoAtual = Dormir; }

Imagine como a implementação da FSM ficaria num caso desses! Nota: Na área de programação. Acredito que essas funções sejam auto-explicativas. Vamos continuar com o exemplo do NPC que vive em uma casa virtual. Com o uso de funções. No momento em que o NPC entra no estado “Assistir TV”. flexível e expansível. podemos encontrar agentes onde cada um contém dezenas de estados e transições entre todos os estados. Uma solução mais elaborada para esse problema é implementar a FSM através de funções. } } break. *LPSTATE. // Ponteiro p/ função chamada quando entra no estado void (*execute)(void). bagunçando ainda mais o código. HORA DA AULA PDJzine – Revista Eletrônica de Distribuição Livre da Programadores e Desenvolvedores de Jogos assim como possa existir a necessidade de executar outro código no momento em que a máquina entre em um estado. um código contendo muitos if­else’s e switch’s aninhados costuma receber o nome de spaghetti code. crie mais estados e faça mais transições entre os estados. No entanto. nós eliminamos tais problemas. No método anterior. Poderia ser declarado como // sendo do tipo enum: FSM_Estados state. e são voltados para programação orientada a objetos. Nota: O método de FSM’s via funções que apresento nesse tópico foi adaptado do padrão de projeto conhecido por State. // Ponteiro p/ função que processa o estado void (*exit)(void). dessa vez. } Listagem 1: Implementação parcial da FSM da Figura 1. // Ponteiro p/ função chamada quando sai do estado } STATE. // outros estados da FSM. Quando o NPC sai do estado “Assistir TV”. talvez seja necessário que um determinado código seja executado no momento em que a máquina saia de um estado. mas não é a melhor solução para o problema. uma vez que você pode ficar perdido entre os diversos estados e transições. afinal. mas sugiro que você tente implementar todos os estados e transições. case Comer: { if(satisfeito()) { estadoAtual = AssistirTV.. vamos definir uma estrutura que armazene um estado da FSM: typedef struct { unsigned int state. Padrões de projeto (design patterns) são padrões utilizados para resolver determinados problemas de engenharia de software.. Isso pode ser feito com o método da Listagem 1. o NPC liga a TV quando entra na sala. Nessa estrutura STATE. Ainda não descobriu nada de errado? Então. executeState() e exitState(). } } break. resultando na tal dor-decabeça mencionada. Em jogos mais sofisticados.. podemos definir uma ação em que ele desliga a TV para economizar energia elétrica enquanto não está na sala. Para cada estado da FSM. Talvez o problema não tenha ficado muito claro porque mostrei apenas parte da FSM implementada. seria necessário definir novos estados. além do índice do estado. Após um certo tempo. surge uma pergunta: quando cada uma dessas funções deve ser chamada no jogo? Antes de responder essa pergunta. Embora seja uma solução aparentemente simples e aceitável (e que muitos programadores costumam utilizar). tais como “Saindo da sala” e “Entrando na sala”. definimos uma outra ação. vamos definir três funções: enterState().if(apertado()) { estadoAtual = IrAoBanheiro.. implementar uma FSM dessa maneira pode resultar em desastres e dor-de-cabeça. o código se tornará difícil de ser compreendido e depurado. foram definidos três ponteiros para as funções . // Índice do estado. tornando o código mais compreensível. além de permitir a execução de códigos durante os momentos em que um agente entra e sai de um estado. void (*enter)(void). códigos com esse tipo de estrutura tendem a ficar uma bagunça! Implementação de FSM’s através de funções Além do “spaghetti code” encontrado na implementação de FSM’s no tópico anterior.

} // Função chamada quando o NPC sai do estado void npcExitTV(void) HORA DA AULA PDJzine – Revista Eletrônica de Distribuição Livre da Programadores e Desenvolvedores de Jogos { printf(“Desligando a TV e saindo da sala. STATE curState.. npcExecuteTV. Bom. Vamos imaginar agora que o NPC já comeu o lanche. } // Função que processa o comportamento do NPC no estado void npcExecuteTV(void) { printf(“Assistindo TV. A implementação dessa idéia funciona. a primeira coisa que o NPC faz é sair da cozinha (saiu do estado “Comer”). // Função chamada quando o NPC entra no estado void npcEnterTV(void) { printf(“Indo para a sala ligar a TV. ficou satisfeito e quer continuar assistindo TV.enter = npcEnterTV.\n”). O que acontece durante a transição do estado “Assistir TV” para o estado “Comer”? A primeira ação do NPC nessa transição é desligar a TV e sair da sala (ou seja.execute = npcExecuteTV. se nós criamos essas funções. Transição de estados Vamos agora responder a pergunta de quando chamar cada uma das funções do estado. // Armazena o estado atual da FSM //­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­ // changeState() ­> Faz transição de estados de uma FSM // . como veremos a seguir. as funções do estado mostrarão apenas uma mensagem no console.exit = npcExitTV. Os estados da FSM Para exemplificar como usar a estrutura STATE. nem precisaríamos ter criado a estrutura STATE. Tendo conhecimento dessa seqüência. o NPC saiu do estado “Assistir TV”). npcExitTV }..\n”). entrar no novo estado e permanecer nele até ocorrer outra transição. De repente. como na Listagem 3. é mais conveniente utilizar a estrutura STATE. Depois. Há um padrão a ser seguido em todas as transições: sair do estado atual. return(0). Em seguida. npcExecuteTV() e npcExitTV()) que realizam todo o trabalho de processar os eventos do estado. Uma vez na cozinha. certo? Afinal..\n”). // Veja Listagem 1 stateTV. stateTV. o nosso NPC está no estado “Assistir TV”. ele entra na sala. liga a TV (entrou no estado “Assistir TV”) e continua assistindo aos programas até que resolva fazer outra coisa. O código da Listagem 2 não faz muito a não ser criar um novo estado e configurá-lo. Veja a Listagem 2. npcEnterTV. Já temos as funções. Nessa situação.. vamos criar o estado “Assistir TV” da FSM do nosso NPC. stateTV. char *argv[]) { STATE stateTV. executeState e exitState. // Nota: também podemos criar um estado com a seguinte linha: // STATE stateTV = { AssistirTV. } int main(int argc. a estrutura armazena um índice do estado e ponteiros para as três funções. Isso é verdade. Porém.. algo importante é mostrado nesse trecho de código: nós criamos três funções (npcEnterTV().state = AssistirTV. Encontramos outra transição de estados. basta criar uma variável int (ou enum) que armazena o índice do estado. Observe as seqüências de ações tomadas pelo NPC. stateTV. ele sente fome e resolve ir até a cozinha. ele vai até a cozinha preparar um lanche (entrou no estado “Comer”). } Listagem 2: Definindo um estado da FSM.. O raciocínio para chegar a resposta é a seguinte: Em um certo momento. Como estamos querendo aprender a usar a estrutura STATE. // Estado “Assistir TV” // Define o estado e suas funções enterState.citadas anteriormente. Mas da maneira que iremos implementar a FSM. ele permanece lá até ficar satisfeito. podemos escrever uma função changeState().

Para isso. poderíamos passar um outro parâmetro (o estado atual de um agente) para a função. não podendo conter nenhuma função dentro dela. *LPFSM. Minha solução envolve o uso de outra estrutura de dados: typedef struct { STATE curState. Comecei a pesquisar sobre funções dentro de estruturas.enter = newState. // Estado atual recebe propriedades do novo estado HORA DA AULA PDJzine – Revista Eletrônica de Distribuição Livre da Programadores e Desenvolvedores de Jogos curState. // Entra no novo estado if(curState. de que compiladores C++ interpretam tal estrutura como uma classe. Fiquei confuso. curState.exit(). E realmente não podem. uma função não pode ser membro de uma estrutura.state. // Nota: Assumimos que as funções npcEnter*() e npcExit*() já foram // declaradas e codificadas. quando aprendemos a programar em linguagem C. o protótipo da nova função seria declarado da seguinte maneira: void changeState(STATE &curState. O que ocorre nesse caso é que o código foi compilado usando um compilador C++. que codifica a transição entre dois estados do nosso NPC de exemplo. STATE newState). as pessoas nos ensinam que uma estrutura possui somente tipos de dados.enter. } Listagem 3: Transição de estados. que é utilizada nos programasexemplos do livro. mas essa explicação parece funcionar com muita gente. uma vez que a função utiliza a variável global curState. curState. Aliás. Essa descoberta me fez lembrar das vezes em que ouvi (já até perdi a conta disso) programadores C++ explicando. isso é errado! Na linguagem C.execute = newState. que uma classe pode ser imaginada como uma estrutura que contém funções.exit) curState. não é? Afinal.enter(). isso não é possível. vamos ver como podemos utilizá-la nos jogos. acompanhe a Listagem 4. e quando esse tipo de estrutura é criado. Aqui está então a tal estrutura com funções! Agora que possuímos a função changeState().exit.enter) curState. grosso modo. Essa solução é válida. assim como o índice (tipo enum) dos estados e // o estado STATE stateMorto e suas funções. curState. .// in: STATE newState ­> Estado que a FSM estará entrando // out: <nenhum valor> //­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­ void changeState(STATE newState) { // Sai do estado atual (se função exit() estiver definida) if(curState. o compilador interpreta a estrutura como sendo uma classe em C++. Para usar a função com qualquer agente. Lógico que uma classe não é só uma estrutura que contém funções. Assim. pois aprendi que funções não podiam fazer parte de uma estrutura. estamos declarando uma função dentro da estrutura! Como isso é possível? Na verdade. // Estado atual da FSM //­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­ // changeState() ­> Faz transição de estados de uma FSM // // in: STATE newState ­> Estado que a FSM estará entrando // out: <nenhum valor> //­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­ void changeState(STATE newState) { // O código desta função é o mesmo código da função changeState() // da Listagem 3. Compiladores “C-only” irão reclamar dessa estrutura! Nota: Resolvi levantar essa questão porque certa vez estava estudando alguns algoritmos e me deparei com esse tipo de estrutura.execute.state = newState. } } FSM.exit = newState. e descobri esse fato interessante. Essa estrutura FSM é muito estranha. O código acima é válido somente para um agente. Mas aqui. Eu mesmo digo isso quando programadores C me pedem para definir o que é uma classe. mas quero mostrar uma outra solução possível.

 npcExecuteComer. O trecho de código é idêntico. // Diminui tempo de vida do NPC e verifica se morreu envelhecerNPC(). Observe agora o código das funções npcExecuteTV() e npcExecuteComer(). if(morto()) fsm. // Diminui tempo de vida do NPC e verifica se morreu envelhecerNPC(). } // Define dois estados do NPC. if(morto()) fsm.\n”). // Chama a função execute do estado atual } return(0). Também podemos verificar como a função “execute” de cada estado da FSM é chamada.. para que { // seja possível finalizar o programa. } Listagem 4: Realizando transições de estados.changeState(stateMorto). npcEnterTV. muda o estado para “Comer” if(comFome()) fsm. que é executado junto com o estado atual da FSM.changeState(stateComer). a estrutura FSM recebe um novo membro globalState e uma nova função update(). // Se o NPC estiver com fome. dentro da main(). podemos criar um estado global. npcExitTV }. if(fsm. que é a chamada à função envelhecerNPC() e a verificação se o NPC está morto. Para situações que possam ocorrer em qualquer estado da FSM (como o envelhecimento e morte do NPC).curState != Morto) // Definimos um novo estado “Morto”. STATE stateTV = { AssistirTV. Podemos verificar que a transição entre estados é simples de ser realizada.execute(). “Assistir TV” e “Comer”. STATE stateComer = { Comer. char *argv[]) { // FSM para fazer transição entre os estados FSM fsm.changeState(stateMorto). ao invés de repetir o código em cada estado. e seria melhor se pudéssemos codificar tal trecho apenas uma vez. while(fsm.. npcExecuteTV..// Função que processa o comportamento do NPC no estado “Assistir TV” HORA DA AULA PDJzine – Revista Eletrônica de Distribuição Livre da Programadores e Desenvolvedores de Jogos void npcExecuteTV(void) { printf(“Assistindo TV.changeState(stateTV). npcExitComer }. //­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­ // update() ­> Atualiza/processa comportamento da FSM // HORA DA AULA PDJzine – Revista Eletrônica de Distribuição Livre da Programadores e Desenvolvedores de Jogos // in: <nenhum valor> // out: <nenhum valor> //­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­ . Temos uma repetição de código. muda o estado para “Assistir TV” if(satisfeito()) fsm.\n”).execute) fsm. Repare como não há necessidade de chamar funções de nomes diferentes para cada estado da FSM. } // Função que processa o comportamento do NPC no estado “Comer” void npcExecuteComer(void) { printf(“Comendo um lanchinho. que deve ser chamada no lugar da execute() (Listagem 5). Assim. // Se o NPC estiver satisfeito. npcEnterComer. pois o ponteiro para função elimina esse inconveniente. int main(int argc. // A estrutura FSM possui esse novo membro e nova função: STATE globalState..

line-ofsight.execute(). que salva o estado anterior da FSM. Nota: Chegamos ao final do artigo. grafos e path-following.execute(). . movimentação de agentes. processa o mesmo if(curState. } int main(int argc. // Assumindo que stateGlobal foi declarado while(fsm. Para melhorar a nossa FSM. processa o mesmo if(globalState. podemos acrescentar um novo estado prevState à estrutura FSM.globalState = stateGlobal. uso de mensagens nas FSM’s (temporização e conversa entre NPC’s e objetos).execute) curState. Assim. algoritmos de busca (DFS.update(). exemplos visuais (não somente modo console). é possível retornar ao estado que estava em processamento antes da última transição de estados.execute) globalState. acrescentar o novo estado à estrutura FSM.curState != Morto) fsm. A*). // Se existe um estado atual na FSM. // Chama a função execute do estado global e do atual return(0).void update(void) { // Se existe um estado global na FSM. } Listagem 5: Estado global de uma FSM. char *argv[]) { fsm. em relação à FSM e IA: explicação sobre ponteiros de funções (usados neste artigo). Para aqueles que ficaram interessados ou curiosos sobre o que mais é abordado no livro. BFS. Dijkstra.