Hockey Game – Arduino/Processing

Initiation:

Ever since we did the simple example of the ball using Processing and Arduino together, I had the idea of implementing a hockey game using two potentiometers. Unfortunately, I was not able to visit the electronics store during the weekend and so I just decided to make player 2 use the mouse instead. Unlike previous assignments where I keep changing my mind over which idea to implement and what to do, I was pretty decided on my assignment for this week.

References:

I used example 4 from the folder which Aaron provided us with in order to have a proper serial event on Processing which can take in multiple inputs from Arduino and complete the handshake. I also used the example titled Bounce from the Processing application as reference.

Implementation:

I decided to make a class for every single object in order to have much deeper and control and power over what exactly happens in the game. At first, I only had gameOn and !gameOn as conditions for playing or a screen with instructional text. But then I realized that hockey usually involves 7 rounds and there should be some transitional phase between each round which requires some interaction from the players, so I added a situation named roundEnd and functions called display() in each class which show the pucks at the middle and the ball at the very middle and sets their positions there accordingly until the players click anywhere to continue playing, at which point the pucks go back to depending on the potentiometer and mouseX for position. The conditioning for this took some time because there are many factors involved and I need to calculate both scores simultaneously while keeping count of the rounds in order to finish the game at round 7 and declare a winner.

how the game looks like before playing/ after each round is finished

Challenges:

One major problem I faced which I actually didn’t exactly resolve was the collision of the ball with the pucks, where the blocks are wide and very thin so calculating the distance from the middle of the ball to the middle of the block varies greatly from one end to another, and I just took some average distance of collision as part of the equation but sometimes when the ball touches the far end of the block it looks as if it lags and is moving horizontally on the surface of the block before continuing on. I hope I can improve this aspect because I faced similar problems in past assignments when it comes to calculating distance.

I also realized that a player would not really be focusing on changing how dark/light their puck is but rather try to ensure they hit the ball quickly every time, so perhaps the photocell usage was just an extra feature that may not be very necessary or functional, even though it is still a cool effect in the theme of the game.

Improvement:

One thing I improved from the midterm is actually using mousePressed() instead of mouseClicked(), which is more effective when it comes to restarting the game and works better, the other required a particularly long click in order to work.

Here is my perspective as the player (very sad playing this alone and very hard as well):

Here is the actual final game:

Below is the Arduino Code:

//code inspired from example 4

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

void loop() {
  if(Serial.available()>0){
    char inByte=Serial.read();
    int knobValue = analogRead(A0);
    delay(1);
    int color = analogRead(A1);
    delay(1);
    Serial.print(knobValue);
    Serial.print(',');
    Serial.println(color);
  }
}

 

Below is the Processing Code:

import processing.serial.*;
Serial myPort;
Tab block;
Tab2 block2;
Ball ball;
int rounds = 0;
boolean gameOn = true;
boolean roundEnd = true;
int score1 = 0;          //initializing score to 0
int score2 = 0;
PFont f;              //f object for all the texts in the game

//inspired from bounce example

class Ball {
  
  int radius = 20;        // Width of the shape
  float xpos, ypos;    // Starting position of shape    
  
  float xspeed = 2;  // Speed of the shape
  float yspeed = 2;  // Speed of the shape
  
  int xdirection = 1;  // Left or Right
  int ydirection = 1;  // Top to Bottom
  
  
 Ball() 
  {  
    
    noStroke();
    ellipseMode(CENTER);
    // Set the starting position of the shape
    xpos = random(radius, width - radius);
    ypos = radius;
  }
  
  void display() {
    xpos = width/2;
    ypos = height/2;
    ellipse(xpos, ypos, radius, radius);
  }
  
  void update(int x, int y) 
  {
    
    // Update the position of the shape
    xpos = xpos + ( xspeed * xdirection );
    ypos = ypos + ( yspeed * ydirection );
    
    // Test to see if the shape exceeds the boundaries of the screen
    // If it does, reverse its direction by multiplying by -1
    if (dist(x, y, xpos, ypos) < radius + 15) {
      ydirection *= -1;
    }
    
    if (xpos > width-radius || xpos < radius) {
      xdirection *= -1;
    }
  
    // Draw the shape
    fill(255);
    ellipse(xpos, ypos, radius, radius);
  }
  
}

class Tab {
  int xPos=0;
  int yPos = height - 50;
  int rectWidth = 80;
  int rectHeight = 10;
  int colorInput=4;
  
  Tab() {
    fill(colorInput - 4, colorInput, random(230,255)); 
    rect(xPos,height - 50, rectWidth, rectHeight);
  }
  
  void display() {
    fill(colorInput - 4, colorInput, random(230,255)); 
    xPos = width/2;
    rect(xPos, yPos, rectWidth, rectHeight);
  }
  
  void update() {
    fill(colorInput - 4, colorInput, random(230,255)); 
    rect(xPos,yPos, rectWidth, rectHeight);
  }


  void serialEvent(Serial myPort){
    String s=myPort.readStringUntil('\n');
    s=trim(s);
    if (s!=null){
      int values[]=int(split(s,','));
      if (values.length==2){
        xPos=(int)map(values[0],0,1023, 0 + rectWidth/2 , width - rectWidth/2);
        colorInput=(int)map(values[1],0,1023, 150, 10);
      }
    }
    println(xPos);
    myPort.write('0');
  }
}

class Tab2 {
  int xPos= width/2;
  int yPos = 50;
  int rectWidth = 80;
  int rectHeight = 10;
  
  Tab2() {
    fill(255, 50, 50);
    rect(xPos,height - 50, rectWidth, rectHeight);
  }
  
  void display() {
    fill(random(230,255), 70, 75); 
    xPos = width/2;
    rect(xPos, yPos, rectWidth, rectHeight);
  }
  
  void update() { 
    fill(random(230,255), 70, 75); 
    xPos = (int)map(mouseX, 0, width, rectWidth/2, width - rectWidth/2);
    rect(xPos,yPos, rectWidth, rectHeight);
  }
  

}

void setup(){
  size(600,680);
  printArray(Serial.list());
  String portname=Serial.list()[2];
  println(portname);
  myPort = new Serial(this,portname,9600);
  myPort.clear();
  myPort.bufferUntil('\n');
  rectMode(CENTER);
  block = new Tab();
  block2 = new Tab2();
  ball = new Ball();
  f = createFont("Raleway-Bold.ttf",72);      //aesthetic font that fits teh theme
}

void draw(){
  background(0);
  
  if (gameOn && !roundEnd) {
    block.serialEvent(myPort);
    block.update();
    block2.update();
    ball.update(block2.xPos,block2.yPos);
    ball.update(block.xPos,block.yPos);
    
    if ( rounds == 7) {
      gameOn = false;
      //roundEnd = false;
    }
    
    if (ball.ypos < 59) {
      score1++;
      rounds++;
      roundEnd = true;
    }
    else if (ball.ypos > height - 59) {
      score2++;
      rounds++;
      roundEnd = true;
    }
    
  }
  
  else if (roundEnd && gameOn) {
    ball.display();
    block.display();
    block2.display();
    
    textAlign(CENTER);
    String round = "ROUND: ";
    String Score1 = "Blue Player: ";          //constantly show score at the corner
    String Score2 = "Red Player: ";          //constantly show score at the corner
    textFont(f,20);
    fill(255);
    text(Score1 + score1, width/2, height/2 - 15);        //score text bottom right
    text(Score2 + score2, width/2, height/2 - 35);        //score text bottom right
    textFont(f,24);
    text(round + rounds, width/2, height/2 + 35);
  }
  
  else {
    textAlign(CENTER, CENTER);
    
    textFont(f,40); 
    //same shade as in the flag
    String first;
    if (score1 > score2) {
      fill(#002868);
      first = "BLUE PLAYER WINS!!";
    }
    else {
      fill(#BF0A30);
      first = "RED PLAYER WINS!!";
    }
    
    text(first, width/2, height/2 - 90);
    
    textFont(f,24);
    //same shade as in the flag
    fill(255);
    String second = "CLICK TO PLAY AGAIN!";
    text(second, width/2 - 5, height/2 + 90);      //game over text
    
    textFont(f,20);
    fill(255);
    String third = "Blue Player: ";
    String fourth = "Red Player: ";
    fill(#002868);
    text(third + score1, width/2, height - 20);        //score text bottom right
     fill(#BF0A30);
    text(fourth + score2, width/2, height - 50);        //score text bottom right
  }
  
}

void mousePressed() {
      
   if (gameOn == false){        //if mouse is clicked when the game is off, turn it on and reset everything to zero and empty lists
      rounds = 0;
      score1 = 0;
      score2 = 0;
      ball.ypos = height/2;
      gameOn = true;   
   }
   
   else if (roundEnd == true) {
      gameOn = true;
      roundEnd = false;
   }
}

 

This assignment was pretty fun to make and I look forward to diving deeper into the combination of the Arduino and Processing worlds.

Leave a Reply