• Hey! Guest! The 39th GMC Jam will take place between November 26th, 12:00 UTC and November 30th, 12:00 UTC. Why not join in! Click here to find out more!

Code Bank - share useful code!

rytan451

Member
Quicksort for sorting by some property of elements of arrays
GML:
function shuffle = function(array) {
  for (var i = array_length(array) - 1; i >= 0; i++) {
    array[@ i] = array[irandom(i)];
  }
}

function array_sort_by(array, map) {
  static _swap = function(array, a, b) {
    var c = array[a];
    array[@ a] = array[b];
    array[@ b] = array[a];
  };
  static _quicksort = function(array, map, start, end) {
    // Base case
    if (start == end) {
      return;
    } else if (start == end - 1) {
      if (map(array[start]) > map(array[end])) {
        _swap(array, start, end);
      }
    }
    // Partition
    var end_of_lesser = start;
    for (var i = end_of_lesser; i < end - 1; i++) {
      if (map(array[i]) < map(array[end])) {
        _swap(array, i, end_of_lesser);
        ++end_of_lesser;
      }
    }
    _swap(array, end, end_of_lesser);
    // Recurse
    _quicksort(start, end_of_lesser - 1);
    _quicksort(end_of_lesser + 1, end);
  };
  shuffle(array);
  _quicksort(array, map, 0, array_length(array) - 1);
}
 

Joe Ellis

Member
Alphabetized "Maps" (maybe could be called alphamaps)
Searching for things by name usually is a linear scan through a list, but with alphamaps™ you can add them to an array structure based on what letter they begin with, and end with, so in most cases it narrows the amount of names down so that each name has it's own slot, with only a few others in the same slot, which reduces search iterations by about 90%
So instead of iterating through a list 50 times before it gets to the name it's searching for, it simply gets 2 grid coordinates, the cell with the ord value of the first character on the x axis and the ord of the last character on the y axis, then the cell will only contain a few things and will only have to iterate 3 or so times.

So, the search function is this:

GML:
///map_search(map, name)

var m = argument0, name = argument1;

var
i = wrap(ord(string_char_at(name, 1)), 0, 26),
j = wrap(ord(string_char_at(name, string_length(name))), 0, 26);
var c = m[i, j], n;
if c != 0
{
n = c[_mapcell.names]
i = 0
repeat n[0]
{
if n[++i] = name
{
n = c[_mapcell.nodes]
return n[i]
}
}
}
return 0
To create a map to begin with:

GML:
///map_create()

var m, i = -1;

repeat 26
{m[++i, 25] = 0}

return m
Then to add things to the map:

GML:
///map_add(map, thing)

var m = argument0, n = argument1;

var name = n[_node.name];
var
i = wrap(ord(string_char_at(name, 1)), 0, 26),
j = wrap(ord(string_char_at(name, string_length(name))), 0, 26);

var c = m[i, j];
if c = 0
{
c[_node.type] = _type.mapcell
c[_mapcell.map] = m
c[_mapcell.i] = i
c[_mapcell.j] = j
c[_mapcell.names] = float(0)
c[_mapcell.nodes] = float(0)
m[@ i, j] = c
}

var l = c[_mapcell.nodes];
l[@ ++l[@ 0]] = n
l = c[_mapcell.names]
l[@ ++l[@ 0]] = n[_node.name]
and the wrap function is this:
GML:
///wrap(val, min, max)

var val = argument0;
var min_val = argument1;
var max_val = argument2;
var delta = abs(min_val - max_val);

if min_val = max_val
{return min_val}

if val < min_val
{
while val < min_val
{val += delta}
}

if val >= max_val
{
while val >= max_val
{val -= delta}
}

return val
I did make a newer version of wrap using frac, but I kind of made it then forgot to update my project with it so it's still this at the moment, it's hardly any slower though cus in this case it's just used to make numbers wrap and become part of the alphabet, so things like "Thing 1" "Thing 2" will be classed as "Thing A" "Thing B" and have different slots in the map.

And "float(0)" is just a quick way I use of making a blank array with only 1 (0) index:

GML:
var a;
a[0] = argument0
return a
But you may aswell just use this:

GML:
///array_init()
var a;
a[0] = 0
return a
I also always use the [0] index of arrays to hold how many things are in the array\list, instead of using array_length, so that's what the "c[0]" is used for. You can of course change this and use array_length and start the values at 0. I just do it this way cus I like the 1st thing in a list to have the index of 1, and the 39th to have the index of 39, instead of 38. It just seems ridiculous to always have to subtract 1. If it was up to me the 0 index wouldn't exist and it would start at 1, but hay ho :D
 
Last edited:

Samuel Venable

Time Killer
run the application.
click a window to capture a screenshot of it.
paste into mspaint.

C++:
#include <thread>
#include <chrono>
#include <iostream>
#include <windows.h>

using std::string;
using std::cout;
using std::endl;

int main() {
  RECT rc;
  std::this_thread::sleep_for (std::chrono::seconds(1));
  while (true) {
    if ((GetKeyState(VK_LBUTTON) & 0x100) != 0) {
      break;
    }
  }
  std::this_thread::sleep_for (std::chrono::seconds(1));
  HWND hwnd = GetForegroundWindow();
  GetWindowRect(hwnd, &rc);
  HDC hdcScreen = GetDC(NULL);
  HDC hdc = CreateCompatibleDC(hdcScreen);
  HBITMAP hbmp = CreateCompatibleBitmap(hdcScreen, rc.right - rc.left, rc.bottom - rc.top);
  SelectObject(hdc, hbmp);
  PrintWindow(hwnd, hdc, 0);
  OpenClipboard(NULL);
  EmptyClipboard();
  SetClipboardData(CF_BITMAP, hbmp);
  CloseClipboard();
  DeleteDC(hdc);
  DeleteObject(hbmp);
  ReleaseDC(NULL, hdcScreen);
  cout << "successfully copied to clipboard, please paste it into 'mspaint'" << endl;
  return 0;
}
Based on this stackoverflow answer: https://stackoverflow.com/a/7292773/4821390
 
Last edited:

EvanSki

King of Raccoons
GML:
// check for numbers
var _string = "123";

for (var i = 0; i < 10; i++)
{
    //show_message(i) //DEBUG
    if (string_pos((i),_string))
    {
        _string = real(string(_string));
    }
    //other wise the string will stay a string
}
Simple loop checking for numbers,
its not fool proof in any means but its a start, if anyone wants to improve this they are more then encouraged to do so, this is the same amount of a duct tape fix as depth = -y
 

ZeDuval

Member
GML:
// check for numbers
var _string = "123";

for (var i = 0; i < 10; i++)
{
    //show_message(i) //DEBUG
    if (string_pos((i),_string))
    {
        _string = real(string(_string));
    }
    //other wise the string will stay a string
}
Simple loop checking for numbers,
its not fool proof in any means but its a start, if anyone wants to improve this they are more then encouraged to do so, this is the same amount of a duct tape fix as depth = -y
GML:
if( string_digits( str ) == str ) str = real( str )
 

Joe Ellis

Member
-EDIT-
Ignore this, I got confused and thought that ZeDuval had written the original loop version and didn't realize the code under it was his solution to it, which is all that's needed.
 
Last edited:

Samuel Venable

Time Killer
I wrote a quick API that can extract values from a string representing screen coordinates in the form of WidthxHeight+X+Y as well as put those values back into that originally formatted string. I might make this into an extension, but it will need a lot of other stuff to go with it otherwise I don't see people actually using it. The code may be edited and tested online here as well - http://cpp.sh/4smsc

C++:
#include <iostream>
#include <string>
#include <cerrno>

typedef std::string rectangle_t;

static const char ldgrs[256] = {
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0, 0,
  0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
  2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0,
  0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0
};

std::string string_digits(std::string str) {
  std::string ret;
  for (const char *c = str.c_str(); *c; c++)
    if (ldgrs[(unsigned char)*c]&4) ret += *c;
  return ret;
}

std::size_t string_count(std::string substr, std::string str) {
  std::size_t pos = 0, occ = 0;
  const std::size_t sublen = substr.length();
  while ((pos = str.find(substr, pos)) != std::string::npos)
    occ++, pos += sublen;
  return occ;
}

void print_help(std::string argv0) {
  std::cout << "usage:     " << argv0 << " <flag>           <WxH+X+Y>  "      << std::endl;
  std::cout << "xposition: " << argv0 << " --xpos   (or -x) 640x480+0+0"      << std::endl;
  std::cout << "yposition: " << argv0 << " --ypos   (or -y) 640x480+0+0"      << std::endl;
  std::cout << "width:     " << argv0 << " --width  (or -w) 640x480+0+0"      << std::endl;
  std::cout << "height:    " << argv0 << " --height (or -h) 640x480+0+0"      << std::endl;
  exit(0);
}

void print_error() {
  std::cout << "error parsing or invalid rectangle!" << std::endl;
  exit(1);
}

void print_int(int value) {
  std::cout << value << std::endl;
  exit(0);
}

void print_uint(unsigned value) {
  std::cout << value << std::endl;
  exit(0);
}

void rectangle_getter(rectangle_t rect, int *ret_x, int *ret_y,
  unsigned *ret_w, unsigned *ret_h, bool *ret_e) {
  bool error = false;
  std::size_t wpos = 0;
  std::size_t hpos = rect.find("x") + 1;
  if (hpos == wpos) error = true;
  if (string_count("+", rect) != 2) error = true;
  std::size_t xpos = rect.find_first_of("+") + 1;
  if (xpos == wpos) error = true;
  std::size_t ypos = rect.find_last_of("+") + 1;
  if (ypos == wpos) error = true;
  std::string w = rect.substr(wpos, hpos - wpos - 1);
  if (string_digits(w).empty()) error = true;
  std::string h = rect.substr(hpos, xpos - hpos - 1);
  if (string_digits(h).empty()) error = true;
  std::string x = rect.substr(xpos, ypos - xpos - 1);
  if (string_digits(x).empty()) error = true;
  std::string y = rect.substr(ypos);
  if (string_digits(y).empty()) error = true;
  *ret_x = !error ? (int)strtol(x.c_str(), nullptr, 10) : 0;
  if (errno == ERANGE) error = true;
  *ret_y = !error ? (int)strtol(y.c_str(), nullptr, 10) : 0;
  if (errno == ERANGE) error = true;
  *ret_w = !error ? (unsigned)strtoul(w.c_str(), nullptr, 10) : 0;
  if (errno == ERANGE) error = true;
  *ret_h = !error ? (unsigned)strtoul(h.c_str(), nullptr, 10) : 0;
  if (errno == ERANGE) error = true;
  *ret_e = error;
}

rectangle_t rectangle_setter(int x, int y, unsigned w, unsigned h) {
  return std::to_string(w) + "x" + std::to_string(h) + "+" +
    std::to_string(x) + "+" + std::to_string(y);
}

unsigned rectangle_get_width(rectangle_t rect) {
  bool ret_e;
  int ret_x, ret_y;
  unsigned ret_w, ret_h;
  rectangle_getter(rect, &ret_x, &ret_y, &ret_w, &ret_h, &ret_e);
  if (ret_e) { print_error(); }
  return ret_w;
}

unsigned rectangle_get_height(rectangle_t rect) {
  bool ret_e;
  int ret_x, ret_y;
  unsigned ret_w, ret_h;
  rectangle_getter(rect, &ret_x, &ret_y, &ret_w, &ret_h, &ret_e);
  if (ret_e) { print_error(); }
  return ret_h;
}

int rectangle_get_x(rectangle_t rect) {
  bool ret_e;
  int ret_x, ret_y;
  unsigned ret_w, ret_h;
  rectangle_getter(rect, &ret_x, &ret_y, &ret_w, &ret_h, &ret_e);
  if (ret_e) { print_error(); }
  return ret_x;
}

int rectangle_get_y(rectangle_t rect) {
  bool ret_e;
  int ret_x, ret_y;
  unsigned ret_w, ret_h;
  rectangle_getter(rect, &ret_x, &ret_y, &ret_w, &ret_h, &ret_e);
  if (ret_e) { print_error(); }
  return ret_y;
}

int main (int argc, char **argv) {
  if (argc == 1) { print_help(argv[0]); }
  if (argc > 3)  { print_error(); }
  std::string flag = argv[1];
  rectangle_t rect = argv[1];
  if (argc == 3) { rect = argv[2]; }
  if (argc == 2 && (flag == "--help" || flag == "-h"))
  { print_help(argv[0]); }
  int x = rectangle_get_x(rect);
  int y = rectangle_get_y(rect);
  unsigned w = rectangle_get_width(rect);
  unsigned h = rectangle_get_height(rect);
  if (flag == "--xpos" || flag == "-x")   { print_int(x); }
  if (flag == "--ypos" || flag == "-y")   { print_int(y); }
  if (flag == "--width" || flag == "-w")  { print_uint(w); }
  if (flag == "--height" || flag == "-h") { print_uint(h); }
  rectangle_t r = rectangle_setter(x, y, w, h);
  std::cout << "unparsed: " << r << std::endl;
  std::cout << "parsed x: " << x << std::endl;
  std::cout << "parsed y: " << y << std::endl;
  std::cout << "parsed w: " << w << std::endl;
  std::cout << "parsed h: " << h << std::endl;
  return 0;
}
It can be done in pure GML I'm sure, but this extension would be more for helping people learn C++ than anything else. Using negative numbers for the width or height of the rectangle will result in those values being treated as UINT_MAX, (which equals 4294967295).

So, entering this into the terminal:
Code:
/path/to/rectparser.exe -1x-1+0+0
...will result in the following output:
Code:
unparsed: 4294967295x4294967295+0+0
parsed x: 0
parsed y: 0
parsed w: 4294967295
parsed h: 4294967295
 
Last edited:

Samuel Venable

Time Killer
I wrote another example of how to use my process information extension. This time I demonstrate how to execute notepad.exe (and the equivalent for other platforms) so that the program stays on top of the game while forcing it to close after an alarm event has triggered.

Create Event:
GML:
globalvar process, directory, command, stayontop;
process[0] = 0;

directory_create(game_save_id);
file_copy(working_directory + "readme.txt", game_save_id + "readme.txt");

if (os_type == os_windows) {
  directory = "C:\\Windows\\System32\\";
  command[0] = "notepad.exe \"" + game_save_id + "readme.txt\"";
} else if (os_type == os_macosx) {
  directory = "/Applications/";
  if (file_exists(directory + "TextEdit.app/Contents/MacOS/TextEdit")) { // older than macOS 10.15 Catalina
    command[0] = directory + "TextEdit.app/Contents/MacOS/TextEdit \"" + game_save_id + "readme.txt\"";
  } else { // macOS 10.15 Catalina or later
    directory = "/System" + directory;
    command[0] = directory + "TextEdit.app/Contents/MacOS/TextEdit \"" + game_save_id + "readme.txt\"";
  }
} else {
  if (os_type == os_linux) {
    directory = "/usr/bin/";
  } else if (os_type == os_freebsd) {
    directory = "/usr/local/bin/";
  }
  if (os_type == os_linux || os_type == os_freebsd) {
    if (file_exists(directory + "gedit")) { // GNOME Text Editor
      command[0] = directory + "gedit \"" + game_save_id + "readme.txt\"";
    } else if (file_exists(directory + "pluma")) { // MATE Text Editor
      command[0] = directory + "pluma \"" + game_save_id + "readme.txt\"";
    } else if (file_exists(directory + "kate")) { // KDE Advanced Text Editor
      command[0] = directory + "kate \"" + game_save_id + "readme.txt\"";
    } else if (file_exists(directory + "kwrite")) { // KDE Text Editor
      command[0] = directory + "kwrite \"" + game_save_id + "readme.txt\"";
    } else if (file_exists(directory + "mousepad")) { // XFCE Text Editor
      command[0] = directory + "mousepad \"" + game_save_id + "readme.txt\"";
    } else if (file_exists(directory + "leafpad")) { // LXDE Text Editor
      command[0] = directory + "leafpad \"" + game_save_id + "readme.txt\"";
    } else { // if you have anything else, forget about it, kid!
      show_error("Missing Text Editor or Unsupported Desktop Environment!", true);
    }
  } else {
    show_error("Unsupported Operating System!", true);
  }
}

stayontop[0] = true;
alarm[0] = 10;
Alarm 0 Event:
GML:
process_create(process[0], command[0], stayontop[0]);
alarm[1] = 500;
Alarm 1 Event:
GML:
process_destroy(process[0]);
process_create.gml Script:
GML:
function process_create(process, command, stayontop) {
  process_execute_async(int64(process), string(command));
  if (stayontop) {
    var hwnd = wid_from_top(), pid = pid_from_wid(hwnd);
    while (pid == 0 || hwnd == wid_from_window(window_handle()) ||
      (pid != process_current(int64(process)) &&
      pid_exists(process_current(int64(process))))) {
      pid = pid_from_wid(hwnd);
      hwnd = wid_from_top();
    }
    wid_set_pwid(hwnd, wid_from_window(window_handle()));
  }
}
process_destroy.gml Script:
GML:
function process_destroy(process) {
  if (process_exists(process)) {
    pid_kill(process_current(int64(process)))
    process_clear_out(int64(process));
    process_clear_pid(int64(process));
    wid_to_top(wid_from_window(window_handle()));
  }
}
process_exists.gml Script:
GML:
function process_exists(process) {
  var ppid = ppid_from_pid(process_current(int64(process)));
  if (string_copy(cmd_from_pid(ppid), 0, 8) == "/bin/sh ") { ppid = ppid_from_pid(ppid); }
  return (pid_exists(process_current(int64(process))) && pid_from_self() == ppid);
}
You can get an example project here:
Or if you want to try the code snippets in your own project, you'll need this extension:
p.s. os_freebsd is just a macro for os_unknown :p
 
Last edited:

Bart

WiseBart
The following script allows you to draw a texture group's layout while running the game, which might come in handy for debugging purposes.
It basically shows the same information as the texture preview option in the game options.
You can modify it further by adding specific colors for each resource type, by choosing which resource type to draw, etc.

GML:
/// dbg_draw_texgroup(x, y, texgroup_name)
var _x = argument0;
var _y = argument1;
var texgroup_name = argument2;

var arr_sprites = texturegroup_get_sprites(texgroup_name);
var arr_fonts = texturegroup_get_fonts(texgroup_name);
var arr_tilesets = texturegroup_get_tilesets(texgroup_name);

var len_sprites = array_length_1d(arr_sprites);
var len_fonts = array_length_1d(arr_fonts);
var len_tilesets = array_length_1d(arr_tilesets);

if (len_sprites == 0 && len_fonts == 0 && len_tilesets == 0) {
    // Nothing to draw
    return;
}

// Get texture page dimensions from the first resource we can find on the page
var resource, tex;
if (len_sprites > 0) {
    var resource = arr_sprites[0];
    var tex = sprite_get_texture(resource, 0);
} else if (len_fonts > 0) {
    var resource = arr_fonts[0];
    var tex = font_get_texture(resource);
} else if (len_tilesets > 0) {
    var tls = arr_tilesets[0];
    var tex = tileset_get_texture(arr_tilesets[0]);
}
var w = 1/texture_get_texel_width(tex);
var h = 1/texture_get_texel_height(tex);

// Keep things tidy by storing the current draw color
var c = draw_get_color();

// Now change the color
draw_set_color(c_red);
draw_rectangle(_x, _y, _x + w, _y + h, true);
draw_set_color(c_white);

// Loop through all different resources that take up space on a texture
for(var i = 0;i < len_sprites;i++) {
    var spr = arr_sprites[i];
    var arr_uvs = sprite_get_uvs(spr, 0);
    draw_rectangle(_x + arr_uvs[0]*w, _y + arr_uvs[1]*h, _x + arr_uvs[2]*w, _y + arr_uvs[3]*h, true);
    draw_text(_x + arr_uvs[0]*w, _y + arr_uvs[1]*h, sprite_get_name(spr));
}

for(var i = 0;i < len_fonts;i++) {
    var fnt = arr_fonts[i];
    var arr_uvs = font_get_uvs(fnt);
    draw_rectangle(_x + arr_uvs[0]*w, _y + arr_uvs[1]*h, _x + arr_uvs[2]*w, _y + arr_uvs[3]*h, true);
    draw_text(_x + arr_uvs[0]*w, _y + arr_uvs[1]*h, font_get_name(fnt));
}

for(var i = 0;i < len_tilesets;i++) {
    var tls = arr_tilesets[i];
    var arr_uvs = tileset_get_uvs(tls);
    draw_rectangle(_x + arr_uvs[0]*w, _y + arr_uvs[1]*h, _x + arr_uvs[2]*w, _y + arr_uvs[3]*h, true);
    draw_text(_x + arr_uvs[0]*w, _y + arr_uvs[1]*h, tileset_get_name(tls));
}

// Restore draw state
draw_set_color(c);
Using the script:
GML:
/// Draw event
dbg_draw_texgroup(x, y, "Default");
 
Last edited:

angelwire

Member
This is a dumb little thing I do because I'm scared I'll forget to come back to a "//Todo: do something" comment. I make it a function instead:

GML:
todo("Give the player a pair of pants");

///@function todo(arg)
function todo(_arg)
{
    //Show as a debug message
    show_debug_message("You forgot to: " + string(_arg));
    //Or throw an error
    //throw ("You forgot to: " + string(_arg));
}
 

hippyman

Member
With these four functions you use JSON files to generate structs at runtime.
EDIT: I added a few more functions so that you can save structs to a JSON file.

test.json
JSON:
{
    "object": { "innerNum": 50 },
    "array": [1,2,3],
    "string_var": "Howdy!"
}
GML:
json_struct = json_load_from_file("test.json");

// json_struct.object == { "innerNum": 50 }
// json_struct.object.innerNum == 50
// json_struct.array == [5,10,15]
// json_struct.array[0] == 5
// json_struct.array[1] == 10
// json_struct.array[2] == 15
// json_struct.string_var == "Howdy!"

GML:
function struct_save_to_file(struct,path) {
    var json_string = struct_get_string(struct);
    var file = file_text_open_write(path);
    file_text_write_string(file,json_string);
    file_text_close(file);
}
GML:
/// @func json_load_from_file
function json_load_from_file(path) {
    var json_string = json_file_get_string(path);
    var json_map = json_decode(json_string);
    var json_object = ds_map_to_struct(json_map);
    ds_map_destroy(json_map);
    return json_object;
}
GML:
/// @func json_file_get_string
function json_file_get_string(path) {
    if (!file_exists(path)) show_error(path + " doesn't exist. Check the path.",true);
    var file = file_text_open_read(path);
    var json_string = ""
    while (!file_text_eof(file)) {
        json_string += file_text_readln(file);
    }
    file_text_close(file);
    return json_string;
}
GML:
/// @func ds_map_to_struct
function ds_map_to_struct(map) {
    var struct = {};
    var key = ds_map_find_first(map);
    var count = ds_map_size(map);
    for (var i = 0; i < count; ++i) {
       
        if (ds_map_is_map(map,key)) {
            // Value is a ds_map
            variable_struct_set(struct,key,ds_map_to_struct(map[? key ]));
        } else if (ds_map_is_list(map,key)) {
            // Value is a ds_list
            variable_struct_set(struct,key,ds_list_to_array(map[? key ]));
        } else {
            // Value is a real/string/boolean
            variable_struct_set(struct,key,map[? key ]);
        }
       
        key = ds_map_find_next(map,key);
    }
   
    return struct;
}
GML:
/// @func ds_list_to_array
function ds_list_to_array(list) {
    var count = ds_list_size(list);
    var array = array_create(count);
    for (var i = 0; i < count; ++i) {
        //var val = list[| i ];
       
        if (ds_list_is_map(list,i)) {
            // Value is a ds_map
            array[ i ] = ds_map_to_struct(list[| i ]);
        } else if (ds_list_is_list(list,i)) {
            // Value is a ds_list
            array[ i ] = ds_list_to_array(list[| i ]);
        } else {
            // value is a real/string/boolean
            array[ i ] = list[| i ];
        }
    }
    return array;
}
GML:
function array_get_string(array) {
    var array_str = "[";
    var count = array_length(array);
    for (var i = 0; i < count; ++i) {
        var val = array[i];
        if (is_struct(val)) {
            val = struct_get_string(val);
        } else if (is_array(val)) {
            val = array_get_string(val);
        } else if (is_string(val)) {
            val = "\"" + val + "\"";
        } else {
            val = string(val);
        }
        
        array_str += val;
        if (i < count-1) {
            array_str += ",";
        }
    }
    
    array_str += "]";
    
    return array_str;
}
GML:
function struct_get_string(struct) {
    var struct_str = "{";
    var var_count = variable_struct_names_count(struct);
    var var_names = variable_struct_get_names(struct)
    for (var i = 0; i < var_count; ++i) {
        var var_name = var_names[i];
        var var_val = variable_struct_get(struct,var_name);
        if (is_struct(var_val)) {
            var_val = struct_get_string(var_val);
        } else if (is_array(var_val)) {
            var_val = array_get_string(var_val);
        } else if (is_string(var_val)) {
            var_val = "\"" + var_val + "\"";
        } else {
            var_val = string(var_val);
        }
        
        struct_str += "\"" + var_name + "\"" + ":" + var_val;
        if (i < var_count-1) {
            struct_str += ",";
        }
    }
    
    struct_str += "}";
    
    return struct_str;
}
 
Last edited:

rytan451

Member
A basic linked list implementation in pure GML (2.3). Untested.

All methods are O(1) time complexity, O(1) space complexity.

new LinkedList()
Creates a new linked list.

LinkedList#size
The size (number of elements) of the linked list.

LinkedList#insertFirst(value)
Inserts an element into the start of the list.

LinkedList#insertLast(value)
Inserts an element into the end of the list.

LinkedList#deleteFirst()
Deletes the first element in the list; returns the value of that element.

LinkedList#deleteLast()
Deletes the last element in the list; returns the value of that element.

LinkedList#clear()
Clears the list; returns the number of elements destroyed.

LinkedList#empty()
Returns true if the list is empty, and false otherwise.

LinkedList#getViewFirst()
Returns a view (type LLView) of the first element. This allows looping through the list.

LinkedList#getViewLast()
Returns a view (type LLView) of the last element. This allows looping through the list.

LLView#current
Returns the value of the viewed element. Setting does not affect the linked list.

LLView#previous()
Moves the view to the previous element. Returns true if said element exists, and false otherwise.

LLView#insertBefore(value)
Inserts an element before the current (viewed) element.

LLView#insertAfter(value)
Inserts an element after the current (viewed) element.

LLView#destroyBefore()
Destroys the element before the current (viewed) element; returns the value of the destroyed element.

LLView#destroyAfter()
Destroys the element after the current (viewed) element; returns the value of the destroyed element.

LLView#set(value)
Sets the value of the current (viewed) element.

GML:
/// @desc Linked List implementation
/// @func LinkedList()
function LinkedList() constructor {
  first = undefined;
  last = undefined;
  size = 0;
  _size = 0;
  static t_element = function(value, next, previous, ll) {
    v = value;
    n = next;
    p = previous;
    par = ll;
    static insertBefore = function(value) {
      var e = new t_element(value, self, p, par);
      if (!is_undefined(p)) p.n = e;
      else par.first = e;
      p = e;
      par._size += 1;
      par.size = _size;
    }
    static insertAfter = function(value) {
      var e = new t_element(value, n, self, par);
      if (is_undefined(n)) par.last = e;
      else n.p = e;
      n = e;
      par._size += 1;
      par.size = _size;
    }
    static destroy = function() {
      if (is_undefined(p)) par.first = n;
      else p.n = n;
      if (is_undefined(n)) par.last = p;
      else n.p = p;
      par._size -= 1;
      par.size = _size;
    }
  }
  static LLView = function(viewing) {
    _c = viewing;
    current = is_undefined(_c) ? undefined : _c.value;
    static previous = function() {
      if (is_undefined(_c) || is_undefined(_c.p)) {
        return false;
      } else {
        _c = _c.p;
        current = is_undefined(_c) ? undefined : _c.value;
        return true;
      }
    }
    static next = function() {
      if (is_undefined(_c) || is_undefined(_c.n)) {
        return false;
      } else {
        _c = _c.n;
        current = is_undefined(_c) ? undefined : _c.value;
        return true;
      }
    }
    static insertBefore = function(value) {
      if (!is_undefined(_c)) {
        _c.insertBefore(value);
      }
    }
    static insertAfter = function(value) {
      if (!is_undefined(_c)) {
        _c.insertAfter(value);
      }
    }
    static destroyBefore = function(value) {
      if (!is_undefined(_c) && !is_undefined(_c.p)) {
        var v = _c.p.value;
        _c.p.destroy();
        return v;
      }
      return undefined;
    }
    static destroyAfter = function(value) {
      if (!is_undefined(_c) && !is_undefined(_c.n)) {
        var v = _c.n.value;
        _c.n.destroy();
        return v;
      }
      return undefined;
    }
    static set = function(value) {
      if (!is_undefined(_c)) {
        _c.value = value;
      }
    }
    static destroy = function() {};
  }
  
  static empty = function() {
    return _size == 0;
  }
  static clear = function() {
    var s = _size;
    first = undefined;
    last = undefined;
    _size = 0;
    size = _size;
    return s;
  }
  static insertFirst = function(value) {
    var e = new t_element(value, first, undefined, self);
    if (!is_undefined(first)) {
      first.p = e;
    }
    if (is_undefined(last)) {
      last = e;
    }
    first = e;
    _size += 1;
    size = _size;
  }
  static insertLast = function(value) {
    var e = new t_element(value, undefined, last, self);
    if (!is_undefined(last)) {
      last.n = e;
    }
    if (is_undefined(first)) {
      first = e;
    }
    last = e;
    _size += 1;
    size = _size;
  }
  static deleteFirst = function() {
    if (is_undefined(first)) {
      return undefined;
    }
    var v = first.value;
    first = first.n;
    if (is_undefined(first)) {
      last = first;
    } else {
      first.p = undefined;
    }
    _size -= 1;
    size = _size;
    return v;
  }
  static deleteLast = function() {
    if (is_undefined(last)) {
      return undefined;
    }
    var v = last.value;
    last = last.p;
    if (is_undefined(last)) {
      first = last;
    } else {
      last.n = undefined;
    }
    _size -= 1;
    size = _size;
    return v;
  }
  static getViewFirst = function() {
    return new LLView(first);
  }
  static getViewLast = function() {
    return new LLView(last);
  }
  
  static destroy = function() {}
}
 

Joe Ellis

Member
Here are two functions for HSL color handling (hue, sat, lum)
I've always preferred color pickers that use lum instead of val, cus I find it awkward to create pastel colors with hue sat val. You have to adjust the sat and val separately and lum makes it a lot simpler. I generally just prefer the way it works cus it seems more logical and I'm used to it from using paint since I was little.

I got the original code from https://www.easyrgb.com/en/, which is a great resource for anything color related.

I can't actually explain what the maths is doing cus I don't understand it 100%, more like 65% lol
but it is explained on https://www.easyrgb.com/en/

So this is the function for creating a color using hsl: (It's basically the same as make_color_hsv, but with lum instead)

GML:
function make_color_hsl(hue, sat, lum) {

//Same as make_color_hsv only it uses lum instead of val (Like the ms paint color picker)
//and returns a standard gm color value (u32 integer)
//Note: the input ranges are: hue(0 - 1530), sat(0 - 255), lum(0 - 510)
//This means you're able to use all 1530 possible hues instead of just 255 like make_color_hsv

    //Convert input values to 0-1 range
    hue /= 1530
    sat /= 255
    lum /= 510

    var
    r = hue + 0.333,
    g = hue,
    b = hue - 0.333,
    v1, v2, _range;
  
    if lum < 0.5
    {v1 = lum * (1.0 + sat)}
    else
    {v1 = lum + sat - lum * sat}

    v2 = 2 * lum - v1

    _range = v1 - v2

    //Wrap rgb values to 0-1 range
    if r > 1
    || r < 0
    {r -= sign(r)}
  
    if g > 1
    || g < 0
    {g -= sign(g)}
  
    if b > 1
    || b < 0
    {b -= sign(b)}

    //Red
    if 6 * r < 1
    {r = v2 + _range * 6 * r}
    else
    {
    if 2 * r < 1
    {r = v1}
    else
    {
    if 3 * r < 2
    {r = v2 + _range * (0.666 - r) * 6}
    else
    {r = v2}
    }
    }

    //Green
    if 6 * g < 1
    {g = v2 + _range * 6 * g}
    else
    {
    if 2 * g < 1
    {g = v1}
    else
    {
    if 3 * g < 2
    {g = v2 + _range * (0.666 - g) * 6}
    else
    {g = v2}
    }
    }

    //Blue
    if 6 * b < 1
    {b = v2 + _range * 6 * b}
    else
    {
    if 2 * b < 1
    {b = v1}
    else
    {
    if 3 * b < 2
    {b = v2 + _range * (0.666 - b) * 6}
    else
    {b = v2}
    }
    }

    return make_color_rgb(r * 255, g * 255, b * 255)
}

And this is the function for getting the hue, sat and lum values of a color (A standard gm color as the input argument)

GML:
function color_get_hsl(color) {

//Gets the hue, sat and lum of a given color (standard gm color value)
//and returns them in a vec3 array
//Note: the output ranges are: hue(0 - 1530), sat(0 - 255), lum(0 - 510)

    var
    r = colour_get_red(color) / 255,
    g = colour_get_green(color) / 255,
    b = colour_get_blue(color) / 255,
    _min, _max, _range, hue, sat, lum;

    _min = min(r, g, b)
    _max = max(r, g, b)
    _range = _max - _min

    lum = (_max + _min) / 2

    if _range = 0
    {
    hue = 0
    sat = 0
    }
    else
    {

    if lum < 0.5
    {sat = _range / (_max + _min)}
    else
    {sat = _range / (2 - _max - _min)}

    if r = _max
    {
    if g > b
    {hue = (g - b) / _range}
    else
    {hue = 6 - ((b - g) / _range)}
    }
    else
    {
    if g = _max
    {hue = 2 + (b - r) / _range}
    else
    {hue = 4 + (r - g) / _range}
    }
    if h < 0
    {++hue}
    if h > 6
    {--hue}
    }

    return [round(hue * 1530), round(sat * 255), round(lum * 510)]
}

So now y'all guys can make hsl color pickers! 🤗 No credit required lol
 

Samuel Venable

Time Killer
Remember from GameMaker 8.1 splash_show_video(fname, loop)?
GML:
// Initialize defaults...
function splash_initialize() {
  globalvar splash_get_main, splash_get_caption, splash_get_fullscreen,
  splash_get_border, splash_get_volume;

  splash_get_main = true;
  splash_get_caption = "";
  splash_get_fullscreen = false;
  splash_get_border = true;
  splash_get_volume = 100;
}

// splash_set_main(main) Indicated whether the splash screen must be
// shown in the main game window (true, default) or in a separate window
// (false).

function splash_set_main(main) {
  splash_get_main = main;
}

// splash_set_caption(cap) Sets the caption for the splash window. This
// only has effect when is a separate splash window is used. Default the
// empty string is used.

function splash_set_caption(cap) {
  splash_get_caption = cap;
}

// splash_set_fullscreen(full) Indicates whether to use a full screen
// window or not. This only has effect when is a separate splash window is
// used. Default a normal window is used.

function splash_set_fullscreen(full) {
  splash_get_fullscreen = full;
}

// splash_set_border(border) Indicates whether the window should have a
// border. This only has effect when is a separate normal splash window is
// used. Default a border is used.

function splash_set_border(border) {
  splash_get_border = border;
}

// splash_set_volume(vol) Indicates the percent of volume the splash
// screen is to have if video or audio. Default the value is 100.

function splash_set_volume(vol) {
  splash_get_volume = vol;
}

// splash_show_video(fname,loop) Shows a video splash screen. fname is the
// name of the video file. loop indicates whether to loop the video.

function splash_show_video(fname, loop) {
  globalvar video;
  var wid, wstr, hstr, xstr, ystr, size, pstn, geom;

  if (splash_get_main) {
    if (os_type == os_macosx) {
      wid = window_get_contentview(string(int64(window_handle())));
    } else { wid = string(int64(window_handle())); }
  } else { wid = "-1"; }

  if (splash_get_fullscreen) splash_get_fullscreen = "yes";
  else splash_get_fullscreen = "no";

  if (splash_get_border) splash_get_border = "yes";
  else splash_get_border = "no";

  if (splash_get_main) splash_get_main = "no";
  else splash_get_main = "yes";

  if (loop) loop = "yes";
  else loop = "no";

  wstr = string(window_get_width());
  hstr = string(window_get_height());
  xstr = string(window_get_x());
  ystr = string(window_get_y());
  size = wstr + "x" + hstr;
  pstn = xstr + "+" + ystr;
  geom = size + "+" + pstn;

  video = video_add(fname);
  video_set_option_string(video, "volume", string(splash_get_volume));
  video_set_option_string(video, "input-default-bindings", "no");
  video_set_option_string(video, "title", splash_get_caption);
  video_set_option_string(video, "fs", splash_get_fullscreen);
  video_set_option_string(video, "border", splash_get_border);
  video_set_option_string(video, "keepaspect-window", "no");
  video_set_option_string(video, "taskbar-progress", "no");
  video_set_option_string(video, "ontop", splash_get_main);
  video_set_option_string(video, "geometry", geom);
  video_set_option_string(video, "config", "no");
  video_set_option_string(video, "loop", loop);
  video_set_option_string(video, "osc", "no");
  video_set_option_string(video, "wid", wid);

  video_play(video);
  var canpause = true;
  if (os_type == os_windows) {
    while (video_is_playing(video)) {
      /* handle video pause and stop actions here */
      /* use keyboard_check_direct for pause/stop */
      /* you can use window_has_focus in if check */
      /* keyboard_check_direct is broken on linux */
      /* use async video playback for linux input */
      // example:
      if (window_has_focus()) {
        if (keyboard_check_direct(vk_escape)) {
          video_stop(video);
        } else if (keyboard_check_direct(vk_space)) {
          if (canpause) {
            video_pause(video);
            canpause = false;
          }
        } else {
          canpause = true;
        }
      }
    }
    video_delete(video);
  }
}

// Process Pause/Stop input on non-Windows
function splash_update() {
  if (os_type != os_windows) {
    if (video_exists(video)) {
      if (keyboard_check_pressed(vk_escape)) {
        video_stop(video);
        video_delete(video);
      } else if (keyboard_check_pressed(vk_space)) {
        video_pause(video);
      } else if (!video_is_playing(video)) {
        video_delete(video);
      }
    }
  }
}
Needs this extension.

Example...

Create Event:
GML:
if (os_type == os_macosx) {
  /* macOS-specific instructions for building & running games */   

  // Backspace the signing identity of your mac app to skip code
  // signing; doing so will allow you to move the necessary libs
  // to the correct place in your mac bundle. After building the
  // game, you will need to move ALL *.dylib files in the *.app,
  // EXCEPT "libmpv.1.dylib" and "libvidplayer.dylib" from *.app
  // YouAppBundle.app/Contents/Resources to Contents/Frameworks.
  // After you have moved these files to your frameworks folder,
  // you may then codesign your app manually, and open your app.

  // install libmpv on Mac OS X
  // look at macOS Game Options
  // disable filesystem sandbox

  var src, dst;
  dst = "/usr/local/opt/mpv";
  directory_create(dst);
  dst = "/usr/local/opt/mpv/lib";
  directory_create(dst);
  src = working_directory;
  file_copy(src + "/libmpv.1.dylib",
    dst + "/libmpv.1.dylib");
}

// requirement: any video file supported by ffmpeg & mpv media player
// see online manual for option strings https://mpv.io/manual/stable/
// video_set_option_string() wraps the mpv_set_option_string function

// Ubuntu packages install needed:
// sudo apt-get install libmpv-dev

// Initialize defaults...
splash_initialize();

// splash_set_main(main) Indicated whether the splash screen must be
// shown in the main game window (true, default) or in a separate window
// (false).

splash_set_main(true);

// splash_set_caption(cap) Sets the caption for the splash window. This
// only has effect when is a separate splash window is used. Default the
// empty string is used.

splash_set_caption(window_get_caption());

// splash_set_fullscreen(full) Indicates whether to use a full screen
// window or not. This only has effect when is a separate splash window is
// used. Default a normal window is used.

splash_set_fullscreen(window_get_fullscreen());

// splash_set_border(border) Indicates whether the window should have a
// border. This only has effect when is a separate normal splash window is
// used. Default a border is used.

splash_set_border(true);

// splash_set_volume(vol) Indicates the percent of volume the splash
// screen is to have if video or audio. Default the value is 100.

splash_set_volume(100);

// splash_show_video(fname,loop) Shows a video splash screen. fname is the
// name of the video file. loop indicates whether to loop the video.

splash_show_video(working_directory + "example.webm", false);
Step Event:
GML:
// Process Pause/Stop input on non-Windows
splash_update();

if (!video_exists(video)) {
  // End the game when video playback is ogre...
  // ...it's never ogre...
  // ...shrek is love...
  // ...shrek is life...
  game_end();
}
 
Last edited:

rytan451

Member
An (untested) implementation of the Union-Find Disjoint Set data structure (GC-safe). This data structure works well for Kruskal's Algorithm (for finding minimum/maximum spanning trees), as well as incremental graph connectivity.

GML:
/// @func UnionFindDisjointSet
/// @desc A Union-Find Disjoint Set implementation
function UnionFindDisjointSet(size) constructor {
  var _set, _rank;
  _rank = array_create(size, 0);
  _set = array_create(size);
  for (var i = 0; i < size; i++) {
    _set[i] = i;
  }

  static find = function(element) {
    if (_set[element] != element && _set[_set[element]] != _set[element]) {
      _set[element] = find(_set[element])
    }
    return _set[element];
  }

  static union = function(e1, e2) {
    e1 = find(e1);
    e2 = find(e2);
    switch (sign(_rank[e1] - _rank[e2])) {
      case 0:
        _rank[e1] += 1;
      case 1:
        _set[e2] = e1;
        break;
      case -1:
        _set[e1] = e2;
    }
  }
}
This implementation uses both path compression and union by rank, so their asymptotic time complexity is O(a(n)), where a(n) is the inverse Ackermann function.

(For some reason, I keep implementing data structures and algorithms in GML, but I never end up using them in any games. Or working on any games in any form of consistency)
 
Top