Your first SFML game, Part II : The board

Hello reader!

In this post we’re creating the necessary code to display the  game’s board. It should be very easy to understand, so if you have any problems or suggestions please make a comment.

The main loop :

////////////////////////////////////////////////////////////
// main.cpp
////////////////////////////////////////////////////////////
#include <SFML/Graphics.hpp>
#include "board.h"

int main()
{
    // Create main window, using the best available resolution
    sf::RenderWindow App(sf::VideoMode::GetMode(0),
            "Your first SFML game: 4 in a row!");

    // Reduce CPU usage by limiting the times a frame
    // is drawn per second
    App.SetFramerateLimit(30);

    // This is the main loop
    // It will loop until you exit the program
    while (App.IsOpened())
    {
        // Here we process the events list
        sf::Event Event;
        while (App.GetEvent(Event))
        {
            // Close window : exit
            if (Event.Type == sf::Event::Closed)
                App.Close();
        }

        // Clear the screen with a color
        App.Clear();

        // Here you will draw all stuff in the frame buffer

        // Render the frame on screen
        App.Display();
    }

    return EXIT_SUCCESS;
}

If you compile this code, it will create a black window. This is the base of our code.

To create a board we shall have a class called Board. It should have a method to draw the board on screen, one to restart the game, one to insert tokens in a column and of course one to know if a player has won.

First we include the SFML headers and we define the size of the board:

////////////////////////////////////////////////////////////
// board.h
////////////////////////////////////////////////////////////

#ifndef BOARD
#define BOARD

#include <SFML/Graphics.hpp>

#define BOARD_Y_SIZE 6
#define BOARD_X_SIZE 7

Then we define some enums that we’ll use later on.

// These are the kind of tokens found in the board
enum Token
{
    Empty,
    Red,
    Yellow,
};

// And these, the outline types of the tokens
enum Outline
{
    Normal,
    Highlight
};

// Returned when trying to place a token in a column
enum Move
{
    Placed,
    Full,
    EndOfGame
};

Then we define the Board class with the necessary methods.

// This class has all the methods and properties to be used
// in the game board
class Board
{
    public:
        // Constructor
        Board(sf::RenderWindow &App);
        // Destructor
        ~Board();
        // Sets the board to empty tokens
        void restart();

        // Used to draw the board on screen
        void draw();

        // Called in each turn
        Move addTokenInColumn(unsigned int column, Token playerColor);

        // Gets the original window width
        int getWindowWidth();

        // The color of the gameboard
        sf::Color::Color boardColor;
    private:

        // Used to know if a player has won
        bool isEndOfGame   (const unsigned int y, const unsigned int x);
        bool fourHorizontal(const unsigned int y, const unsigned int x);
        bool fourVertical  (const unsigned int y, const unsigned int x);
        bool fourDiagonal  (const unsigned int y, const unsigned int x);

        // The matrix that represents the board in memory
        // Add borders to ease the search of a winnefr combination
        Token board[BOARD_Y_SIZE+2][BOARD_X_SIZE+2];

        Outline boardOutline[BOARD_Y_SIZE+2][BOARD_X_SIZE+2];

        // The window where to draw the board
        sf::RenderWindow &myWindow;

        // Colors of the board
        sf::Color redColor;
        sf::Color yellowColor;
        sf::Color emptyColor;
        sf::Color lineColor;
        sf::Color highlightColor;

        // Properties that define the size of a token
        float windowHeight;
        float windowWidth;
        // The size of the outline token brush
        float outline;
        // The radius of a token
        float radius;
        // The space between tokens
        float spaceY, spaceX;

};

#endif

We proceed then with the implementation. The constructor is a bit long I know;), but it initializes the colors of the board, the size of the tokens and the board itself.

Just a note, myWindow is initialized with the reference of the RenderWindow (the frame where you draw your stuff^^) passed from the main loop :

////////////////////////////////////////////////////////////
// board.cpp
////////////////////////////////////////////////////////////

#include "board.h"

Board::Board(sf::RenderWindow &App) : myWindow (App)
{
    // Set the colors of the board
    boardColor      = sf::Color::Color(0, 102, 153);
    redColor        = sf::Color::Color(214, 71, 0);
    yellowColor     = sf::Color::Color(235,235,0);
    emptyColor      = sf::Color::Color(224,224,224);
    lineColor       = sf::Color::Color(0, 51, 102);
    highlightColor  = sf::Color::Color(255, 153, 0);

    // Get the size of the window
    // in order to calculate the size of a token
    windowHeight = myWindow.GetHeight();
    windowWidth  = myWindow.GetWidth();

    // Get the best circle size
    if (windowHeight < windowWidth)
    {
        radius = windowHeight/(BOARD_Y_SIZE*2.0);
        // set the vertical space between circles equal
        // to 1/2 of the radius
        spaceY = 1.0/2.0 * radius;
        // set the outline of the circle as 1/15 of the radius
        outline = 1.0/15.0 * radius;
        // decrease the radius, by substracting the space
        // between circles and its outline
        radius = radius - spaceY/2.0 - outline;
        // set the horizontal space, knowing that there are
        // BOARD_X_SIZE circles and BOARD_X_SIZE + 1 spaces in a row
        spaceX = (windowWidth-(BOARD_X_SIZE*2.0)*radius)/(BOARD_X_SIZE + 1.0);
    }
    else
    {
        radius = windowWidth/(BOARD_X_SIZE*2.0);
        // set the horizontal space between circles equal
        // to 1/2 of the radius
        spaceX = 1.0/2.0 * radius;
        // set the outline of the circle as 1/15 of the radius
        outline = 1.0/15.0 * radius;
        // decrease the radius, by substracting the space
        // between circles and its outline
        radius = radius - spaceX/2.0 - outline;
        // set the horizontal space, knowing that there are
        // BOARD_Y_SIZE circles and BOARD_Y_SIZE + 1 spaces in a row
        spaceX = (windowWidth-(BOARD_Y_SIZE*2.0)*radius)/(BOARD_Y_SIZE + 1.0);
    }

    // Empty the board
    restart();
}

// Fill this destructor if needed
Board::~Board(){}

Then, the method to initialize the board itself (the tokens and its outline):

void Board::restart()
{
    // Change the value of all the elements of board to Empty
    for(int y=0; y<BOARD_Y_SIZE+1; y++)
    {
        for(int x=0; x<BOARD_X_SIZE+1; x++)
        {
            board[y][x] = Empty;
            boardOutline[y][x] = Normal;
        }
    }
}

The method to draw the circles (tokens) on screen are called at least 30 times per second. Every time we have to redraw everything on screen :

void Board::draw()
{
    for(int y=1; y<=BOARD_Y_SIZE; y++)
    {
        // Get the center of the next row of circles
        float centerY = (spaceY + radius)*y + radius*(y-1);
        for(int x=1; x<=BOARD_X_SIZE; x++)
        {
            // Get the center of the next column of circles
            float centerX = (spaceX + radius)*x + radius*(x-1);
            // Set the color of the circle
            sf::Color fillColor;
            if(board[y][x] == Empty)
                fillColor = emptyColor;
            else if(board[y][x] == Red)
                fillColor = redColor;
            else
                fillColor = yellowColor;
            // Set the color of the tokens outline
            sf::Color outlineColor;
            if (boardOutline[y][x] == Highlight)
                outlineColor = highlightColor;
            else
                outlineColor = lineColor;
            // Draw the circle in the buffer
            myWindow.Draw(sf::Shape::Circle(centerX, centerY, radius,
                                    fillColor, outline, outlineColor));
        }
    }
}

Each time a player adds a token in a column, we have to determine its “y” (row) position, to add it to the matrix if there is space available in the column of course. Then, we have to verify if the player has won by searching vertical, horizontal and diagonal combinations of 4 or more tokens of the same colour.

Move Board::addTokenInColumn(unsigned int column, Token playerColor)
{
    // Search for empty spaces in the column, from bottom to top
    for(int i = BOARD_Y_SIZE; i>0; i--)
    {
        // add the token (red or yellow) if there is space in the column
        if(board[i][column] == Empty)
        {
            board[i][column] = playerColor;
            //Verify if the player has won
            if (isEndOfGame(i, column))
                return EndOfGame;
            else
                return Placed;
        }
    }
    // if it ends up here, it means the column is already full
    // and the token cannot be placed in the column
    return Full;
}

The code that verifies if a player has won, and highlights the winning line(s):

bool Board::isEndOfGame(const unsigned int y, const unsigned int x)
{
    // Search for combinations of 4 or more tokens of the same
    // color and highlight them
    // We create a temp variable to force the call of all the
    // four in a row searches, this way, all the combinations
    // of four or more tokens get highlighted
    bool t = false;
    t |= fourHorizontal(y, x);
    t |= fourVertical(y, x);
    t |= fourDiagonal(y, x);
    return t;
}

bool Board::fourHorizontal(const unsigned int y, const unsigned int x)
{
    // Search for combinations of 4 or more tokens of the same
    // color in a horizontal line
    Token tokenColor = board[y][x];
    int cnt = 1, i;
    // search right the same color tokens
    for(i = x + 1; board[y][i]==tokenColor; i++)
        cnt++;
    // search left the same color tokens
    for(i = x - 1; board[y][i]==tokenColor; i--)
        cnt++;
    // Highlight the winner row
    if(cnt>=4)
    {
        while(board[y][++i] == tokenColor)
                boardOutline[y][i] = Highlight;
        return true;
    }
    else
        return false;
}

bool Board::fourVertical(const unsigned int y, const unsigned int x)
{
    // Search for combinations of 4 or more tokens of the same
    // color in a vertical line
    Token tokenColor = board[y][x];
    int cnt = 1, i;
    // search down the same color tokens
    for(i = y + 1; board[i][x]==tokenColor; i++)
        cnt++;
    // search up the same color tokens
    for(i = y - 1; board[i][x]==tokenColor; i--)
        cnt++;
    // Highlight the winner row
    if(cnt>=4)
    {
        while(board[++i][x] == tokenColor)
                boardOutline[i][x] = Highlight;
        return true;
    }
    else
        return false;
}
bool Board::fourDiagonal(const unsigned int y, const unsigned int x)
{
    // Search for combinations of 4 or more tokens of the same
    // color in a vertical line
    Token tokenColor = board[y][x];
    bool t = false;
    int cnt = 1, i, j;
    // search down-left the same color tokens
    for(i = y + 1, j = x - 1; board[i][j]==tokenColor; i++, j--)
        cnt++;
    // search up-right the same color tokens
    for(i = y - 1, j = x + 1; board[i][j]==tokenColor; i--, j++)
        cnt++;
    // Highlight the winner diagonal
    if(cnt>=4)
    {
        while(board[++i][--j] == tokenColor)
                boardOutline[i][j] = Highlight;
        t = true;
    }
    cnt = 1;
    // search down-right the same color tokens
    for(i = y + 1, j = x + 1; board[i][j]==tokenColor; i++, j++)
        cnt++;
    // search up-left the same color tokens
    for(i = y - 1, j = x - 1; board[i][j]==tokenColor; i--, j--)
        cnt++;
    // Highlight the winner diagonal
    if(cnt>=4)
    {
        while(board[++i][++j] == tokenColor)
                boardOutline[i][j] = Highlight;
        return true;
    }
    else
        return t;
}

And finally the code to get the windows width, without having a reference to “the window”:

int Board::getWindowWidth()
{
    // Returns the current width of the board/window
    // We use this as the UI doesn't have a reference
    // to the window (App or myWindow)
    return myWindow.GetWidth();
}

This is for the board implementation, it is certainly the biggest part of the game. But hey!, we’re almost finished ;) We just need a user interface, to enable the player to interact with the game, and that’s it. With a few more lines of code, you’ll be able to play you’re first SFML game!!

About these ads

8 comments on “Your first SFML game, Part II : The board

  1. I am having a few problems though… Whenever I run first part, it runs then it gets stuck trying to make the window. Then crashes. Any ideas? I don’t get any debug info so…

  2. Pingback: 2010 in review « Redkiing's Blog

    • Yes it should be sf::Color boardColor, strangely the compiler didn’t complain. I’m wondering whether this code would compile in other platforms / compilers. Or might it be a c++ feature ^^ ?. Which compiler are you using ?

  3. This really helped me to understand the basics of SFML. First I just copied all the code and tried to think how it works by myself. After that I checked it at your code with your comments. It was almost completely right! :D

    Thank you soo much..

  4. great tutorial but when i download the .exe the game wont come up. it just runs in the task manager but never comes up, same thing happens when i compile and run the source code.. mhmmm

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s