0

I'm trying to make TicTacToe with SFML as a beginner project, to figure stuff out first I tried to display 9 boxes on my window (grid like), but I'm getting a duplicate definition error and I'm not really sure where the problem is to be honest. Would appreciate it if you gave it a look.

main.cpp:

#include "grid.h"

using sf::Event, sf::Keyboard;


int main() {
    // game objects
    sf::RenderWindow window(sf::VideoMode(500, 500), "TicTacToe", sf::Style::Titlebar || sf::Style::Close);
    sf::Event EVENT;
    
    grid::Init();

    // main loop
    while (window.isOpen()) {
        
        // events handler
        while (window.pollEvent(EVENT)) {
            switch (EVENT.type) {
                // window closed
                case Event::Closed:
                    window.close();
                    break;

                // ESCAPE key pressed
                case Event::KeyPressed:
                    if (Keyboard::isKeyPressed(Keyboard::Escape)) {
                        window.close();
                    }

                    break;
            }
        }

        // renderer
        window.clear();

        // game grid
        grid::Render(window);

        window.display();
    }

    return 0;
}

grid.h:

#ifndef TILE_H
#define TILE_H

#include <vector>
#include <iostream>

#include <SFML/Graphics.hpp>

using sf::RenderTarget, std::vector, sf::RectangleShape, sf::Color, sf::Vector2f;


namespace grid {
    // classes
    class Cell {
        private:
            // attributes
            RectangleShape shape;

        public:
            // constructor - destructor
            Cell(int size, float xPos, float yPos, Color color);
            virtual ~Cell();

            // accessors
            RectangleShape getShape();

            // modifiers
            void setSize(int size);
            void setPosition(float xPos, float yPos);
            void setColor(Color color);
    };

    // variables
    vector<Cell> cells;

    // functions
    void Init();
    void Render(sf::RenderTarget& target);
};


#endif // #ifndef TILE_H

grid.cpp:

#include "grid.h"


// functions
void grid::Init() {
    std::cout << "initalizing grid" << std::endl;

    int cellSize = (500 / 3) - (10 * 2);
    float xPos = 0;
    float yPos = 0;

    for (int i = 0; i < 9; i++) {
        xPos += (500 / 3);

        if (i % 3 == 0) {
            yPos += (500 / 3);
        }

        grid::Cell newCell(cellSize, xPos, yPos, Color::White);
        grid::cells.push_back(newCell);
    }

    std::cout << "initalized grid" << std::endl;
}

void grid::Render(sf::RenderTarget& target) {
    for (auto cell: grid::cells) {
        target.draw(cell.getShape());
    }
}


// constrictor - destructor
grid::Cell::Cell(int size, float xPos, float yPos, Color color) {
    grid::Cell::setSize(size);
    grid::Cell::setPosition(xPos, yPos);
    grid::Cell::setColor(color);
}

grid::Cell::~Cell() {}


// accessors
RectangleShape grid::Cell::getShape() {
    return grid::Cell::shape;
}


// modifiers
void grid::Cell::setSize(int size) {
    grid::Cell::shape.setSize(Vector2f(size, size));
}

void grid::Cell::setPosition(float xPos, float yPos) {
    grid::Cell::shape.setPosition(Vector2f(xPos, yPos));
}

void grid::Cell::setColor(Color color) {
    grid::Cell::shape.setFillColor(color);
}

makefile:

main: grid.cpp main.cpp
    g++ grid.cpp main.cpp -o "tictactoe" -I C:/Users/Marsel/Documents/SFML-2.5.1/include -L C:/Users/Marsel/Documents/SFML-2.5.1/lib -D SFML_STATIC -lsfml-graphics -lsfml-window -lsfml-system && tictactoe.exe

Output (Error):

g++ grid.cpp main.cpp -o "tictactoe" -I C:/Users/Marsel/Documents/SFML-2.5.1/include -L C:/Users/Marsel/Documents/SFML-2.5.1/lib -D SFML_STATIC -lsfml-graphics -lsfml-window -lsfml-system && tictactoe.exe
c:/programdata/chocolatey/lib/mingw/tools/install/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/11.2.0/../../../../x86_64-w64-mingw32/bin/ld.exe: C:\Users\Marsel\AppData\Local\Temp\ccqymyhM.o:main.cpp:(.bss+0x0): multiple definition of `grid::cells'; C:\Users\Marsel\AppData\Local\Temp\cca8rixi.o:grid.cpp:(.bss+0x0): first defined here
collect2.exe: error: ld returned 1 exit status
make: *** [main] Error 1
[Finished in 9.1s]
Marseel
  • 11
  • 3
  • 2
    We require all code to be added to the question itself as text, not as a link or a screenshot. – HolyBlackCat Jul 02 '22 at 03:18
  • 1
    Variables declared in headers at namespace scope have to be either `inline` or `extern` (the latter then need to be defined in a single .cpp file). – HolyBlackCat Jul 02 '22 at 03:19
  • According to the error message, the symbol with duplicate definitions is named `grid::cells`. You might try searching all source files for that entire string. Failing that, try narrowing things down by searching for a file containing both strings `grid` and `cells` (to allow for things like `namespace grid {void cells() {}}` where the name is not fully qualified) and then inspect the files found. If that fails, you need to inspect more closely (e.g. looking for macro usage that expands to `cells` or `grid` or `cells::grid`). – Peter Jul 02 '22 at 04:03

2 Answers2

0

apparently adding inline before vector<Cell> cells; in grid.h fixed it. Not sure why but I won't complain. If anyone got an explanation I'd like to hear it.

updated grid.h:

#ifndef TILE_H
#define TILE_H

#include <vector>
#include <iostream>

#include <SFML/Graphics.hpp>

using sf::RenderTarget, std::vector, sf::RectangleShape, sf::Color, sf::Vector2f;


namespace grid {
    // classes
    class Cell {
        private:
            // attributes
            RectangleShape shape;

        public:
            // constructor - destructor
            Cell(int size, float xPos, float yPos, Color color);
            virtual ~Cell();

            // accessors
            RectangleShape getShape();

            // modifiers
            void setSize(int size);
            void setPosition(float xPos, float yPos);
            void setColor(Color color);
    };

    // variables
    inline vector<Cell> cells;

    // functions
    void Init();
    void Render(sf::RenderTarget& target);
};


#endif // #ifndef TILE_H
Marseel
  • 11
  • 3
0

To understand what's going on here, we first have to understand how header files and #include work in C++.

In C++, if you have the files:

example.h

class Example {
    // ...
};

example.cpp

#include "example.h"

Example obj; 

Then, the C++ compiler will just copy-paste example.h into example.cpp, like so:

example.cpp after including example.h

class Example {
    // ...
};

Example obj; 

Basically, #include "example.h" just means "Take the content of file example.h, and put that into this file"

This can become a problem sometimes, however. Let's take a look at a simplified version of your code (I've removed everything that isn't relevant):

grid.h

#include <vector>

namespace grid {
    class Cell {
        // ...
    };

    std::vector<Cell> cells;
}

grid.cpp

#include "grid.h"

// define functions ...

main.cpp

#include "grid.h"

int main() {
    // ...
}

After the #include "grid.h" is replaced with the file grid.h, this is what the resulting .cpp files look like*:

grid.cpp

#include <vector>

namespace grid {
    class Cell {
        // ...
    };

    std::vector<Cell> cells;
}

// define functions ...

main.cpp

#include <vector>

namespace grid {
    class Cell {
        // ...
    };

    std::vector<Cell> cells;
}

int main() {
    // ...
}

Can you see the problem? When the compiler compiles grid.cpp, it finds the variable grid::cells, but when it compiles main.cpp, it finds another variable that's also called grid::cells! Which one should it use? It can't figure that out, and so it returns the error "multiple definition of grid::cells".

There are 2 ways to fix this.

  1. extern. Define cells as extern std::vector<Cell> cells; in grid.h. This tells the compiler "There is a variable grid::cells, but it's not defined here, please look for it somewhere else." And then in grid.cpp, simply add the line std::vector<grid::Cell> grid::cells; which tells the compiler "You know that variable grid::cells I told you existed? Well, it's defined here, so please use this variable whenever you see grid::cells"

  2. inline. Define cells as inline std::vector<Cell> cells; in grid.h. This tells the compiler "I know that this variable (grid::cells) will be in multiple .cpp files, but I only want there to be one variable grid::cells, not two, so could you please make sure that all grid::cells variables are the same?"

In your case, both of these should work. inline might be easier (you don't need to add anything to grid.cpp).**


  • Note that #include <vector> would also be replaced with the file containing the implementation of the std::vector class. But let's ignore that since it's not relevant in this case; we only care about the grid.h file.

** Note that using inline like this is only possible since C++17, so if you need to use very old versions of C++ (or if you're writing C) you need to use the extern option.

Joel Niemelä
  • 168
  • 1
  • 3
  • 13