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]
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.
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
"
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
).**
#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.