GML GML Joseki Series: for Block Recipe Cards

FrostyCat

Member
GML Joseki Series: for Loop Recipe Cards

GM Version
: GMS 2.3+
Target Platform: All
Download: N/A
Links: N/A
Summary: A quick reference for a range of common use cases for the for loop.

Summary
This guide documents a number of common for loop patterns, best practices, and novice mistakes as reference. GML novices should practice and memorize these to avoid off-by-one bugs and inappropriate looping conditions.

Generic Loops

These are the fundamental use cases of the for loop. Developers of all levels should memorize all of these.

Zero-Indexed Loop

This type is the most common as zero-indexing is the natural way to count in computer science. In particular, novices should be aware that to iterate N times, the numbering runs 0 to N-1. It is commonly used to iterate through arrays, lists and simple numeric ranges.

Important: Pay special attention to the comparison operator in the middle!

Front-to-Back:

GML:
for (var i = 0; i < N; ++i) {
    /* Use i here */
}
Back-to-Front:
GML:
for (var i = N-1; i >= 0; --i) {
    /* Use i here */
}
Replace N with:
  • For numbers: The total number of times to iterate.
  • For arrays: array_length(arr)
  • For lists: ds_list_size(list)

One-Indexed Loop

This type is less common in non-novice code, but sometimes it is unavoidable. For example, strings in GML start counting from 1 for legacy reasons, and retrofitting code written by inexperienced coders often means having to work with systems designed to count from 1.

Important: Pay special attention to the comparison operator in the middle!

Front-to-Back:

GML:
for (var i = 1; i <= N; ++i) {
    /* Use i here */
}
Back-to-Front:
GML:
for (var i = N; i >= 1; --i) {
    /* Use i here */
}
Replace N with:
  • For numbers: The total number of times to iterate.
  • For strings: string_length(str)

Range Loop

This is a generalized version of the above patterns, commonly used on coordinate positions and other values spaced apart in fixed intervals.

Upward:
GML:
for (var i = MIN; i <= MAX; i += INCREMENT) {
    /* Use i here */
}
Downward:
GML:
for (var i = MAX; i >= MIN; i -= DECREMENT) {
    /* Use i here */
}
Double Nested Loop

This type is formed by nesting a for loop inside another, producing every possible pairing of values between the two loops involved. It is commonly used to iterate through all positions in grids, 2D arrays, and coordinates in a grid layout.

Front-to-Back example with zero-indexing:
GML:
for (var i = 0; i < M; ++i) {
    for (var j = 0; j < N; ++j) {
        /* Use i and j here */
    }
}
Back-to-Front example with zero-indexing:
GML:
for (var i = M-1; i >= 0; --i) {
    for (var j = N-1; j >= 0; --j) {
        /* Use i and j here */
    }
}
For the examples above, replace M and N with:
  • For 2D arrays: array_length(arr) and array_length(arr[i])
  • For grids (row-major order): ds_grid_height(grid) and ds_grid_width(grid)
  • For grids (column-major order): ds_grid_width(grid) and ds_grid_height(grid)

Unique Pairing Loop

Use this when you want to iterate pairwise values without counting the same combination twice (e.g. (2, 5) and (5, 2)) or using the same number twice (e.g. (5, 5)).

Front-to-Back
GML:
for (var j = 1; j < N; ++j) {
    for (var i = 0; i < j; ++i) {
        /* Use i and j here */
    }
}
To include same-number combinations, change i < j to i <= j.

Back-to-Front
GML:
for (var j = N-1; j > 0; --j) {
    for (var i = j-1; i >= 0; --i) {
        /* Use i and j here */
    }
}
To include same-number combinations, change var i = j-1; to var i = j;.

Calendar Loop

Use this when you want to loop over a linear structure or number range while dividing it into fixed-width intervals, like days on a calendar. This is commonly used to present data in arrays or lists on screen, without using an additional grid or 2D array.
GML:
for (var i = 0; i < N; ++i) {
    var xx = i mod INTERVAL;
    var yy = i div INTERVAL;
    /* Use xx and yy here */
}
In visual applications, xx and yy are often multiplied by the visual width and height of each cell respectively, plus optionally an offset for the top-left corner's coordinates. An example for 16x24 cells starting at (32, 48) would draw at an X coordinate of 32+16*xx and a Y coordinate of 48+24*yy.

Notice that this pattern does NOT hold in one-indexing, due to the properties of integer division and remainder. It is an example of why zero-indexing is the natural way to count in computer science.

Loops with Deletion

When looping through a list and making deletions along the way, loop back-to-front whenever feasible. That way the deleted gaps collapse behind you, and you don't need an ongoing size check.
GML:
for (var i = ds_list_size(list)-1; i >= 0; --i) {
    /* You can call ds_list_delete(list, i) here without manual adjustments */
}
If you have to loop front-to-back, remember to: 1) skip incrementing when you delete, and 2) always use the latest size of the list.
GML:
var noDelete = 1;
for (var i = 0; i < ds_list_size(list); i += noDelete) {
    noDelete = 1;
    /* You must set noDelete = 0; before calling ds_list_delete(list, i); here */
}
Specific Loops

These are GML-specific use cases of the for loop. Memorize the ones you will be using often.

Loop Over collision_*_list Collisions

If the order does not matter, loop back to front whenever feasible. This allows you to use collision_*_list's return value directly without an additional size variable.
GML:
for (var i = collision_*_list(...)-1; i >= 0; --i) {
    /* Use list[| i] here */
}
Otherwise, save the result of collision_*_list(...) in a variable, and use that as the upper limit of a forward zero-indexed loop. DO NOT use collision_*_list(...) directly in the terminating condition --- collision functions are computationally expensive.
GML:
var collisions = collision_*_list(...);
for (var i = 0; i < collisions; ++i) {
    /* Use list[| i] here */
}
Loop Over Lines in a File

This pattern can be used to run through all lines in a plain text file.

Important: If you return, exit or break from within the loop, you must call file_text_close(f); beforehand to avoid dangling file handles!
GML:
for (var f = file_text_open_read(filename); !file_text_eof(f); file_text_readln(f)) {
    var line = file_text_read_string(f);
    /* Use line here */
}
file_text_close(f);
Loop Over Map

This pattern can be used to enumerate all key-value pairs when working with JSON using json_decode.
GML:
for (var key = ds_map_find_first(map); !is_undefined(key); key = ds_map_find_next(map)) {
    /* Use key and map[? key] here */
}
Note that this pattern should NOT be used to check the existence of a key or find the value for a key. Use ds_map_exists or the [? ] accessor instead.

Loop Over Struct

This pattern can be used to enumerate all key-value pairs when working with structs in general or JSON using json_parse.
GML:
var keys = variable_struct_get_names(strc);
for (var i = array_length(keys)-1; i >= 0; --i) {
    var key = keys[i];
    var value = keys[$ key];
    /* Use key and value here */
}
Note that this pattern should NOT be used to check the existence of a key or find the value for a key. Use variable_struct_exists or the [$ ] accessor instead.

Common Novice Mistakes

Below are some common novice pitfalls when using for loops. They are easily avoidable if you understand the role of loops in GML and have already memorized the generic patterns.

#1: Inverted Condition

for loops terminate when the middle condition becomes false, not when it becomes true. Here are some incorrect examples for trying to loop N times:
GML:
// Never runs
for (var i = 0; i != N; ++i) {
    ...
}
GML:
// Also never runs
for (var i = 0; i >= N; ++i) {
    ...
}
#2: Off-by-One

For numeric for loops, pay special attention to the comparison operator used for the middle. Including or excluding equivalence inappropriately will result in an off-by-one bug. Here are some incorrect examples for trying to loop N times:
GML:
// Once too many --- should be i < N for zero-indexed
for (var i = 0; i <= N; ++i) {
    ...
}
GML:
// Once too few --- should be i <= N for one-indexed
for (var i = 1; i < N; ++i) {
    ...
}
#3: Non-Instant Loops

DO NOT attempt to use loops to do things gradually over time, such as fading out or moving toward a target. Instead, execute the increments once at a time in the Step event. For more examples, see: What's the Difference: Loop Structures vs. Step Checks and Alarms
 
Last edited:
Top