Tutorial – Constants

This tutorial covers one of the most basic concepts of programming/scripting: User defined constants. It is meant for beginning coders and OpenBOR script writers. Upon completion, you should understand all of the following:

  • The definition of “Constant” in computer science (programming) as opposed to other uses of the term (such as mathematics).
  • How constants work in relation to other types of identifiers.
  • Where and how to apply constants.
  • Use of constants within the OpenBOR scripting engine.

Constants vs. Literals & Variables

To fully comprehend why constants are useful it is imperative to understand what a constant really is, especially compared to variables and literals. Literals are of particular note because it is very easy to confuse literals with constants. In the field of computer science, variables, constants and literals are usually defined as follows (Rao, 2017):

  • Variable: A variable is a storage location and an associated identifier which contains some known or unknown quantity or information, a value.
  • Constant: A constant is an identifier with an associated value that the program can not normally modify.
  • Literal: A literal is a notation for representing a fixed value.

Note the unfortunate caveat: “usually”. This is because some languages (C in particular) refer to both literals and constants as “constants” (Constants, 2020). They are then differentiated as “literal constants” and “symbolic constants” respectively. In programing circles, numeric literals are also called “magic numbers” (Definition of constants and magic numbers 2001). To avoid confusion between them we will henceforth only use the references “literal” and “constant”.

In practical terms, you may think of variables as named values that you can modify, constants as named values that you can’t modify and literals as values that mean exactly what they say. The question is how do these definitions and meanings apply in real world programming. To find out let’s start with variables and literals using this example multiplier:

i*2 = y

Applying our definitions above, there are two variables (i, y) and a single literal (2) in play:

  • i represents some number that can be whatever we want.
  • y is the product result.
  • 2 is just two – it’s a literal value.

Now observe as we translate our equation into some code and see it in action:

int i = 0;
int y = 1;

for (i = 0; i < 10; i++)
{   
    y = y * 2;
    print("Result %i: %i\n", i+1, y);
}

This will get us a simple geometric progression. First, we assign variable i a value of zero. Then we increment i by one in a loop until it is no longer less than ten. At each pass we assign y the product of y * 2. Then we print the cursor row and current value of y, giving us the following output:

Result 1: 2
Result 2: 4
Result 3: 8
Result 4: 16
Result 5: 32
Result 6: 64
Result 7: 128
Result 8: 256
Result 9: 512
Result 10: 1024

Observe how our literal value 2 worked in relation to the variables surrounding it. Note the number 10 is also a literal, but for simplicity let’s just worry about 2. We could run this loop to infinity and it will always multiply y * 2 and assign the result to y. The variables i and y are continuously modified while 2 never changes. Easy enough yes? Remember:

Literals are static values written directly into source code; they “literally” mean what they say. Variables are IDs which are assigned values that can be (and usually are) modified while the program executes.

Now what would happen if we need to calculate our equation again, only dividing by 2 rather than multiplying? As an example, we’ll make a copy of our loop with a minor modification and run both in sequence.

int i = 0;
int y = 1;

print("\n Multiply %i:\n\n", 2);

for (i = 0; i < 10; i++)
{   
    y = y * 2;
    print("Result %i: %i\n", i+1, y);
}

print("\n\n Divide %i:\n\n", 2);

for (i = 0; i < 10; i++)
{   
    y = y / 2;
    print("Result %i: %i\n", i+1, y);
}

We’ll get the following output.

Multiply 2:

Result 1: 2
Result 2: 4
Result 3: 8
Result 4: 16
Result 5: 32
Result 6: 64
Result 7: 128
Result 8: 256
Result 9: 512
Result 10: 1024

Divide: 2

Result 1: 512
Result 2: 256
Result 3: 128
Result 4: 64
Result 5: 32
Result 6: 16
Result 7: 8
Result 8: 4
Result 9: 2
Result 10: 1

As you can see in the second example, we start with the geometric progression pattern from earlier and then reverse it. For purposes of our example, the output is not that important. What matters here is how we use the literal 2 four times in succession – once for each label and each operation. No problem thus far. But suppose later we need to substitute 2 with 10? Just edit the code and be done right? With examples like this, of course! Unfortunately real world coding is not so simple. Consider a real script or program. Even a something simple like some of the scripts powering this website may comprise several pages of code. For perspective, the OpenBOR engine contains ~600K lines of source code. Some of the largest applications can number in the millions. Now imagine trying to locate and edit every instance of 2. Or worse, trying to locate specific instances of 2, and leaving the rest alone. Even with advanced regex expressions, assuming those are available at all, copy/paste operations can be effectively impossible. Hand editing is time prohibitive and error prone. This is where constants are so valuable. Let’s look at our example once again, with a little something extra added.

#define SOMENUM 2

int i = 0;
int y = 1;

print("\n Multiply %i:\n\n", SOMENUM);

for (i = 0; i < 10; i++)
{   
    y = y * SOMENUM;
    print("Result %i: %i\n", i+1, y);
}

print("\n\n Divide %i:\n\n", SOMENUM);

for (i = 0; i < 10; i++)
{   
    y = y / SOMENUM;
    print("Result %i: %i\n", i+1, y);
}

As you’ve no doubt guessed, this and the previous example produce identical results. The difference is we no longer rely on a literal 2. Instead, this line creates a constant identified as SOMENUM and assigns it the value of 2:

#define SOMENUM 2

Just as i represents a loop counter value, SOMENUM represents the number 2. But unlike i and Y, SOMENUM can never change while the program is running. It maintains a constant value, hence the name. Effectively we have given a potentially VERY common value (2) its own unique identification. Attaching an identification to values that never change is an extra step that may seem silly at first, but in the long run actually saves you time. It is almost universally considered best practice (Recommended C Style and Coding Standards 2000).

  • There is no need to modify the code in multiple places. If we want to substitute our constant value for something else, we merely change the #define. Depending on scope of the program, this can range from being a simple time saver to enabling adjustments that are otherwise untenable or virtually impossible.
  • You may give constants nearly any imaginable label. That in turn enables creation of organized naming conventions for cleaner, more human readable code. This means superior design malleability, greater run time stability, and easier debugging when anomalies do occur.
  • It is possible to write programs with configuration blocks for simplistic modification of basic behavior and quick porting to other hardware platforms.
  • Separation of code and content! Why write a formula that only adds 2+2 when X+Y can perform the same task with infinite re-usability?

How To Use Constants In Your Code

Using constants is amazingly simple, but like any programming technique also sometimes mercurial. Every coding environment will have its own set of operators, methods, capabilities, and quirks. I will be focusing mainly on the the C based scripting engine of OpenBOR. A cursory Google search should provide all the instruction you need for other tools of choice.

OpenBOR

OpenBOR’s scripting engine is a C derivative and supports the define directive for constants. A define comprises three parts:

  1. Definition: Always “#define”.
  2. Identification: This is the name of your constant.
  3. Token: The value. This is what the computer will interpret whenever the constant is encountered in code.
#define SOME_IDENTIFICATION somevalue

Don’t worry about memory or CPU resources. The define directive is prepossessed, meaning the application handles it on start up. In other words, constants have no memory or run-time CPU cost.

General Tips

Naming Conventions

Decide on a naming convention before you start writing code. You will want names that are unique enough to avoid conflict while remaining reasonably succinct. Common practice is to use all caps for the identifier, with an underscore separator between each segment. The segments themselves should maintain Big Endian order.

#define BOVINAE_BISON			"Bison"	// Best in the west!
#define BOVINAE_BOS				"Cow"	// It's what's for dinner.
#define SIZE_LARGE				16		// Full meal.
#define SIZE_MEDIUM				12		// Light snack.
#define SIZE_SMALL				10		// For the kiddies.
#define TEMP_DONE_MAX			100		// Mmmmummm, that's the stuff!
#define TEMP_DONE_MIN			71		// Nice and brown.
#define TEMP_MEDIUM_MAX			65		// Little cold, but not bad.
#define TEMP_MEDIUM_MIN			60		// In a hurry?
#define TEMP_MEDIUM_WELL_MAX	69		// Not too shabby.
#define TEMP_MEDIUM_WELL_MIN	65		// Add some Worcestershire and we'll talk.
#define TEMP_RARE_MAX			55		// Somebody call a blood bank.
#define TEMP_RARE_MIN			52		// Moo!

Note the larger group of Subfamily appears first, followed by Genus. We follow a similar convention for cut size and cooking temperature. Who’s in the mood for a SIZE_LARGE BOVINAE_BOS TEMP_WELL_MAX?

Text File Use

In OpenBOR, any constants available in a model’s animation script are also available in the model text. This means constants are usable inside of @script tags or as function arguments in @cmd tags.

Reading this function call is very difficult unless the creator has a perfect memory, and it’s impossible for anyone else….

@cmd set_bind_state 7 1

…but with constants, the creator can instantly identify what the function call should do, and if need be can easily find related calls with a text search. Other readers may not understand right away, but they can probably get a rough idea and debug from context:

@cmd set_bind_state BIND_STATE_FACE_DOWN DIRECTION_RIGHT

When/Where Should Constants Be Used?

One of the more common questions involving constants is “When should I use them?” The answer is “whenever possible.” As a more usable interpretation of “whenever possible”, consider the following:

  • Is the value static?
  • Will you use the value more than once?
  • Does the value lack infinite range and variation?

If the value fits these criteria, then replace it with a constant. Remember, defined constants in OpenBOR are “free”, they don’t consume any more resources than the literal values they replace.

Final Words

I hope this helps you to understand how simple, elegant and useful constants can be, and I would encourage you to make full use of them in your own projects. Lastly, please feel free to leave comments below with any questions or opinions you may have, and I will see to them in short order. If you have any questions about the OpenBOR engine, stop by at ChronoCrash and we’ll be glad to help.

Thanks for reading!
DC

References

C Constants. (2020). https://www.w3schools.in/c-tutorial/constants/.

Def. (2001). Definition of constants and magic numbers. https://www.inf.unibz.it/~calvanese/teaching/05-06-ip/lecture-notes/uni04/node17.html.

Rao, S. (2017, January 27). Informit. InformIT. https://www.informit.com/articles/article.aspx?p=2755729.

Def. (2001). Definition of constants and magic numbers. https://www.inf.unibz.it/~calvanese/teaching/05-06-ip/lecture-notes/uni04/node17.html.


Originally published 2013-02-04.

Code Snip – Collision Checking

Notes from 2016-11-26 – Revamping OpenBOR collision detection.

With the possibility of several dozen or more entities on screen, collision detection must be precise with minimal resource intensity.

With the possibility of several dozen or more entities on screen, collision detection must be precise with minimal resource intensity.

Currently coordinates (s_hitbox) exist as static sub-structures in s_collision_attack and s_collision_body. See below…

typedef struct
{
	int x;
	int y;
	int width;
	int height;
	int z1;
	int z2;
} s_hitbox;

// s_collision_attack
typedef struct
{
    int                 attack_drop;        // now be a knock-down factor, how many this attack will knock victim down
    int                 attack_force;
    int                 attack_type;        // Reaction animation, death, etc.
    int                 blast;              // Attack box active on hit opponent's fall animation.
    int                 blockflash;         // Custom bflash for each animation, model id
    int                 blocksound;         // Custom sound for when an attack is blocked
    s_hitbox            coords;
    int                 counterattack;      // Treat other attack boxes as body box.
    ...

This was done for simplicity, and with current logic wastes no memory as coordinates are always required for a collision box.

However, the addition of multiple collision box support has exposed the need to break collision detection down into smaller functions. This in turn requires a lot of passing around the entire s_hitbox structure. Given the rate this functionality is used (multiple collision evaluations on every entity on every animation frame @200 frames per second), efficiency is absolutely imperative. Replacing the static coords declaration with a pointer and using dynamic allocation will add some code complexity initially, but in the long term should simplify breaking down collision logic and save substantial resources.

The following are in progress logic functions, they will need reworking to accommodate new pointer.

// Caskey, Damon V.
// 2016-11-25
//
// Get 2D size and position of collision box.
s_coords_box_2D collision_final_coords_2D(entity *entity, s_hitbox coords)
{
    s_hitbox        temp;
    s_coords_box_2D result;

    temp.z1 = 0;

    // If Z coords are reversed, let's correct them.
    // Otherwise we use
    if(coords.z2 &gt; coords.z1)
    {
        temp.z1 = coords.z1 + (coords.z2 - coords.z1) / 2;
    }

    // Get entity positions with Z offset
    // included, and cast to integer.
    temp.x    = (int)(entity-&gt;position.x);
    temp.y    = (int)(temp.z1 - entity-&gt;position.y);

    // Use temporary positions to get final dimensions
    // for collision boxes.
    if(entity-&gt;direction == DIRECTION_LEFT)
    {
        result.position.x   = temp.x - coords.width;
        result.size.x       = temp.x - coords.x;
    }
    else
    {
        result.position.x   = temp.x + coords.x;
        result.size.x       = temp.x + coords.width;
    }
    result.position.y   = temp.y + coords.y;
    result.size.y       = temp.y + coords_owner.height;

    return result;
}

bool collision_check_contact_2D(s_coords_box_2D owner, s_coords_box_2D target)
{
    // Compare the calculated boxes. If any one check
    // fails, then the boxes are not in contact.
    if(owner.position.x &gt; target.size.x)
    {
        return FALSE;
    }
    if(target.position.x &gt; target.size.x)
    {
        return FALSE;
    }
    if(owner.position.y &gt; target.size.y)
    {
        return FALSE;
    }
    if(target.position.y &gt; target.size.y)
    {
        return FALSE;
    }
}

bool collision_check_contact_Z(entity *owner, s_hitbox coords_owner, s_hitbox coords_target)
{
    int Z_distance = 0;
    int z1 = 0;
    int z2 = 0;

    if(coords_owner.z2 &gt; coords_owner.z1)
    {
        z1 += coords_owner.z1 + (coords_owner.z2 - coords_owner.z1) / 2;
        zdist = (coords_owner.z2 - coords_owner.z1) / 2;
    }
    else if(coords_owner.z1)
    {
        zdist += coords_owner.z1;
    }
    else
    {
        zdist += attacker-&gt;modeldata.grabdistance / 3 + 1;    //temporay fix for integer to float conversion
    }

    if(coords_target.z2 &gt; coords_target.z1)
    {
        z2 += coords_target.z1 + (coords_target.z2 - coords_target.z1) / 2;
        zdist += (coords_target.z2 - coords_target.z1) / 2;
    }
    else if(coords_target.z1)
    {
        zdist += coords_target.z1;
    }

    zdist++; // pass &gt;= &lt;= check if(diff(z1, z2) &gt; zdist)
    {
        return FALSE;
    }
    
    return TRUE; 
}

// Caskey, Damon V.
// 2016-11-25
//
// Compare collision boxes and return
// TRUE if they are in contact.
bool checkhit_collision(entity *owner, entity *target, s_hitbox coords_owner, s_hitbox coords_target)
{
    s_coords_box_2D owner_final;
    s_coords_box_2D target_final;

    bool result;
    
    // First check Z contact.
    result = collision_check_contact_Z(owner, coords_owner, coords_target);    

    // If result is TRUE, then run
    // 2D plane checks.
    if(result)
    {
        // Get final collision box 2D plane sizes.
        owner_final     = collision_final_coords_2D(owner, coords_owner);
        target_final    = collision_final_coords_2D(target, coords_target);
        
        // Compare the 2D boxes and get result.
        result = collision_check_contact_2D(owner_final, target_final);
    }
    
    // return final result.
    return result;
}

// Find center of attack area
s_axis_f_2d collision_center()
{

    leftleast = attack_pos_x;

    if(leftleast &lt; detect_pos_x) { leftleast = detect_pos_x; } rightleast = attack_size_x; if(rightleast &gt; detect_size_x)
    {
        rightleast = detect_size_x;
    }

    medx = (float)(leftleast + rightleast) / 2;
}