3
\$\begingroup\$

Can you improve my state engine which consists of the state engine based on an interface and an example menustate? It all works fine but seems a bit cumbersome in terms of the way I have to change states and also report on what state the engine is in. Can this be coded more elegantly?

public class StateEngine {
    //Pointer to cureent state
    private StateInterface mystate;

    StateEngine() {
        //Create new state of mainmenu at start
        setstate(new StateMainmenu());
    }

    //Set the state to the new state and capture a pointer
    final void setstate(StateInterface newstate) {
        this.mystate = newstate;
    }

    //Use the current state and point to its tellstate method
    final void tellstate() {
        mystate.tellstate();
    }

    //Use the current state, changestate method
    final void changestate(){
        mystate.changestate(this);
    }

    final void update(){
        mystate.update();
    }

    final void render(Graphics g){
        mystate.render(g);
    }
}

The interface

public interface StateInterface {
    void changestate(StateEngine currentstate);
    void tellstate();
    void update();
    void render(Graphics g);
}

Example state for the menu

public class StateMainmenu implements StateInterface {
    //private StateEngine state;
    static String[] mainmenu = new String [] {"Start","Options","Quit"};
    static int menuselection;
    public BufferedImage menubackgroundscreen;
    private int myfontsize=60;
    private int width=Inferno.appwidth, height=Inferno.appheight;

    StateMainmenu(){
        System.out.print("Initialising Menu, "); 
        menuselection=0;
        menubackgroundscreen=new BufferedImage(width,height,BufferedImage.TYPE_INT_ARGB);
        Graphics g=menubackgroundscreen.getGraphics();
    }

    @Override
    public void changestate(StateEngine currentstate) {
        System.out.println("Changing from menu");

        switch(menuselection){
            case 0:  System.out.println("Option 1 Selected");
                        currentstate.setstate(new Selectionstate(currentstate));     
                        break;
            case 1:  System.out.println("Option 2 Selected");
                        break;
            case 2:  System.out.println("Option 3 Selected");
                        Inferno.running=false;
                        break;
        }
    }

    @Override
    public void tellstate() {
        System.out.println("Menu state active");
    }

    public void nextitem(){
        menuselection++;
        if (menuselection==mainmenu.length) menuselection=0;
    }

    public void previtem(){
        menuselection--;
        if (menuselection==-1) menuselection=mainmenu.length-1;
    }

    @Override
    public void update() {
        if (InputManager.keydown){
            nextitem();
            InputManager.keydown=false;
        }

        if (InputManager.keyup){
            previtem();
            InputManager.keyup=false;
        }

       if (InputManager.keyenter){
           InputManager.keyenter=false;
           Inferno.gamestate.changestate();
        }
    }

    @Override
    public void render(Graphics g) {
        g.drawImage(menubackgroundscreen,0,0, width, height, null);
        for (int i=0; i<mainmenu.length;i++){
            g.setFont(new Font("Arial",Font.BOLD,myfontsize));        
            g.setColor(Color.DARK_GRAY);
            if (menuselection==i) g.setColor(Color.WHITE);
            g.drawString(mainmenu[i], 100, 250+(i*myfontsize));    
        }  
    }
}
\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

There are more than a few things that are concerning in your code. For a start, you have a combination of static, and instance logic happening in your StateMainmenu:

static String[] mainmenu = new String [] {"Start","Options","Quit"};
static int menuselection;
public BufferedImage menubackgroundscreen;
private int myfontsize=60;
private int width=Inferno.appwidth, height=Inferno.appheight;

You have non-final non-private statics, and I presume they are accessed from all over the place. Additionally, the menubackgroundscreen is public?

Your classes should be properly encapsulated, and state changes should be only possible from triggers that are managed from inside the class.

The System.out.println() messages in places like:

@Override
public void tellstate() {
    System.out.println("Menu state active");
}

are misleading. Why are you debugging in a method like this? Is it debugging, or is that a core output of the program? Is the only thing needed to do a println? The method should be called tellState as well, with a capital S.

The methods:

public void nextitem(){
    menuselection++;
    if (menuselection==mainmenu.length) menuselection=0;
}

public void previtem(){
    menuselection--;
    if (menuselection==-1) menuselection=mainmenu.length-1;
}

manipulate a static field, but they are instance methods. Also, the use of a modulo would help (and a capital I for Item):

The nextItem (with a capital I) would be:

public void nextItem(){
    menuselection = (menuselection + 1) % mainmenu.length;
}

prevItem (with a capital I) is more complicated, but also:

public void prevItem(){
    menuselection = (menuselect + mainmenu.length - 1) % mainmenu.length;
}

The update() method seems contrived, and will also do multiple things depending on multiple states. Should the methods just return instead of falling through to the next check. Odd, but the current code could be right, just unusual. The way they call nextItem and prevItem implies those methods can be private.

Now, about the overall state system.

It's hard to get a feel for the actual state manipulation. You only have one state, and it is controlled mostly by static manipulations. The actual state changes are 'wrapped' in a class you do not show to us:

currentstate.setstate(new Selectionstate(currentstate));

What's this 'Selectionstateclass? (and why is it not calledSelectionState`?).

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.