Trouble splitting code into core loop and satellite programs

I recently started learning C++ and SDL2 because my ideas stretched beyond my skills. I'm not entirely new to programming, having done some R and python in the distant past. So, I'm entirely qualified to write a computer game from scratch in a language I'm only just learning...in my dreams.

I have two parallel code developments: the horribly long eye bleeding monstrosity that somehow works even as it balloons to a ridiculous size and the idyllic non-working game loop which I abstracted out in hopes of creating a central universal core engine to which I can tweak, add, and subtract the smaller individualized satellite programs it references for input, commands, graphics, etc. There is no actual game logic as yet. This is all universal elements and gui.

The trouble is, whenever I try to split my large block of code beyond one massive header file and one massive source file, I get long lists of errors involving missing or duplicate definitions. I resolve one error and my solution merely generates another. I haven't even touched the header. This was just from trying to divide that mammoth source file.

I know this is somewhat basic, but I'm having trouble wrapping my head around it. I'm including the idyllic broken version of my main and game headers and source files and my current satellite program headers to give an idea of what overlaps where. I wrote all of it except the functions in helper.h, to which I credit and profusely thank Will Usher. Also, I'm not sure whether to initialize the window and renderer in main.cpp, game.cpp, or graphics.cpp?

//main.cpp

void game_run();

int main(int arcg, char** arcv)
{
game_run();

return 0;
}


//game.h

#pragma once
/*
* game.h
*/
#ifndef GAME_H_
#define GAME_H_

int mouse_x;
int mouse_y;

void game_run();
void game_init();
void game_loop();
void game_input_process();
void game_state_manage();
void game_clean();

enum GameState {GAME_CREDITS, GAME_EDITOR, GAME_EXIT, GAME_MENU, GAME_PLAY, GAME_RULES, GAME_SETTINGS};

GameState gamestate;

#endif


//game.cpp

#include <SDL/SDL.h>
#include "game.h"

#include "helper.h"
#include "input.h"
#include "graphics.h"
#include "command.h"


void game_run()
{
game_init();
game_loop();
game_clean();
}

void game_init()
{
graphics_system_init();
graphics_background_load();
graphics_button_load();
graphics_button_clip();
}

gamestate = GAME_MENU;

void game_loop()
{
while (gameState != GAME_EXIT)
{
game_input_process();
command_state_manage();
game_state_manage();
}
}

void game_input_process()
{
//Event Handler
SDL_Event e;

while (gameState != GAME_EXIT)
{
//insert timestep code here later
while (SDL_PollEvent(&e))
{
switch (e.type)
{
case SDL_MOUSEMOTION:
mouse_x = e.motion.x;
mouse_y = e.motion.y;

input_longbutton_mousemotion();
input_roundbutton_mousemotion();
input_gamemodebutton_mousemotion();
break;

case SDL_MOUSEBUTTONDOWN:
mouse_x = e.button.x;
mouse_y = e.button.y;

input_longbutton_mousebuttondown();
input_roundbutton_mousebuttondown();
input_gamemodebutton_mousebuttondown();
break;

case SDL_MOUSMOTIONUP:
mouse_x = e.button.x;
mouse_y = e.button.y;

input_longbutton_mousebuttonup();
input_roundbutton_mousebuttonup();
input_gamemodebutton_mousebuttonup();

break;
}
}
}
}

void command_state_manage();

void game_state_manage()
{
switch (gamestate)
{
case GAME_CREDITS:
graphics_credits_render();
break;

case GAME_EDITOR:
graphics_editor_render();
break;

case GAME_EXIT:
game_clean();
break;

case GAME_MENU:
graphics_menu_render();
break;

case GAME_PLAY:
graphics_play_render();
break;

case GAME_RULES:
graphics_rules_render();
break;

case GAME_SETTINGS:
graphics_settings_render();
break;
}
}

void game_clean()
{
cleanup(longbutton_image, roundbutton_image, gamemodebutton_image, renderer, window);
IMG_Quit();
SDL_Quit();
}


//input.h

#pragma once
/*
* input.h
*/
#ifndef INPUT_H_
#define INPUT_H_

void input_longbutton_mousemotion();
void input_roundbutton_mousemotion();
void input_gamemodebutton_mousemotion();

void input_longbutton_mousebuttondown();
void input_roundbutton_mousebuttondown();
void input_gamemodebutton_mousebuttondown();

void input_longbutton_mousebuttonup();
void input_roundbutton_mousebuttonup();
void input_gamemodebutton_mousebuttonup();

bool mouseOverRect(int x, int y, Graphic &graphic)

#endif

// graphics.h

#pragma once
/*
* graphics.h
*/
#ifndef GRAPHICS_H_
#define GRAPHICS_H_

int Screen_Width = 640;
int Screen_Height = 480;

const int GAMEMODEBUTTON_HEIGHT = 256;
const int GAMEMODEBUTTON_WIDTH = 256;

const int LONGBUTTON_HEIGHT = 128;
const int LONGBUTTON_WIDTH = 256;

const int ROUNDBUTTON_HEIGHT = 256;
const int ROUNDBUTTON_WIDTH = 256;

bool colorState = true;
bool fogState = false;
bool timerState = true;

int total_points = 600;
int TREEKILL_POINTS = 100;
int KNIGHTKILL_POINTS = 150;
int KNIGHTSHUFFLE_POINTS = 100;
int TURNKILL_POINTS = 200;

struct Graphic
{
string label;
string type;
int x;
int y;
int h;
int w;
}

void graphics_system_init();

void graphics_background_load();
void graphics_button_load();

void graphics_button_clip();

void graphics_credits_render();
void graphics_editor_render();
void graphics_exit_render();
void graphics_menu_render();
void graphics_play_render();
void graphics_rules_render();
void graphics_settings_render();

//long button states
enum CreditsButtonSprite { CREDITS_DEFAULT, CREDITS_HOVER, CREDITS_INACTIVE, CREDITS_PRESSED, CREDITS_TOTAL };
enum ExitButtonSprite { EXIT_DEFAULT, EXIT_HOVER, EXIT_INACTIVE, EXIT_PRESSED, EXIT_TOTAL };
enum RulesButtonSprite { RULES_DEFAULT, RULES_HOVER, RULES_INACTIVE, RULES_PRESSED, RULES_TOTAL };
enum MenuButtonSprite { MENU_DEFAULT, MENU_HOVER, MENU_INACTIVE, MENU_PRESSED, MENU_TOTAL };

//round button states
enum TreeKillButtonSprite { TREEKILL_DEFAULT, TREEKILL_HOVER, TREEKILL_INACTIVE, TREEKILL_PRESSED, TREEKILL_TOTAL };
enum KnightKillButtonSprite { KNIGHTKILL_DEFAULT, KNIGHTKILL_HOVER, KNIGHTKILL_INACTIVE, KNIGHTKILL_PRESSED, KNIGHTKILL_TOTAL };

//gamemode button states
enum ColorButtonSprite {COLOROFF_DEFAULT, COLOROFF_HOVER, COLORON_DEFAULT, COLORON_HOVER, COLOR_TOTAL};
enum FogButtonSprite {FOGOFF_DEFAULT, FOGOFF_HOVER, FOGON_DEFAULT, FOGON_HOVER, FOG_TOTAL};
enum TimerButtonSprite {TIMEROFF_DEFAULT, TIMEROFF_HOVER, TIMERON_DEFAULT, TIMERON_HOVER, TIMER_TOTAL};

#endif

// command.h

#pragma once
/*
* command.h
*/
#ifndef COMMAND_H_
#define COMMAND_H_

enum CommandState { INIT_CREDITS, INIT_EDITOR, INIT_EXIT, INIT_MENU, INIT_PLAY, INIT_RULES, INIT_SETTINGS, MOVE_EAST, MOVE_NORTH, MOVE_SOUTH, MOVE_WEST };

CommandState commandState;

void command_state_manage();

#endif

//helper.h

#ifndef HELPER_H
#define HELPER_H

#include <string>
#include<iostream>
#include <utility>
#include <SDL/SDL.h>

//Log Error Function
void logSDLError(std::ostream &os, const std::string &msg)
{
os << msg << " error: " << SDL_GetError() << std::endl;
}

//Cleanup Function
/*
* Recurse through the list of arguments to clean up, cleaning up
* the first one in the list each iteration.
*/
template<typename T, typename... Args>
void cleanup(T* t, Args&&... args) {
//Cleanup the first item in the list
cleanup(t);
//Recurse to clean up the remaining arguments
cleanup(std::forward<Args>(args)...);
}
/*
* These specializations serve to free the passed argument and also provide the
* base cases for the recursive call above, eg. when args is only a single item
* one of the specializations below will be called by
* cleanup(std::forward<Args>(args)...), ending the recursion
* We also make it safe to pass nullptrs to handle situations where we
* don't want to bother finding out which values failed to load (and thus are null)
* but rather just want to clean everything up and let cleanup sort it out
*/
template<>
inline void cleanup<SDL_Window>(SDL_Window* window) {
if (!window) {
return;
}
SDL_DestroyWindow(window);
}
template<>
inline void cleanup<SDL_Renderer>(SDL_Renderer* renderer) {
if (!renderer) {
return;
}
SDL_DestroyRenderer(renderer);
}
template<>
inline void cleanup<SDL_Texture>(SDL_Texture* texture) {
if (!texture) {
return;
}
SDL_DestroyTexture(texture);
}
template<>
inline void cleanup<SDL_Surface>(SDL_Surface* surface) {
if (!surface) {
return;
}
SDL_FreeSurface(surface);
}

#endif
I'm not sure whether to initialize the window and renderer in main.cpp, game.cpp, or graphics.cpp?
There is this raii concept, see:

https://en.cppreference.com/w/cpp/language/raii

The point is that you create and initialize an object at the moment you use it. For instance:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct context_type
{
  std::unique_ptr<window> m_Window;
  std::unique_ptr<renderer> m_Renderer;

  context_type() : m_Window{std::make_unique<window>()}, m_Renderer{std::unique_ptr<renderer>()}
  {
  }
};

std::shared_ptr<context_type> retrieve_context()
{
  static std::shared_ptr<context_type> result = std::make_shared<>();
  return result;
};
This way everything will be created when it is accessed the first time.

While static isn't the best solution though.

This:
1
2
3
4
5
6
7
8
9
int mouse_x;
int mouse_y;

void game_run();
void game_init();
void game_loop();
void game_input_process();
void game_state_manage();
void game_clean();
Looks object oriented. So why not like this:
1
2
3
4
5
6
7
8
9
10
11
struct game_type
{
int mouse_x;
int mouse_y;

game();
void run();
void loop();
void input_process();
void state_manage();
};
I get long lists of errors involving missing or duplicate definitions


A function/member body can only be defined once within the solution (see exception below). If the linker finds that the body has been defined more than once, it emits an error re duplicate definitions.

This means that the code for function/member body should only be included as a compilation unit once. Note that using code guards/#pragma once doesn't fix this as these just work within the same compilation unit - not across compilation units.

If you really have to have multiple compilation units containing the same function/member body definition, then mark them as inline (comes before the return type). This means the linker will only try to link them once and not generate all the duplicate errors.
Topic archived. No new replies allowed.