Pong, Restructured

In the SNAKE tutorial, we did the entire game inside of one main function. That’s fine for short games, but it becomes more and more problematic the longer your game gets.

This time around, we’re going to organize our game in a way that will be much more intuitive. By splitting our one big function into lots of smaller ones, it’ll be easier to organize our thoughts and understand the logic of our game as we expand it.

Which is…​ AWESOME!

Instructions

If a Pong Ball’s in a Forest…​

Let’s take a moment to think about the defining essence of our game.

This is a pong game, so what does a pong game need? In the last section, we made a nice list to answer that question.

  • A table

  • A ball

  • Two paddles

  • A score board

Now, that’s what a pong game has in it, but what about gameplay? What happens in the game?

  • There’s a field; it just sits there

  • Either player can move a paddle up or down

  • A ball bounces around the screen

  • If it hits a paddle, it bounces back the other way

  • If it goes the past paddle, the other player scores a point, and the field resets

  • First one to 9 points wins, and the game starts over

  • The opponent in our game will be a computer

  • Everything needs to be drawn to the screen

That’s everything that happens in our game, so that’s how we should organize it.

A pattern you will commonly see when programming is a setup / run cycle. That is:

  • A setup phase where all the key variable and starting conditions for the program are set.

  • A run phase where the program continues to operate based on your parameters.

It’s kind of like playing with dominoes. You set up all the dominoes, then run away when they all start falling down (lol).

We had setup and run code in SNAKE, but we did it haphazardly just when we needed it. Now we’re going to do it for real.

Running the Game

Let’s move what we have into a running loop. This allows us to restart the game at will, and redraw the screen outside of the game loop, which is useful for things like animations, events, even title screens.

Splitting code into functions also empowers you, as you can control larger and larger behaviors with the same amount of effort.

We’ll throw a gfx.Clear in there too because we’ll need one as soon as things start moving.

HelloPong.spin
VAR
    byte    i
 
PUB Main
    lcd.Start(gfx.Start)
    ctrl.Start
 
    repeat
        RunGame
 
PUB RunGame
 
    gfx.Clear
 
    repeat i from 0 to 7
        gfx.Sprite (@centerline_gfx, 64 - gfx.Width (@centerline_gfx) / 2, i*8, 0)
 
    gfx.Sprite (@ball_gfx, 64 - gfx.Width (@ball_gfx) / 2, 32 - gfx.Height (@ball_gfx) / 2, 0)

Better, but still not great. Let’s move the drawing into its own function too.

HelloPong.spin
    repeat
        RunGame
 
PUB RunGame
 
    gfx.Clear
 
    repeat i from 0 to 7
        gfx.Sprite (@centerline_gfx, 64 - gfx.Width (@centerline_gfx) / 2, i*8, 0)
 
    gfx.Sprite (@ball_gfx, 64 - gfx.Width (@ball_gfx) / 2, 32 - gfx.Height (@ball_gfx) / 2, 0)
    gfx.Sprite (@paddle_gfx, 4, 32 - gfx.Height (@paddle_gfx) / 2, 0)
    gfx.Sprite (@paddle_gfx, 124 - gfx.Width (@paddle_gfx), 32 - gfx.Height (@paddle_gfx) / 2, 0)
    DrawGraphics
 
    lcd.Draw
 
PUB DrawGraphics
 
    repeat i from 0 to 7
        gfx.Sprite (@centerline_gfx, 64 - gfx.Width (@centerline_gfx) / 2, i*8, 0)
 
    gfx.Sprite (@ball_gfx, 64 - gfx.Width (@ball_gfx) / 2, 32 - gfx.Height (@ball_gfx) / 2, 0)
    gfx.Sprite (@paddle_gfx, 4, 32 - gfx.Height (@paddle_gfx) / 2, 0)
    gfx.Sprite (@paddle_gfx, 124 - gfx.Width (@paddle_gfx), 32 - gfx.Height (@paddle_gfx) / 2, 0)
 
DAT
 
    ball_gfx
    word    0
    word    8, 8

"BUT WHY?", you may be asking. It doesn’t make the program faster. it doesn’t add a feature, but it does make the program easier to read.

That, my friends, is called refactoring, and it doesn’t look like much, but it could mean the difference between being able to write a 1,000 line program and a 1,000,000 line program.

Setting Up the Game

We need to restart all the moving parts we’re going to have in pong, but currently there are none. Let’s tie the paddles and ball to some variables, so that we’ll be able to interact with them.

There are two players, one for each paddle, so for simplicity, let’s control them separately.

HelloPong.spin
    ctrl : "LameControl"
 
VAR
    byte    i
 
    byte    ballx
    byte    bally
 
    byte    playerx
    byte    playery
 
    byte    opponentx
    byte    opponenty
 
PUB Main
    lcd.Start(gfx.Start)
    ctrl.Start
 
    repeat

We’re going to create a new function that runs before the game loop that sets the starting locations for all of our sprites.

HelloPong.spin
PUB Main
    lcd.Start(gfx.Start)
    ctrl.Start
 
    SetupGame
 
    repeat
        RunGame
 
PUB SetupGame
 
PUB RunGame
 
    gfx.Clear
 
    DrawGraphics

We already found all the starting values in the previous section. We just need to move them into the new function.

This is good too because all those gfx.Width/gfx.Height calls were getting messy. Now we only call them when we need them.

HelloPong.spin
    repeat
        RunGame
 
PUB SetupGame
 
    ballx := 64 - gfx.Width (@ball_gfx) / 2
    bally := 32 - gfx.Height (@ball_gfx) / 2
 
    playerx := 4
    playery := 32 - gfx.Height (@paddle_gfx) / 2
 
    opponentx := 124 - gfx.Width (@paddle_gfx)
    opponenty := 32 - gfx.Height (@paddle_gfx) / 2
 
PUB RunGame
 
    gfx.Clear

Then we change all of our gfx.Sprite commands (except @centerline) to use the new variables. The center line doesn’t move so it doesn’t need a variable.

HelloPong.spin
    lcd.Draw
 
PUB DrawGraphics
 
    repeat i from 0 to 7
        gfx.Sprite (@centerline_gfx, 63, i*8, 0)
 
    gfx.Sprite (@ball_gfx, ballx, bally, 0)
    gfx.Sprite (@paddle_gfx, playerx, playery, 0)
    gfx.Sprite (@paddle_gfx, opponentx, opponenty, 0)
 
DAT
 
    ball_gfx
    word    0

Well, that wasn’t so bad! Organizing your code takes a little extra work, and it’s hard to see the results of it immediately, but it’s totally worth it. It’ll be so much easier to understand in the long run, as the program gets more complicated.

The Code

HelloPong.spin
CON
    _clkmode = xtal1 + pll16x
    _xinfreq = 5_000_000
 
OBJ
    lcd  : "LameLCD"
    gfx  : "LameGFX"
    ctrl : "LameControl"
 
VAR
    byte    i
 
    byte    ballx
    byte    bally
 
    byte    playerx
    byte    playery
 
    byte    opponentx
    byte    opponenty
 
PUB Main
    lcd.Start(gfx.Start)
    ctrl.Start
 
    SetupGame
 
    repeat
        RunGame
 
PUB SetupGame
 
    ballx := 64 - gfx.Width (@ball_gfx) / 2
    bally := 32 - gfx.Height (@ball_gfx) / 2
 
    playerx := 4
    playery := 32 - gfx.Height (@paddle_gfx) / 2
 
    opponentx := 124 - gfx.Width (@paddle_gfx)
    opponenty := 32 - gfx.Height (@paddle_gfx) / 2
 
PUB RunGame
 
    gfx.Clear
 
    DrawGraphics
 
    lcd.Draw
 
PUB DrawGraphics
 
    repeat i from 0 to 7
        gfx.Sprite (@centerline_gfx, 63, i*8, 0)
 
    gfx.Sprite (@ball_gfx, ballx, bally, 0)
    gfx.Sprite (@paddle_gfx, playerx, playery, 0)
    gfx.Sprite (@paddle_gfx, opponentx, opponenty, 0)
 
DAT
 
    ball_gfx
    word    0
    word    8, 8
    word    %%11111111
    word    %%11111111
    word    %%11111111
    word    %%11111111
    word    %%11111111
    word    %%11111111
    word    %%11111111
    word    %%11111111
 
    paddle_gfx
    word    0
    word    8, 16
    word    %%11111111
    word    %%11111111
    word    %%11111111
    word    %%11111111
    word    %%11111111
    word    %%11111111
    word    %%11111111
    word    %%11111111
    word    %%11111111
    word    %%11111111
    word    %%11111111
    word    %%11111111
    word    %%11111111
    word    %%11111111
    word    %%11111111
    word    %%11111111
 
    centerline_gfx
    word    0
    word    2, 4
    word    %%33
    word    %%33
    word    %%33
    word    %%33

View this example at /tutorials/HelloPong/PongRestructured.spin.

Total Recap

In this section, you learned:

  • How to refactor a large function into smaller functions

  • How and why it’s important to separate startup logic from run logic

  • How to organize a project for growth

Think about this!

  1. What will happen if you don’t call SetupGame before running the game?

  2. What’s the advantage of making DrawGraphics a separate function?

  3. Inside RunGame, we create a function called DrawGraphics that, well, drew graphics. Can you guess the names of some other functions we will create as we add more to the game? What do you think makes a good function name?

results matching ""

    No results matching ""