Game Time!

Bounce Game

Let’s combine all our knowledge, with some new actions to make a game! Let’s make a game like Pong or Breakout. We’ll have a paddle on the bottom of the stage that we have to move back and forth to catch a bouncing ball.

Bounce game

Draw Loop

To control the graphics for our game, we’ll use something called a “draw loop”. A draw loop is a simple concept. We make an action to draw our game elements on the stage, and we call it over and over in a loop until the game is over.

After each call we should sleep for a few milliseconds to avoid too much flicker on the screen.

To start we’ll make a new action called draw_stage, and we’ll start our draw loop.

gníomh draw_stage() {
    >-- We'll fill in our drawing logic in here later
}

>-- Loop forever
nuair-a fíor {
    >-- Call the draw_stage action
    draw_stage()

    >-- Sleep for a few milliseconds
    codladh(10)
}

The Paddle

Let’s get started with our paddle. We want a paddle that we can move around on the bottom of the stage. We’ll set up some variables and then use some stage actions in the draw_stage action to draw the paddle.

Let’s make variables called paddle_height and paddle_width. For the height we can use something like 20, as a small but visible value. For the paddle width we can divide the stage width so the paddle is always 20% of the stage wide. We can use our integer division operator (//) to make sure we round down to a whole number.

Let’s add the following code to the top of the program.

paddle_height := 20
paddle_width := fad_x@stáitse // 5

Now we can make 2 more variables, paddle_x and paddle_y, these are the x and y coordinates of the paddle. Specifically the top right corner. We want to start with the paddle in the bottom left of the stage, so let’s set paddle_x to be 0. To get the paddle on the bottom you might be tempted to set paddle_y to be fad_y@stáitse (the height of the stage), but this won’t work, we need to subtract the paddle height to account for how tall the paddle is.

paddle_y

Now our variables at the top of the program look like this:

paddle_height := 20
paddle_width := fad_x@stáitse // 5

paddle_x := 0
paddle_y := fad_y@stáitse - paddle_height

Draw the Paddle

Now that we know where our paddle is, and how big it is, let’s add the logic on how to draw it to the draw_stage action.

Before we draw our paddle, we should clear the stage of any old things we drew before. We use the glan@stáitse action to do this. “Glan” translates as “clean” or “clear”, and it clears the stage.

After we clear the stage we can change the colour, I’d like to draw a red paddle, so I’ll type dath@stáitse("dearg") to change the colour to “dearg” (red).

Now we can draw our paddle! We can use the dron_lán@stáitse action (short for “dronuilleog lán” meaning “full rectangle”) to draw a rectangle. We need to give it 4 arguments, the x coordinate, the y coordinate, the width and the height.

Now our draw_stage action looks like this:

gníomh draw_stage() {
    >-- Clear the stage
    glan@stáitse()

    dath@stáitse("dearg") >-- Red pen

    dron_lán@stáitse(paddle_x, paddle_y, paddle_width, paddle_height)
}

Let’s try out our program now:

Don’t forget to press to stop the program otherwise it will go forever!

You should see a small red paddle being drawn at the bottom of the stage.

Move the Paddle

Now that we can draw the paddle, how do we move it? We can use the “méarchlár” (meaning “keyboard”) action to get keyboard presses from the user!

First let’s make a new action called key_control. This is the action that will be called when the user presses a key. It takes one argument, the key that was pressed.

gníomh key_control(key) {
}

Let’s fill in scríobh(key) for now.

gníomh key_control(key) {
    scríobh(key)
}

Now we can tell Setanta to use this action with the keyboard by passing it to the méarchlár@stáitse action by calling méarchlár@stáitse(key_control). Let’s add that to our program.

Run this program, press the arrow keys on your keyboard and then check the console.

You should see that “ArrowRight”, “ArrowLeft”, “ArrowDown” or “ArrowUp” have been printed in your console. Each key you press has a name that describes it, we can check the name of the key that was pressed in the key_control action to behave differently depending on what key was pressed.

Note that the name of the keys are in English, this is because these names don’t come from Setanta, they come from the browser.

Let’s add a new variable called paddle_speed to control the speed of the paddle (how far to move the paddle when the user presses an arrow key). I’ll start with a value of 50 as I think it works well.

Then we can change the code in the key_control action to change the paddle_x variable to move the paddle left and right depending on the key that was pressed:

paddle_speed := 50

gníomh key_control(key) {
     key == "ArrowLeft" {
        paddle_x -= paddle_speed
    }   key == "ArrowRight" {
        paddle_x += paddle_speed
    }
}

As you can see if the left arrow is pressed we decrease the x coordinate, and if the right arrow is pressed we increase the x coordinate.

Try out the code now: The paddle should move when you press the arrow keys!

The Ball

Next let’s add a ball to bounce around the stage. Just like with the paddle we’ll add 2 new variables for the x and y coordinates of the ball, ball_x and ball_y, let’s start the ball in the top left corner (0, 0).

Let’s add another variable for the radius of the ball, ball_rad, and start it with a value of 30.

Finally then we need two more variables to control the direction the ball is moving in. We call these ball_dx and ball_dy. ball_dx is the change of the ball in the x direction, and ball_dy is the change in the y direction. We’ll start with a value of 2 for each, meaning the ball will start off moving diagonally towards the bottom right.

ball_x := 0
ball_y := 0

ball_rad := 40

ball_dx := 2
ball_dy := 2

Draw the Ball

Let’s add the logic to draw the ball to our draw_stage action. We’ll switch the colour of the pen to blue (“gorm”), and use the ciorcal_lán (meaning “full circle”) to draw the ball.

gníomh draw_stage() {
    >-- Clear the stage
    glan@stáitse()

    dath@stáitse("dearg") >-- Red pen

    >-- Draw paddle
    dron_lán@stáitse(paddle_x, paddle_y, paddle_width, paddle_height)

    dath@stáitse("gorm") >-- Blue pen

    >-- Draw ball
    ciorcal_lán@stáitse(ball_x, ball_y, ball_rad)
}

Ball Movement

We want the ball to move, let’s make a new action called update_ball that will be called in our draw loop. We should use this action to move the ball a tiny amount (ball_dx and ball_dy specifically).

gníomh update_ball() {
    ball_x += ball_dx
    ball_y += ball_dy
}

Now we include a call to update_ball into our draw loop

>-- Loop forever
nuair-a fíor {
    >-- Call the draw_stage action
    draw_stage()

    >-- Update ball
    update_ball()

    >-- Sleep for a few milliseconds
    codladh(10)
}

Let’s run the code we have so far:

No collisions!

Oh no! The ball just flies off the screen! This is because we haven’t programmed in what the ball should do when it hits the paddle or the walls. Let’s do that now.

Bounce

To add bouncing logic we should change our update_ball action. Let’s start with the walls and we’ll do the paddle logic after.

Before we update the balls position, we can check if it will go past each of the walls, and if it will we should turn it around.

For example, if the x coordinate is going to be less than 0, then it will have gone past the left wall, so we should turn the ball around in the x direction. Similarly if the x coordinate is greater than fad_x@stáitse we should turn the ball around in the x direction. If the y coordinate is less than 0, then the ball is going off the top side so we should change it’s y direction.

Let’s put two new variables in our update_ball action called pred_x and pred_y, these will be the predicted position of the ball, then we can use these values to check if it’s going to go over an edge.

gníomh update_ball() {
    >-- Predicted coordinates
    pred_x := ball_x + ball_dx
    pred_y := ball_y + ball_dy

    >-- We'll do our bounce logic here


    >-- After bounce checks, update ball position
    ball_x += ball_dx
    ball_y += ball_dy
}

Now we insert our checks if the predicted positions are over an edge. To change the direction of the ball in the x direction we want to make ball_dx equal to it’s negative. The same can be done with ball_dy.

When the ball goes over the bottom edge, the game is over. We can use the stop action to completely stop the program.

gníomh update_ball() {
    >-- Predicted coordinates
    pred_x := ball_x + ball_dx
    pred_y := ball_y + ball_dy

     pred_x < 0 {
        >-- Gone over the left edge
        >-- Change ball_dx direction
        ball_dx = -ball_dx
    }
     pred_x > fad_x@stáitse {
        >-- Gone over the right edge
        >-- Change ball_dx direction
        ball_dx = -ball_dx
    }
     pred_y < 0 {
        >-- Gone over the top edge
        >-- Change ball_dy direction
        ball_dy = -ball_dy
    }
     pred_y > fad_y@stáitse {
        >-- Gone over the bottom edge
        scríobh("GAME OVER")
        stop()
    }

    >-- After bounce checks, update ball position
    ball_x += ball_dx
    ball_y += ball_dy
}

Paddle Bounce

Now that we’ve taken care of the top, left and right edges, we need to take care of the paddle.

When we detect that the ball is going to go past the paddle, we can do an additional check to see if the paddle is under the ball by checking if the ball_x is between paddle_x and paddle_x + paddle_width.

If the ball has touched the paddle we can turn it around as if it hit a wall.

gníomh update_ball() {
    >-- Predicted coordinates
    pred_x := ball_x + ball_dx
    pred_y := ball_y + ball_dy

     pred_x < 0 {
        >-- Gone over the left edge
        >-- Change ball_dx direction
        ball_dx = -ball_dx
    }
     pred_x > fad_x@stáitse {
        >-- Gone over the right edge
        >-- Change ball_dx direction
        ball_dx = -ball_dx
    }
     pred_y < 0 {
        >-- Gone over the top edge
        >-- Change ball_dy direction
        ball_dy = -ball_dy
    }
     pred_y > fad_y@stáitse {
        >-- Gone over the bottom edge
        scríobh("GAME OVER")
        stop()
    }

     pred_y > paddle_y {
        >-- Ball gone past paddle_y
        >-- Check if paddle is underneath
         pred_x >= paddle_x & pred_x <= paddle_x + paddle_width {
            >-- Turn ball_dy around
            ball_dy = -ball_dy
        }
    }

    >-- After bounce checks, update ball position
    ball_x += ball_dx
    ball_y += ball_dy
}

Let’s try it out!

It works!

Ta-Da! It works. The ball bounces around off the walls and the paddle, but the game ends if the ball goes over the bottom edge.

Challenge

There are lots of changes we could make to our game to make it better. Here’s just a few things you could try:

  • Try and keep score, the score increases with every bounce off the paddle. Print the score when the game is over
  • Implement a lives system. The player starts with some amount of lives and each time the ball bounces off the bottom they lose a life.
  • The collisions are currently calculated with the center of the ball ball_x, ball_y. Add or subtract the radius of the ball to make the collisions work with the edge of the ball instead.