16

I am trying to design a Chess Game using OOPs concepts that has a UI element to it. My idea is to show the number of squares / cells a piece can travel to when selected. Basically I want to show the paths / directions in which the it can travel / attack in a different color.

Some thing like the following

(enter image description here)

So I have designed an abstract Piece class, which among others, has a Map object that keeps track of all the cells to which it can travel direction-wise, something like this Map<Direction, LinkedList<Cell>>

Now, say a Piece of the Queen's own team comes in its way of attack in the FORWARD direction and puts itself on the cell that's located at chessBoard[6][4]

What best way can I notify the Queen or any other piece watching that particular cell of the event so that each piece can call its own update method and redraw its path of attack?

I was thinking of going with the Observer pattern where every cell will be a subject and only the pieces watching them will be observers. So in any case anything happens in any of the cells watched by a particular set of pieces, only those pieces will be notified.

But then I am not sure if this is a good approach as I have to have at max 32 listeners for every cell. Not sure if it will be scalable at all.

Are there any better ways to do it?

Thanks for taking the time to read.

Auro
  • 291

3 Answers3

43

Having a "Map object that keeps track of all the cells to which it can travel" looks to me like an example of premature optimization.

Instead of remembering all those cells for each piece "just in case it could become slow", why not give a piece a method with the board as parameter, which returns the related List<Cell> (or Map<Direction, LinkedList<Cell>>) by calculating it when the method is called? This way, no notifications or observer pattern or "32 listeners" are needed any more. It may be debatable if it should really be a method of the piece, or a method of the board. However, if you put this method into the board instead of the piece, pieces will not have to know anything about the board, so this avoids a cyclic dependency between boards and pieces.

If it turns out later that this method is called quite often for the same, unchanged board so it becomes a performance bottleneck (which I doubt), you can still cache its results using memoization. That would lead to a solution where a Map<Direction, LinkedList<Cell>> per piece is required, but without the necessity of actively observing other board changes.

Doc Brown
  • 218,378
23

My suggestion is to make things as simple as possible. The more information you give to an object, the more complex it becomes, and the harder it is to keep track of it.

Let's break your program down into layers. What is there?

  • The pieces. They only know what kind of piece they are, and what color they are (black or white)
  • The board. It knows only about the pieces and how they're arranged.
  • The game state. The game state knows about the board, and also about extraneous information such as captured pieces.
  • The UI. The UI knows about the game state. It also knows about any graphics information like sprites.

The Pieces. A piece is... just a piece. It has a color, and a type (rook, king, queen, etc), but that's it. It can answer:

  • What am I? (Rook, King, Queen, etc)
  • Which side do I belong? (Black, White)

The Board. A board is just a 2D array where cells either do, or don't, have pieces. It can answer:

  • Where are the pieces?
  • Where are the empty squares?

In addition, based on the current state of the board, we can ask it to calculate:

  • What squares can be attacked by the piece at a particular location?
  • Is a particular piece in danger?
  • Is the King in Check?
  • Is a proposed move legal?

Example: Take the move B3 to A4.

  • If there's no piece on B3, it's illegal.
  • If the piece can't move diagonally, it's illegal.
  • If A4 is occupied by a piece of the same color, it's illegal.
  • If the King is in Check and the move doesn't protect the King, it's illegal. And so on.

The game state. The game state keeps track of what's happened so far in the game, along with the current state of the board. This includes:

  • What the board looks like now
  • History of moves (Like "Queen takes B4")
  • Who has captured what pieces
  • Whose turn is it?

The UI. Tbe UI is responsible for drawing the game state and the board. We can ask it:

  • How do things look? (What sprites or graphics primitives are used to draw a piece or the board?)
  • Where should I draw stuff?
  • When should I draw stuff?

The UI should be the only one that handles events. Pieces don't handle events. The game state doesn't handle events. Nor does the board handle events. When an event happens, the UI:

  • Checks if whatever happened was legal
  • Updates the game state if the thing was legal
  • Draws the updated board (and any associated update animations)

How do I do things, like highlight possible moves? When a user mouses over the board, the UI

  • Figures out which square it is
  • Gets the board from the game state
  • Asks the board what squares can be attacked by that piece.

This final question can be implemented in the form of a member function of the board that takes a position as input, and returns a list (or array) of positions that can be legally attacked.

Why use this approach? Every field, every property, every member that you add to class adds state. Every bit of state is something you have to manage, keep track of, and update as necessary. The code becomes a mess, and making changes is hard because one small change suddenly requires you to modify a dozen different classes.

Calculating a list of legal moves based on a 2D array of pieces is a lot cheaper than continually updating a Map<Direction, LinkedList<Cell>> for every piece. It'll be less code, too.

By breaking apart the program into layers, and giving each layer a single responsibility - to control the layer under it - we can write a program that is clean, efficent, and modular.

guntbert
  • 109
  • 3
Alecto
  • 571
3

The other answers already responded how you could do what you asked for. I want only to point out here that neither OOP nor design patterns (yes, they are quite different things) should be used just for the sake of using them.

They are tools and, as such, they should be chosen according to the purpose you want to reach, not the other way around.

I can't grasp from your question if you need to make a chess program and has chosen to do it with OOP, or if you want to learn OOP and then choose Chess as a conduit for learning it.

If it's the latter, as I suspect, my suggestion is that you start with a lower bar, though.

Board games are as nice a way to learn OOP as many other, and better than most I've seen being used. However, I'd suggest you star with a simpler game, say tic-tac-toe, and then move up the ladder of complexity (maybe checkers, minesweeper, backgammon etc) until you reach chess and even more complex games.

If your goal is to learn OOP, the approach I'm suggesting would lead you to learn a lot of more concepts and practices than just how to do Chess over OOP.