Posts Game State Pattern in C
Post
Cancel

Game State Pattern in C

Foreword

Recently I've begun to write some sort of game/demo system from scratch in which I needed some kind of mechanism for states management. That's because I didn't want to drown in tons of switch statements. Long time ago, I've stumbled upon managing game states in c++ article. Ideas presented there are good, but there was no option for me to use pure virtual functions and all that fancy C++ stuff, because of low memory & cpu budget. But still, I wanted to use similar mechanisms in my C program. The code below isn't platform dependent, so you can use it in your own programs on any platform. To get something from this article you should know something about C programming. I also suggest to check out the article about the C++ approach to get the overall idea what program state management is all about.

I’ve figured out, that I need three things:

  • custom stack mechanism (there is no STL vectors in C language),
  • game state definition
  • game state manager

Ok, so let’s start…

Game states model

This is the easiest part. We need a possibility to initialise our state, handle it's logic and deinitialise when needed. I've made this with basic structure with function pointers. Additionally we should have possibility to pause/resume it's processing, thus pauseGamestate()/resumeGamestate() functions were added.

note: BOOL is my custom type, replace it with something applicable returning true/false value

gamestate.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#ifndef __GAMESTATE_H__
#define __GAMESTATE_H__

typedef unsigned int (*funcPtrInt)();

typedef struct {
  funcPtrInt init;
  funcPtrInt process;
  funcPtrInt deinit;
  BOOL bPaused;
} sGameState;

void initGamestate (sGameState *state,const void *init,const void *process,const void *deinit);
void pauseGamestate (sGameState *state);
void resumeGamestate (sGameState *state);

#endif

gamestate.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 #include "gamestate.h"

void initGamestate (sGameState *state,const void *init,const void *process,const void *deinit){
  state->init=init;
  state->process=process;
  state->deinit=deinit;

  state->bPaused=FALSE;

};

inline void pauseGamestate (sGameState *state){
 state->bPaused=TRUE;
}

inline void resumeGamestate (sGameState *state){
 state->bPaused=FALSE;
}

I've had to add possibility to prepare game state structure before use and added simple functions to pause/resume their workings.

Custom stack mechanism

It's a requirement for our state manager. More on it's basic functionality is here. I will not discuss further, the only thing you should remember that there is only one element accessible on stack, the one which was placed last and is on top of the stack.

This is my custom stack implementation and can be used with any generic data/element type which has the same type and size in bytes. Additionally I've added mechanism similar to C++ STL vector, every time we reach the maximum stack size, the stack tries to resize itself, by adding DEFAULT_MAXSTACK slots (you can change it of course if you like). If it isn't possible (we run out of memory) we have stack overflow.

stack.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#ifndef __STACK_H__
#define __STACK_H__

//stack for storing game states
#define DEFAULT_MAXSTACK 20

typedef struct {
 size_t top;  // top of stack
 size_t size; // current max size of a stack, if we try to go past this threshold, then
              // it's size will be increased by DEFAULT_MAXSTACK elements
 unsigned long elementSize;
 void *stack;
} tStack;

//if initialMaxSize==0, then maximal initial size is set to DEFAULT_MAXSTACK
signed long initStack(tStack *pPtr, tMEMSIZE initialMaxSize, unsigned int elementSize);

//void element has to be of the constant size
void pushStack(tStack *pPtr, const void *newElement);
void popStack(tStack *pPtr);
void *getTopStackElement();
BOOL isStackFull(tStack *pPtr);
BOOL isStackEmpty(tStack *pPtr);
void deinitStack(tStack *pPtr);

#endif

stack.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
 #include "stack.h"

//if initialMaxSize==0, then maximal initial size is set to DEFAULT_MAXSTACK
signed long initStack(tStack *pPtr, tMEMSIZE initialMaxSize, U32 elementSize) {
 pPtr->top=;
 pPtr->stack=;
 pPtr->elementSize=elementSize;

 if(initialMaxSize==) {
  pPtr->size = DEFAULT_MAXSTACK;
 }else{
  pPtr->size = initialMaxSize;
 }

//allocate memory
 void *pNewStack=;

 pNewStack=malloc(elementSize*pPtr->size);

 if(pNewStack==) return -1;

 memset(pNewStack,,elementSize*pPtr->size);

 pPtr->stack=pNewStack;

 return ;
}

//void element has to be of the constant size
void pushStack(tStack *pPtr, const void *newElement){
 
 if(pPtr->top==pPtr->size){
 
//stack underflow
 pPtr->size=pPtr->size + DEFAULT_MAXSTACK;

 if(realloc(pPtr->stack,pPtr->size*pPtr->elementSize)==NULL){
  //Houston we have a problem. nothing can be done... we are dead...
  puts("Warning: Stack overflow!\r\t");
 }
 return;
}
else{
 unsigned long dst;

 dst=((unsigned long)pPtr->stack)+((++pPtr->top)*(pPtr->elementSize));

 memcpy((void *)dst,newElement,pPtr->elementSize);
 return;
}

}

void *getTopStackElement(tStack *pPtr){

 //we assume stack is not empty
 unsigned long adr=((unsigned long)pPtr->stack)+(pPtr->top*pPtr->elementSize);

 //return removed element
 return (void *)adr;
}

void popStack(tStack *pPtr){

if(pPtr->top==){
 //stack underflow
 puts("Warning: Stack underflow!\r\t");
}
else {
 --pPtr->top;
}
}

BOOL isStackFull(tStack *pPtr){
 if(pPtr->top==(pPtr->size-1))
  return TRUE;
 else
  return FALSE;
}

BOOL isStackEmpty(tStack *pPtr){

if(pPtr->top==)
 return TRUE;
else
 return FALSE;

}

void deinitStack(tStack *pPtr){

 free(pPtr->stack);

 pPtr->top=;
 pPtr->stack=;
 pPtr->elementSize=;
 pPtr->size = ;

}

Game state manager

I assumed only one instance of game state manager. It has to be initialised/deinitialised before/after use with **initGameStateMgr()**/**deinitGameStateMgr()** functions.

Three functions:

  • void changeState(sGameState *pState);
  • void popState();
  • void pushState(sGameState *pState);

Are for pushing/popping states to/from the stack and changing current game state to other one.

void process();
This one launches current state(from top of the stack) process() function;

BOOL isGameRunning();
Function returns true value if game is running.

void Quit();
Function turns off internal flag, so isGameRunning() returns false thus terminating the main loop and exiting our program.

statemgr.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#ifndef __STATE_MGR__H__
#define __STATE_MGR__H__

#include "gamestate.h"

signed int initGameStateMgr(void);
void deinitGameStateMgr();

void changeState(sGameState *pState);
void popState();
void pushState(sGameState *pState);

void process();
BOOL isGameRunning();
void Quit();

statemgr.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
 #include "statemgr.h"
#include "stack.h"

static tStack gameStateStack;
static BOOL bRunningFlag=FALSE;

signed int initGameStateMgr(){
  if(initStack(&gameStateStack, , sizeof(sGameState))<) return -1;
  bRunningFlag=TRUE;
 return ;
}

void deinitGameStateMgr(){
  deinitStack(&gameStateStack);
 return ;
}

void changeState(sGameState *pState){
  sGameState *ptempState=;
 
  if(!isStackEmpty(&gameStateStack)){
   
    //cleanup current state
    ptempState=(sGameState *)getTopStackElement(&gameStateStack);
    //deinit it
    ptempState->deinit();
    //remove it
    popStack(&gameStateStack);
  }
 
  //store and init new state
  pushStack(&gameStateStack, (const void *)pState);
  ptempState=(sGameState *)getTopStackElement(&gameStateStack);
  ptempState->init();
}

void popState(){
  sGameState *ptempState=;
 
  if(!isStackEmpty(&gameStateStack)){
   
    //cleanup current state
    ptempState=(sGameState *)getTopStackElement(&gameStateStack);
    //deinit it
    ptempState->deinit();
    //remove it
    popStack(&gameStateStack);
  }
 
  //resume previous
  if(!isStackEmpty(&gameStateStack)){
      ptempState=(sGameState *)getTopStackElement(&gameStateStack);
      resumeGamestate (ptempState);
    }
}

void pushState(sGameState *pState){
sGameState *ptempState=;
   
  if(!isStackEmpty(&gameStateStack)){
 
    ptempState=(sGameState *)getTopStackElement(&gameStateStack);
    pauseGamestate (ptempState);
  }
 
  pushStack(&gameStateStack, (const void *)pState);  
  ptempState=(sGameState *)getTopStackElement(&gameStateStack);
  ptempState->init();
 
}


void process(){
  sGameState *ptempState=;
  ptempState=(sGameState *)getTopStackElement(&gameStateStack);
  ptempState->process();
}

BOOL isGameRunning(){
  return bRunningFlag;
}

void Quit(){
  bRunningFlag=FALSE;
}

And here is an example usage in main program function (main.c):
//our game state manager
#include "states/statemgr.h"

//headers with all the states
#include "IntroState.h"
#include "MainMenuState.h"
#include "CreditsState.h"
#include "SettingsState.h"

int main(int argc, char **argv){

// ...init all subsystems here (input, sound, video etc.. )

//init game states
initAndRegisterIntroState();
initAndRegisterMmenuState();
initAndRegisterSettingsState();
initAndRegisterCreditsState();

if(initGameStateMgr()<){

  puts("Error: state manager error\n");
  // deinit stuff here
  // ...
 return 1;
}

// load the intro
changeState(&introState);

// main loop
while ( isGameRunning() == TRUE ){
 process();
}

deinitGameStateMgr();

// deinit all subsystems here (input, sound, video etc.. )

return ;

And here is an example usage in main program function (main.c):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
 //our game state manager
#include "states/statemgr.h"

//headers with all the states
#include "IntroState.h"
#include "MainMenuState.h"
#include "CreditsState.h"
#include "SettingsState.h"

int main(int argc, char **argv){

// ...init all subsystems here (input, sound, video etc.. )

//init game states
initAndRegisterIntroState();
initAndRegisterMmenuState();
initAndRegisterSettingsState();
initAndRegisterCreditsState();

if(initGameStateMgr()<){

  puts("Error: state manager error\n");
  // deinit stuff here
  // ...
 return 1;
}

// load the intro
changeState(&introState);

// main loop
while ( isGameRunning() == TRUE ){
 process();
}

deinitGameStateMgr();

// deinit all subsystems here (input, sound, video etc.. )

return ;

And example, minimal state implementation can look like this:
IntroState.h

1
2
3
4
5
6
7
8
9
10
#ifndef __INTRO_STATE_H__
#define __INTRO_STATE_H__

#include "states/gamestate.h"

sGameState introState;

void initAndRegisterIntroState();

#endif

IntroState.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
 #include "states/statemgr.h"
#include "IntroState.h"
#include "MainMenuState.h"

static unsigned long IntroStateInit(){
 puts("Intro State Init\r\n");
 return 1;
}

static unsigned long IntroStateDeinit(){
 puts("Intro State deinit. See ya!\r\n");
 return ; //quits the program
}

static void IntroStateUpdate(){
 puts("Intro State update\r\n");
}

static void IntroStateDraw(){
 puts("Intro State draw\r\n");

}

static void IntroStateHandleInput(){

puts("Intro State handle input\r\n");
// for example, on certain input from mouse/keyboard ...
// to go to another state call changeState(&someState)
// or pause current state and go to another with pushState(&someState)
// or resume previous state which is on stack with popState();
// or exit running program loop and quit program by calling Quit();

}

static unsigned long IntroStateMainLoop(){

if(!introState.bPaused){
 IntroStateHandleInput();
 IntroStateUpdate();
 IntroStateDraw();
}

//screenswap

 return 1;
}

void initAndRegisterIntroState(){
 initGamestate(&introState,IntroStateInit,IntroStateMainLoop,IntroStateDeinit);
}

So as you see, we initialise all of the states variables first and our game state manager afterwards, then change state to initial one (intro state in above example) and enter main loop in which **process()** function of current state is executed.

We terminate main loop when somewhere in state we call **Quit()** function of our game state manager (for example when certain key was pressed).

Conclusion

States are self contained, easier to maintain and debug than switch statements - this is the main selling point of [program/game] state manager.

This post is licensed under CC BY 4.0 by the author.