Jump to content

Game programming/Abstract Data Types

From Wikiversity

Notice this page was automatically translated from Spanish. This will be fixed when the author wants to do it.

"A master in the art of life makes no much distinction between his work and play, his work and leisure, his mind and body, his education and recreation, his love and his religion. He just distinguishes which is which. Simply collecting his vision of excellence in every thing he does, leaving others to decide if he is playing or working. In his own eyes, he is always doing both." Anonymous

Requirements

[edit | edit source]
  • C knowledge
  • To know what an abstract data type (ADT) is, and why using that approach is preferable to using several unlinked variables.
  • To know how to implement an ADT in C programming language.
  • To learn how to modify data in an ADT through indirection/pointers.

Programming example

[edit | edit source]

With the introduction over, let's go inside the field of C language and data abstract types implementation.

An important feature of C related to programming games is the creation of abstract data types (ADTs). ADTs have a higher level of abstraction from the basic data types in the language (int, float, double and char). With ADTs it is possible to create new ways of organizing game data: sprites (all moving objects), states of sprites, achievements, menus, buttons, animations, linked lists, players, levels, games, Events, etc..

To clarify this matter see this example:

 // Code 1.1
 // File Sprite.h 
 typedef struct _sprite 
 { 
  int x, y; // Position in the world (could also be unsigned).
  unsigned int speed; // Speed at which sprite is travelling.
  int angle; // Direction of sprite's movement
  int frame; // Number of the current animation (could also be a pointer).
  int framespeed; // How long each frame lasts for.
  int energy; // Energy (or other resource).
  // And so on
 } Sprite; 

As a reminder, typedef is the keyword for defining a new name for a type, allowing us to use Sprite instead of struct _sprite. This snippet creates a sprite type, which contains data for position, speed, angle, animation and energy variables. To use this type, you might do (in a program that already has a main() function):

 // Code 1.2 
 # include "Sprite.h" 
 … 
 void InitGame (void) 
 { 
  Sprite sprite; 
  sprite.x = 0; 
  sprite.y = 0; 
  sprite.speed = 0; // Initially is still 
  sprite.angle = 0; // Looking to the right 
  sprite.frame = 0; 
  sprite.framespeed = 10 // Change the animation every 10 frames 
  sprite.energy = 10; 
 } 
 … 

As can be seen, you can set the variables in this way. However, only upon the initialization of the ADT, you can use a different notation:

Sprite sprite = {.x = 0, .y = 0, .speed = 0, .angle = 0, .frame = 0, .framespeed = 10, .energy = 10};

This snippet sets a sprite's data. A real game might initialize this data from a file or use different values for energy and framerates depending on the sprite, which would each be initialized with different values. Another thing to note is the difference from the basic data types, which are declared and then assigned values. Here, the Sprite is a collection of variables and thus to access a sprite's data, we must use the period operator (.).

Why do we use ADTs? Why not to use theunstructured variables?

This could be as follows:

 // Code 1.3 
 … 
 void InitGame (void) 
 { 
  int spriteX, spriteY; 
  unsigned int spriteSpeed; 
  int spriteAngle; 
  int spriteFrame; 
  int spriteFrameSpeed; 
  int energy; 
  spriteX = 0; 
  spriteY = 0; 
  spriteSpeed = 0; 
  spriteAngle = 0; 
  spriteFrame = 0; 
  spriteFrameSpeed = 0; 
  spriteEnergy = 10; 
 } 
 …

This is somewhat equivalent to the first fragment (Code 1.2). However, it possesses several flaws. The first one is that when we need several sprites (and we will need many sprites), we should have parallel arrays of the variables or have variables such as:

 ...
 int spriteX2, spriteY2; 
 ...

Which is by any standard unworkable if we have several types of sprites. The why is to do with alignment (though to get around this one can use an array of ints - the flaw being that we must access speed as a uint. Plus, it gets worse if we need larger data types, also due to alignment). The second thing is that the data is encapsulated in the Code 1.2, but in 1.3 it is loose. The advantage to having the data encapsulated is that if we need to do something with the data we have to spend only a Sprite pointer, as follows:

 // Code 1.4
 // A feature that takes away a point energy to a sprite
 void DamageSprite(Sprite *sprite, unsigned int damage)
 {
  sprite->energy -= damage;
  // Check if the sprite ceased to exist
  if (sprite-> energy == 0)
  {
   // The sprite is dead, do something 
   PlaySound("death.wav");
  }
 }

This function is very simple. Using a pointer to a sprite, it reduces their energy. To modify the data we use the arrow operator (->). This operator can be thought to access the value pointed to by sprite, then access whatever variable in the struct has the name following (note that if you look at the assembly it works a bit differently). It is, in fact, equivalent to:

(*sprite).energy -= damage;

This is called pass by reference in C. You can also simple as int rates:

 // Code 1.5
 void swap(int *ptr_x, int *ptr_y)
 {
  int temp = *ptr_x ;
  *ptr_x = *ptr_y;
  *ptr_y = temp ;
 }
 // the call
 void doswap()
 {
  int x , y;
  x = 1 ;
  y = 2 ;
  swap(&x, &y) ;
  // At this point, x == 2 and y == 1.
 }

The reason the values swap is because we pass by reference. If passed by value, due to the copying mechanism, the original x and y values remain unchanged. We use the reference operator * to access and modify the original values. That is what makes (*sprite).energy or, in abbreviated form for a ADT: sprite->energy.

Remember:

We use an ADT to avoid declaring separate variables and to encapsulate the information in a single type. Knowing this, we use the Sprite struct.

All of the above ideas are joined in the following code. If you did not understand some code above, it is advised that you return to it and go over it to better understand this block.

 // Code 1.6: Sprite array
 // Filename is sprites.c
 #include "includes.h"
 // Define the largest number of sprites we need to have room for
 #define MAX_SPRITES	10
 // Array of sprites
 Sprite sprites[MAX_SPRITES];
 // The function to initialize them
 void InitSprite(Sprite *sprite)
 {
  sprite->x = 0;
  sprite->y = 0;
  sprite->speed = 0;
  sprite->angle = 0;
  sprite->frame = 0;
  sprite->framespeed = 10;
  sprite->energy = 10;
 }
 void InitSprites()
 {
  for(int i = 0; i < MAX_SPRITES; i++)
  {
   InitSprite(&sprites[i]); // Or can be InitSprite(sprites + i * sizeof(Sprite));
  }	
 }
 void InitGame(void)
 {
  ...
  InitSprites();
  ...
 }

This code closes this chapter. My style is to enter the field from the start so that the reader curious about what can be achieved as soon as possible. If they found very difficult to advise you to review the key concepts of C-pointers, structures, step by reference, etc.. If you have not understood do not worry because the other chapters will be clarified the concepts outlined here.

Exercises

[edit | edit source]

Note from person checking English translation: I can't really figure out much of this part, and the challenges seem a bit obfuscated, so I wouldn't worry about being unable to do them.

1. Show the code to declare a TAD animation. An animation must have a displacement x, y (a BMP file in which all are), a pointer to bmp (type SDL_Surface), width, height, number of tables animation.

2. Show the code for declaring a player named Player of TAD. Must have lives, score, color, name and sprite (sprites) partner. Hint:

 typedef struct _player
 {
  int score;	
  // Escribir el resto de campos
 } Player;

3. Declare and initialize a list of under 4 players. Lives, score and color should all be 0. The name of each should get out of a settlement (char *names[4]). The sprite must emerge from an array of sprites (Sprite sprites *[4]);

4. Declare a variable pointer of a player to create a dynamic array of players. The function will receive the number of players, dynamically allocate the memory and initialize all fields to 0 (Hint: use the functions of memory management found in mem.h)