If you never return from functions, you may risk running out of stack space, but practically speaking it sounds like your case may be subject to tail call optimization. It's not an optimization I'd rely on, though; you may want to consider passing some concept of "current state" around or storing it, and letting a "main loop" (literally a loop in your main() function or some other early method) dispatch to other functions based on this state -- it can be a big switch block, or a data structure that relates states to function pointers, or something else.
Those functions can then run, possibly set state, and return to main().
One of your states can therefore trigger an "exit main loop" via break or the like.
A quick sketch of a very simple "state machine" pattern:
int main() {
bool isRunning = true;
int nextGameState = GAME_START;
while ( isRunning ) {
switch ( nextGameState ) {
case GAME_START: nextGameState = doGameStart(); break;
// other states here, following the above pattern
case EXIT_GAME: isRunning = false; break;
default: /* error handling here */; break;
}
}
return 0;
}
Another quick possibility would be something like:
extern bool g_isRunning = true; // global (yuck?)
typedef unsigned (*GameStateFunctionPointer)();
static const GameStateFunctionPointer s_gameStateTable[] =
{
doGameStart, // index = 0 entry
// TODO other state functions here
doGameEnd // index = n entry
};
static const unsigned NUM_GAME_STATES = sizeof( s_gameStateTable ) /
sizeof( s_gameStateTable[0] );
int main() {
unsigned nextGameState = GAME_START;
while ( g_isRunning ) {
if ( nextGameState >= NUM_GAME_STATES ) {
// bad state
abort(); // or other error handling
}
// call through function pointer to function that
// does the work of the this state, then determines next state
nextGameState = s_gameStateTable[ nextGameState ]();
}
return 0;
}
Re "going back", you may be able to keep a state history -- a list of prior game states -- rather than just a single nextGameState as above. You can then "go back" trivially by popping a state off this container.
(You may want to look at Deterministic Finite Automata / Nondeterministic Finite Automata -- sometimes more colloquially referred to as "state machines", although there are some non-overlapping bits -- for ideas of how you might design this. I'd strongly advise diagramming your state machine on "paper" if possible!)
(You may also just want to look around for other "game loop" examples, especially for text adventures.)
There are various advantages and disadvantages to both of the above very simple examples. If you're getting into more complicated state machines you might want to look into existing libraries for state machine / DFA / NFA representation.
By the way: exit(0) in and of itself isn't that evil. The OS will clean up after you. The disadvantage is you can't run all your shutdown code, and it makes things like leak tracking or taking other actions on normal shutdown (save game, update high scores, or any number of other things) more difficult.
exitfunction is a perfectly fine way to exit your program. A bit dirty (especially if you want to track down memory leaks), but fine. I wouldn't worry about it at this stage. – Laurent Couvidou Feb 27 at 16:54