Arduino Pac-Man part 2 – It lives!

In the first entry of this series, I discussed what led me to experimenting with video output on an Arduino UNO. Today I will begin retracing my steps of what I did on the night of February 9th, 2014, which led to be creating the basic structure of an unplanned Pac-Man style video game.

To hook video up to an Arduino, all you need is two resistors and an RCA connector. I used an old audio/video cable for a digital camera, and had to visit my local Radio Shack to pick up one of the resistors I did not already have. On the TVout project page, you will see you need 1K ohm resistor, and a 470 ohm resistor. Each resistor connects to specific pins of the Ardunio, then they are wired together on the other side and connected to the center (video) wire of the RCA cable/jack. The shield wire of the RCA cable/jack goes to the Arduino ground. That’s all there is too it. Here is a cluttered photo of how I wired things up, using an iTead Studios screw shield to make things a bit easier, and a random terminal block screw thing just to hold some wires together:

20140210-122859.jpg

To install the TVout library, visit the Google-hostred project page and download the zip file. I extracted the zip file, and then from the Arduino IDE I selected “Sketch -> Import Library -> Add Library…”

Screenshot 2014-02-09 13.09.27After this, I was able to create a new project based on one of the included TVout examples:

Screenshot 2014-02-09 13.10.51I built and uploaded this example project, and watched it run on the only thing I had handy with composite video input: some old video goggles I bought from a liquidator clearance years ago for dirt cheap. These low resolution displays were useless for most things, but would have more resolution than what the TVout could provide: 120×96 default.

The demo looks like this (video from YouTube user Cottees):

It lives! I must have connected the two wires and two resistors properly!

Now, with video output, the first thing I wanted to do was create my own sketch that drew something. I gutted the demo leaving just the bare lines of code:

#include <TVout.h>

TVout TV;

void setup()
{
  TV.begin(NTSC, 120, 96);

  TV.clear_screen();

  TV.draw_circle(TV.hres()/2,TV.vres()/2,TV.vres()/3,WHITE);
}

void loop()
{
}

This simple sketch draw a circle in the middle of the display. Once I had this working, my next thought was to test animation by making the circle bounce around the screen. I added variables to track X and Y position, and movement for X and Y (either +1 or -1, or 0 if not moving). I also added edge detection so the ball wouldn’t fly off the screen. (The TVout library appears to be coded for speed and does little if any error checking – drawing things outside of displayed screen crashed my UNO).

The circle blazed across the screen leaving a trail of pixels.

#include <TVout.h>

TVout TV;

void setup()
{
  TV.begin(NTSC, 120, 96);

  TV.clear_screen();
}

void loop()
{
  uint8_t  x, y;    // X and Y position of ball
  int8_t   xm, ym;  // X and Y movement of ball
  
  // Center of screen
  x = TV.hres()/2;
  y = TV.vres()/2;
  
  // Start moving to the right, and down.
  xm = 1;
  ym = 1;
  
  // We will do our own control loop here.
  while(1)
  {
    TV.draw_circle(x, y, 4, WHITE);
    
    x = x + xm;
    if (x<4 || x>TV.hres()-4) xm = -xm;
    y = y + ym;
    if (y<4 || y>TV.vres()-4) ym = -ym;
  }
}

To resolve this, I added some code to erase the circle before drawing it at a new position. This created much flickering, so I used the “wait for frame” function so I could slow things down and do the drawing/erasing at the end of a screen being updated.

#include <TVout.h>

TVout TV;

void setup()
{
  TV.begin(NTSC, 120, 96);

  TV.clear_screen();
}

void loop()
{
  uint8_t  x, y;    // X and Y position of ball
  int8_t   xm, ym;  // X and Y movement of ball
  
  // Center of screen
  x = TV.hres()/2;
  y = TV.vres()/2;
  
  // Start moving to the right, and down.
  xm = 1;
  ym = 1;
  
  // We will do our own control loop here.
  while(1)
  {
    // Wait for end of screen to be drawn.
    TV.delay_frame(1);
    
    // Erase circle
    TV.draw_circle(x, y, 4, BLACK);

    x = x + xm;
    if (x<4 || x>=TV.hres()-4) xm = -xm;
    y = y + ym;
    if (y<4 || y>=TV.vres()-4) ym = -ym;
    
    TV.draw_circle(x, y, 4, WHITE);
  }
}

Next, I wondered how many more balls I could bounce before it started to slow down. I changed my X and Y variables to arrays, and used a #define to set the number of balls. I also made a #define for the ball size, and could use this for checking the edge of the screen. If a circle size is 4, the center will be at the specified X and Y coordinate, and the circle will be drawn 4 pixels away from that. So, if you tried to “draw_circle(0, 0, 4)”, the left and top of the circle would be drawn off the screen. To adjust, X and Y should never go lower than the circle size, and should never go larger than screen width/height minus circle size. (Note the use of “BALLSIZE-1” in the example below. TVout will report the horizontal size as 120, and vertical size as 96. The pixels are actually 0-119 and 0-95, so you subtract one to get the end pixel.)

#include <TVout.h>

TVout TV;

#define BALLS    10 // Number of balls to bounce.
#define BALLSIZE 4  // Size of balls.

void setup()
{
  TV.begin(NTSC, 120, 96);

  TV.clear_screen();
}

void loop()
{
  uint8_t  x[BALLS], y[BALLS];    // X and Y position of ball
  int8_t   xm[BALLS], ym[BALLS];  // X and Y movement of ball
  uint8_t  i;       // counter
  
  // Initialize balls.
  for (i=0; i<BALLS; i++)
  {
    // Random position
    x[i] = random(BALLSIZE, TV.hres()-BALLSIZE-1);
    y[i] = random(BALLSIZE, TV.vres()-BALLSIZE-1);
  
    // Start moving to the right, and down.
    xm[i] = 1;
    ym[i] = 1;
  }
  
  // We will do our own control loop here.
  while(1)
  {
    // Wait for end of screen to be drawn.
    TV.delay_frame(1);
    
    for (i=0; i<BALLS; i++)
    {
      // Erase balls.
      TV.draw_circle(x[i], y[i], BALLSIZE, BLACK);
    
      x[i] = x[i] + xm[i];
      if (x[i]<=BALLSIZE || x[i]>TV.hres()-BALLSIZE-1) xm[i] = -xm[i];
      
      y[i] = y[i] + ym[i];
      if (y[i]<=BALLSIZE || y[i]>=TV.vres()-BALLSIZE-1) ym[i] = -ym[i];
      
      TV.draw_circle(x[i], y[i], BALLSIZE, WHITE);
    }
  }
}

Ten circles was still fast. Twenty showed some slowdown. But it still looked cool. To make it cooler, you could randomize the ball directions:

    // Random direction
    xm[i] = random(2)*2 - 1;
    ym[i] = random(2)*2 - 1;

The random(2) function will return 0 or 1, so multiplying that by 2 produces 0 or 2, and subtracting 1 from that produces -1 or 1. (It took me a bit to figure this out. Math is hard.)

My next thought was to put a player character on the screen and allow it to be moved by the joystick. (This could also be done by the serial console and keyboard presses.) I initially drew a square to make it look different from the circles, but then I had to do different math for screen edge detection since X and Y of a square is the top left corner. I decided to use a filled circle. I would move it around using the analog joystick.

#include <TVout.h>

TVout TV;

#define BALLS      10 // Number of balls to bounce.
#define BALLSIZE   4  // Size of balls.
#define PLAYERSIZE 6  // Size of player. 

#define ANALOGXPIN 0  // Pin 0 is X on iTead joystick
#define ANALOGYPIN 1  // Pin 1 is Y on iTead joystick

void setup()
{  
  TV.begin(NTSC, 120, 96);
  Serial.begin(9600);

  TV.clear_screen();
}

void loop()
{
  uint8_t  x[BALLS], y[BALLS];    // X and Y position of ball
  int8_t   xm[BALLS], ym[BALLS];  // X and Y movement of ball
  uint8_t  i;       // counter

  uint8_t  px, py;                // X and Y position of player
  
  // Initialize balls.
  for (i=0; i<BALLS; i++)
  {
    // Random position
    x[i] = random(BALLSIZE, TV.hres()-BALLSIZE-1);
    y[i] = random(BALLSIZE, TV.vres()-BALLSIZE-1);
  
    // Random direction
    xm[i] = random(2)*2 - 1;
    ym[i] = random(2)*2 - 1;
  }
  
  // Initialize player.
  px = TV.hres()/2;
  py = TV.vres()/2;
  
  // We will do our own control loop here.
  while(1)
  {
    // Wait for end of screen to be drawn.
    TV.delay_frame(1);
    
    for (i=0; i<BALLS; i++)
    {
      // Erase balls.
      TV.draw_circle(x[i], y[i], BALLSIZE, BLACK);
    
      x[i] = x[i] + xm[i];
      if (x[i]<BALLSIZE || x[i]>TV.hres()-BALLSIZE-1) xm[i] = -xm[i];
      
      y[i] = y[i] + ym[i];
      if (y[i]<=BALLSIZE || y[i]>=TV.vres()-BALLSIZE-1) ym[i] = -ym[i];
      
      TV.draw_circle(x[i], y[i], BALLSIZE, WHITE);
    }
    
    // Erase player
    TV.draw_circle(px, py, PLAYERSIZE, BLACK, BLACK);
    
    // Read joystick (0-1023) and convert to screen resolution.
    px = analogRead(ANALOGXPIN)/(1024/TV.hres());
    if (px<PLAYERSIZE) 
    {
      px = PLAYERSIZE;
    } else if (px>TV.hres()-PLAYERSIZE-1)
    {
      px = TV.hres()-PLAYERSIZE-1;
    }
    
    py = analogRead(ANALOGYPIN)/(1024/TV.vres());
    if (py<PLAYERSIZE) 
    {
      py = PLAYERSIZE;
    } else if (py>TV.vres()-PLAYERSIZE-1)
    {
      py = TV.vres()-PLAYERSIZE-1;
    }
    
    // Draw player.
    TV.draw_circle(px, py, PLAYERSIZE, WHITE, WHITE);
  }
}

At this point, I was starting to think a simple game might be “dodge the circles” and the player would see how long they could survive moving their larger filled circle around while trying to avoid all the bouncing, smaller circles.

But I wasn’t done experimenting with TVout yet. There was still more it could do, with bitmaps, to draw things that weren’t just lines or circles.

Next time, I will discuss how I turned my filled circle in to a simple Pac-Man type character, which made the rest of the night turn in to an attempt to recreate that game.

8 thoughts on “Arduino Pac-Man part 2 – It lives!

    1. Allen Huffman Post author

      If I knew how to turn on the video screens and stuff in a CoCo, I would love to get out mine and hook it up to the TV and start coding. It would be nice to have a website with simple code snippets showing how to do this in the various modes, read the keyboard or joystick, etc.

      Reply
  1. Pingback: Arduino Pac-Man part 3 – animating bitmaps | Sub-Etha Software

  2. Pingback: Arduino Pac-Man part 6 – moving | Sub-Etha Software

  3. Pingback: Arduino Pac-Man part 5 – dot dilemma | Sub-Etha Software

  4. Pingback: Ardunio Pac-Man part 8 – dot dilemma 2 | Sub-Etha Software

  5. Pingback: Arduino Pac-Man part 7 – what’s next? | Sub-Etha Software

  6. Pingback: Arduino Pac-Man part 9 – ghosts | Sub-Etha Software

Leave a Reply