Skip to content

Game state

ignacioerrico edited this page Jan 10, 2013 · 4 revisions

It will be necessary to keep track (and possibly store and retrieve) the game state, as defined in the terminology.

In this document I will describe:

  • Essential data structures
  • How to display game state

Essential data structures

There might be two to four players. Each player is identified by one of the following colors: green, red, blue and yellow. We will start with an enumeration of that set. Here's the pseudocode:

enum Color { Green, Red, Blue, Yellow };

Let's now define what a player is. Here's the pseudocode, and comments clarifying some details follow:

class Player
{
    /* Constructor */
    public Player(string name, Color color)
    {
        this.Name = name;
        this.Color = color;

        /* Initialize the player's coins.  Initially, they are all in the pocket. */
        for (int idx = 0; idx < 4; ++idx)
        {
            this.coin[idx] = 0;
        }
    }

    /* Name of the player */
    public string Name { get; }

    /* Color with which the player is identified */
    public Color Color { get; }

    /* Position of the player's coins */
    private TrackRange[] coin = new TrackRange[4];   // Each player has four coins (of the same color)

    /* Advance a given coin a number of squares */
    public void Advance(CoinRange coinNumber, DieRange squares)
    {
        TrackRange currentPosition = this.coin[coinNumber];

        if (currentPosition + squares > 59)
        {
            /* If the position exceeds 59, the player got a large number on the die
               and must 'bounce' back from the home pocket */
            this.coin[coinNumber] = 59 - (squares - (59 - currentPosition));
        }
        else
        {
            /* Otherwise, simply advance the current position by the number of given squares */
            this.coin[coinNumber] = currentPosition + squares;
        }
    }

    /* Returns the position of the given coin */
    public TrackRange PositionOfCoin(CoinRange coinNumber)
    {
        return this.coin[coinNumber];
    }

    /* Returns true if the coin number specified is in the pocket */
    public bool IsCoinInPocket(CoinRange coinNumber)
    {
        return this.coin[coinNumber] == 0;
    }

    /* Returns true if the coin number specified is in the home pocket */
    public bool IsCoinInHomePocket(CoinRange coinNumber)
    {
        return this.coin[coinNumber] == 59;
    }

    /* Returns lowest index of a coin that is in the pocket
       or -1 if all pockets have left the pocket */
    public int GetCoinInPocket()
    {
        for (int idx = 0; idx < 4; ++idx)
        {
            if (this.coin[idx] == 0)
            {
                return idx;
            }
        }

        return -1;
    }
}

In the pseudocode above I used three types that, for the sake of clarity, I haven't defined before. These are:

  • TrackRange, the possible values that the private coin property can hold. If that value is, say, 17, then that coin has left its pocket and has advanced 17 squares. If that value is 0, then the coin is still in the pocket. The maximum value that can be stored in the array is 59, which would correspond to the home pocket (final destination of the coin). So, briefly, TrackRange is a value in the range from 0 to 59 inclusive.

Why 59? We said zero corresponds to the pocket, so the range from 1 to 52 (inclusive) will be a square on the track. The next five values (53 to 58 inclusive) will represent the squares of the home run. So that leaves 59 for the home pocket.

  • CoinRange, a value that identifies one of the coins of the player. A player has four coins, so CoinRange is a value in the range from 0 to 3 inclusive (or 1 to 4 inclusive, if one-based indexing is preferred).

  • DieRange, an outcome of a die roll. Clearly, it is a value in the range from 0 to 5 inclusive (or 1 to 6 inclusive, if one-based indexing is preferred).

Please note: I used the three ranges defined above to be exactly precise about what I mean. Some programming languages don't allow this level of granularity. In those languages, the type to be used will be unsigned byte or equivalent (an integer in the range from 0 to 127 inclusive).

All the above allows us to define our players. Here's the pseudocode:

Player[] Players = new Player[4];   // At most there will be four players

Here's the pseudocode for a sample game initialization between two players:

/* Definition of the game's players */
Players player = new Players();

/* Setup of the first player */
player[0] = new Player("Ignacio", Color.Red);

/* Setup of the second player */
player[1] = new Player("Sudipta", Color.Green);

/* No more players, so I make that clear.  More players might be able to join later, though. */
player[2] = null;
player[3] = null;

Now, say Ignacio rolls the die and gets a six. We can do something like in this pseudocode:

int idx = player[0].GetCoinInPocket();
player[0].Advance(idx, 1);

Later in the game, Sudipta gets a four and decides he wants to move his coin identified by index 2. We would use this pseudocode:

player[1].Advance(2, 4);

It's worth noting that the above is not set on stone. It just lays a framework to think about the next problem: displaying game state

How to display game state

Displaying game state means showing on the screen the board with all the coins in the correct position.

I will make the following assumptions:

  1. The board follows a discrete rotational symmetry of the fourth order with respect to its center. That is, the board will look exactly the same (though with colors rotated) if it is rotated by an angle of 90° (= 360°/4).
  2. The squares of the track are all of the same dimensions.

Those assumptions agree with the image of the Ludo board that you see on Wikipedia, for example. Furthermore, they allow us to use different board images and even implement different variants of the game easily. Most importantly, they simplify the process to display game state, as I will describe next.

I will now define a coordinate system on the board. I will define the x axis on the top edge of the board and the y axis on the left edge of it. The origin, i.e. the point (0, 0), will be of course where the axes cross---on the top left corner of the board.

A board will have two points associated to it:

  • (x_c, y_c): the center of the board
  • (x_0, y_0): the top left corner of the left branch of the track

Read x_c as x sub c.

I will also define the length of the side of each square as h.

Additionally, I will need the coordinates of each of the coins in the top left pocket (the green pocket in the colored version). Let's call them (x_Pn, y_Pn), where n is in the range from 0 to 3 inclusive.

The first three parameters are illustrated in the following image.

NB: The board has been grayed out for clarity.

The three parameters associated to a board

I have numbered the squares in sequential order to be able to refer to them in an absolute way.

We want to define a function that, given a number that represents a square, returns the corresponding coordinates on the board. We will call this function AbsoluteToCoordinates. For example, AbsoluteToCoordinates(1) will return (x_0 + h, y_0), AbsoluteToCoordinates(2) will return (x_0 + 2 * h, y_0) and AbsoluteToCoordinates(7) will return (x_0 + 6 * h, y_0 - 2 * h).

From the section above, it should be clear that the parameter we pass to AbsoluteToCoordinates for coin n would be obtained from PositionOfCoin(n).

Now, the above worked fine for the top left pocket (the green one in the colored version of the board). What about the three others? Thanks to the assumptions I made at the beginning, the coordinates for the coins of the other colors can be easily obtained through a rotation around the center of the board, i.e. around (x_c, y_c).

For example, for red, we first obtain the coordinates for green and then rotate 90° clockwise. The matrix that performs such a rotation is [ [0, 1], [-1, 0] ].

For blue we have to rotate 180° clockwise (but a counter-clockwise rotation would produce the same effect in this case) and for yellow we have to rotate 270° clockwise.

Clone this wiki locally