FrostyCat
Redemption Seeker
GML Joseki Series:
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
Summary
This guide documents a number of common
Generic Loops
These are the fundamental use cases of the
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 runs 0 to <N. Back-to-front runs N-1 to >=0.
Front-to-Back:
Back-to-Front:
Replace N with:
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 runs 1 to <=N, back-to-front runs N to >=1.
Front-to-Back:
Back-to-Front:
Replace N with:
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:
Downward:
Double Nested Loop
This type is formed by nesting a
Front-to-Back example with zero-indexing:
Back-to-Front example with zero-indexing:
For the examples above, replace M and N with:
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
To include same-number combinations, change
Back-to-Front
To include same-number combinations, change
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.
In visual applications,
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 an array or DS 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.
If you have to loop front-to-back, remember to: 1) skip incrementing when you delete at the current position, and 2) always use the latest size.
Specific Loops
These are GML-specific use cases of the
Loop Over
If the order does not matter, loop back to front whenever feasible. This allows you to use
Otherwise, save the result of
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
Loop Over Struct (Post-2.3)
This pattern can be used to enumerate all key-value pairs when working with structs in general or JSON using
Note that this pattern should NOT be used to check the existence of a key or find the value for a key. Use
Loop Over Map (Pre-2.3)
This pattern can be used to enumerate all key-value pairs when working with JSON using
Note that this pattern should NOT be used to check the existence of a key or find the value for a key. Use
Common Novice Mistakes
Below are some common novice pitfalls when using
#1: Inverted Condition
#2: Off-by-One
For numeric
#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
for
Loop Recipe CardsGM 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 runs 0 to <N. Back-to-front runs N-1 to >=0.
Front-to-Back:
GML:
for (var i = 0; i < N; ++i) {
/* Use i here */
}
GML:
for (var i = N-1; i >= 0; --i) {
/* Use i here */
}
- 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 runs 1 to <=N, back-to-front runs N to >=1.
Front-to-Back:
GML:
for (var i = 1; i <= N; ++i) {
/* Use i here */
}
GML:
for (var i = N; i >= 1; --i) {
/* Use i here */
}
- 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 */
}
GML:
for (var i = MAX; i >= MIN; i -= DECREMENT) {
/* Use i here */
}
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 */
}
}
GML:
for (var i = M-1; i >= 0; --i) {
for (var j = N-1; j >= 0; --j) {
/* Use i and j here */
}
}
- For 2D arrays:
array_length(arr)
andarray_length(arr[i])
- For grids (row-major order):
ds_grid_height(grid)
andds_grid_width(grid)
- For grids (column-major order):
ds_grid_width(grid)
andds_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 */
}
}
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 */
}
}
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 */
}
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 an array or DS 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 = array_length(arr)-1; i >= 0; --i) {
/* You can call array_delete(arr, i, count); here without manual adjustments */
}
GML:
for (var i = ds_list_size(list)-1; i >= 0; --i) {
/* You can call ds_list_delete(list, i); here without manual adjustments */
}
GML:
var noDelete = 1;
for (var i = 0; i < array_length(arr); i += noDelete) {
noDelete = 1;
/* You must set noDelete = 0; before calling array_delete(arr, i, count); here */
}
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 */
}
These are GML-specific use cases of the
for
loop. Memorize the ones you will be using often.Loop Over
collision_*_list
CollisionsIf 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 */
}
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 */
}
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);
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 */
}
variable_struct_exists
or the [$ ]
accessor instead.Loop Over Map (Pre-2.3)
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 */
}
ds_map_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) {
...
}
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) {
...
}
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: