Mint Banjo

Gamedev & games enthusiast

Catan clone

About

One of the first games I worked on was a C++ implementation of the board game “Settlers of Catan”. It is a turn-based strategy game based on resource collection, expansion and trade. (I own no rights to anything Catan related, the implementation was made purely for practice purposes).

It is coded in C++ using the SFML framework. SFML provides functions to create a game window, render graphical objects to the screen, and access other functions of the underlying system like input and audio. SFML proved very easy to learn and use, with excellent docs and a modern, object-oriented design.

The game has functioning local multiplayer, ie. several people could play a game locally on one machine if they didn’t own a board. AI and online multiplayer could be added in future. The art is pretty awful and only intended as a placeholder, but screenshots can be found below:

Development

Co-ordinate system

The game takes place on a grid of upright hexagons. You can build houses on the vertices; roads on the edge mid-points between vertices; and the number counters go on the center of the hexagon tile.

There are many different co-ordinate systems for hexagonal grids. I discovered the most useful approach for Catan is to use 3 co-ordinates, spaced like this:

Technically, one co-ordinate is obsolete, by the formula s + t + u = 0. (This also provides a way to check if a co-ordinate is valid). However, this approach has several advantages:

  • Each point has a unique combination of s, t and u.
  • Vertices, edges and tile centers (see red dots on image) all have INTEGER co-ordinates. This is useful for comparison of points and other operations.
  • Easy calculation of distance, and which vertices are connected with which edge and tile, etc.

For example, given a co-ordinate (s,t,u), the following code can check if it’s an edge, tile or vertex point:

bool isTile(int s, int t, int u) {
	std::div_t sDiv6 = std::div(s,6);
	std::div_t tDiv6 = std::div(t,6);
	std::div_t uDiv6 = std::div(u,6);
	return (sDiv6.rem == 0 && 
tDiv6.rem == 0 && uDiv6.rem == 0);
}

bool isVertex(int s, int t, int u) {
	std::div_t sDiv6 = std::div(s,6);
	std::div_t tDiv6 = std::div(t,6);
	std::div_t uDiv6 = std::div(u,6);
	std::div_t sDiv2 = std::div(s,2);
	std::div_t tDiv2 = std::div(t,2);
	std::div_t uDiv2 = std::div(u,2);
	return (sDiv6.rem != 0 && tDiv6.rem != 0 && uDiv6.rem != 0 &&
		sDiv2.rem == 0 && tDiv2.rem == 0 && uDiv2.rem == 0);
}

bool isEdge(int s, int t, int u) {
	std::div_t sDiv3 = std::div(s,3);
	std::div_t tDiv3 = std::div(t,3);
	std::div_t uDiv3 = std::div(u,3);
	return (sDiv3.rem == 0 && tDiv3.rem == 0 && uDiv3.rem == 0 &&
		!isTile(s,t,u));
}

And the code to find the distance between two points is easy:

int coordDistance(const Coord &c1, const Coord &c2) {
	return (std::abs(c1.s - c2.s) + std::abs(c1.t - c2.t) + std::abs(c1.u - c2.u)) / 2;
}

Code structure

The structure was fairly simple, with a main Game class handling the input and rendering and owning the game data:

class Game {
public:
	Game();
	void run();
	void changeToScreen(ScreenID screenID);
	void updateScreen();
	static std::map<std::string, sf::Texture> textures;
	static sf::Font font;
	static unsigned numPlayers;
					
private:
	void handleInput();
	void render();
	void loadTextures();

	sf::RenderWindow window;
	Data data;
	std::unique_ptr<Screen> currentScreen = nullptr;
	boost::optional<ScreenID> newScreen = boost::none;
};

SFML has no in-built GUI system so a simple UI system was rolled, focusing around a custom Button class:

class Button {
public:
	Button(sf::Vector2f position, sf::Vector2f size, std::function<void()> onClick,
	   std::string text, unsigned fontSize,
	   /* default params: */
	   float outlineThickness = 3.0, sf::Color recColor = sf::Color::Red,
	   sf::Color outlineColor = sf::Color::Black, bool canSelect = true);

	bool isMouseIn(sf::Vector2f mousePos) const;

	void onClick() const;

	void draw(sf::RenderWindow &window) const;

	bool isSelectable() const {return canSelect;}

	void setSelectable(bool in) {canSelect = in;}

private:
	sf::RectangleShape rec;
	std::function<void(void)> onClickFunction;
	sf::Text text;
	bool canSelect;
};