How NOT to use switch

Discussion in 'Tutorials' started by FrostyCat, Sep 30, 2016.

  1. FrostyCat

    FrostyCat Member

    Joined:
    Jun 26, 2016
    Posts:
    4,033
    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: Dec 18, 2018
    Eyeson15, EvanSki, NeZvers and 23 others like this.
  2. yakmoon

    yakmoon Member

    Joined:
    Sep 20, 2016
    Posts:
    30
    thank you.
     
  3. Ermine Notyours

    Ermine Notyours Member

    Joined:
    Nov 17, 2016
    Posts:
    1
    The one and only example for switch in the GameMaker Manual http://gamemaker.info/en/manual/401_13_switch is thus:

    Code:
    switch (keyboard_key)
    {
      case vk_left:
      case vk_numpad4:
        x -= 4; break;
      case vk_right:
      case vk_numpad6:
        x += 4; break;
    }
    How is this different from the code in Misguided Belief #1?
     
    Abrexas, Bentley and (deleted member) like this.
  4. Gradius

    Gradius Member

    Joined:
    Jun 21, 2016
    Posts:
    72
    @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.
     
  5. Alice

    Alice Toolmaker of Bucuresti Forum Staff Moderator

    Joined:
    Jun 20, 2016
    Posts:
    712
    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?
     
    FrostyCat likes this.
  6. JaimitoEs

    JaimitoEs Member

    Joined:
    Aug 9, 2016
    Posts:
    132
    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;
     }
    }
     
  7. Aura

    Aura Puppeteer of Hail

    Joined:
    Jun 22, 2016
    Posts:
    1,420
    @JaimitoEs: It might be faster, but FrostyCat is trying to explain correct function usage syntax there, not the fastest way to handle movement.

    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.
     
  8. JaimitoEs

    JaimitoEs Member

    Joined:
    Aug 9, 2016
    Posts:
    132
    Right! but explain a third alternative method for the tutorial, to do correctly with switch statements, is apreciated too.:)
     
    Last edited: Nov 20, 2016
    Lonewolff likes this.
  9. FrostyCat

    FrostyCat Member

    Joined:
    Jun 26, 2016
    Posts:
    4,033
    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.
     
  10. YellowAfterlife

    YellowAfterlife ᴏɴʟɪɴᴇ ᴍᴜʟᴛɪᴘʟᴀʏᴇʀ Forum Staff Moderator

    Joined:
    Apr 21, 2016
    Posts:
    2,296
    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.
     
  11. JaimitoEs

    JaimitoEs Member

    Joined:
    Aug 9, 2016
    Posts:
    132
    But is perfect for an editor tool using _pressed;)
     
  12. gmx0

    gmx0 Member

    Joined:
    Jul 10, 2016
    Posts:
    58
    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.
     
  13. FrostyCat

    FrostyCat Member

    Joined:
    Jun 26, 2016
    Posts:
    4,033
    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.
     

Share This Page

  1. This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
    By continuing to use this site, you are consenting to our use of cookies.
    Dismiss Notice