Arithmetic Wrap in GMS2

I’m learning GameMaker Studio 2 because my 10-year old nephew wants to make video games (and the 10 year old inside of me wants to make video games, too). It’s a nice toolkit and IDE for games, very beginner-friendly, with a friendly community. It’s even been used in some highly polished and popular games. If you’re curious, there’s a ~90 minute tutorial playlist that’s easy to skim as a demo.

Pricing is reasonable, it’s $100 to export desktop games, couple hundred more to export to iOS + Android, HTML 5, even PS4 and Xbox. And this week version 1.4 (with a free trial and discounted upgrade to version 2) is on sale cheap.

The games are coded in “GameMaker Language”, which is approximately PHP 3. Little more OO, little less coercion, but it feels similarly very focused on its niche without much experience behind it. I’ve been mostly coding Haskell the last few weeks, so there’s a bit of whiplash moving between the two.

There’s some great video tutorials on YouTube showing how to make games like NES Zelda, Farming RPG, and Platformer. All three of those channels are worth clicking around on as they have other good playlists or one-off videos.

On that last channel I saw a video on Useful Scripts for GMS2 presenting five code snippets. In GML, a “script” is roughly a singleton static method object used to centralize game state or encapsulate snippets of functionality.

The third script presented at 5:18 in the video was wrap(value, min, max) which wraps values that exceed the min or max back around to the other side. So wrap(5, 0, 9) is 5 because it’s in the bounds, but wrap(11, 5, 9) is 6 because it wraps 2 past 9. There’s a visual in the video at 5:30 that makes it real clear. There’s also a screenshot of the code:

/// @description Wrap(value, min, max)
/// @function Wrap
/// @param value
/// @param min
/// @param max
// Returns the value wrapped, values over or under will be wrapped around

if (argument0 mod 1 == 0)
{
	while (argument0 > argument2 || argument0 < argument1)
	{
		if (argument0 > argument2)
			argument0 += argument1 - argument2 - 1;
		else if (argument0 < argument1)
			argument0 += argument2 - argument1 + 1;
	}
	return(argument0);
}
else
{
	var vOld = argument0 + 1;
	while (argument0 != vOld)
	{
		vOld = argument0;
		if (argument0 < argument1)
			argument0 = argument2 - (argument1 - argument0);
		else if (argument0 > argument2)
			argument0 = argument1 + (argument0 - argument2);
	}
	return(argument0);
}

There’s a lot going on there. This is probably hot-path code that runs every frame, but it has branches inside a loop for something that could almost certainly be an arithmetic one-liner. So for practice with GML and the IDE in general, I rewrote it.

I saved the above script as original_wrap and created my own wrap implementation:

/// @description wrap(value, min, max)
/// @function wrap
/// @param value The value to wrap into the bounds
/// @param min Minimum bound, inclusive
/// @param max Maximum bound, inclusive
// Returns the value wrapped to the range [min, max] (min and max can be swapped).
// Calls floor() on reals, but GML's modulo is doing something weird and original_wrap just hangs indefinitely on some values anyways so oh well.

var value = floor(argument0);
var _min = floor(min(argument1, argument2));
var _max = floor(max(argument1, argument2));
var range = _max - _min + 1; // + 1 is because max bound is inclusive

return (((value - _min) % range) + range) % range + _min;

Some oddities, like JavaDoc instead of a function signature, so arguments have automatic names (reminds me of perl 5). In dev I got a compiler error for referencing argument1 before argument0 – I’m not sure what that could be but look forward to reading the manual. I can’t unimport/shadow the global max and min, the convention seems to be to use a leading underscore for colliding names.

To test it, I created another script called test and invoked it from an object’s create event. Which felt a little roundabout for specifying that I wanted it to run at startup, but I’ve barely touched the manual so I’m probably missing something obvious. I know there’s 3rd-party test library but I wanted to hand-roll to see more moving pieces. I generated a golden master test suite to exercise a bunch of test data, though I didn’t go all the way into property-based testing:

// make this easy to spot in the build output
show_debug_message("HELLO &&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&");

for(i = -5; i < 25; i++) {
  var orig = original_wrap(i, -2, 5);
  var new = wrap(i, -2, 5)
  if (orig != new) {
    show_debug_message("int failed " + string(i) + " orig " + string(orig) + " new " + string(new));
  }
}

//for(i = -5.0; i < 19.2; i = i + 1.1) {
//  show_debug_message(i);
//  var orig = original_wrap(i, -2.1, 5.3);
//  var new = wrap(i, -2.1, 5.3)
//  if (orig != new) {
//    show_debug_message("real failed " + string(i) + " orig " + string(orig) + " new " + string(new));
//  }
//}

The commented-out testcase is, I think, about the broken behavior with regards to reals. I’m pretty sure the (argument0 mod 1 == 0) exists to type-check if argument0 is an integer or real. I’m not sure because GML has typeof for typechecks (is_real in 1.4, which feels very PHP 3), but this is probably well-copy-pasted newbie code. When I tried to test the behavior, I ran into a bug where original_wrap hung indefinitely on some inputs (the existing test triggers this, if you want to uncomment and run). I didn’t want to keep tinkering, so I dropped in some floor calls and moved on.

Anyways, this was some fun tinkering. I’m looking forward to working through the official tutorial with my nephew and maybe making some small games. (Oh, and I release this into the public domain, feel free to use it with or without credit.)

Want more? I'm not as good at forgetting to update @pushcx on Twitter.