Arduino Project Hub
Project tutorial

Virtual Arduino Tennis © GPL3+

Play tennis on a Neopixel matrix and an accelerometer with an Arduino! It's like Mario Tennis meets makers!

  • 171 views
  • 3 comments
  • 2 respects

Components and supplies

Necessary tools and machines

09507 01
Soldering iron (generic)

Apps and online services

About this project

This project has 3 parts: the game board/display, the controller, and the master router (Python script).

Part 1 - The Display

The display is a 15x10 neopixel matrix that runs off of an Arduino Nano. It handles the game logic and input from the Python Script running off of a host computer. It displays a ball that grows and moves further down the matrix- giving an illusion of depth and the ball coming at you. If it detects that the ball is hit the ball will be redrawn going 'away' from you.

The ball's direction (either left or right) changes with each hit. If you miss the ball, the COM gets a point, but if the COM misses (a 25% chance), you get the point. This game design is similar to the Wii Sports version of tennis. The points are displayed whenever the COM scores against the player.

There is a running high-score that is stored on an I2C EEPROM, in the 0x04 address. It keeps a byte that holds the high score. If you score higher than the previous high-score, it overwrites the address. If the COM gets a score of 10, it's game over... If that is confusing, I made a logic chart:

Making the matrix will take some time, so follow the guide I made here.

Part 2 - The Controller

The controller is another Arduino Nano that has a 9DoF stick (3v3 only!) and a HC-05 BLE module attached to it. If the acceleration of either the x or y axis is greater than a specified threshold, it sends a '1' via serial to the host PC, where it gets relayed to the display. The Nano takes a reading of the x and y accelerometer at a set interval where it then checks if the values fall above or below 1.5 grams. In order to access the I2C 9 DoF stick, you must install the Sparkfun library which can be downloaded here. There is also a tutorial on how to hook up the sensor and run basic programs with it here. To build the racquet I used a box and then cut out a racquet shape on a piece of cardboard. From there it was easy- just put a piece of white paper over the cardboard cutout and draw a design over the paper. I used a 9v battery to power the setup, just be sure to run the battery through the Vin pin on the Nano, or else it will be an unregulated 9v! Here is a picture of the internals inside the racquet:

So now you may be wondering, what controls this complex operation? How does the racquet talk to the display? Here's a secret: they don't talk to each other! There is a simple Python script that uses the incoming Bluetooth COM port and the matrix COM port to relay information between them. It also helps to sync the two systems, as they both reset when the serial ports initialize. The racquet sends a '1' to the Python script via Bluetooth, and then the Python script sends a '1' to matrix. There is a debounce of 1 sec so the serial ports don't get their buffers overflowed (they only have 32 bit buffers).

Here is a walk through of how I built the racket:

Have fun with your new tennis game!

A video of tennis being played:

Schematics

Controller Schematic
Controller scematic bb cyf7rhpzmp
Matrix Schematic
Thumbnail bb 4tjx0di86l

Code

Matrix CodeC/C++
Goes on the Arduino attached to the matrix.
#include <Adafruit_GFX.h>
#include <Adafruit_NeoMatrix.h>
#include <Adafruit_NeoPixel.h>
#include <Wire.h>
#include <EEPROM.h>

#define PIN 6 //Data pin for matrix
#define EEPROM_ADR 0x50 //The I2C address of the EEPROM
#define HS_ADR 0x02 //The address of the highscore byte in the EEPROM

#define NOTE_C1  33
#define SPKR_PIN 3

Adafruit_NeoMatrix matrix = Adafruit_NeoMatrix(15, 10, PIN,
  NEO_MATRIX_BOTTOM     + NEO_MATRIX_LEFT +
  NEO_MATRIX_COLUMNS + NEO_MATRIX_ZIGZAG,
  NEO_GRB            + NEO_KHZ800);
  
int ball_x = 7; //Ball's X coord
int ball_y = 2; //Ball's Y coord
int radius = 1; //Ball's radius
int r_incr = 1; //How much to increase radius by
bool isSwung = false; //Is the raquet swung
int ball_x_dir = 1; //Ball's X direction
int ball_y_dir = 1; //Ball's Y direction
bool isDirRight = true;
int score = 0; //The score of the current game
int COM_score = 0;
int highscore = 0; //The high score of all games
int framerate = 50; //How many ms between each frame
int serial_data;
String score_string = "";
uint16_t colors[] = {matrix.Color(255,0,0),matrix.Color(0,255,0),matrix.Color(150,200,0)};
int melody[] = {0};
int tempo[] = {0};
static unsigned long lastFrame = 0;

void setup(){
  Serial.begin(9600);
  matrix.begin();
  matrix.fillScreen(0);
  matrix.setTextColor(colors[1]);
  randomSeed(analogRead(A2));
  display_scores();
  highscore = read_HS();
  Serial.println(highscore,DEC);
  matrix.setCursor(0,1);
  matrix.print("HS: ");
  matrix.show();
  delay(1000);
  matrix.fillScreen(0);
  matrix.setCursor(0,1);
  matrix.print(highscore,DEC);
  matrix.show();
  delay(1000);
  score_string = "";
}

void loop(){
  if((lastFrame+framerate)< millis()){
    update_frame();
    lastFrame = millis();
  }
}

void update_frame(){
  serial_data = Serial.parseInt();
  if(serial_data){
    isSwung = true;
  }
  else if(!serial_data){
    isSwung = false;
  }
  if(ball_y >= 5 && ball_y < 7 && isSwung){
    if(isDirRight){
    ball_x_dir = -1;
    ball_y_dir = -1;
    }
    else if(!isDirRight){
      ball_x_dir = 1;
      ball_y_dir = -1;
    }
    r_incr = -1;
  }
  else if(ball_y >= 8){
    COM_score += 1;
    end_round();
    
  }
  else if(ball_y <= 2){
    isDirRight = !isDirRight;
    int randNum = random(4);
    Serial.println(randNum);
    if(randNum == 2){ //25% chance of COM missing
      score += 1;
      if(score > highscore){
      write_HS();
  }
      end_round;
    }
    else{
      if(isDirRight){
      ball_x_dir = 1;
      ball_y_dir = 1;
      }
      else if(!isDirRight){
        ball_x_dir = -1;
        ball_y_dir = 1;
      }
      r_incr = 1;
    }
  }
  ball_x += ball_x_dir;
  ball_y += ball_y_dir;
  radius += r_incr;
  matrix.fillScreen(0);
  matrix.fillCircle(ball_x,ball_y,radius,colors[2]);
  matrix.show();
}

void end_round(){
  
  if(COM_score >= 10){
    end_game();
  }
  isDirRight = true;
  r_incr = 1;
  ball_x_dir = 1;
  ball_y_dir = 1;
  ball_x = 7;
  ball_y = 2;
  radius = 1;
  display_scores();
  matrix.fillScreen(0);
  matrix.fillCircle(ball_x,ball_y,radius,colors[2]);
  matrix.show();
  
}

void end_game(){
  matrix.fillScreen(0);
  matrix.setCursor(0,1);
  matrix.setTextColor(colors[0]);
    matrix.drawLine(3,0,12,9,colors[0]);
    matrix.drawLine(11,0,2,9,colors[0]);
    matrix.show();
    delay(500);
  while(1){
  }
}

void display_scores(){
  matrix.fillScreen(0);
  matrix.setTextColor(colors[1]);
  matrix.setCursor(0,1);
  score_string = String(score) + "-" + String(COM_score);
  scrollText(score_string);
  matrix.fillScreen(0);
  delay(2000);
  matrix.show();
}

void scrollText(String text){
  int pass = 0;
  int x = matrix.width();
  for(int i=0;i<24;i++){
  matrix.fillScreen(0);
  matrix.setCursor(x,2);
  matrix.print(text);
  x -= 1;
  matrix.show();
  delay(150);
  }
}

void write_HS(){
  EEPROM.write(0x04,int(score));
}

int read_HS(){
  byte HS = EEPROM.read(0x04); //Read from address 4
  return HS;
}
Racquet CodeC/C++
#include <Wire.h>
#include <SPI.h>
#include <SparkFunLSM9DS1.h>

LSM9DS1 imu;

#define LSM9DS1_M  0x1E // Would be 0x1C if SDO_M is LOW
#define LSM9DS1_AG  0x6B // Would be 0x6A if SDO_AG is LOW

#define PRINT_SPEED 10 // 10 ms between checks
static unsigned long lastPrint = 0; // Keep track of print time

float accelx = 0;
float accely = 0;

void setup() {
  // put your setup code here, to run once:
Serial.begin(9600);
imu.settings.device.commInterface = IMU_MODE_I2C;
  imu.settings.device.mAddress = LSM9DS1_M;
  imu.settings.device.agAddress = LSM9DS1_AG;
  if (!imu.begin())
  {
   //failed
    while (1)
      ;
  }
}

void loop() {
   if ((lastPrint + PRINT_SPEED) < millis())
  {
  // put your main code here, to run repeatedly:
  if ( imu.accelAvailable() )
  {
    // To read from the accelerometer, first call the
    // readAccel() function. When it exits, it'll update the
    // ax, ay, and az variables with the most current data.
    imu.readAccel();
  }
  lastPrint = millis(); // Update lastPrint time
  }
  accelx = imu.calcAccel(imu.ax);
  accely = imu.calcAccel(imu.ay);

  if(accelx <= -1.5 || accelx >= 1.5){
    Serial.print(1);
    delay(600);
  }
  else if(accely <= -1.5 || accely >= 1.5){
    Serial.print(1);
    delay(600);
}
}
Python CodePython
import serial
import time

matrix_port = "COM3"
raquet_port = "COM9"

matrix = serial.Serial(matrix_port, 9600)
racquet = serial.Serial(raquet_port,9600)
time.sleep(10)
while 1:
    data = racquet.read()
    print data
    if data == "1":
        print "hit"
        matrix.write("1")
        time.sleep(1)
    time.sleep(.05)

Comments

Similar projects you might like

Caravaggio, a Drawing Machine

Project showcase by michele1993

  • 4,860 views
  • 4 comments
  • 37 respects

ADITI: Affordable Diagnostic Thermal Incubator

Project tutorial by Team ADITI

  • 425 views
  • 0 comments
  • 3 respects

Arduino Wireless Weather Station

Project tutorial by Nick Koumaris

  • 8,211 views
  • 3 comments
  • 36 respects

Transformer's Baby - Arduino Avoiding Obstacle Robot

Project tutorial by diyProjectsStepByStep

  • 217 views
  • 0 comments
  • 2 respects

How to make Arduino based Automatic Door Opening

Project tutorial by vijendra kumar

  • 2,141 views
  • 4 comments
  • 9 respects

Ghostbusters Trap Armband

Project showcase by Danielle Gormley

  • 157 views
  • 0 comments
  • 2 respects
Add projectSign up / Login