A new class "State"? How to tackle bigger programs?

edited July 2015 in How To...

hello all,

I am working on a bigger program and I am a bit overwhelmed by its complexity at the moment.

So I want to reduce the complexity.

At the moment, the screen buttons (the ones you click with the mouse) are a mess.

I am using good old states but in each state/ screen I have like 3 to 10 different screen buttons.

So I was tinking about making a class State now finally and an array of this class State.

Thus I could say states[currentState].displayButtons(); or in mousePressed I could say Button currButton = states[currentState].getPressedButton(); and so on.

Since all states have a different number of buttons (no, I am not using a lib for the buttons) I'd use an ArrayList<Button> buttons = new ArrayList(); in the class State. Different for each state.

Each state would hold another list of buttons (and a different number of buttons).

Also, I was thinking to give each button a commandID - when the button gets pressed all states could use the same executeCommandID() function outside the class that gets an ID and executes it (with switch(commandID)).

This last point is a bit vague, would it work?

The class would also handle the keyPressed() (not done yet)

idea:

class State {

    int id;
    String name; 
    ArrayList<Button> buttons = new ArrayList();

    void displayButtons() {
        for (Button currButton : buttons) {
            currButton.display();
        }// for
    } // method

    void getPressedButton() {
        for (Button currButton : buttons) {
            if (currButton.over()) {
                return currButton; 
            }
        }// for
    } // method

} // class

My question:

  • has somebody done this?

  • What is the right path here?

Also normally I use

  • drawStuffForStateSplashScreen

  • drawStuffForStateHelpScreen

  • drawStuffForStateGameScreen

but how can I implement this into a class State?

because when I make a new state I can not pass a String (or function) "drawStuffForStateSplashScreen" or "drawStuffForStateHelpScreen" to the class State for using this function (which would be outside the class) because functions can not be used as a param....

or I would have a long if else if construct / switch clause in the class State:

void displayTheScreenForTheState() {

    switch(id) {

    case stateSplashScreen:
    drawStuffForStateSplashScreen();
    break; 

    case stateHelpScreen:
    drawStuffForStateHelpScreen();
    break; 

    case stateGameScreen:
    drawStuffForStateGame();
    break; 

    } // switch

} // method

with drawStuffForStateSplashScreen() etc. being outside the class.

but this looks a bit longwinded............

And I would have the

final int stateSplashScreen = 0;
final int stateHelpScreen = 1;
final int stateGameScreen = 2;

etc. outside the class too?

oh dear............... what a mess...............

Thank you for your thoughts,

Chrisir.... ;-)

Tagged:

Answers

  • Answer ✓

    Are you me? I've been kicking this idea about myself.

    What you really want to do is avoid using switch at all.

    class Button{
      int x,y;
      Button(int ix, int iy){
        x=ix;
        y=iy;
      }
      void draw(){
        fill(0,0,255);
        rect(x,y,20,20);
      }
      void onMousePress(){
        if(mouseX>x&&mouseX<x+20&&mouseY>y&&mouseY<y+20){
          println("click");
        }
      }
    }
    
    class LargeButton extends Button{
      LargeButton(int ix, int iy){
        super(ix,iy);
      }
      void draw(){
        fill(0,0,255);
        rect(x,y,40,40);
      }
      void onMousePress(){
        if(mouseX>x&&mouseX<x+40&&mouseY>y&&mouseY<y+40){
          println("CLICK");
        }
      }
    }
    
    class NextButton extends Button{
      NextButton(int ix, int iy){
        super(ix,iy);
      }
      void draw(){
        fill(255,0,255);
        rect(x,y,40,40);
      }
      void onMousePress(){
        if(mouseX>x&&mouseX<x+40&&mouseY>y&&mouseY<y+40){
          currentState++;
        }
      }
    }
    
    class State {
      int id;
      State() {
        id = states.size();
        println("Base class State constructor - created State " + id );
      }
      void draw() {
      }
      void onMousePress() {
      }
    }
    
    class StartScreenState extends State {
      String welcomeText;
      StartScreenState() {
        super();
        welcomeText = "Welcome to the Start Screen State!";
      }
      void draw() {
        background(0);
        fill(255);
        text(welcomeText, 20, 20);
      }
      void onMousePress() {
        currentState++;
      }
    }
    
    class CounterState extends State {
      int counter;
      CounterState() {
        super();
      }
      void draw() {
        background(64);
        fill(255);
        text(counter, 20, 20);
      }
      void onMousePress() {
        counter++;
        if (counter==11) {
          currentState++;
        }
      }
    }
    
    class ButtonState extends State {
      ArrayList<Button> buttons;
      ButtonState() {
        super();
        buttons = new ArrayList();
        buttons.add( new Button(40,40) );
        buttons.add( new Button(70,40) );
        buttons.add( new LargeButton(100,40) );
        buttons.add( new NextButton(150,40) );
      }
      void draw() {
        background(128,0,0);
        for(int i=0; i<buttons.size();  buttons.get(i++).draw() );
      }
      void onMousePress() {
        for(int i=0; i<buttons.size();  buttons.get(i++).onMousePress() );
      }
    }
    
    class EndState extends State {
      EndState() {
        super();
      }
      void draw() {
        background(128);
        fill(255);
        text("DONE", 20, 20);
      }
      void onMousePress() {
      }
    }
    
    ArrayList<State> states = new ArrayList();
    int currentState = 0;
    
    void setup() {
      size(600, 400);
      states.add( new StartScreenState() );
      states.add( new CounterState() );
      states.add( new ButtonState() );
      states.add( new EndState() );
    }
    
    void draw() {
      states.get(currentState).draw();
    }
    
    void mousePressed() {
      states.get(currentState).onMousePress();
    }
    
  • wow, that's absolutely a cool approach.

    but basically, you have to rewrite the whole thing for each sketch and you can't have one framework class State that you use and that stays fixed in all sketches while you build the individual sketch around it every new sketch, right?

    Are there other approaches too?

  • Answer ✓

    When I started building more complex JS applications I found some really useful resources on design patterns. A search for 'java design patterns' might turn up some useful ideas; though maybe others here who have much more experience with Java than me can recommend some directly ;)

    Must admit that looking at that Wikipedia page is a bit overwhelming: there are a lot there and some arcane language to comprehend... So, the observer and mediator patterns both spring out as ones I found useful and more relevant to what you're trying to achieve. One other commonly used pattern for interfaces is Model View Controller (MVC) so that's also worth searching for...

  • edited July 2015 Answer ✓

    Essentially, the problem boils down to a few questions:

    • What do I need to do to setup being in state X?
    • What do I draw when in state X?
    • What happens when I do input Y in state X?

    No matter how you go about it, you will still need to answer these questions for any program state.

    My above example answers these questions by (respectively) the constructor, draw() and onMousePress() methods of a new class that extends the base State class

    But there are other ways that you can deal with this too. If you can work out how to call a function based on its name, you can have a centralized State Manager that you can register functions to call when the above questions need answers.

    The MVC framework Blindfish mentions is another option. Have one Model of your data (which would manage all your data and states), and then Views of it (so you can, for instance, know what to draw at a given moment), and also Controllers that allow you to manipulate that data (as you might have to when dealing with inputs like mouse clicks).

  • edited July 2015

    It's also interesting that Processing itself has easy answers to these questions.

    setup() lets us setup a sketch.

    draw() lets us update it and display things.

    mousePressed() (and functions like it) let use deal with input.

    Maybe we should see if we can extend the PApplet somehow?

  • great discussion, thanks to both of you!

    Chrisir

  • The 'state manager' you describe sounds a lot like the mediator pattern. In JS it's incredibly easy to register a callback function to launch on a given event; or call a method based on a string; but from the questions I've seen on the forum it's obviously less straightforward to do this in Java. Demos of both of these might prove really useful!

  • _vk_vk
    edited July 2015

    In java callbacks are done with reflection... An example is the selectFoder() from Processing's API.

    In the source at line 6359 one can see that selectFolder() calls

    selectCallback(selectedFile, callbackMethod, callbackObject);
    

    and in line 6365 goes the selectCallback() code:

    static private void selectCallback(File selectedFile,
                                         String callbackMethod,
                                         Object callbackObject) {
        try {
          Class<?> callbackClass = callbackObject.getClass();
          Method selectMethod =
            callbackClass.getMethod(callbackMethod, new Class[] { File.class });
          selectMethod.invoke(callbackObject, new Object[] { selectedFile });
    
        } catch (IllegalAccessException iae) {
          System.err.println(callbackMethod + "() must be public");
    
        } catch (InvocationTargetException ite) {
          ite.printStackTrace();
    
        } catch (NoSuchMethodException nsme) {
          System.err.println(callbackMethod + "() could not be found");
        }
      }
    

    there are some good answers in this question:

    http://forum.processing.org/two/discussion/570/how-to-pass-a-function-method-as-a-parameter-to-another-function-method-callback-function

  • edited July 2015

    Don't forget that we can call a function by its String name via method("") or thread("")! =P~

  • What? Really?

    Well then. That's just super.

  • Just asking why it's best to avoid using switch in draw(), other than a bit of time overhead it does work. Are there other reasons?

  • Good OOP? No reason?

    Anyway, here's a StateSystem class & associated code, with a small demo sketch...

    StateSystem ss = new StateSystem();
    
    void setup() {
      size(400, 400);  
    
      ss.add_state( "StartState" );
      ss.register_draw( "StartState", "start_draw" );
      ss.register_mousePressed( "StartState", "start_mousePressed" );
    
      ss.add_state( "EndState" );
      ss.register_draw( "EndState", "end_draw" );
    }
    
    void start_draw() {
      background(0);
      text("START", 20, 20);
    }
    
    void start_mousePressed() {
      ss.to_state("EndState");
    }
    
    void end_draw() {
      background(128);
      text("END", 20, 20);
    } 
    
    
    
    // --- No need to modify below this point.
    
    class StateSystem {
      ArrayList<State> s = new ArrayList();
      int c = 0;
      StateSystem() {
      }
      void draw() {
        s.get(c).draw();
      }
      void mousePressed() {
        s.get(c).mousePressed();
      }
      void mouseReleased() {
        s.get(c).mouseReleased();
      }
      void keyPressed() {
        s.get(c).keyPressed();
      }
      void keyReleased() {
        s.get(c).keyReleased();
      }
      void keyTyped() {
        s.get(c).keyTyped();
      }
      int add_state(String n) {
        s.add( new State(n) );
        return s.size();
      }
      void remove_state(String n) {
        for (int t=s.size()-1; t>=0; t--) {
          if (s.get(t).named(n)) {
            s.remove(t);
          }
        }
      }
      void register_draw( String n, String f ) {
        for (int t=s.size()-1; t>=0; t--) {
          if (s.get(t).named(n)) {
            s.get(t).register_draw(f);
          }
        }
      }
      void register_mousePressed( String n, String f ) {
        for (int t=s.size()-1; t>=0; t--) {
          if (s.get(t).named(n)) {
            s.get(t).register_mousePressed(f);
          }
        }
      }
      void register_mouseReleased( String n, String f ) {
        for (int t=s.size()-1; t>=0; t--) {
          if (s.get(t).named(n)) {
            s.get(t).register_mouseReleased(f);
          }
        }
      }
      void register_keyPressed( String n, String f ) {
        for (int t=s.size()-1; t>=0; t--) {
          if (s.get(t).named(n)) {
            s.get(t).register_keyPressed(f);
          }
        }
      }
      void register_keyReleased( String n, String f ) {
        for (int t=s.size()-1; t>=0; t--) {
          if (s.get(t).named(n)) {
            s.get(t).register_keyReleased(f);
          }
        }
      }
      void register_keyTyped( String n, String f ) {
        for (int t=s.size()-1; t>=0; t--) {
          if (s.get(t).named(n)) {
            s.get(t).register_keyTyped(f);
          }
        }
      }
      void to_state( String n ) {
        for (int t=s.size()-1; t>=0; t--) {
          if (s.get(t).named(n)) {
            c = t;
          }
        }
      }
    }
    
    void draw() {
      ss.draw();
    }
    
    void mousePressed() {
      ss.mousePressed();
    }
    
    void mouseReleased() {
      ss.mouseReleased();
    }
    
    void keyPressed() {
      ss.keyPressed();
    }
    
    void keyReleased() {
      ss.keyReleased();
    }
    
    void keyTyped() {
      ss.keyTyped();
    }
    
    class State {
      String[] s = new String[7];
      State(String n) {
        s[0] = n;
        for(int t=1; t<s.length;s[t++]="");
      }
      boolean named(String n) {
        return s[0].equals(n);
      }
      void draw() {
        call(1);
      }
      void mousePressed() {
        call(2);
      }
      void mouseReleased() {
        call(3);
      }
      void keyPressed() {
        call(4);
      }
      void keyReleased() {
        call(5);
      }
      void keyTyped() {
        call(6);
      }
      void call(int w) {
        if (!s[w].equals("")) method(s[w]);
      }
      void register_draw(String f) {
        s[1] = f;
      }
      void register_mousePressed(String f) {
        s[2] = f;
      }
      void register_mouseReleased(String f) {
        s[3] = f;
      }
      void register_keyPressed(String f) {
        s[4] = f;
      }
      void register_keyReleased(String f) {
        s[5] = f;
      }
      void register_keyTyped(String f) {
        s[6] = f;
      }
    }
    
  • Yes, I like your method, neat. I have an int variable that I use in a switch to choose what gets displayed. It works. I'm self taught in OOP so I imagine my code could do with a lot of improving ...

  • amzing, TFGuy44

    can you explain, briefly, what is it doing?

  • Answer ✓

    Sure. There's a StateSystem class, which I created an instance of, called ss. Now the state system will track all your sketch's states. But to do that, it needs to know which states you want. So you can tell it to add or remove states with add_state() and remove_state(), and pass in a String that is the name of the state that you want to add or remove.

    ... Actually don't use remove_state(). I didn't test it, and thinking about it now, it will probably break the sketch if you use it (imagine removing the active state, say).

    After a state is added to the state system, you can also tell the state system which functions you want to run when that state is the active state. This is done with the register_*() functions. To register a draw function, you tell the state system which state you are registering it for (by the state's name) and the name of the function you want to call (or "" to remove it), by calling register_draw("StateName", "StatesDrawFunction" );

    Now, Processing handles when to call draw(), mousePressed(), mouseReleased, keyPressed(), keyTyped(), and keyReleased(). When those functions are called, they simply call the state system and tell it which function was called. The state system then calls the correct registered function for the active state. I picked these ones because they are the most common (and only?) means of doing user input.

    There's also a to_state() function that lets you change the active state. You can call this from your registered functions to go to a different state.

    There are some drawbacks. For example, data in one state is not associated with that state, unlike in the inheriting version above. Tracking varibles is something that you will still have to handle. There's also a lot of function overhead. Plus you should only have one instance of the state system, as Processing's draw() is hard coded to call ss.draw().

    A State, of course, is just an array of 7 Strings. One for the state's name and six more for the names of the registered functions to call (if any).

    Does that help explain it?

  • Wow, that's good work indeed,

    thank you so much!

  • @TFGuy44 :

    So on an everyday basis: would you use your first solution?

    It's good but complex.....

  • edited July 2015 Answer ✓

    No, I wouldn't use either of them. Usually I write sketches that are not complex enough to warrant a state management system at all. Usually all the data the sketch needs is fine at the global scope, and switch statements inside draw() or mousePressed() are enough to route input.

    If, however, I was going to write a more complex sketch, then, for the record, yes, I still like the inheriting approach better, for the simple reason that it also encapsulates the data associated with each state.

  • edited July 2015

    I see

    There would also be other classes than state (player, enemy...)

    And some global vars that are used by all states maybe

    Thank you again, great advice!

Sign In or Register to comment.