4

I'm working on a somewhat simple game. Currently trying to implement the game logic for moving the pieces around.

Logic is something like this:

does player have pieces in inventory?
    if yes:
        did they try to move it to an empty location
            if yes: ...
            if no: ...
    if no:
        ...

As you can see it's a bunch of nested if-else statements. My question is how could one refactor the code (or perhaps even approach the design) to avoid large nested blocks of if-else statements.

My game is not chess, but if you take chess for example, how should you approach the design as to avoid conditionals where the game logic is quite complex.

From the very brief search I've done polymorphism seems to be one(1)

(1) https://stackoverflow.com/questions/7264145/if-less-programming-basically-without-conditionals

francium
  • 149
  • 4
  • 2
    If the game logic is complex, then the logic is complex, and there is nothing you can do about it. You can remove if statements, you can't remove the complexity. – gnasher729 Mar 21 '16 at 16:47
  • possible duplicate of [Elegant ways to handle if(if else) else](http://programmers.stackexchange.com/q/122485/31260) – gnat Mar 21 '16 at 16:48
  • See the first comment on the linked page: "What is this company that your colleague speak of? I ask so I can avoid them. " – gnasher729 Mar 21 '16 at 16:49
  • As an exercise, write down exactly when you can take one of your opponent's pawns in a game of chess. It's complicated; that's all there is to it. – gnasher729 Mar 21 '16 at 16:51
  • You need to design your classes in a way an instantiated object is a representation of an `if` case, rather than a class' property. This way you delegate the decision from your business logic to factories, which are responsible for returning the desired implementation and making it seem like the logic is simpler, even though it's actually much more complicated because of the introduced abstraction. You will require an interface which can mean both or more cases of the `if` block and specific implementations of it. – Andy Mar 21 '16 at 17:05
  • Possible duplicate of [Elegant ways to handle if(if else) else](https://softwareengineering.stackexchange.com/questions/122485/elegant-ways-to-handle-ifif-else-else) – gnat Jul 31 '17 at 15:48

4 Answers4

4

One alternative to embedding complex decision logic directly into your code is to build up a custom data structure which represents the complex series/collection of decisions and/or states which you might otherwise be wiring into your code.

This technique is sometimes known as Rule-based programming, which has some association with weak forms of AI, where the structures which encapsulate your logic/decisions are known as "Rules".

The rationale for doing this is that data structures are declarative rather than procedural; sometimes it is much easier (and more expressive/idiomatic) to represent complex decisions in a declarative data structure than it is to represent those same decisions in procedural code.

Using some of the rules of a chess as per your question; consider an XML-based structure for the various different kinds of move patterns allowed for different pieces:

<ChessPieceMoves>

  <Piece type="Rook">
    <Moves capture="true" move="true">
      <CanMove pattern="Horizontal" />
      <CanMove pattern="Vertical" />
    </Moves>
  </Piece>

  <Piece type="King">
    <Moves capture="true" move="true">
      <CanMove pattern="Horizontal" rangeLimit="1" />
      <CanMove pattern="Vertical" rangeLimit="1"  />
      <CanMove pattern="ForwardDiagonal" rangeLimit="1" />
      <CanMove pattern="BackwardDiagonal" rangeLimit="1" />
    </Moves>
    <Moves capture="false" move="true">
      <CanMove pattern="Castle" firstMoveOnly="true" />
    </Moves>
  </Piece>

  <Piece type="Pawn">
    <Moves capture="true" move="false">
      <CanMove pattern="ForwardDiagonal" rangeLimit="1" />
    </Moves>

    <Moves capture="false" move="true">
      <CanMove pattern="Forward" firstMoveOnly="true" rangeLimit="2" />
      <CanMove pattern="Forward" rangeLimit="1" />
    </Moves>
  </Piece>

</ChessPieceMoves>

(Note - the above may or may not be a good structure for chess moves; I spent less than 5 minutes thinking about it).

Of course, then you need to write the code which binds those 'rules' into your code (i.e. code which reads the XML and represent the procedural code for each different behaviour/decision in that data); and this isn't necessarily a trivial task (Although something like C#'s XmlSerializer makes it a lot easier). The binding code should be significantly less complex than the logic to embed those decisions/rules into procedural code however.

Also, there are existing declarative logic languages and various kinds of rules engines already in existence; so you might choose to learn a logic language such as Prolog.

If the code logic ends up being more complex, then the data structure is probably bad and needs re-thinking; chances are that it would need more information (elements/attributes) to cover all the different possible moves in chess, and would probably need restructuring too.

Ben Cottrell
  • 11,656
  • 4
  • 30
  • 41
  • 2
    This answer represents a serious inner platform minefield. – whatsisname Mar 22 '16 at 04:25
  • 1
    @whatsisname I see it as an expressive [DSL](https://en.wikipedia.org/wiki/Domain-specific_language) that significantly reduces the complexity and cognitive overhead. – Izkata Mar 22 '16 at 14:27
2

I would tend to approach it something like:

val legalMoves = generateAllMovesWithin8()
  .filter(inBounds)
  .filterNot(passesThroughFriendlyPiece)
  .filterNot(passesEnemyPieceAfterTaking)
  .filterNot(putsOwnKingInCheck)

If you choose your data structures carefully, mostly only the first line would need to be piece-specific, with a couple small exceptions for knights and castling. The trick is to separate concerns so each function is dead simple.

Karl Bielefeldt
  • 146,727
  • 38
  • 279
  • 479
1

I advocate for the middle path. There's nothing wrong with if statements but deep nesting is something you want to avoid. As a rule of thumb, if you get to the third level of nesting, you should probably restructure the code. The first and most basic approach is to create new methods. So in your example, you'd have the top level if and then call methods that handle the second level. This is a perfectly fine approach but you might run into new issues. The main problem is if you have a bunch of calculations that you have to use in all the layers of nesting. Resist the temptation to move them to a higher-scope (like the object) simply to avoid passing parameters. That way lies madness.

The second approach is to use polymorphism. A good example create a class for each chess piece. They would each implement a method (or methods) determining their legal moves. You avoid having a big if statement saying something like if queen ..., if king ... if pawn ... Going even deeper, get a handle on the Strategy pattern. It's one of the best examples of effective OO design and highly relevant to games.

JimmyJames
  • 24,682
  • 2
  • 50
  • 92
  • Great answer. Could you please expand on what you means by "resist the temptation to move them to a higher-scope(like the object)... That way lies madness." – francium Mar 30 '16 at 03:26
  • 1
    When you are breaking down methods into smaller component methods, you can end up with a long list of parameters that you need to pass to the component methods. Often, to avoid this, developers will create variables in the object scope to 'pass' the variables around. This is a lesser form of the global variable anti-pattern. You should never put variables at a higher scope that is absolutely necessary. If you are truly buried in parameters, you should consider refactoring the method in to a object: http://refactoring.com/catalog/replaceMethodWithMethodObject.html – JimmyJames Mar 30 '16 at 13:09
0

you could make use of OOP to solve this.

var piece = GetPiece(..);
if (piece != null)
{
   board.Move(piece,location);
}
else
{
   //do something
}

Now the first line, piece != null handles the

does player have pieces in inventory?

The board.Move(piece,location) would have the logic to see

did they try to move it to an empty location.

Obviously piece has it's logic such as movement pattern.

Low Flying Pelican
  • 1,602
  • 9
  • 13