Lab3 Bruno Cesar

aluno: Bruno César Alves
ano/sem: 2008/2o.
data do laboratório (num. da semana) : 06/08/2008 (2)

Introdução

Este relatório refere-se a construção de uma máquina de estados determinista, entendo como determinista qualquer transição que tenha uma única combinação entrada e saída. Minha máquina de estados permite a saída de um estado para vários, entretanto cada saída está relacionada a um evento diferente.
Além da máquina de estados, foi construida uma aplicação que a utiliza para definir o status de um personagem de um game.

Desenvolvimento

Nas instruções do relatório, foi citada a criação das classes UndefinedEventException e UndefinedStateException, porém, para não causar erro nos testes, tomei a liberdade de alterar o nome dessas classes para UndefinedEventError e UndefinedStateError.
Segue a máquina de estados criada no blueJ:

29bn6z8.jpg

De forma geral, o papel da classe StateMachine é realizar todas as requisições do usuário da máquina, esta classe guarda as principais informações sobre a máquina e realiza todos os links do seu usuário com o resto do pacote, ela utiliza o padrão observer e deve guardar, além dos estados e eventos possíveis, todos os seus observadores. Os métodos que criam eventos e estados usam overload para diferentes tipos de criação. Os métodos setInitialState e setCurrentState diferem, pois o segundo executa o commando de estado.

package stateMachine;
import java.util.Vector;
public class StateMachine
{
    //variáveis
    private Vector <State> states = new Vector();             //vetor que armazena todos os estados da máquina
    private Vector <Event> events = new Vector();             //vetor que armazena todos os eventos da máquina
    private Vector <Observer> observers = new Vector();       //vetor que armazena todos os observadores da máquina
    private State currentState;                               //guarda o estado atual da máquina
    private Observer observer;                                //variável usada para guardar um observer usado em algum processo
    //add um observador que deseja observar a máquina
    public void registerObserver(Observer observer){
        observers.add(observer);
    }
    //usada para atualizar os observadores
    protected void notifyObservers(){
        for(Observer obs : observers){
            obs.update(this, currentState.getName());
        }
    }
    //métodos para a definição da máquina
    //criaum estado com um comando de estado
    public void createState(String name, StateCommand onEnter){
        states.add(new State(this,name, onEnter));
    }
    //criaum estado sem um comando de estado
    public void createState(String name){
        states.add(new State(this,name));
    }
    //define um estado incial para a máquina
    public void setInitialState(String name){
        for(State state_ : states){
            if(name.equals(state_.getName())) currentState=state_;
        } 
    }
    //cria um evento e transições de varios estados para um estado com um comando de evento
    public void createEvent(String name, String[] from, String to, EventCommand onSuccess){
        events.add(new Event(this, name,from,to, onSuccess));
    }
    //cria um evento e transições de varios estados para um estado sem comando de evento
    public void createEvent(String name, String[] from, String to){
        events.add(new Event(this, name,from,to));
    }
    //cria um evento de um estado para outro com comando de evento
    public void createEvent(String name, String from, String to,  EventCommand onSuccess){
        events.add(new Event(this, name,from,to, onSuccess));    
    }
    //cria um evento de um estado para o outro sem comando de evento
    public void createEvent(String name, String from, String to){
        events.add(new Event(this, name,from,to));
    }
    //cria uma transição de um estado para outro em um evento já existente
    public void appendTransitionToEvent(String event, String fromState, String toState){
        for(Event event_ : events){
             if(event.equals(event_.getName())) event_.AppendTransition(fromState, toState);
        }
    }
    //cria varias transições de estados para um estado em um evento já existente
    public void appendTransitionToEvent(String event, String[] fromState, String toState){
        for(Event event_ : events){
            if(event.equals(event_.getName())){
                for(String fromState_ : fromState) event_.AppendTransition(fromState_,toState);
            }
        }
    }
    //cria uma guarda para limitar a execução d eum determinado evento
    public void setGuardToEvent(String event,EventGuard g){
        for( Event event_ : events){
            if(event.equals(event_.getName())) event_.setEventGuard(g);
        }
    }
   // métodos para a execução da máquina
    //executa um evento da máquina de estados 
    //os exceptions evitam que eventos, estados ou transições inxistentes sejam realizadas
    public boolean fireEvent(String name) throws UndefinedTransitionException{ 
        for(Event event_ : events){
            if(name.equals(event_.getName())){
                event_.fireTransition(this);
                return true;
            }
         }
         throw new UndefinedEventError();
    }
    //retorna o estado atual da máquina            
    public String getCurrentStateName(){
        return this.currentState.getName();
    }
    //define um novo estado para a máquina executando o comando de estado
    public boolean setCurrentState(String newState){
        for( State state_ : states){
            if(newState.equals(state_.getName())){
                currentState = state_;
                state_.fireCommand();
                return true;
            }
        }
        System.out.println("O estado " + newState + "não existe");
        return false;
    }
}

A classe State instancia os estados da máquina, seu papel é guardar as variáveis do estado, bem como seu comando de estado, que é definido como vazio na construção da instância caso não seja solicidado.

package stateMachine;
public class State
{
    private String name;      //guarda o nome do estado
    private StateCommand sc;  //guarda o comando que deve ser executado quando a variável chega em um estado
    private StateMachine sm;  //guarda a máquina de estado a qual o estado pertence
    //contrutor que define a maquina a qual pertence e o nome do estado
    public State(StateMachine sm, String name)   
    {
        this.sm = sm;
        this.name = name;
        setInitialStateCommand();
    }
    //contrutor que define a maquina a qual pertence, o nome do estado e o comando de estado
    public State(StateMachine sm, String name, StateCommand sc)
    {
        this.sm = sm;
        this.name = name;
        setStateCommand(sc);
    }
    //define o comando de estado 
    public void setStateCommand(StateCommand sc)
    {
        this.sc = sc;
    }
    //define um comando de estado vazio
    public void setInitialStateCommand()
    {
        sc = new StateCommand(sm) {
            public void execute() {
                ;
            }
        };
    }
    //retorna o nome do estado
    public String getName()
    {
        return this.name;
    }
    //executa o comando de estado
    public void fireCommand()
    {
        sc.execute();
    }
}

A classe Event instância os eventos da máquina, como na classe State, existe mais de um construtor. Os métodos setNoGuard e setNoCommand auxiliam no trabalho com as respectivas classes anônimas no caso em que elas não são solicitadas pelo usuário. Para que a determinação seja mantida, na criação de transições dentro do evento, o método verifyTransition procura por uma transição que já tenha o estado da nova transição.

package stateMachine;
import java.util.Vector;
 
public class Event
{
    private Vector<EventTransition> transitions = new Vector<EventTransition>(); //armazena todos as transições realizadas pelo evento
    private String name;                                                         //guarda o nome do evento
    private EventCommand ec;                                                     //armazena o comando de evento
    private EventGuard eg;                                                       //armazena a guarda do evento    
    private StateMachine sm;                                                     //armazena a máquina de estados a qual o evento pertence
    //construtor  que isntancia um evento e uma transição
    public Event(StateMachine sm,String name, String from, String to)
    {
        this.sm = sm;
        this.name = name;
        transitions.add(new EventTransition(this,from,to));
        setNoGuard();
        setNoCommand();
    }
    //construtor  que isntancia um evento e varias transições
    public Event(StateMachine sm,String name, String[] from, String to)
    {
        this.sm = sm;
        this.name = name;
        for( String from_ : from){
                   if(verifyTransition(from_,to)) transitions.add(new EventTransition(this,from_,to));
        }
        setNoGuard();
        setNoCommand();
    }
    //construtor  que isntancia um evento e uma transição com um comando de evento
    public Event(StateMachine sm,String name, String from, String to, EventCommand ec)
    {
        this.sm = sm;
        this.name = name;
        this.ec =ec;
        if(verifyTransition(from,to)) transitions.add(new EventTransition(this,from,to));
        setNoGuard();
    }
    //construtor  que isntancia um evento e varias transições com um comando de evento
    public Event(StateMachine sm,String name, String[] from, String to, EventCommand ec)
    {
        this.sm = sm;
        this.name = name;
        this.ec = ec;
        for( String from_ : from){
            if(verifyTransition(from_,to)) transitions.add(new EventTransition(this,from_,to));
        }
        setNoGuard();
    }
    //cria uma nova transição para o evento
    public void AppendTransition(String fromState, String toState){
             if(verifyTransition(fromState,toState)) transitions.add(new EventTransition(this,fromState,toState));
        }
    //define um comando de evento para o evento
    public void setEventCommand(EventCommand ec)
    {
        this.ec = ec;
    }
    //define uma guarda para o evento
    public void setEventGuard(EventGuard eg)
    {
        this.eg = eg;
    }
    //deixa o vento sem guarda
    public void setNoGuard()
    {
        this.eg = new EventGuard() {
            public boolean shouldPerform() {
                return true;
            }
        };
    }
    //deixa o evento sem comando de evento
    public void setNoCommand()
    {
        this.ec = new EventCommand(sm) {
            public void execute(String previousState) {
            }
        };
    }
    //retorna a guarda do evento
    public EventGuard getEventGuard()
    {
        return eg;
    }
    //retorna o nome do evento
    public String getName()
    {
        return this.name;
    }
    //executa a transição do evento
    public boolean fireTransition(StateMachine sm) throws UndefinedTransitionException
    {
        for( EventTransition et : transitions){
            if(et.getFrom().equals(sm.getCurrentStateName())&eg.shouldPerform()){
                if(!sm.setCurrentState(et.getTo())) throw new UndefinedStateError();
                sm.notifyObservers();
                ec.execute(et.getTo());
                   return true;
            }
        }
        throw new UndefinedTransitionException();
    }
    //verifica se existe alguma transição que parte do estado: StateFrom.
    //essa verificação é importante para que nao ocorram transição duplas para um mesmo evento
    public boolean verifyTransition(String stateFrom,String stateTo){
        for(EventTransition transition_ : transitions){
            if(transition_.getFrom().equals(stateFrom)) {
                System.out.println("A criação da transição: \"" + stateFrom + "\" para \"" + stateTo + "\" foi impedida pois causaria indeterminação na máquina");
                return false;
            }
        }
        return true;
    }
 
}

A classe EventTransition se refere à transição de um estado para o outro, sua função é armazenar o estado de saida e de chegada de uma transição.

package stateMachine;
 
public class EventTransition
{
    private String from;  //guarda o estado de saida da transição
    private String to;    //guarda o estado de chegada da transição
    private Event event;  //guarda o evento ao qual a transição pertence
    //construtor
    public EventTransition(Event event, String from, String to)
    {
        this.from =from;
        this.to =to;
        this.event = event;
    }
    //retorna o estado de saida da transição
    public String getFrom(){
        return from;
    }
    //retorna o estado de chegada da transição
    public String getTo(){
        return to;
    }
}

Teste da máquina de estados

A máquina de estados foi testada com sucesso, segue o resultado obtido:

dr6v7o.jpg

Aplicação

A aplicação desenvolvida tem como objetivo controlar um personagem de um game, de forma que seus estados estao sob influência de um usuário e do proprio game.

Segue a aplicação feita no blueJ:

os5tts.jpg

A classe Goblin sobreescreve a classe StateMachine, sua função é configurar a máquina de estados para o personagem goblin.

import stateMachine.*;
public class Goblin implements stateMachine.Observer
{
    protected StateMachine sm;
    protected String state;
    protected boolean macho;
    //contrutor
    public Goblin() {
         sm = new StateMachine();
         setUpSM();
         sm.registerObserver(this);
    }
    //dispara o evento
    public void fire(String eventName){
        try{
          sm.fireEvent(eventName);
        } catch(UndefinedTransitionException e) {
            System.out.println("ERRO: trasição não definida: " + e.getMessage());
        }
    }
    //retorna o estado atual
    public String getState(){
        return sm.getCurrentStateName();
    }
    //configura a maquiana de estados para os estados e eventos do goblin
    private void setUpSM() {
        //criando estados
        sm.createState("parado", new stateMachine.StateCommand(sm) {
            public void execute() {
                System.out.println("Estou parado");
            }
        } );
        sm.createState("correndo", new stateMachine.StateCommand(sm) {
            public void execute() {
                System.out.println("Estou correndo");
            }
        } );
        sm.createState("caido", new stateMachine.StateCommand(sm) {
            public void execute() {
                System.out.println("Estou caido");
            }
        } );
        sm.createState("lasagra", new stateMachine.StateCommand(sm) {
            public void execute() {
                System.out.println("Rodolpho Castro");
            }
        } );       
        sm.createState("jandaia", new stateMachine.StateCommand(sm) {
            public void execute() {
                System.out.println("O que?");
            }
        } );
        sm.createState("boladao", new stateMachine.StateCommand(sm) {
            public void execute() {
                System.out.println("So many Goblins!!!!!");
            }
        } );
        sm.setInitialState("parado");
 
        String[] from1 = {"parado", "correndo","caido","lasagra","boladao"};        
        sm.createEvent("fritar", from1, "jandaia", new stateMachine.EventCommand(sm) {
            public void execute(String previousState) {
                System.out.println("momento altista...");
            }
        });
        String[] from2 = {"parado", "correndo","caido","jandaia","boladao"};
        sm.createEvent("negao", from2, "lasagra", new stateMachine.EventCommand(sm) {
            public void execute(String previousState) {
                System.out.println("eeeeeeepa...");
            }
        });
        String[] from3 = {"correndo","caido","lasagra","jandaia","boladao"};
        sm.createEvent("parar", from3, "parado");
        String[] from4 = {"parado","lasagra","jandaia","boladao"};
        sm.createEvent("correr", from4, "correndo");
        String[] from5 = {"parado", "correndo","lasagra","jandaia","boladao"};
        sm.createEvent("cair", from5, "caido");
        String[] from6 = {"parado", "correndo","caido","lasagra","jandaia"};
        sm.createEvent("bolar", from6, "boladao");
        sm.setGuardToEvent("negao", new stateMachine.EventGuard() {
            public boolean shouldPerform(){
               return !isMutchoMatcho();  //nunca fica lasagra se eh macho
            }
        });
    }
    //retorna se o goblin eh mutchomatcho
    protected boolean isMutchoMatcho(){
        return macho;
    }
    //coloca um novo valor para a variavel macho
    protected void setMacho(String is){
        if(is.equals("sim")) macho = true;
        else macho = false;
    }
    //faz alteração de acordo com o update dado na clase observer da maquina de estados
    public void update(StateMachine sm, String newStateName){
        state= newStateName;
    }
}

A classe usuario tem como função receber inputs do teclado do usuario e executar eventos que alteram o estado do goblin.

public class Usuario
{
    private Goblin goblin;
    public Usuario(Goblin goblin)
    {
    this.goblin = goblin;
    }
    public void enviarInput(String input){
        if(input.equals("p")) goblin.fire("parar");
        if(input.equals("c")) goblin.fire("correr");
    }
}

A classe Game tem como função alterar o estado do goblin de acordo com acontecimentos do jogo.
public class Game
{
    private Goblin goblin;
    public Game(Goblin goblin)
    {
    this.goblin = goblin;
    }
    public void tomarTiro(){
        goblin.fire("cair");
    }
    public void verNegao(){
        goblin.fire("negao");
    }
    public void sairDaRealidade(){
        goblin.fire("fritar");
    }
    public void receberBonus(){
        goblin.fire("bolar");
    }
}

A classe de teste testgame aplica os métodos de game e usuario com o objetivo de testar as alterações de estado do goblin. Segue o código da classe de testes e os seus resultados.

public class Game
{
    private Goblin goblin;
    public Game(Goblin goblin)
    {
    this.goblin = goblin;
    }
    public void tomarTiro(){
        goblin.fire("cair");
    }
    public void verNegao(){
        goblin.fire("negao");
    }
    public void sairDaRealidade(){
        goblin.fire("fritar");
    }
    public void receberBonus(){
        goblin.fire("bolar");
    }
}
291yov8.jpg

Conclusão

Este laboratório foi o de maior aprendizado pra mim, o tempo para a realização foi adequado para um laboratório duplo. A forma que o padrão Command e as classes anônimas foram explicadas e usadas possibilitaram o aprendizado de forma natural. Durante a criação da aplicação do personagem de uma game pude aplicar o que foi aprendido em relação ao padrão Observer, imagino que seria desgastante fazer o personagem receber informações automáticas da máquina de estados sem este padrão de projeto.
A explicação das exceções (nas instruções do laboratório) foi muito aquém do necessário para seu uso na construção da máquina de estados. Considerei interessante o uso das condições de guarda, seu uso possibilida uma flexibilidade maior para o usuário da maquina.
O encapsulamento da máquina de estaodos realmente criou uma boa separação entre o usuário e o responsável pela manutenção, permitindo suas alterações sem que a essência da máquina seja alterada. Considero que a máquina de estados criada cumpriu com o esperado, porém se mostra limita no sentido que não existe a opção de excluir os eventos nem transições.

Add a New Comment
Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License