Pong DX - variations on the Pong tutorial

This is mostly work by @crait from his Pong Tutorial. After posting my variations in the comments for that post I was encouraged by other members to make my own thread for it.

Having completed the tutorial series I wanted to play around with the code a bit and came up with the following.

Gameplay changes:

  1. The direction of the ball is randomised after each point is scored.
  2. The player paddle moves twice as fast (I prefer how this feels).
  3. The score is shown at the bottom of the screen with a slightly smaller play area that doesn’t overlap the score.

Additions:

  1. Title Screen with mode select
  2. Different gameplay modes (1 player, 2 player, endless)
  3. Text for the win/lose screens
    note: I moved chunks of the code to different functions as that helped my brain when I was editing it.

Gameplay modes:

  • 1 Player
    • you vs the CPU!
    • first to 10 wins the game
  • 2 Player
    • you vs a friend
    • first to 10 wins the game
    • player 1 is up/down
    • player 2 is a/b
    • designed for 2 people sitting facing each other with the Arduboy held sideways between them
  • Endless
    • the computer will return every shot without fail
    • you score a point for each shot you return
    • miss one and shot it’s all over!
    • gives your score and the current highscore after each game
    • note: the highscore is lost when the Arduboy is turned off

Here is the code!

// Tom Bedford (oldmantom)
// July 22nd 2017
// PONG DX
// variations on Jonathan Holmes (crait)'s Pong
// see: https://community.arduboy.com/t/make-your-own-arduboy-game-part-7-make-pong-from-scratch/2615

#include <Arduboy.h>
Arduboy arduboy;

// variables declared here
int gamestate = 0;
int gametype = 0;
int screenwidth = 127;
int screenheight = 47;

// button press buffers
int buffera = 0;
int bufferb = 0;
int bufferup = 0;
int bufferdown = 0;
int bufferleft = 0;
int bufferright = 0;

// ball variables
int ballx = 62; // ball x position
int bally = 0; // ball y position
int ballsize = 4; // ball size
int ballright = 1; // ball horizontal direction
int balldown = 1; // ball vertical direction

// paddle / player variables
int paddlewidth = 4;
int paddleheight = 9;
int playerx = 0;
int playery = 0;

// AI variables
int computery = 0; // default = top
int computerx = screenwidth - paddlewidth; // default = far right - paddlewidth

// scoring
int playerscore = 0;
int computerscore = 0;
int player2score = 0;
int neededtowin = 10; // score needed to win 1 or 2 player
int highscore = 0; // highscore for endless rally

void setup() {
  // start arduboy
  arduboy.begin();
  // seed the random number generator
  srand(7/8);
  // set the frame rate to 60 FPS
  arduboy.setFrameRate(60);
  // randomise the horizontal direction of the first ball for the game
  randomballright();
  // clear the screen
  arduboy.clear();
}

void loop() {
  // prevent the game from running too fast (i.e. if not ready to display next frame, wait)
  if(!arduboy.nextFrame()) {
    return;
  }
  arduboy.clear();
  
  // game code here
  switch(gamestate) {
    case 0:
      // title screen
      gameselect();
      break;
    case 1:
      // game screen
      gameplay();
      break;
    case 2:
      // win screen
      win();
      if(arduboy.pressed(A_BUTTON) and buffera == 0) {
        buffera = 1;
        resetgame();
        gamestate = 0;
      }
      break;
    case 3:
      // game over screen
      gameover();
      if(arduboy.pressed(A_BUTTON) and buffera == 0) {
        buffera = 1;
        resetgame();
        gamestate = 0;
      }
      break;
  }

  resetbuttonbuffers();

  arduboy.display();
}

void gameplay() {

  if (gametype == 0) {
    arduboy.setCursor(40, 52);
    arduboy.print(" VS CPU ");
  }
  if (gametype == 1) {
    arduboy.setCursor(40, 52);
    arduboy.print("   VS   ");
  }
  if (gametype == 2) {
    arduboy.setCursor(40, 52);
    arduboy.print("ENDLESS");
  }
  
  // display the player score
  arduboy.setCursor(4, 52);
  arduboy.print(playerscore);

  // display the computer/player2 score
  if (gametype == 0 or gametype == 1) {
    arduboy.setCursor(115, 52);
    arduboy.print(computerscore);
  }
  
  // draw the ball
  arduboy.fillRect(ballx, bally, ballsize, ballsize, WHITE); // draw the ball
  
  // horizontal movement
  if(ballright == 1) {
    ballx = ballx +1; // move right
  }
  if(ballright == -1) {
    ballx = ballx -1; // move left
  }

//  // horizontal bounces
//  if(ballx == 0) { // if at left edge of screen
//    ballright = 1; // move right
//  }
//  if(ballx + ballsize == screenwidth) { // if at right edge of screen
//    ballright = -1; // move left
//  }

  // vertical movement
  if(balldown == 1) {
    bally = bally + 1; // move down
  }
  if(balldown == -1) {
    bally = bally - 1; // move up
  }
  // vertical bounces
  if(bally == 0) { // if at top of screen
    balldown = 1; // move down
  }
  if(bally + ballsize == screenheight) { // if at bottom of screen
    balldown = -1; // move up
  }
  
  // draw the paddle
  arduboy.fillRect(playerx, playery, paddlewidth, paddleheight, WHITE);
  // move the paddle with arrows
  if (arduboy.pressed(UP_BUTTON) and playery > 0 ) { // if up pressed and not at top
    playery = playery - 2;
  }
  if (arduboy.pressed(DOWN_BUTTON) and playery + paddleheight < screenheight) { // if down pressed and not at bottom
    playery = playery + 2;
  }
  
  // draw the computer's paddle
  arduboy.fillRect(computerx, computery, paddlewidth, paddleheight, WHITE);

  // 1 Player (computer will lose sometimes)
  if (gametype == 0) {
    // computer AI (won't always win)
    if (ballx > 115 or rand()% 20 == 1) { // originally... ballx > 115 or rand()% 20 i.e. if ball on right or ~1/20 frames
      // if ball is...
      // ...towards right hand side of screen (i.e ballx > position on screen)
      // ...or if our random number is equal to 1 or random number of frames passed
      if(bally < computery ) { // if the ball is higher than the computer
        computery = computery - 2; // raise the computer paddle
      }
      if(bally + ballsize > computery + paddleheight ) { // if the ball is lower than the computer
        computery = computery + 2; // lower the computer paddle
      }
    }
  }

  // 2 Player (A and B control computer paddle)
  if (gametype == 1) {
    // move the paddle with A and B
    if (arduboy.pressed(B_BUTTON) and computery > 0 ) { // if B pressed and not at top
      computery = computery - 2; // raise the computer paddle
    }
    if (arduboy.pressed(A_BUTTON) and computery + paddleheight < screenheight) { // if A pressed and not at bottom
      computery = computery + 2;
    }
  } 

  // Endless Rally (computer will always win)
  if (gametype == 2) {
    // computer AI (will always win)
    if(bally < computery ) { // if the ball is higher than the computer
      computery = computery - 1; // raise the computer paddle
    }
    if(bally + ballsize > computery + paddleheight ) { // if the ball is lower than the computer
      computery = computery + 1; // lower the computer paddle
    } 
  }
  
  // if the ball moves off the left end of the screen...
  if (ballx < -10) {
    if (gametype == 2) {
      gamestate = 3; // lose screen - you lost the endless rally!
    }
    ballx = screenwidth / 2; // move the ball to the middle
    randomballright(); // randomise ball horizontal direction
    // scoring for 1 or 2 player 
    if (gametype == 0 or gametype == 1) {
      computerscore = computerscore + 1; // give the computer/player2 a point
    }
  }

  // if the ball moves off the right end of the screen...
  if (ballx > 130) {
    ballx = screenwidth / 2; // move the ball to the middle
    randomballright(); // randomise ball horizontal direction
    // scoring for 1 or 2 player 
    if (gametype == 0 or gametype == 1) {
      playerscore = playerscore + 1; // give the player a point
    }
  }

  // check if player has won
  if (playerscore == neededtowin) {
    gamestate = 2; // win screen
  }
  
  // check if computer has won
  if (computerscore == neededtowin) {
    gamestate = 3; // lose screen
  }
  
  // bounce off player paddle (left side)
  if (ballx == playerx + paddlewidth and playery < bally + ballsize and playery + paddleheight > bally) {
    // if the ball is at the horizontal position of the paddle...
    // ...and the ball is at the vertical position of the paddle...
    ballright = 1; // ...bounce off left paddle (i.e. move right)
    // scoring for endless rally (add 1 for each return shot)
    if (gametype == 2) {
      playerscore = playerscore + 1;
    }
  }
  // bounce of computer paddle (right side)
  if (ballx + ballsize == computerx and computery < bally + ballsize and computery + paddleheight > bally) {
    // if the ball is at the horizontal position of the computer paddle...
    // ...and the ball is at the vertical position of the computer paddle...
    ballright = -1; // ...bounce off right paddle (i.e. move left)
  }
}

void gameselect() {
  
  // print title
  arduboy.setCursor(0,0);
  arduboy.print("PONG DX");
  
  // show gametypes
  arduboy.setCursor(0,16);
  arduboy.print("  1 Player");
  arduboy.setCursor(0,32);
  arduboy.print("  2 Player");
  arduboy.setCursor(0,48);
  arduboy.print("  Endless");

  // set the cursor position for the gametype
  if (gametype == 0) {
    arduboy.setCursor(0,16);
  }
  if (gametype == 1) {
    arduboy.setCursor(0,32);
  }
  if (gametype == 2) {
    arduboy.setCursor(0,48);
  }

  // print the indicator for the gametype
  arduboy.print(">");

  // change gametype when up/down pressed
  if(arduboy.pressed(UP_BUTTON) and bufferup == 0 and gametype > 0) {
    bufferup = 1;
    gametype = gametype - 1;
  }
  if(arduboy.pressed(DOWN_BUTTON) and bufferdown == 0 and gametype < 2) {
    bufferdown = 1;
    gametype = gametype + 1;
  }
  
  // advance to gameplay if A pressed
  if(arduboy.pressed(A_BUTTON) and buffera == 0) {
    buffera = 1;
    gamestate = 1;
  }
  
}

void randomballright() {
  // randomise the horizontal direction of the ball
  int randomnumber = rand() % 2;
  if (randomnumber == 0) {
    ballright = 1;
  }
  if (randomnumber == 1) {
    ballright = -1;
  }
}

void resetgame() {
  ballx = screenwidth / 2;
  if (gametype == 2) {
    if (playerscore > highscore) {
      // set the new highscore
      highscore = playerscore;
    }
  }
  playerscore = 0;
  computerscore = 0;
  // gametype = 0;
  // I used to reset the gametype...
  // ...but I think people are more likely to want to replay same gametype
}

void resetbuttonbuffers() {
  // reset buffer state
  if(arduboy.notPressed(A_BUTTON)){
    buffera = 0;
  }
  if(arduboy.notPressed(B_BUTTON)){
    bufferb = 0;
  }
  if(arduboy.notPressed(UP_BUTTON)){
    bufferup = 0;
  }
  if(arduboy.notPressed(DOWN_BUTTON)){
    bufferdown = 0;
  }
  if(arduboy.notPressed(LEFT_BUTTON)){
    bufferleft = 0;
  }
  if(arduboy.notPressed(RIGHT_BUTTON)){
    bufferright = 0;
  }
}

void win() {
  
  // 1 player  
  if (gametype == 0) {
    arduboy.setCursor(0,0);
    arduboy.print("You Won");
    arduboy.setCursor(0,32);
    arduboy.print("Congratulations!");
    arduboy.setCursor(0,48);
    arduboy.print("Can you do it again?");
  }

  // 2 player
  if (gametype == 1) {
    arduboy.setCursor(0,0);
    arduboy.print("Game Over");
    arduboy.setCursor(0,32);
    arduboy.print("Player 1 Wins!");
    arduboy.setCursor(0,48);
    arduboy.print("Player 2 Loses!");
  }

  // endless
  if (gametype == 2) {
    arduboy.setCursor(0,0);
    arduboy.print("This is wrong...");
    arduboy.setCursor(0,32);
    arduboy.print("How did you win?");
    arduboy.setCursor(0,48);
    arduboy.print("It's not endless?");
  }
  
}

void gameover() {

  // show game over
  arduboy.setCursor(0,0);
  arduboy.print("Game Over");

  // 1 player  
  if (gametype == 0) {
    arduboy.setCursor(0,32);
    arduboy.print("Better luck");
    arduboy.setCursor(0,48);
    arduboy.print("next time...");
  }

  // 2 player
  if (gametype == 1) {
    arduboy.setCursor(0,32);
    arduboy.print("Player 2 Wins!");
    arduboy.setCursor(0,48);
    arduboy.print("Player 1 Loses!");
  }

  // endless
  if (gametype == 2) {
    arduboy.setCursor(0,32);
    arduboy.print("You scored: ");
    arduboy.print(playerscore);
    arduboy.setCursor(0,48);
    if (playerscore > highscore) {
      arduboy.print("New highscore!");
    } else {
      arduboy.print("The highscore is: ");
      arduboy.print(highscore);
    }
  }
  
}

This time around I didn’t implement:

  • different difficulties
  • “art”
  • sounds
  • screensaver mode (CPU vs CPU)

I’ve had a lot of fun playing around with this today. I hope this code is helpful for some of you and perhaps you will enjoy playing it! I think the 2 player mode is the most fun, or taking turns with someone in endless mode.

3 Likes

Awesome job, dude! I love that you were able to take my tutorial and extend it out and add your own features! That really is a testament to how thoroughly you read through them! :smiley:

As I mentioned in one of my Arduboy Magazine articles, when you’re new to making games, sometimes the best way to come up with something great is to take a tutorial and go as far with it as you can. :smile:

1 Like

@oldmantom See, told you @crait would be pleased with it :P.

Keep up the good work!

1 Like

Sorry for my absence! A lot of work keeping me busy, including the Steam release for Circuit Dude that’s coming out tomorrow! :smiley: :zap:

1 Like

My wallet is crying ; n ;.

1 Like

Good luck with tomorrow’s release!

2 Likes

I just noticed something I never noticed the first time round - you’re using srand.

I don’t know if you’re aware but the random number generator is only pseudo-random which means if you seed it with the same number every time it will always generate the same sequence of numbers.

If you want it to be ‘more random’ (i.e. make the sequence different each time) you should call arduboy.initRandomSeed(). It uses noise from an unconnected input pin combined with the time since boot to randomise the initial seed.

Again, sorry if you already knew that, but I know it’s a common mistake and using srand is uncommon in most games so I just wanted to make sure that wasn’t a mistake.

Oh yes, that was a leftover from the initial tutorial series. I came across the method you mentioned after doing some searching as I was concerned the srand was creating the same sequence of numbers each time.

I had gone on to start a game that generates a stream of ants to crush that used arduboy.initRandomSeed() but it wasn’t especially fun to play so I didn’t post it yet. I think it could be fun in a two player mode where you are competing to crush the ants but I haven’t modified it to do that yet.

I was going to work on the ant game again today with the sprites being broken and all but it seems there have been a lot of developments for me to go and catch up on in that thread!

As long as it was a conscious decision and you aren’t worried about it.

I guess it depends how hard the ants are to crush.

That might be a bit awkward to handle if that’s happening at the same time (depending on the controls). Turn-based might work better, though I’d recommend a time limit so the other player doesn’t get bored. Come to think of it, being up against the clock might make ant crushing more fun (as opposed to just having an ‘endless’ mode). That or having some form of ‘life’ system (e.g. defend the picnic, sandwhiches instead of lives).

Sorry, my brain ran away a bit there. Could be good, will be interesting to see when it’s further along. Don’t be afraid to stop and ask for peoples’ input, people here won’t judge a half finished game for having bugs or flaws (pun intended).

1 Like

The ant game taught me a lot… using classes, arrays, random numbers. At the moment you just press a button and a foot comes down to crush some ants from endlessly repeating waves comprised of random number of ants, random speed, random space between them and random mix of regular and queen ants. My idea was to have to crush a certain number before too many escaped off the other side of the screen.

For multiplayer I wanted to have ants coming in from both sides of the screen. Each player would have a foot, so one covers the left side and the other has the right side. As each wave came in you’d have the opportunity to crush ants on your side of the screen, and then coming in from the other side would be ants that the other player missed to crush from their own wave. You’d only use one button each to make the foot go down (e.g. player one down arrow, player two A button).

ants

I want to do a bit more on the questor/bones game now that the sprites are fixed but will likely come back to the ants and get that posted sometime. I spend all day thinking about working on these after work then the evening goes so fast!

1 Like

That’s a very good skill to have, even if you’re only using it at the basic levels of just being a data container or data with a few functions.

I think part of the reason that isn’t as fun as you’d hope is because it’s too random.
The best uses for randomness tend to be having a structured baseline with some means of progression and then using the randomness to deviate from that baseline.

That seems playable.
It might not be something you’d want to do for hours on end but it sounds like a nice little time killer.

I’d recommend getting a friend involved if possible and splitting the tasks up between you.

Also work out what you’re going to do on paper before you write the code, if you can work out the problems on paper it means the time you spend actually writing the code is more likely to just be a matter of just getting the solution running instead of being stuck for what to type or trying to solve the problem and write the code at the same time. My desk is literally littered with class diagrams and attempts at refining algorithms.

As the saying goes - work smarter, not harder.