Skip to main content

User Input

With a moving snake, you've done a good part of the game. This part lets you give the control into the players hands. WASM-4 supports both gamepad and mouse input, but for this tutorial we will only use gamepad input. If the player presses a certain button, you just have to change the values of the "Direction"-Property of your snake.

That's it.

But first, you need to understand how WASM-4 handles user input.

Gamepad Basics#

WASM-4 accepts up to 4 gamepads and provides 4 variables that represent the current state of this gamepads. It contains a value 0 (zero) if nothing has been pressed; otherwise, it contains a sum of the buttons pressed, based on the table below:

ButtonValue
Button 11
Button 22
Left16
Right32
Up64
Down128

So if the player presses "Right" and "Button 1", the value would be 33. Now are all values "a power of two", meaning you can set and check them using binary operators.

If you want to check if Button 1 is pressed, simply use the binary AND:

This is true for all other buttons too.

Keyboard Layout#

For the player side of things, WASM-4 tries to cover most keyboard layouts:

  • X and Space = Button 1
  • Y, C and Z = Button 2

This should cover QWERTY, QWERTZ and Dvorak layouts.

The gamepads for the players 2, 3 and 4 are currently not implemented.

Detecting justPressed#

Since the current state of the gamepad is stored in a single variable, you need to compare it to the previous state.

You can achieve this by using the bitwise XOR operator. To make it short, here is the code snippet you can use:

const uint8_t just_pressed = *GAMEPAD1 & (*GAMEPAD1 ^ prev_state);

The variable just_pressed now holds all buttons that were pressed this frame, prev_state which we will define in a moment, holds the gamepad state from the previous frame. You can check the state of a single button like this:

if (just_pressed & BUTTON_UP){    //move snake up} 

If you don't care why that is, skip to the next part.

Like I explained in "Gamepad Basics", the value of Gamepad 1 is a combination of all currently pressed buttons. If we store it and use XOR or later on, we only get the differences.

Let's assume the right button is currently pressed. In that case Gamepad 1 has the value 32 (Right = 32). Now the player presses Button 1. The value changes from 32 to 33 (Button 1 + Right = 1 + 32 = 33). By using XOR, we get 1 as a result.

In the next step, we compare it to the current state. Like: What buttons are new this frame.

Here's a "hands-on" example:

Frame 0: Gamepad1 = 0 (No buttons are pressed)Frame 1: Gamepad1 = 32 (Right button is pressed)
Difference between Frame 0 and 1:  00000000 (0)^ 00100000 (32)= 00100000 (32)
What's new:  00100000 (32)& 00100000 (32)= 00100000 (32)
Result: "32" is new
----
Frame 2: Gamepad1 = 33 (Right button and Button 1 are pressed)
Differences between Frame 1 and 2:  00100000 (32)^ 00100001 (33)= 00000001 (1)
What's new:  00000001 (1)& 00100001 (33)= 00000001 (1)
Result: "1" is new
----
Frame 3: Gamepad1 = 1 (Button 1 is pressed, Right button got released)
Difference between Frame 2 and 3:  00100001 (33)^ 00000001 (1)= 00100000 (32)
What's new:  00100000 (32)& 00000001 (1)= 00000000 (0)
Result: No new key was pressed this frame.

Changing Directions#

Now that you know how to detect if a key was pressed in the current frame, it's time you let the player change the direction of the snake.

Like most of this tutorial, this is step is rather easy once you've grasped how it works.

For this, you need to change the update function of in the main file. Remember, this is how it currently looks like:

void update () {    frame_count++;    if(frame_count % 15 == 0)    {      snake_update(&snake);    }    snake_draw(&snake);}

The classic processing loop goes like this: Input, Process the input, output the result. Or in case of most games: User-Input, Update, Render. The last two steps are already in place. Now it's time to add the first part.

It's a good idea to handle the input in its own function. Something like this could be on your mind:

void input(){  const uint8_t just_pressed = *GAMEPAD1 & (*GAMEPAD1 ^ prev_state);
  if (just_pressed & BUTTON_UP)  {    //move snake up  } }
void update () {  frame_count++;
  input();
  if(frame_count % 15 == 0)  {    snake_update(&snake);  }  snake_draw(&snake);}

If you try to compile this, you should get an error as prev_state is not yet declared. Place prev_state at the top of main.c:

#include "wasm4.h"#include "snake.h"#include <stdlib.h>
struct snake snake;int frame_count = 0;uint8_t prev_state = 0;

To notice any change in the gamepad, you have to store the current state at the end of the input. This will make it the previous state. And while you're at it, why not add the other 3 directions along the way:

void input(){    const uint8_t just_pressed = *GAMEPAD1 & (*GAMEPAD1 ^ prev_state);
    if (just_pressed & BUTTON_UP)    {        //move snake up    }     if(just_pressed & BUTTON_DOWN)    {        //move snake down    }    if(just_pressed & BUTTON_LEFT)    {        //move snake left    }    if(just_pressed & BUTTON_RIGHT)    {        //move snake right    }     prev_state = *GAMEPAD1;}

If you want to check if it works: Use the trace function provided by WASM-4. Here's an example:

    if (just_pressed & BUTTON_UP)    {        trace("Button up");    } 

If you use trace in each if-statement, you should see the corresponding output in the console.

Now, instead of using trace to confirm everything works as intended, you should replace it with something like this:

    if (just_pressed & BUTTON_UP)    {        snake_up(&snake);    } 

Fill in equivalent code for the remaining three directions and then add the function declarations to the snake.h file:

void snake_up(struct snake *snake);void snake_down(struct snake *snake);void snake_left(struct snake *snake);void snake_right(struct snake *snake);

Now add the bodies of these functions to snake.c. Here's an example for snake_down:

void snake_down(struct snake *snake){    if(snake->direction.y == 0)    {         snake->direction = (struct point){0,1};    }}

First, it checks if the direction is already changing the Y-Direction. Only if it isn't allow the change. And then change the Y-Direction to 1. The Up direction requires a Y-Direction of -1. Left and Right don't check the Y, but the X and change it accordingly (Left: -1, Right: 1).

With this knowledge, you should be able to implement them. If you're unsure, check the source in the repository.

Controlled Snake