Elegant way to count how many pieces of each type are present in a position

I have an abstract class which has 1 attributes like so:

PieceColor {
    WHITE, BLACK
}

abstract class Piece {
    PieceColor color

    abstract char getAbbr();
}

This class Piece has been extended by the classes Bishop Knight Rook King Queen and Pawn. Each of those class have a method called getAbbr() which returns the abbreviation of the Piece, KQBNRP.

Now I have a Map<Piece, Tile> piecePlacements which represents all the pieces currently on the board at a particular time.

I need to count how many pieces are there of a type say White Rook, Black Queen etc. But the normal way of doing this gets too long and is very ugly to look at. This is how I did it…

//0: White pieces, 1: Black pieces; 0-5: KQRBNP
    int[][] pieceCount = new int[2][6];

    for(Piece piece : piecePlacements.keySet()) {
        switch (piece.getAbbr()) {
            case 'K' :
                if (piece.color == PieceColor.WHITE) {
                    pieceCount[0][0]++;
                } else {
                    pieceCount[1][0]++;
                }
                break;
            case 'Q' :
                if (piece.color == PieceColor.WHITE) {
                    pieceCount[0][1]++;
                } else {
                    pieceCount[1][1]++;
                }
                break;
            case 'R' :
                if (piece.color == PieceColor.WHITE) {
                    pieceCount[0][2]++;
                } else {
                    pieceCount[1][2]++;
                }
                break;
            case 'B' :
                if (piece.color == PieceColor.WHITE) {
                    pieceCount[0][3]++;
                } else {
                    pieceCount[1][3]++;
                }
                break;
            case 'N' :
                if (piece.color == PieceColor.WHITE) {
                    pieceCount[0][4]++;
                } else {
                    pieceCount[1][4]++;
                }
                break;
            case 'P' :
                if (piece.color == PieceColor.WHITE) {
                    pieceCount[0][5]++;
                } else {
                    pieceCount[1][5]++;
                }
                break;
         }
    }

There has to be a more elegant way to this, which takes fewer lines of code. I mean what if there were more than 6 types of pieces and 2 types of colors. I cant just keep adding cases to the switch statement like this right? But I cant figure how to make this more elegant.

Answer

tl;dr

Define enums for both color and role of each piece.

Use the misnamed Enum#ordinal to get the index number of each enum object’s position within its definition. Use that number as the index into your arrays.

for ( Piece piece : pieces )          // `Piece` is class with two member fields: a `Color` enum object and a `Role` enum object.
{
    pieceCount                        // Your two-dimensional array. 
        [ piece.color().ordinal() ]   // Get the `Piece` object’s `Color` enum object’s definition position.
        [ piece.role().ordinal() ]    // Get the `Piece` object’s `Role` enum object’s definition position.
        ++                            // Using those two index numbers as index into your arrays, increment the count.
    ;
}

Details

Using enums makes this task much simpler. Enums offer the method ordinal, a misnomer as it actually returns the zero-based index (not one-based ordinal) number of the order in which an enum object was defined. We can use this enum index number as the index into your arrays.

Let’s define your Piece record with a pair of nested enums, Color and Role.

A record is a new feature in Java 16, for a brief way to write a class whose main purpose is transparently and immutably communicating data. The compiler implicitly creates constructor, getters, equals & hashCode, and toString. Not necessary for this solution, but ideal fit for your Piece class.

package work.basil.example.chess;

public record Piece(Color color , Role role)
{
    public enum Color
    { WHITE, BLACK }

    public enum Role
    { KING, QUEEN, ROOK, BISHOP, KNIGHT, PAWN }
}

So Piece.Color.WHITE.ordinal() returns 0, while BLACK returns 1.

We need some sample data.

List < Piece > pieces = List.of(
        new Piece( Piece.Color.BLACK , Piece.Role.QUEEN ) ,
        new Piece( Piece.Color.BLACK , Piece.Role.KNIGHT ) ,
        new Piece( Piece.Color.WHITE , Piece.Role.PAWN )
);

We can define your arrays in a soft-coded way, by asking each enum for its length.

int[][] pieceCount = new int[ Piece.Color.values().length ][ Piece.Role.values().length ]; // [2][6]

Next we loop the pieces on the board. We ask each Piece object’s Color and Role enum member field object for its index position by calling that misnamed ordinal() method.

for ( Piece piece : pieces )
{
    pieceCount[ piece.color().ordinal() ][ piece.role().ordinal() ]++;
}

Here is entire example app class.

package work.basil.example.chess;

import java.util.Arrays;
import java.util.List;

public class App
{
    public static void main ( String[] args )
    {
        App app = new App();
        app.demo();
    }

    private void demo ( )
    {
        List < Piece > pieces = List.of(
                new Piece( Piece.Color.BLACK , Piece.Role.QUEEN ) ,
                new Piece( Piece.Color.BLACK , Piece.Role.KNIGHT ) ,
                new Piece( Piece.Color.WHITE , Piece.Role.PAWN )
        );
        int[][] pieceCount = new int[ Piece.Color.values().length ][ Piece.Role.values().length ]; // [2][6]
        for ( Piece piece : pieces )
        {
            pieceCount[ piece.color().ordinal() ][ piece.role().ordinal() ]++;
        }

        System.out.println( "pieces = " + pieces );
        for ( int i = 0 ; i < pieceCount.length ; i++ )
        {
            System.out.println( Arrays.toString( pieceCount[ i ] ) );
        }
    }
}

When run.

pieces = [Piece[color=BLACK, role=QUEEN], Piece[color=BLACK, role=KNIGHT], Piece[color=WHITE, role=PAWN]]
[0, 0, 0, 0, 0, 1]
[0, 1, 0, 0, 1, 0]

Leave a Reply

Your email address will not be published. Required fields are marked *