1
\$\begingroup\$

I am creating a sudoku game using the android sdk. I have a PreferenceActivity filled with different settings, such as "Highlight all digits" or "activate the visual helper". The problem is that I am trying to avoid using the if else pattern everywhere in my code in order to check if each settings is activated to execute a specific code. I am looking for the best pattern that could help me to handle my different settings. My code is pretty clear(I think), I have a Grid class containing all the grid information and a Game view, that handles inputs and interacts with the Grid class.

\$\endgroup\$
2
  • \$\begingroup\$ Could you add an example of the kind of if/else you're trying to work your way around? \$\endgroup\$
    – Vaillancourt
    Commented Aug 8, 2015 at 13:52
  • \$\begingroup\$ (Please edit your question :] ) \$\endgroup\$
    – Vaillancourt
    Commented Aug 8, 2015 at 13:52

3 Answers 3

3
\$\begingroup\$

One way to avoid using a load of if/else/else if statements that always checks the same set of things is to use the state machine design pattern.

You start by defining the different states that influence the behaviour, based on the player preferences.

Then you extract the behaviour from where it is now, and add it to a method of a class that derives from a base class.

Depending on the user's preferences, you instantiate the appropriate derived class, and delegate the function calls to that object.

If you have these states:

  • stateHighlightAllDigits
  • stateActivateVisualHelper
  • stateActivateVisualHelper_HighlightAllDigits

And if your code looks like (pseudo code):

class Grid:
  function draw:
    // ...

    if param[HighlightAllDigits] and [ActivateVisualHelper]:
      // draw stuff with the visual helper AND all digits highligted
    else if param[HighlightAllDigits] and not [ActivateVisualHelper]:
      // draw stuff with all digits highligted
    else if not param[HighlightAllDigits] and [ActivateVisualHelper]:
      // draw stuff with the visual helper
    else:
      // default way of drawing

    // ...

You would split your code into something like this:

class BaseState:
  function draw:
    // default way of drawing

class StateHighlightAllDigits inheritsFrom BaseState:
  function draw override:
    // draw stuff with all digits highligted

class StateActivateVisualHelper inheritsFrom BaseState:
  function draw override:
    // draw stuff with the visual helper

class StateActivateVisualHelper_HighlightAllDigits inheritsFrom BaseState:
  function draw override:
    // draw stuff with the visual helper AND all digits highligted

And your Grid:

class Grid:
  BaseState mCurrentState

  function init:
    // you still have to do the checks, but you do it only once
    if param[HighlightAllDigits] and [ActivateVisualHelper]:
      mCurrentState = new StateActivateVisualHelper_HighlightAllDigits()
    else if param[HighlightAllDigits] and not [ActivateVisualHelper]:
      mCurrentState = new StateHighlightAllDigits()
    else if not param[HighlightAllDigits] and [ActivateVisualHelper]:
      mCurrentState = new StateActivateVisualHelper()
    else:
      mCurrentState = new BaseState()

  function draw:
    // ...
    // Delegate the way of doing things to a specialized method.
    mCurrentState.draw()
    // ...

This way, your code is very clean: you can see clearly what it's doing without getting bothered by a bloat of if/else/else if everywhere.

\$\endgroup\$
2
  • \$\begingroup\$ The state classes hierarchy is hardly ideal. With 2 settings you need 1 additional subclass. With 3 settings you'd need 4 (AB, AC, BC, ABC), with 4 settings 11 subclasses (2^4 - 1 - 4) and so on... \$\endgroup\$
    – Trang Oul
    Commented Feb 28, 2023 at 18:50
  • \$\begingroup\$ @TrangOul You're right. Without more details about the user's situation, it's difficult to provide the best solution to their issue. \$\endgroup\$
    – Vaillancourt
    Commented Mar 1, 2023 at 21:55
0
\$\begingroup\$

As mentioned by @alexandre, without specific examples it's hard to offer specific fixes...

But!

It sounds like your intuition is, reasonably, that by simplifying your code structure, you'll reduce the maintenance burden and reduce the chance of bugs.

Another approach to help in this regard is unit tests. This aims to nail down the functionality independently from the implementation. A unit test is like a "mini app" that uses your code/classes/engine, but only does one thing at a time and confirms the result.

void test1() {
    Game game(setup params);
    game.moreSetup();
    game.event(someEvent);
    auto x = game.someQuery();
    assertEquals(whatXShouldBe, x);
    game.teardown();
}

This moves your focus from coding style to code correctness.

And then, as you add features and maybe think there's too many if-elses or what not, and want to try a different approach, you can refactor but keep the same tests.

Code always trends towards messiness. Sometimes we can reduce the decay a little... And unit tests are a way to know that it still works, even when it's messy.

\$\endgroup\$
4
  • \$\begingroup\$ I don't think unit testing is suitable for games. Its extremely hard to unit test graphics code, and thats one of the key points of games. I speak from experience, I have tried, but abandoned testing because it slowed down my progress by a factor of 10. \$\endgroup\$
    – akaltar
    Commented Aug 9, 2015 at 10:21
  • \$\begingroup\$ @akaltar On graphics code I agree with you. For game logic, physics engine, level loading and such, I've had good success with TDD. Doing so also guides you towards a cleaner separation of logic & graphics. "Testing slows us down" is a common fallacy. Here's one: jamesgolick.com/2007/8/22/… \$\endgroup\$ Commented Aug 9, 2015 at 13:20
  • \$\begingroup\$ Mine is a one man project, and as I realized, the main greatness of TDD comes from better teamwork. In small teams, where everyone knows what he has to do clearly, I believe TDD is just a slowdown. But with larger projects and more contributors it definitely is a great idea. \$\endgroup\$
    – akaltar
    Commented Aug 9, 2015 at 20:26
  • \$\begingroup\$ @akaltar (y) It's all tradeoffs. If it works, then, AOK! \$\endgroup\$ Commented Aug 10, 2015 at 0:41
0
\$\begingroup\$

What you can do is combine the Strategy pattern and Factory pattern.

Use the Factory pattern to build a drawer class that implements a given drawing strategy. In this way, you use composition or inheritance to determine behaviour at run time, and your if-else statements would be concentrated in the factory, instead of being scattered everywhere.

An example:

class GridDrawer {
    // selected strategies
    CellDrawer cell;
    LineDrawer line;
    ...
    GridDrawer(Map options) {
        // factory
        cell = BasicCellDrawer();
        line = BasicLineDrawer();
        if (options.get("highlight")) {
            cell = HighlightedCellDrawer(cell, options.get("highlightColor"));
            line = HighlightedLineDrawer(line);
        }
        if (options.get("helper")) {
            cell = VisualHelperDrawer(cell);
        }
    }
    void draw(Canvas canvas, Game game) {
        ...
    }
}

class HighlightedCellDrawer implements CellDrawer {
    HighlightedCellDrawer(CellDrawer base, Color color) { ... }
    void draw(Canvas canvas, Game game) {
        // ... draw something behind the cell ...
        base.draw(canvas, game);
        // ... draw something above the cell ...
    }
}
\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .