Arduino Pong

Absolutely amazing!!!

Sorry for kicking the project, but it is worth it.

I have made this project and I have used (TIP!) two variable resistors for pin 8 (synch) and instead of the 75 Ohm. That makes it easier to reach those values.

The game works in part:

  • startup is crystal (clear)
  • playing is very bad, the problem is with the updates (they should fall between scanlines but they don't making the screen flicker)
  • between two rounds (after a 'ball' is missed) the screen is nice again.

putting UPDATE_INTERVAL to 100 helps (although it flickers after that), but the games is unplayable then...

I use CLI so that should be ok. I have a duemilanove with an 328 uC.

[UPDATE:]
It was caused by delayMicroseconds(10); around line 558. removing that made my game work at least ;).

Is the 75 Ohm resistor really needed? The linked page that explains signal generation put this resistor INSIDE the TV. The Other arduino TV-library did not recommend this resistor either!

I don't think it's neccesary for most TVs. The 75ohm resistor is implied as being in the TV.
See this thread at AVRFreaks for probably more information than you wanted (note that the resistor values are slightly different because of the inline diodes).

Hope it`s ok to bump this thread again.
I have a small problem. I get the loading image fine, and every time the game pauses I have a clear image, but when the ball moves, the screen jumps alot. I can see the paddle move, so it works aswell.
Anyone have some thoughts?

This is an awesome project, if I can get it to work I`ll make my own cases and PCB, and give as DIY-kits to my 10-12 year old cousins. "This is what we played in the old days" :slight_smile:

I just saw jopiek`s update "It was caused by delayMicroseconds(10); around line 558. removing that made my game work at least ."

It didn`t work when I removed that line. But when I changed the delay to around 75 almost all flickering was gone.

I was inspired by this post to create my own implementation of Pong with my newly acquired Arduino. Though NTSC isn't really my thing, so I made use of this awesome TV out library:

http://code.google.com/p/arduino-tvout/

It comes complete with fun intro screen with a "ball" that slowly eats away at the title (spoiler alert: it doesn't ever eat the whole thing, it checkerboards it as you might expect if you think hard about the mechanics of it) and a "Game Over" screen. Here are some more little tidbits:

  • The game plays to a score of 7 right now because I didn't feel like implementing multi-digit scores.
  • There's no color at all (just black and white). I have no idea how to make it color, since I don't know anything about how the TV interface works.
  • It can be played using one "controller" or two. I only had one potentiometer at the time, so it's configured for one controller that controls both paddles. But having two controllers should work just fine. Just alter the WHEEL_TWO_PIN setting.
  • Paddle sizes are configurable if you want to mess with that.
  • Most of the #defines can be tinkered with to alter the game experience except for "LEFT" and "RIGHT" which are used exclusively for scoring, so just ignore those.
  • There's a very simple hardware setup. I found that only a couple resistors (a 1K and 330) were required for both TVs I tested on.
    Supposedly, with minor modification, the game should work on PAL TVs. But I haven't tried it and small parts of my code are resolution-dependent, so your mileage may vary.

If you don't care to build your own, you can just check out a video here:

(note that I added the "eats away at the title" feature after making the video, so just use your imagination there).

You can see a picture of my hardware setup, along with complete source code, here:

Imgur

I'm very new to Arduino programming (only been doing in a couple weeks), so be gentle when it comes to commenting on my hardware.

Thats great PlayMoney I was going to mention my library in this thread, then you had created a full pong game on top of it, if I had a pot that did not require a screw driver to operate I would have to try it.

As far as color goes thats beyond the scope of the library, gray scale is possible with some slight modifications to the library but it would require twice as much memory.

PAL is super easy just change _NTSC to _PAL when you call begin.

Great work PlayMoney.
But I did have to insert a 75 Ohm resistor between ground and signal on the RCA connector. On a PAL tv that is.

And the speed was way to slow :D, but I found out that I could modify the "if(frame % 3 == 0)" to get different speeds

@robinh: what was the behavior before you added the 75ohm resistor? I've been trying to figure out what circumstances lead to the need for the 75ohm resisotor. How does one know they need it? Which TVs would require it? Thanks.

The behavior was no picture, only sound (I have added some basic audio out through RCA as well).

I knew it worked with 75 Ohm, cause I had used it when I breadboarded the circuit. But when I moved it over to a protoboard I tried without 75 Ohm since several people here mentioned that it wasn`t needed.

The tv is a Full HD 37"

@nootropic:

in German:
"Ein Abschlusswiderstand hat die Impedanz des Übertragungsmediums und sorgt dafür, dass am Kabelende keine Reflexionen auftreten. Dadurch würden reflektierte Schwingungen und Stehwellen entstehen, die in das Kabel zurücklaufen und das übertragene Signal beeinträchtigen. "

i would try to translate it.

An "End-Resistor" (75Ohm) has the impendancy of the signal-way (wire) to get rid of signalreflections on the singal-way ending. The Resistor is killing signal destroying by eliminating returning singnalwaves and errors.

End-Resistor ist Standard by old professional Pal/BNC Video connections...

Middle down...
http://www.itwissen.info/bilder-klein/bnc-komponenten.png

So, if your Signal is destroyed by returning SignalWaves and the resulting Signalerrors or "Signalcrashes" this resistor eliminates the signalreflection on signal-way-ending to corret the signal.

Result is a working or better signal, which now possibly work on your tv. If there are to much errors the signal maybe unusable for your tv.
So, if you have Signalproblems on Pal, you could try a 75Ohm resistor to enhance the signal by eliminating signal "interferences"?1? errors.

Hope i am understandble, hard to explain with german motherlanguage.

Greetings Chris

Thank you, robinh and danke, ChrisS. It sounds like the 75 ohm resistor is required for at least some TVs. I'm considering making a product that generates video and I'm trying to figure out if I should include the 75 ohm resistor (at least as an optional component) and the diodes. I did experiments with diodes and they degraded picture some, and I was told that they were not strictly required. I'm just trying to design a device that would work on the most TVs possible.

mmh, i am not very experienced in electronics i can't help you much more. I wish you good luck with your new Design... keep on!

Greetings
ChrisS

I tried to upload ArduinoPong to Duemilanove with ATmega168 but I couldn't get vertical synchronization. Then I changed this line:

//number of lines to display
//#define DISPLAY_LINES 240
#define DISPLAY_LINES 250

And - bingo! =)

PS. TVout library doesn't produce any signal on my board, I don't know why.

The 168 does not have enough memory for the default resolution. It will return an error while calling TV.begin()/TV.start_render() (error code 4). The horizontal resolution/8*vertical resolution must be lower than the amount of sram that the device has. I don't know how much memory pong requires so a change in resolution will be required but to what I don't know. The lowest horizontal resolution is 104(must be a multiple of 8) and there is no limit to how small the vertical resolution is.

Thanks, TVout works on ATmega168 with 128x48 resolution. =)
128x56 requires too much SRAM.

this is just briliant. i also started with the first PONG version and had some flickerings and strugled as the others did and iwas not really happy about the BIG resolution. but then i have found this briliant tvout application and tried out...a few errors in first but my fault because as i am also a absolute newbie in arduino i did not even know that it is possible to implement librarier or HOW to do that. but now i have this cool PONG from PlayMoney on my arduino duemillanove and it works smooth and without error in my _PAL tv. thanks a lot!! i wonder if you could code another game like tennis? where you have to push a button to release the ball and two potentiometers for each player for horizontal and vertical slide?

you also did that radar thing? (video) any code for this? sorry i am a newbie and i am very "thirsty" for codes like this

many thanks for this great work!!

peroja :slight_smile:

hello. i started to play around with this and i dis some kind of a breakout thingie haha. could be the basic of a breakout game. but i strugled to implement a horizontal paddle...

see yourself

#include <TVout.h>
#include <video_gen.h>
#include <video_properties.h>
#define WHEEL_ONE_PIN 0 //analog
#define WHEEL_TWO_PIN 1 //analog
#define BUTTON_ONE_PIN 2 //digital
#define PADDLE_HEIGHT 12
#define PADDLE_WIDTH 3

#define WALL_HEIGHT (TV.vert_res())
#define WALL_WIDTH 1


#define RIGHT_PADDLE_X (TV.horz_res()-4)
#define LEFT_PADDLE_X 2
#define IN_GAME 0 //in game state
#define IN_MENU 1 //in menu state
#define GAME_OVER 2 //game over state
#define LEFT_SCORE_X (TV.horz_res()/2-15)
#define RIGHT_SCORE_X (TV.horz_res()/2-20)
#define SCORE_Y 4
#define MAX_Y_VELOCITY 1
#define PLAY_TO 7
#define LEFT 0
#define RIGHT 1

TVout TV;
unsigned char x,y;
boolean buttonStatus = false;
int wheelOnePosition = 0;
int wheelTwoPosition = 0;
int rightPaddleY = 0;
int leftPaddleY = 0;
unsigned char ballX = 0;
unsigned char ballY = 0;
char ballVolX = 1;
char ballVolY = 1;


unsigned char blockX = 0;
unsigned char blockY = 0;
char blockVolX = 1;
char blockVolY = 1;





int leftPlayerScore = 0;
int rightPlayerScore = 0;
int frame = 0;
int state = IN_MENU;

void processInputs() {
wheelOnePosition = analogRead(WHEEL_ONE_PIN);
wheelTwoPosition = analogRead(WHEEL_TWO_PIN);
buttonStatus = (digitalRead(BUTTON_ONE_PIN) == LOW);
}



  
  


void drawMenu() {
x = 0;
y = 0;
char volX = 1;
char volY = 1;





TV.clear_screen();
TV.select_font(_8X8);
for(int i=1; i<TV.vert_res() - 4; i+=6) {

TV.draw_line(TV.horz_res()/2,i,TV.vert_res()/2,i, 1);

TV.draw_line(TV.horz_res()/2+20,i,TV.vert_res()/2+20,i, 1);
TV.draw_line(TV.horz_res()/2+40,i,TV.vert_res()/2+40,i, 1);
TV.draw_line(TV.horz_res()/2+60,i,TV.vert_res()/2+60,i, 1);

TV.draw_line(TV.horz_res()/2-20,i,TV.vert_res()/2-20,i, 1);
TV.draw_line(TV.horz_res()/2-40,i,TV.vert_res()/2-40,i, 1);
TV.draw_line(TV.horz_res()/2-60,i,TV.vert_res()/2-60,i, 1);

}
delay(100);
while(!buttonStatus) {
processInputs();
TV.delay_frame(3);
if(x + volX < 1 || x + volX > TV.horz_res() - 1) volX = -volX;
if(y + volY < 1 || y + volY > TV.vert_res() - 1) volY = -volY;
if(TV.get_pixel(x + volX, y + volY)) {

TV.set_pixel(x + volX, y + volY, 0);

TV.set_pixel(x + volX, y + volY, 0);

if(TV.get_pixel(x + volX, y - volY) == 0) {
volY = -volY;
}
else if(TV.get_pixel(x - volX, y + volY) == 0) {
volX = -volX;
}
else {
volX = -volX;
volY = -volY;
}
}
TV.set_pixel(x, y, 0);
x += volX;
y += volY;
TV.set_pixel(x, y, 1);
}
TV.select_font(_5X7);
state = IN_GAME;
}

void setup() {
Serial.begin(9600);
x=0;
y=0;
TV.start_render(_PAL);//for devices with only 1k sram(m168) use TV.begin(_NTSC,128,56)

ballX = TV.horz_res() / 2;
ballY = TV.vert_res() / 2;
}

void loop() {
processInputs();

if(state == IN_MENU) {
drawMenu();
}
if(state == IN_GAME) {
if(frame % 3 == 0) { //every third frame
ballX += ballVolX;
ballY += ballVolY;

if(ballY <= 1 || ballY >= TV.vert_res()-1) ballVolY = -ballVolY;
if(ballVolX < 0 && ballX == LEFT_PADDLE_X+PADDLE_WIDTH-1 && ballY >= leftPaddleY && ballY <= leftPaddleY + PADDLE_HEIGHT) {
ballVolX = -ballVolX;
ballVolY += 2 * ((ballY - leftPaddleY) - (PADDLE_HEIGHT / 2)) / (PADDLE_HEIGHT / 2);
}
if(ballVolX > 0 && ballX == RIGHT_PADDLE_X && ballY >= rightPaddleY && ballY <= rightPaddleY + PADDLE_HEIGHT) {
ballVolX = -ballVolX;
ballVolY += 2 * ((ballY - rightPaddleY) - (PADDLE_HEIGHT / 2)) / (PADDLE_HEIGHT / 2);
}

//limit vertical speed
if(ballVolY > MAX_Y_VELOCITY) ballVolY = MAX_Y_VELOCITY;
if(ballVolY < -MAX_Y_VELOCITY) ballVolY = -MAX_Y_VELOCITY;

if(ballX <= 1) {
//playerScored(RIGHT);
}
if(ballX >= TV.horz_res() - 1) {
//playerScored(LEFT);
}
}
if(buttonStatus) Serial.println((int)ballVolX);

//drawGameScreen();
}
if(state == GAME_OVER) {
//drawGameScreen();
TV.clear_screen();
TV.select_font(_8X8);
TV.print_str(29,25,"GAME");
TV.print_str(68,25,"OVER");
delay(5000);
while(!buttonStatus) {
processInputs();

}
TV.select_font(_5X7); //reset the font
//reset the scores
leftPlayerScore = 0;
rightPlayerScore = 0;
state = IN_MENU;
}

TV.delay_frame(1);
if(++frame == 60) frame = 0; //increment and/or reset frame counter
}

i made a version with the use of three buttons that can be made from some foil; check the playground for more info; you will need this library:
http://www.arduino.cc/playground/Main/CapSense

code:
(only one control for both paddles; need to upgrade the code if you want two independent controls)

#include <TVout.h>
#include <CapSense.h>

#define PADDLE_HEIGHT 12
#define PADDLE_WIDTH 2

#define RIGHT_PADDLE_X (TV.horz_res()-4)
#define LEFT_PADDLE_X 2

#define IN_GAME 0 //in game state
#define IN_MENU 1 //in menu state
#define GAME_OVER 2 //game over state

#define LEFT_SCORE_X (TV.horz_res()/2-15)
#define RIGHT_SCORE_X (TV.horz_res()/2+10)
#define SCORE_Y 4

#define MAX_Y_VELOCITY 3
#define PLAY_TO 7

#define LEFT 0
#define RIGHT 1

CapSense rightButton = CapSense(5,4);        // 10M resistor between pins 4 & 2, pin 2 is sensor pin, add a wire and or foil if desired
CapSense leftButton = CapSense(5,3);        // 10M resistor between pins 4 & 6, pin 6 is sensor pin, add a wire and or foil
CapSense startButton = CapSense(5,2);        // 10M resistor between pins 4 & 8, pin 8 is sensor pin, add a wire and or foil

TVout TV;
unsigned char x,y;

boolean buttonStatus = false;

int wheelOnePosition = 0;
int wheelTwoPosition = 0;
int rightPaddleY = 0;
int leftPaddleY = 0;

unsigned char ballX = 0;
unsigned char ballY = 0;
char ballVolX = 1;
char ballVolY = 1;

int leftPlayerScore = 0;
int rightPlayerScore = 0;

int frame = 0;
int state = IN_MENU;

int basicInput = 50;

void processInputs() {
  long total1 =  startButton.capSense(30);
  if(total1 > basicInput) {
    buttonStatus = true;
  } else {
    buttonStatus = false;
  }
  
  long total2 =  leftButton.capSense(30);
  long total3 =  rightButton.capSense(30);

  if(total2 > basicInput) {
    wheelOnePosition = wheelOnePosition + 3;
    wheelTwoPosition = wheelTwoPosition + 3;
  }

  if(total3 > basicInput) {
    wheelOnePosition = wheelOnePosition - 3;
    wheelTwoPosition = wheelTwoPosition - 3;
  }

leftPaddleY = wheelOnePosition;
if(leftPaddleY < 0) {
  wheelOnePosition = leftPaddleY = 0;
}
if(leftPaddleY > TV.vert_res()-PADDLE_HEIGHT) {
  wheelOnePosition = leftPaddleY = TV.vert_res()-PADDLE_HEIGHT;
}

rightPaddleY = wheelTwoPosition;
if(rightPaddleY < 0) {
  wheelTwoPosition = rightPaddleY = 0;
}
if(rightPaddleY > TV.vert_res()-PADDLE_HEIGHT) {
  wheelTwoPosition = rightPaddleY = TV.vert_res()-PADDLE_HEIGHT;
}


    Serial.print(total1);                  // print sensor output 1
    Serial.print("\t");
    Serial.print(total2);                  // print sensor output 2
    Serial.print("\t");
    Serial.println(total3);                // print sensor output 3

}

void drawGameScreen() {
  TV.clear_screen();
  //draw right paddle
  //rightPaddleY = ((wheelOnePosition / 8) * (TV.vert_res()-PADDLE_HEIGHT))/ 128;
  x = RIGHT_PADDLE_X;
  for(int i=0; i<PADDLE_WIDTH; i++) {
    TV.draw_line(x+i,rightPaddleY,x+i,rightPaddleY+PADDLE_HEIGHT,1);
  }

  //draw left paddle
//  leftPaddleY = ((wheelTwoPosition / 8) * (TV.vert_res()-PADDLE_HEIGHT))/ 128;
  x = LEFT_PADDLE_X;
  for(int i=0; i<PADDLE_WIDTH; i++) {
    TV.draw_line(x+i,leftPaddleY,x+i,leftPaddleY+PADDLE_HEIGHT,1);
  }

  //draw score
  TV.print_char(LEFT_SCORE_X,SCORE_Y,'0'+leftPlayerScore);
  TV.print_char(RIGHT_SCORE_X,SCORE_Y,'0'+rightPlayerScore);

  //draw net
  for(int i=1; i<TV.vert_res() - 4; i+=6) {
    TV.draw_line(TV.horz_res()/2,i,TV.horz_res()/2,i+3, 1); 
  }

  //draw ball
  TV.set_pixel(ballX, ballY, 2);
}

//player == LEFT or RIGHT
void playerScored(byte player) {
  if(player == LEFT) leftPlayerScore++;
  if(player == RIGHT) rightPlayerScore++;

  //check for win
  if(leftPlayerScore == PLAY_TO || rightPlayerScore == PLAY_TO) {
    state = GAME_OVER; 
  }

  ballVolX = -ballVolX;
}

void drawMenu() {
  x = 0;
  y = 0;
  char volX = 1;
  char volY = 1;
  TV.clear_screen();
  TV.select_font(_8X8);
  TV.print_str(10, 5, "Arduino Pong");
  TV.select_font(_5X7);
  TV.print_str(22, 35, "Press Button");
  TV.print_str(50, 45, "To Start");
  delay(1000);
  while(!buttonStatus) {
    processInputs();
    TV.delay_frame(3);
    if(x + volX < 1 || x + volX > TV.horz_res() - 1) volX = -volX;
    if(y + volY < 1 || y + volY > TV.vert_res() - 1) volY = -volY;
    if(TV.get_pixel(x + volX, y + volY)) {
      TV.set_pixel(x + volX, y + volY, 0);

      if(TV.get_pixel(x + volX, y - volY) == 0) {
        volY = -volY;
      }
      else if(TV.get_pixel(x - volX, y + volY) == 0) {
        volX = -volX;
      }
      else {
        volX = -volX;
        volY = -volY; 
      }
    }
    TV.set_pixel(x, y, 0);
    x += volX;
    y += volY;
    TV.set_pixel(x, y, 1);
  }
  TV.select_font(_5X7);
  state = IN_GAME;
}

void setup() {
  Serial.begin(9600);
  x=0;
  y=0;
  TV.start_render(_PAL);//for devices with only 1k sram(m168) use TV.begin(_NTSC,128,56)

  ballX = TV.horz_res() / 2;
  ballY = TV.vert_res() / 2;
}

void loop() {
  processInputs();

  if(state == IN_MENU) {
    drawMenu(); 
  }
  if(state == IN_GAME) {
    if(frame % 3 == 0) { //every third frame
      ballX += ballVolX;
      ballY += ballVolY;

      if(ballY <= 1 || ballY >= TV.vert_res()-1) ballVolY = -ballVolY;
      if(ballVolX < 0 && ballX == LEFT_PADDLE_X+PADDLE_WIDTH-1 && ballY >= leftPaddleY && ballY <= leftPaddleY + PADDLE_HEIGHT) {
        ballVolX = -ballVolX; 
        ballVolY += 2 * ((ballY - leftPaddleY) - (PADDLE_HEIGHT / 2)) / (PADDLE_HEIGHT / 2);
      }
      if(ballVolX > 0 && ballX == RIGHT_PADDLE_X && ballY >= rightPaddleY && ballY <= rightPaddleY + PADDLE_HEIGHT) { 
        ballVolX = -ballVolX; 
        ballVolY += 2 * ((ballY - rightPaddleY) - (PADDLE_HEIGHT / 2)) / (PADDLE_HEIGHT / 2);
      }

      //limit vertical speed
      if(ballVolY > MAX_Y_VELOCITY) ballVolY = MAX_Y_VELOCITY;
      if(ballVolY < -MAX_Y_VELOCITY) ballVolY = -MAX_Y_VELOCITY;

      if(ballX <= 1) {
        playerScored(RIGHT);
      }
      if(ballX >= TV.horz_res() - 1) {
        playerScored(LEFT);
      }
    } 
    if(buttonStatus) Serial.println((int)ballVolX);

    drawGameScreen();
  } 
  if(state == GAME_OVER) {
    drawGameScreen();
    TV.select_font(_8X8);
    TV.print_str(29,25,"GAME");
    TV.print_str(68,25,"OVER");
    while(!buttonStatus) {
      processInputs();
      delay(50); 
    }
    TV.select_font(_5X7); //reset the font
    //reset the scores
    leftPlayerScore = 0;
    rightPlayerScore = 0;
    state = IN_MENU;
  }

  TV.delay_frame(1);
  if(++frame == 60) frame = 0; //increment and/or reset frame counter
}

pic: