How NOT to use switch

FrostyCat

Member
How NOT to use switch

GM Version: N/A
Target Platform: All
Download: N/A
Links: N/A
Summary: A summary of common novice abuses of switch with corresponding alternatives.

Introduction
Some novices learning switch blocks fall into the trap of trying to use it everywhere and inventing imaginary syntax for it. This tutorial lists several common abuses and the corresponding correct code.

Misguided belief #1: "switch automatically plugs arguments into functions"

WRONG:
Code:
switch (keyboard_check_pressed) {
  case vk_up: y -= 5; break;
  case vk_down: y += 5; break;
  case vk_left: x -= 5; break;
  case vk_right: x += 5; break;
}
RIGHT:
Code:
if (keyboard_check_pressed(vk_up)) {
  y -= 5;
}
else if (keyboard_check_pressed(vk_down)) {
  y += 5;
}
else if (keyboard_check_pressed(vk_left)) {
  x -= 5;
}
else if (keyboard_check_pressed(vk_right)) {
  x += 5;
}
Misguided belief #2: "switch automatically makes expressions out of fragments"

WRONG:
Code:
switch (score) {
  case <100: show_message("Below average"); break;
  case =100: show_message("Average"); break;
  case >100: show_message("Above average"); break;
}
RIGHT:
Code:
if (score < 100) {
  show_message("Below average");
}
else if (score == 100) {
  show_message("Average");
}
else if (score > 100) {
  show_message("Above average");
}
Improved:
Code:
if (score < 100) {
  show_message("Below average");
}
else if (score == 100) {
  show_message("Average");
}
else {
  show_message("Above average");
}
Misguided belief #3: "switch accepts ranges as cases"

WRONG:
Code:
switch (score) {
  case 0-99: show_message("You suck"); break;
  case 100-199: show_message("Not bad"); break;
  case 200-299: show_message("Nice"); break;
  default: show_message("You rock"); break;
}
WRONG:
Code:
switch (score) {
  case 0 <= score < 100: show_message("You suck"); break;
  case 100 <= score < 200: show_message("Not bad"); break;
  case 200 <= score < 300: show_message("Nice"); break;
  default: show_message("You rock"); break;
}
WRONG:
Code:
switch (score) {
  case 0 <= score && score < 100: show_message("You suck"); break;
  case 100 <= score && score < 200: show_message("Not bad"); break;
  case 200 <= score && score < 300: show_message("Nice"); break;
  default: show_message("You rock"); break;
}
RIGHT:
Code:
if (0 <= score && score < 100) {
  show_message("You suck");
}
else if (100 <= score && score < 200) {
  show_message("Not bad");
}
else if (200 <= score && score < 300) {
  show_message("Nice");
}
else {
  show_message("You rock");
}
Improved (assuming that score is not negative):
Code:
if (score < 100) {
  show_message("You suck");
}
else if (score < 200) {
  show_message("Not bad");
}
else if (score < 300) {
  show_message("Nice");
}
else {
  show_message("You rock");
}
RIGHT:
Code:
switch (score div 100) {
  case 0: show_message("You suck"); break;
  case 1: show_message("Not bad"); break;
  case 2: show_message("Nice"); break;
  default: show_message("You rock"); break;
}
Note: This form can only be used when the ranges are equally spaced.

Misguided belief #4: "switch goes to the first true case"

WRONG:
Code:
switch (name) {
  case string_length(name)<6: show_message("Short name"); break;
  case string_length(name)=6: show_message("Six-lettered name"); break;
  case string_length(name)>6: show_message("Long name"); break;
}
RIGHT:
Code:
if (string_length(name) < 6) {
  show_message("Short name");
}
else if (string_length(name) == 6) {
  show_message("Six-lettered name");
}
else {
  show_message("Long name");
}
Improved:
Code:
var name_length = string_length(name);
if (name_length < 6) {
  show_message("Short name");
}
else if (name_length == 6) {
  show_message("Six-lettered name");
}
else {
  show_message("Long name");
}
WRONG:
Code:
switch (irandom(2)) {
  case irandom(2)=0: show_message("Zero"); break;
  case irandom(2)=1: show_message("One"); break;
  case irandom(2)=2: show_message("Two"); break;
}
RIGHT:
Code:
switch (irandom(2)) {
  case 0: show_message("Zero"); break;
  case 1: show_message("One"); break;
  case 2: show_message("Two"); break;
}
Misguided belief #5: "switch cases are combined using && and ||" (Also see: How NOT to use && and ||)

WRONG:
Code:
switch (value) {
  case 3 && 5:
    show_message("Three or five");
  break;
}
WRONG:
Code:
switch (value) {
  case 3 || 5:
    show_message("Three or five");
  break;
}
RIGHT:
Code:
switch (value) {
  case 3: case 5:
    show_message("Three or five");
  break;
}
Misguided belief #6: "switch can multitask with , or &&"

WRONG:
Code:
switch (importance, urgency) {
  case 0,0: show_message("Do at your leisure"); break;
  case 0,1: show_message("Unimportant but should be done ASAP"); break;
  case 0,2: show_message("Unimportant and must be done right now"); break;
  case 1,0: show_message("Kind of important but can wait"); break;
  case 1,1: show_message("Kind of important and should be done ASAP"); break;
  case 1,2: show_message("Kind of important and must be done right now"); break;
  case 2,0: show_message("Very important but can wait"); break;
  case 2,1: show_message("Very important and should be done ASAP"); break;
  case 2,2: show_message("Critical task"); break;
}
WRONG:
Code:
switch (importance && urgency) {
  case 0&&0: show_message("Do at your leisure"); break;
  case 0&&1: show_message("Unimportant but should be done ASAP"); break;
  case 0&&2: show_message("Unimportant and must be done right now"); break;
  case 1&&0: show_message("Kind of important but can wait"); break;
  case 1&&1: show_message("Kind of important and should be done ASAP"); break;
  case 1&&2: show_message("Kind of important and must be done right now"); break;
  case 2&&0: show_message("Very important but can wait"); break;
  case 2&&1: show_message("Very important and should be done ASAP"); break;
  case 2&&2: show_message("Critical task"); break;
}
RIGHT:
Code:
switch (importance) {
  case 0:
    switch (urgency) {
      case 0: show_message("Do at your leisure"); break;
      case 1: show_message("Unimportant but should be done ASAP"); break;
      case 2: show_message("Unimportant and must be done right now"); break;
    }
  break;
  case 1:
    switch (urgency) {
      case 0: show_message("Kind of important but can wait"); break;
      case 1: show_message("Kind of important and should be done ASAP"); break;
      case 2: show_message("Kind of important and must be done right now"); break;
    }
  break;
  case 2:
    switch (urgency) {
      case 0: show_message("Very important but can wait"); break;
      case 1: show_message("Very important and should be done ASAP"); break;
      case 2: show_message("Critical task"); break;
    }
  break;
}
RIGHT:
Code:
switch (string(importance) + "|" + string(urgency)) {
  case "0|0": show_message("Do at your leisure"); break;
  case "0|1": show_message("Unimportant but should be done ASAP"); break;
  case "0|2": show_message("Unimportant and must be done right now"); break;
  case "1|0": show_message("Kind of important but can wait"); break;
  case "1|1": show_message("Kind of important and should be done ASAP"); break;
  case "1|2": show_message("Kind of important and must be done right now"); break;
  case "2|0": show_message("Very important but can wait"); break;
  case "2|1": show_message("Very important and should be done ASAP"); break;
  case "2|2": show_message("Critical task"); break;
}
Note: string() is unnecessary when the values to compare are already strings. Make sure that the separator cannot appear in either component.

RIGHT:
Code:
switch (importance*3+urgency) {
  case 0: show_message("Do at your leisure"); break;
  case 1: show_message("Unimportant but should be done ASAP"); break;
  case 2: show_message("Unimportant and must be done right now"); break;
  case 3: show_message("Kind of important but can wait"); break;
  case 4: show_message("Kind of important and should be done ASAP"); break;
  case 5: show_message("Kind of important and must be done right now"); break;
  case 6: show_message("Very important but can wait"); break;
  case 7: show_message("Very important and should be done ASAP"); break;
  case 8: show_message("Critical task"); break;
}
Note: This form can only be used with integer values. The multiplier(s) should be big enough to make each combined value distinct.

RIGHT:
Code:
var messages;
messages[0,0] = "Do at your leisure";
messages[0,1] = "Unimportant but should be done ASAP";
messages[0,2] = "Unimportant and must be done right now";
messages[1,0] = "Kind of important but can wait";
messages[1,1] = "Kind of important and should be done ASAP";
messages[1,2] = "Kind of important and must be done right now";
messages[2,0] = "Very important but can wait";
messages[2,1] = "Very important and should be done ASAP";
messages[2,2] = "Critical task";
show_message(messages[importance, urgency]);
Note: This form can only be used when all case actions vary by a single constant value, and the cases are based on two integer values starting from 0.

What can be converted to switch blocks and what can't

switch blocks are not universal silver bullets, stop treating them like one. Use switch only when you are matching an expression against a discrete set of possible values using == only (must be constant in GMS 1.x and above), optionally plus a catch-all case.

In other words, only code in the following if-else ladder form have an equivalent switch block (final else optional):
Code:
var value = expression;
if (value == constant1) {
  //val1
}
else if (value == constant2) {
  //val2
}
//...
else {
  //default
}
Any code that cannot fit this form should not be written using switch blocks.

Checking for switch abuse

To check a switch block for abuse, convert it back to its equivalent if-else ladder form above. If it doesn't make sense, it's wrong.

Example 1: Is this valid?
Code:
switch (state) {
  case "walking": sprite_index = spr_walking; break;
  case "sleeping": sprite_index = spr_sleeping; break;
  case "fighting": sprite_index = spr_fighting; break;
  default: sprite_index = spr_idle; break;
}
Mapping it back to if-elseif form gives:
Code:
var value = state;
if (value == "walking") {
  sprite_index = spr_walking;
}
else if (value == "sleeping") {
  sprite_index = spr_sleeping;
}
else if (value == "fighting") {
  sprite_index = spr_fighting;
}
else {
  sprite_index = spr_idle;
}
This is valid code, and so is the original.

Example 2: Is this valid?
Code:
switch (mouse_check_button) {
  case mb_left: show_message("left"); break;
  case mb_right: show_message("right"); break;
  case mb_middle: show_message("middle"); break;
}
Mapping it back to if-else form gives:
Code:
var value = mouse_check_button;
if (value == mb_left) {
  show_message("left");
}
else if (value == mb_right) {
  show_message("right");
}
else if (value == mb_middle) {
  show_message("middle");
}
The first line clearly doesn't make sense, so the original is wrong. The right thing to do is proper if-else laddering.
Code:
if (mouse_check_button(mb_left)) {
  show_message("left");
}
else if (mouse_check_button(mb_right)) {
  show_message("right");
}
else if (mouse_check_button(mb_middle)) {
  show_message("middle");
}

Updates:
  • 2018-12-17: Fix a typo in one of the examples.
  • 2016-11-20: Changed example in Misguided Belief #1 as per Alice's suggestion.
  • 2016-11-25: Adjusted example in Misguided Belief #3 to better demonstrate the div approach and closed-min-open-max ranges.
 
Last edited:

Gradius

Member
@Ermine:

keyboard_key is a variable that holds the current key. Keyboard_check (the misguided belief) is a function that needs a key to check.

The manual's code checks the current keyboard_key and if it equals any of those, the one in the original post checks nothing and assumes the switch cases will be passed as an argument to the function.
 

Alice

Toolmaker of Bucuresti
Forum Staff
Moderator
Good tutorial.

One thing I would like to point out: in the first example switch is converted to a bunch of ifs, instead of if/else ladder. It wasn't intended, was it?
 

JaimitoEs

Member
WRONG:
Code:
switch (keyboard_check) {
case vk_up: y -= 5; break;
case vk_down: y += 5; break;
case vk_left: x -= 5; break;
case vk_right: x += 5; break;
}
RIGHT:
Code:
if (keyboard_check(vk_up)) {
y -= 5;
}
if (keyboard_check(vk_down)) {
y += 5;
}
if (keyboard_check(vk_left)) {
x -= 5;
}
if (keyboard_check(vk_right)) {
x += 5;
}
Misguided belief #2: "switch automatically makes expressions out of fragments"
The first piece of code is wrong but making this is faster than "if" statement:
RIGHT:
Code:
if keyboard_check(vk_anykey)
{
 switch (keyboard_key)
 {
 case vk_up: y -= 5; break;
 case vk_down: y += 5; break;
 case vk_left: x -= 5; break;
 case vk_right: x += 5; break;
 }
}
 
A

Aura

Guest
@JaimitoEs: It might be faster, but FrostyCat is trying to explain correct function usage syntax there, not the fastest way to handle movement.

Misguided belief #1: "switch automatically plugs arguments into functions"
First of all, they present a switch statement which uses a function as the condition and arguments as cases, which is wrong. Then they present a code that plugs each argument into different function calls, which is correct. If they used the code you posted, they won't have been able to explain misguided belief #1.
 

JaimitoEs

Member
Right! but explain a third alternative method for the tutorial, to do correctly with switch statements, is apreciated too.:)
 
Last edited:

FrostyCat

Member
Good tutorial.

One thing I would like to point out: in the first example switch is converted to a bunch of ifs, instead of if/else ladder. It wasn't intended, was it?
You're right, it wasn't intended. For clarity, I have changed the example to one tap = one move using keyboard_check_pressed(). It demonstrates the point better with an exact equivalent instead of another that isn't.
 

YellowAfterlife

ᴏɴʟɪɴᴇ ᴍᴜʟᴛɪᴘʟᴀʏᴇʀ
Forum Staff
Moderator
The first piece of code is wrong but making this is faster than "if" statement:
RIGHT:
Code:
if keyboard_check(vk_anykey)
{
 switch (keyboard_key)
 {
 case vk_up: y -= 5; break;
 case vk_down: y += 5; break;
 case vk_left: x -= 5; break;
 case vk_right: x += 5; break;
 }
}
That will not work the same, only detecting a single key at a time (i.e. no diagonal movement).
If you are trying to optimize input checking (which is usually done only once per frame, therefore being of least concern), you might have some other problems.
 

JaimitoEs

Member
That will not work the same, only detecting a single key at a time (i.e. no diagonal movement).
If you are trying to optimize input checking (which is usually done only once per frame, therefore being of least concern), you might have some other problems.
But is perfect for an editor tool using _pressed;)
 

gmx0

Member
Some of these actually don't apply to previous versions of GM before Studio. For example, you can use the first true case in GM8.1.
 

FrostyCat

Member
Some of these actually don't apply to previous versions of GM before Studio. For example, you can use the first true case in GM8.1.
The only exception is that with 8.1 and earlier, you can use runtime-evaluated expressions in case. But even then, all the code that's marked as wrong in the guide is still wrong. Taking your example, the "first true case" example done in working legacy GML would be like this:
Code:
// For 8.1 and below ONLY
switch (true) {
  case string_length(name)<6: show_message("Short name"); break;
  case string_length(name)=6: show_message("Six-lettered name"); break;
  case string_length(name)>6: show_message("Long name"); break;
}
Notice that the switch expression shown here is true, NOT name as shown in the wrong example in #4.
 
Top