Control the Falling Blocks: key(), setTimeOut(), and Arrow Keys

  1. Now we want to detect which key is pressed by the user, so that the user can move a block up, left, down, right by "khjl" (VI style) or "wasd" (left-hand style), respectively.
  2. The method Screen.key() returns a character pressed by the user. Let's consider the following code, which displays the wincursor library version in the second row (y=1) and shows the pressed key in the first row (y=0):
    #include "wincursor.h"
    
    int main()
    {
        Screen myScreen;
        String msg( myScreen.version(), 1, 0 );
        msg.show();
        myScreen.redraw();
    
        char c;
        while (true)
        {
            c = myScreen.key();
            switch (c) {
                case 'q':
                    break;
                default:
                {
                    String k(c);
                    k.show();
                }
            }
            myScreen.redraw();
        }
    
        return 0;
    }
  3. We can detect the character returned by Screen.key(), and move a falling object left or right, as the user presses 'h' or 'l' (VI style), respectively:
    #include "wincursor.h"
    
    int main()
    {
        Screen myScreen;
    
        int y = 0, x = 10;
        String q('Q', y, x);
        q.show();
    
        char c;
        bool finished = false;
        while (!finished)
        {
            for (y=1; y<20; ++y) {
                c = myScreen.key();
                q.hide();
                switch (c) {
                    case 'q':
                        finished = true;
                        break;
                    case 'h':
                        --x;
                        break;
                    case 'l':
                        ++x;
                        break;
                }
                if (finished) break; // The previous break only leaves switch-case.
                q.show(y, x);
                myScreen.redraw();
            }
        }
    
        return 0;
    }
  4. This looks good, until you notice that if you do not press any key, the object does not fall down. The method Screen.key() will suspend your program until the user presses any key! Just like cin and scanf() suspends your program.
  5. Is it possible that our program only waits for the key press for a short time? If the user does not press any key, it keeps falling down. If the user presses a key, it responds accordingly.
  6. Yes. We can specify a timer by the Screen.setTimeOut() method. It takes an integer argument, which will be the milliseconds to await the user. For example, setTimeOut(300) will set the timer to be 0.3 seconds.
    #include "wincursor.h"
    
    int main()
    {
        Screen myScreen;
        myScreen.setTimeOut(300);   // millisecond
    
        int y = 0, x = 10;
        String q('Q', y, x);
        q.show();
    
        char c;
        bool finished = false;
        while (!finished)
        {
            for (y=1; y<20; ++y) {
                c = myScreen.key();
                q.hide();
                switch (c) {
                    case 'q':
                        finished = true;
                        break;
                    case 'h':
                        --x;
                        break;
                    case 'l':
                        ++x;
                        break;
                }
                if (finished) break; // The previous break only leaves switch-case.
                q.show(y, x);
                myScreen.redraw();
            }
        }
    
        return 0;
    }
  7. With this skill, extend your previous TETRIS program. With the draw_block() and erase_block() functions which you developed, you are now ready to control them while they are falling down.
  8. Re-write your main program. Instead of using a for-loop to sequentially iterate from Block 1 to Block 7, now we want to use an infinite loop in which your program
    1. randomly choose a block
    2. randomly generates a starting X coordinate
    The Y coordinate always starts from 0, because this is where the block begins to fall down.
  9. Inside the infinite loop, use Screen.key() to detect whether the player presses a key.
    1. When the player presses the 'h' key, decrement the X coordinate by 1.
    2. When the player presses the 'l' key, increment the X coordinate by 1.
    3. When the player presses the 'j' key or SPACEBAR, run a for-loop to let the block quickly fall down to the bottom.
      (In this version, the block will simply disappear after it reaches the bottom. In next version, the block will remain there and later blocks will be stacked upon it.)
    4. When the player presses 'q", the program ends.
  10. You will certainly wish to call Screen.setTimeOut() method so that if the player did not press any key, the block can still keep falling down (e.g. after 300ms). Note that in this case, you don't need the Sleep() function anymore.
  11. Draw a border to indicate the region where the player can move a block.
    TETRIS (4)
  12. Some students ask whether it is possible to detect arrow keys UP, DOWN, LEFT, RIGHT. Well, the old Screen.key() in the wincursor library only returns a character (8 bits), so it can only detect ASCII characters (A-Z, a-z, 0-9).
  13. If you want to detect the arrow keys, please download the new version (>= 1.2) of wincursor.h and wincursor.obj, in which Screen.key() returns an int. You may match the arrow keys with constants KEY_UP, KEY_DOWN, KEY_LEFT, KEY_RIGHT, which are defined in curses.h (implicited included by wincursor.h).
  14. Please note that you need to prevent a block from being moved out of the region, so you must check whether there is still space for a block to move, when the player presses the LEFT or the RIGHT key.