Solo Pong Joystick

For this week’s assignment, I took two of my earlier assignments and connected them through the magic of serial communication. I modified the code for Solo Pong to read information sent by my arduino, hence enabling me to control the game through a physical joystick controller. 😀

First, I had to re-connect my joystick to the arduino. This time around, though, I cut some wires such that the connections would be as non-intrusive and as clean as possible. For details on how the connections work, you can check my earlier post on the joystick, but for now you can see some pictures! c:

My joystick and the connections to the breadboard.
My joystick and the connections to the breadboard.
A close-up of the connections.
A close-up of the connections.
The whole thing! I added an extra button to control stage skipping.
The whole thing! I added an extra button to control stage skipping.

The next step was setting up serial communication between the arduino and Processing. To do so, I instructed my arduino to send a string of four numbers to Processing in the form “xVal,yVal,joystickButton,skipButton”. Processing would then read the string, convert it into numbers and use them: xVal and yVal would be mapped to horizontal and vertical speeds (if appliable), while the joystickButton and skipButton would return either 0 or 1 and control the shooting of the abll and the stage skip function.

To further showcase the awesomeness of the joystick setup, I added two new stages to the game (though I forgot to add their victory conditions oops). The first one would invert the x and y readings of the joystick, meaning that the paddle now moves based on the joystick’s horizontal position istead of the vertical one. The second stage allowed the paddle to move in both the x and y axes (to a certain degree), allowing further control (and probably causing several collision detection bugs).

Anyhow, a video of it in action is here.

And enjoy the code (I just added the victory conditions yay):

int xVal;
int yVal;
boolean button;
boolean skip;

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
  pinMode(2, INPUT);
  Serial.println("500,500,0,0");
}

void loop() {
  // put your main code here, to run repeatedly:
  xVal = analogRead(5);
  yVal = analogRead(4);
  button = digitalRead(2);
  skip = digitalRead(4);
  if (Serial.available() > 0) {
    int inByte = Serial.read();
    Serial.print(xVal);
    Serial.print(",");
    Serial.print(yVal);
    Serial.print(",");
    Serial.print(button);
    Serial.print(",");
    Serial.println(skip);
  }
}
import processing.serial.*;

Serial myPort;

import ddf.minim.*;
import ddf.minim.analysis.*;
import ddf.minim.effects.*;
import ddf.minim.signals.*;
import ddf.minim.spi.*;
import ddf.minim.ugens.*;

Minim minim;
AudioOutput out;

float playerX = 30;
float playerY = 300;
float wallX = 790;
float playerSpeed; // = 10;
int ballStart = 0;
int stage = 0;
int finalStage = 8;
int prevStage = 0;
float score = 0;
float score1;
float difficulty = 4;
float time = 0;
float lastTime = 0;
float gravity = 1;
int currentSmoke = 0;
int currentRing = 0;
int currentBad = 0;
int badCount = 0;
int initialBoxCount = 10;
int boxCount;
int bounceNote = 0;
int deathCount = 0;
int finalScore = 0;
int totalTime = 0;

Ball b1 = new Ball(playerX + 15, playerY, 10, 10);
Ball b2 = new Ball(playerX + 15, playerY, 5, 10);
Boss boss = new Boss(650, 250, 100, 20, finalStage);

Smoke[] smoke;
Ring[] rings;
BadBall[] bad;
ArrayList<Box> boxes = new ArrayList<Box>(initialBoxCount);
ArrayList<ParticleBlood> blood = new ArrayList<ParticleBlood>();
ArrayList<Confetti> confetti = new ArrayList<Confetti>();
String[] stages = {"Wall!", "Twins!", "Smoke!", "Darkness!", "Blocks!", "Physics?", "Y X is", "Freedom!", "BOSS", "RESULTS!"};
String[] bounceNotes = {"F3", "C4", "Ab3", "Bb3"}; 

PFont Ocra;

void setup() {
  size(800, 600);
  frameRate(60);
  myPort = new Serial(this, "COM3", 9600);
  myPort.bufferUntil('\n');
  minim = new Minim(this);
  out = minim.getLineOut();
  out.setTempo(140);
  String[] fonts = PFont.list();
  printArray(fonts);
  Ocra = createFont("OCR A Extended", 24);

  boss.boxLife = 20;
  bad = new BadBall[20];
  for (int b = 0; b < bad.length; b++) {
    bad[b] = new BadBall(-100, -100, 10, 10);
  }
  smoke = new Smoke[100];
  for (int q = 0; q < smoke.length; q ++) {
    smoke[q] = new Smoke();
  }
  rings = new Ring[5];
  for (int q = 0; q < rings.length; q ++) {
    rings[q] = new Ring();
  }
  //boxes = new Box[5];
  for (int q = 0; q < initialBoxCount; q ++) {
    boxes.add(new Box(random(width/3, width - 33), random(3, height - 33), 60, 1, 4));
    boxCount = q + 1;
  }
}

void draw() {
  screen();
  stageChange();
  //Player
  rectMode(CENTER);
  stroke(255);
  fill(255, 255, 255, 32);
  rect(playerX, playerY, 10, 100);

  //Ball 1
  b1.update();
  if (stage == 1) {
    b2.update();
  }
  //Boxes
  for (int box = boxes.size() - 1; box >= 0; box --) {
    boxes.get(box).life();
    if (boxes.get(box).boxLife <= 0) {
      boxes.remove(box);
    }
  }
  //Boss
  if (stage == finalStage) {
    boss.life();
    boss.move();
  }
  //Rings
  for (int q = 0; q < rings.length; q ++) {
    rings[q].shrink();
    rings[q].die();
  }
  //Smoke
  if (stage == 2) {
    for (int q = 0; q < smoke.length; q ++) {
      smoke[q].obscure();
    }
    smoke[currentSmoke].move();
  }
  //BadBalls
  if (stage == finalStage) {
    for (BadBall b : bad) {
      b.update();
      b.move();
    }
    badCount ++;
    if (badCount >= boss.period && boss.boxLife > 0) {
      badCount = 0;
      bad[currentBad].shoot();
      currentBad = (currentBad + 1) % bad.length;
    }
  }
  //Particles
  for (int q = blood.size() -1; q >= 0; q --) {
    blood.get(q).render();
    if (blood.get(q).life <= 0) {
      blood.remove(q);
    }
  }
  for (int q = confetti.size() -1; q >= 0; q --) {
    confetti.get(q).render();
    if (confetti.get(q).life <= 0) {
      confetti.remove(q);
    }
  }
  //Inputs
  if (keyPressed == true) {
    switch(key) {
    case 'w':
      playerSpeed = -10;
      break;
    case 's':
      playerSpeed = 10;
      break;
    case ' ':
      if (ballStart == 0) {
        ballStart = 1;
        lastTime = frameCount;
        b1.shoot();
        if (stage == 1) {
          b2.shoot();
        }
      }
      break;
    case '1':
      if (prevStage == stage && ballStart == 0) {
        stage = (stage + 1)%stages.length;
      }
      break;
    }
  }
  playerY += playerSpeed;
  //Ball movement
  b1.move();
  if (stage == 1) {
    b2.move();
  }
  if (stage == finalStage) {
    b1.bounceBoss();
  }
  if (stage == 4) {
    b1.bounceBox();
  }
  //Wall
  if (stage == 0) {
    wallX = 790 - difficulty*score*3;
  }
  if (stage == 5) {
    b1.ballSpeedY += gravity;
  }
  //Constrains
  playerY = constrain(playerY, 53, height - 53);
  playerX = constrain(playerX, 30, 250);
  score = constrain(score, 0, 100000);
}

void keyReleased() {
  if (key == '1') {
    prevStage = stage;
    lastTime = frameCount;
    score = 0;
  } else if (key == 'w' || key == 's') {
    playerSpeed = 0;
  }
}

void serialEvent(Serial myPort) {
  String input = myPort.readString();
  String[] numbers = split(input, ',');
  float[] values = float(numbers);
  if (stage != 6) {
    playerSpeed = constrain(map(values[1], 0, 483, -10, 0), -10, 10);
  } else {
    playerSpeed = constrain(map(values[0], 1018, 511, -10, 0), -10, 10);
  }
  if (stage == 7) {
    playerX += constrain(map(values[0], 1018, 511, -10, 0), -10, 10);
  }
  if (ballStart == 0 && values[2] == 1) {
    ballStart = 1;
    lastTime = frameCount;
    b1.shoot();
    if (stage == 1) {
      b2.shoot();
    }
  }
  if (prevStage == stage && ballStart == 0 && values[3] == 1) {
    stage = (stage + 1)%stages.length;
  }
  if (values[3] == 0) {
    if (prevStage != stage) {
      lastTime = frameCount;
      score = 0;
    }
    prevStage = stage;
  }
  myPort.write('x');
}
class BadBall {
  float ballX, ballY, baseSpeed, speed, ballSpeedX, ballSpeedY, ballSize;
  BadBall(float x, float y, float sp, float si) {
    ballX = x;
    ballY = y;
    baseSpeed = sp;
    ballSize = si;
    speed = baseSpeed;
  }
  void shoot() {
    ballX = boss.boxX;
    ballY = boss.boxY + boss.boxSize/2;
    ballSpeedX = -speed*cos(random(-QUARTER_PI, QUARTER_PI));
    ballSpeedY = -speed*sin(random(-QUARTER_PI, QUARTER_PI));
    shootNote();
  }
  void update() {
    rectMode(CENTER);
    stroke(255, 0, 0);
    strokeWeight(5);
    fill(0, 0, 0, 10);
    rect(ballX, ballY, ballSize, ballSize);
  }
  void move() {
    ballX += ballSpeedX;
    ballY += ballSpeedY;
    if (ballY <= 3 || ballY >= 597) {
      ballSpeedY = -ballSpeedY;
    }
    if (ballX >= wallX - 3) {
      ballSpeedX = -ballSpeedX;
    }
    if (ballX <= playerX + 10 && ballX >= playerX && ballY >= playerY - 51 && ballY <= playerY + 51) {
      ballStart = 0;
      deathCount ++;
      boss.boxLife = constrain(boss.boxLife + 1, 0, 20);
    }
  }
}
class Ball {
  float ballX, ballY, baseSpeed, speed, ballSpeedX, ballSpeedY, ballSize, ballAngle;
  float dark = 1;
  Ball (float a, float b, float c, float d) {
    ballX = a;
    ballY = b;
    baseSpeed = c;
    ballSize = d; 
    speed = baseSpeed;
  }
  void shoot() {   
    ballAngle = random(-QUARTER_PI, QUARTER_PI);
    ballSpeedX = speed*cos(ballAngle);
    ballSpeedY = speed*sin(ballAngle);
    ballStart = 2;
  }
  void update() {
    rectMode(CENTER);
    if (dark >= 1) {
      stroke(255);
      strokeWeight(5);
      fill(255, 255, 255, 32);
    } else {
      stroke(0, 0, 0, 10);
      strokeWeight(5);
      fill(0, 0, 0, 10);
    }
    rect(ballX, ballY, ballSize, ballSize);
    speed = baseSpeed+(score*difficulty/500);
    ballAngle = atan(ballSpeedY/ballSpeedX);
    if (ballStart == 2 && stage == 2) {
      smoke[currentSmoke].smokeX = ballX + random(-60, 60);
      smoke[currentSmoke].smokeY = ballY + random(-60, 60);
      currentSmoke = (currentSmoke + 1) % 100;
    }
  }
  void move() {
    if (ballStart == 0) {
      ballX = playerX + 15;
      ballY = playerY;
      dark = 1;
    } else if (ballStart == 2) {
      ballX += ballSpeedX;
      ballY += ballSpeedY;
      if (ballY <= 3 || ballY >= 597) {
        ballSpeedY = -ballSpeedY;
        rings[currentRing].call(ballX, ballY);
        if (stage == 3) {
          dark = random(0, 3);
        }
      }
      if (ballX >= wallX - 3) {
        ballSpeedX = -ballSpeedX;
        rings[currentRing].call(ballX, ballY);
        if (stage == 3) {
          dark = random(0, 3);
        }
      }
      if (ballX <= playerX + 10 && ballX >= playerX && ballY >= playerY - 51 && ballY <= playerY + 51) {
        score ++;
        rings[currentRing].call(ballX, ballY);
        if (stage == 5) {
          gravity = -gravity;
        }
        speed = baseSpeed+(score*difficulty/500);
        ballAngle = -(ballAngle) + radians((ballY - playerY)/2.5);
        ballSpeedX = speed*cos(ballAngle);
        ballSpeedY = speed*sin(ballAngle);
        if (stage == 3) {
          dark = random(0, 3);
        }
      }
      if (ballX <= 0) {
        ballStart = 0;
        totalTime += time;
        deathCount ++;
        lastTime = frameCount;
        score -= 10;
        score = constrain(score, 0, 100000);
        textSize(20);
        fill(255, 0, 0, 175);
        text("-10", playerX+5, playerY-20);
        if (stage == 4){
          for (int q = 0; q < initialBoxCount; q ++) {
            if (boxes.size() < initialBoxCount) {
              boxes.add(new Box(random(width/3, width - 33), random(3, height - 33), 60, 1, 4));
            }
          }
        }
      }
    }
  }
  void bounceBox() {
    for (Box q : boxes) {
      if ((ballX >= q.boxX && ballX <= q.boxX + q.boxSize && ballY >= q.boxY 
        && ballY <= q.boxY + 20 && q.boxLife > 0 && ballSpeedY > 0) 
        || (ballX >= q.boxX && ballX <= q.boxX + q.boxSize && ballY >= q.boxY + q.boxSize 
        && ballY <= q.boxY + q.boxSize - 20 && q.boxLife > 0 && ballSpeedY < 0)) {
        ballSpeedY = -ballSpeedY;
        q.boxLife --;
        rings[currentRing].call(ballX, ballY);
      }
      if ((ballY >= q.boxY && ballY <= q.boxY + q.boxSize && ballX >= q.boxX 
        && ballX <= q.boxX + 20 && q.boxLife > 0 && ballSpeedX > 0) 
        || (ballY >= q.boxY && ballY <= q.boxY + q.boxSize && ballX <= q.boxX + q.boxSize 
        && ballX >= q.boxX + q.boxSize - 20 && q.boxLife > 0 && ballSpeedX < 0)) {
        ballSpeedX = -ballSpeedX;
        q.boxLife --;
        rings[currentRing].call(ballX, ballY);
      }
    }
  }
  void bounceBoss() {
    if ((ballX >= boss.boxX && ballX <= boss.boxX + boss.boxSize && ballY >= boss.boxY 
      && ballY <= boss.boxY + 30 && boss.boxLife > 0 && ballSpeedY > 0) 
      || (ballX >= boss.boxX && ballX <= boss.boxX + boss.boxSize && ballY >= boss.boxY + boss.boxSize 
      && ballY <= boss.boxY + boss.boxSize - 30 && boss.boxLife > 0 && ballSpeedY < 0)) {
      ballSpeedY = -ballSpeedY;
      boss.boxLife --;
      rings[currentRing].call(ballX, ballY);
      hurtNote();
      for (int q = 0; q < 25; q ++) {
        blood.add(new ParticleBlood());
      }
    }
    if ((ballY >= boss.boxY && ballY <= boss.boxY + boss.boxSize && ballX >= boss.boxX 
      && ballX <= boss.boxX + 30 && boss.boxLife > 0 && ballSpeedX > 0) 
      || (ballY >= boss.boxY && ballY <= boss.boxY + boss.boxSize && ballX <= boss.boxX + boss.boxSize 
      && ballX >= boss.boxX + boss.boxSize - 30 && boss.boxLife > 0 && ballSpeedX < 0)) {
      ballSpeedX = -ballSpeedX;
      boss.boxLife --;
      rings[currentRing].call(ballX, ballY);
      hurtNote();
      for (int q = 0; q < 25; q ++) {
        blood.add(new ParticleBlood());
      }
    }
  }
}
class Boss {
  int timer = 0;
  int period = 120;
  int stageAppear;
  float boxX, boxY, boxSize, boxLife, baseSpeed, bossSpeed;
  Boss(float x, float y, float s, float l, int st) {
    boxX = x;
    boxY = y;
    boxSize = s;
    boxLife = l;
    stageAppear = st;
    bossSpeed = 2;
    baseSpeed = 2;
  }
  void life() {
    if (boxLife > 0 && stage == stageAppear) {
      rectMode(CORNER);
      fill(0);
      stroke(255, 0, 0);
      rect(boxX, boxY, boxSize, boxSize);
      period = int(map(boxLife, 1, 20, 30, 120));
      bossSpeed = bossSpeed/baseSpeed;
      baseSpeed = (140 - period)/10;
      bossSpeed = bossSpeed*baseSpeed;
    }
  }
  void move() {
    
    if ((boxY <= 5 && bossSpeed < 0) || ((boxY + boxSize) >= (height - 5) && bossSpeed > 0) ) {
      bossSpeed = -bossSpeed;
    }   
    boxY += bossSpeed;
  }
}
class Box {
  float boxX, boxY, boxSize, boxR, boxG, boxB, boxLife;
  int stageAppear;
  Box(float x, float y, float s, float l, int st) {
    boxX = x;
    boxY = y;
    boxSize = s;
    boxLife = l;
    boxR = random(0, 255);
    boxG = random(0, 255);
    boxB = random(0, 255);
    stageAppear = st;
  }
  void life() {
    if (boxLife > 0 && stage == stageAppear) {
      rectMode(CORNER);
      fill(255);
      stroke(boxR, boxG, boxB);
      rect(boxX, boxY, boxSize, boxSize);
    }
  }
}
class Confetti {
  float xPos, yPos, xSpeed, ySpeed, size, life;
  Confetti() {
    xPos = b1.ballX;
    yPos = b1.ballY;
    xSpeed = random(-5, 5);
    ySpeed = random(-5, 5);
    size = random(1, 7);
    life = random(60, 80);
  }
  void render() {
    rectMode(CENTER);
    stroke(random(0, 255), random(0, 255), random(0, 255));
    noFill();
    rect(xPos, yPos, size, size);
    life --;
    xPos += xSpeed;
    yPos += ySpeed;
  }
}
class ParticleBlood {
  float xPos, yPos, xSpeed, ySpeed, size, life;
  ParticleBlood() {
    xPos = b1.ballX;
    yPos = b1.ballY;
    xSpeed = random(-5, 5);
    ySpeed = random(-5, 5);
    size = random(1, 7);
    life = 60;
  }
  void render() {
    rectMode(CENTER);
    stroke(128, 0, 0);
    noFill();
    rect(xPos, yPos, size, size);
    life --;
    xPos += xSpeed;
    yPos += ySpeed;
  }
}
class Ring {
  float ringX, ringY, ringSize, alpha;
  Ring () {
    ringX = 0;
    ringY = 0;
    ringSize = 0;
    alpha = 255;
  }
  void call(float x, float y) {
    ringX = x;
    ringY = y;
    ringSize = 60;
    currentRing = (currentRing + 1) % rings.length;
    bounceNote();
    if (stage == finalStage + 1) {
      for (int q = 0; q < 25; q ++) {
        confetti.add(new Confetti());
      }
    }
  }
  void shrink() {
    noFill();
    stroke(255, alpha);
    rectMode(CENTER);
    rect(ringX, ringY, 60 - ringSize, 60 - ringSize);
    if (ringSize > 0) {
      ringSize -= 1.5;
    }
  }
  void die() {
    if (ringSize <= 0) {
      alpha = 0;
    } else {
      alpha = 255;
    }
  }
}
class Smoke {
  float smokeX, smokeY, size;
  Smoke () {
    smokeX = -100;
    smokeY = -100;
    size = 100;
  }
  void move() {
      smokeX = random(b1.ballX - 60, b1.ballX + 60);
      smokeY = random(b1.ballY - 60, b1.ballY + 60);
      size = 100;
  }
  void obscure() {
    rectMode(CENTER);
    stroke(128);
    strokeWeight(4);
    fill(64);
    rect(smokeX, smokeY, size, size);
  }
}
void bounceNote() {
  out.pauseNotes();
  out.playNote(0, 0.25, bounceNotes[bounceNote]);
  out.resumeNotes();
  bounceNote = (bounceNote + 1) % bounceNotes.length;
}

void hurtNote() {
  out.pauseNotes();
  out.playNote(0, 0.5, "C3");
  out.resumeNotes();
}

void shootNote() {
  out.pauseNotes();
  out.playNote(0, 0.25, "F4");
  out.playNote(0, 0.25, "Ab4");
  out.resumeNotes();
}
void screen() {
  //bg
  if (stage != finalStage + 1) {
    rectMode(CORNER);
    noStroke();
    fill(0, 0, 0, 10);
    rect(0, 0, width, height);
    //Frame (walls)
    fill(255);
    stroke(255);
    strokeWeight(5);
    line(0, 3, 799, 3);
    line(0, 597, 799, 597);
    //Backwall
    fill(255, 255, 255, 32);
    rect(wallX, 3, width - wallX, height);
    //Score
    textSize(150);
    text(int(score), 300, 375);
    if (ballStart == 2) {
      time = (frameCount - lastTime)/60;
    }
    text(time, 100, 500);
    textSize(30);
    text(stages[stage], 20, 50);
    if (stage == finalStage) {
      text(int(boss.boxLife), 20, 100);
      stroke(0);
      fill(map(boss.boxLife, 1, 20, 255, 0), map(boss.boxLife, 1, 20, 0, 255), 0);
      rect(80, 80, map(boss.boxLife, 0, 20, 0, 500), 20);
    }
  } else {
    background(0);
    textSize(42);
    text("Total time: " + totalTime + " seconds", 150, 200);
    text("You died " + deathCount + " time(s)", 150, 300);
    text("FINAL SCORE: " + finalScore, 150, 400);
  }
}
void stageChange() {
  switch(stage) {
  case 0:
    if (score >= 30) {
      finalScore += score;
      totalTime += time;
      score = 0;
      time = 0;
      ballStart = 0;
      wallX = 790;
      stage ++;
    }
    break;
  case 1:
    if (time >= 25) {
      finalScore += score;
      totalTime += time;
      time = 0;
      score  = 0;
      ballStart = 0;
      stage ++;
    }
    break;
  case 2:
    if (score >= 10) {
      finalScore += score;
      totalTime += time;
      score = 0;
      ballStart = 0;
      stage ++;
    }
    break;
  case 3:
    if (score >= 10) {
      finalScore += score;
      totalTime += time;
      score = 0;
      ballStart = 0;
      stage ++;
    }
    break;
  case 4:
    if (boxes.size() <= 0) {
      finalScore += score;
      totalTime += time;
      score = 0;
      ballStart = 0;
      stage ++;
    }
    break;
  case 5:
    if (score >= 5) {
      finalScore += score;
      totalTime += time;
      score = 0;
      ballStart = 0;
      stage ++;
    }
    break;
  case 6:
    if (score >= 15) {
      finalScore += score;
      totalTime += time;
      score = 0;
      ballStart = 0;
      stage ++;
    }
    break;
  case 7:
    if (score >= 15) {
      finalScore += score;
      totalTime += time;
      score = 0;
      ballStart = 0;
      stage ++;
    }
    break;
  case 8:
    if (boss.boxLife <= 0) {
      finalScore += score;
      totalTime += time;
      score = 0;
      ballStart = 0;
      stage ++;
    }
    break;
  }
}

 

Leave a Reply