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.
3 Answers
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.
-
\$\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\$ 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
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.
-
\$\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\$– akaltarCommented 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\$– akaltarCommented 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
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 ...
}
}
if
/else
you're trying to work your way around? \$\endgroup\$