// Project 0 - Part B // "Tennis for Two" (One Player) // Course:Special Topics in Interactive Art & Computational Design // By: Mehrdad Ghods // For project description please visit: // "https://ems.andrew.cmu.edu/2010spring/projects/project-0/" // declare screen dimensions int screenWidth = 1000; int screenHeight = 600; // declare tennis ball properties Ball ball; PVector velocity; int hitGround; boolean isInHumanHalf; boolean hasPassedNet; // declare ground sicknesses for checking collision float netSickness = 6; float groundSickness = 4; // declare physical properties float gravity = .098; float damping = 0.9; // declare user and computer score PFont font; int humanScore; int robotScore; /////////////// // Main Methods /////////////// void setup() { // setup screen properties size(screenWidth, screenHeight); smooth(); ball = new Ball(10, 5 * screenHeight / 12, 3); velocity = new PVector(7.0, -1.0); // setup font font = createFont("Stencil", 64); textAlign(CENTER); textFont(font); startNewGame(); } void draw() { checkCollision(); drawBackgroundTrd(); drawCourtTrd(); drawScoreBoardTrd(); drawBallTrd(); if(!isInHumanHalf) { robotPlayer(); } } ///////////////// // Helper Methods ///////////////// // this method start new game void startNewGame() { humanScore = 0; robotScore = 0; ball.x = 10; ball.y = 6 * screenHeight / 12; velocity.x = 8.0; velocity.y = -1.0; hitGround = 0; hasPassedNet = false; isInHumanHalf = true; refresh(); } // this method start new set void startNewSet() { ball.x = 10; ball.y = 6 * screenHeight / 12; velocity.x = 8.0; velocity.y = -1.0; hitGround = 0; hasPassedNet = false; isInHumanHalf = true; refresh(); } // this method refresh screen void refresh() { // refresh background noStroke(); fill(125); rect(0, 0, screenWidth, screenHeight); draw(); } // this method draw background in traditional style void drawBackgroundTrd() { // draw background noStroke(); fill(100, 10); rect(0, 0, screenWidth, screenHeight); // draw oscilloscope grid int lineX = 0; int lineY = 0; strokeWeight(1); // vertical lines while (lineX < screenWidth) { lineX += 20; stroke(75); line(lineX, 0, lineX, screenHeight); } // horizental lines while (lineY < screenHeight) { lineY += 20; stroke(75); line(0, lineY, screenWidth, lineY); } } // this method draw scoreboard in traditional style void drawScoreBoardTrd(){ fill(50); text("HUMAN", 1 * screenWidth / 5, 1 * screenHeight / 12); text("VS", 2 * screenWidth / 4, 1 * screenHeight / 12); text("ROBOT", 4 * screenWidth / 5, 1 * screenHeight / 12); text(humanScore, 1 * screenWidth / 5, 2 * screenHeight / 12); text(robotScore, 4 * screenWidth / 5, 2 * screenHeight / 12); } // this method draw tennis court in traditional style void drawCourtTrd(){ // draw ground line stroke(155, 221, 255); strokeWeight(groundSickness); line(0, 10 * screenHeight / 12, screenWidth, 10 * screenHeight / 12); // draw net line stroke(155, 221, 255); strokeWeight(netSickness); line(screenWidth / 2, 9 * screenHeight / 12, screenWidth / 2, 10 * screenHeight / 12); } void drawBallTrd(){ noStroke(); fill(155, 221, 255); ellipse(ball.x, ball.y, 2 * ball.r, 2 * ball.r); } // this method check if ball clashes any of court elements void checkCollision() { boolean noCollision = true; // check net collison if((abs((ball.x) - (screenWidth / 2 - netSickness / 2)) <= ball.r || abs((ball.x) - (screenWidth / 2 + netSickness / 2)) <= ball.r) && (ball.y + ball.r) >= 9 * screenHeight / 12) { netCollision(); noCollision = false; } // check ground collision if((ball.y + ball.r) >= 10 * screenHeight / 12 - groundSickness / 2) { groundCollision(); noCollision = false; } // check if ball pass racket if(ball.x - ball.r <= 0 || ball.x + ball.r > screenWidth) { boundaryCollision(); noCollision = false; } // if there is no collision ball just continue projectile movement if (noCollision) { noCollision(); } } // this method simulate ball and net collision void netCollision() { if(isInHumanHalf) { robotScore++; } else if(!isInHumanHalf) { humanScore++; } startNewSet(); } // this method simulate ball and ground collision void groundCollision() { // check if ball have hit the ground twice hitGround++; if((hasPassedNet && hitGround > 1) || (!hasPassedNet && hitGround > 0)) { if(isInHumanHalf) { robotScore++; } else if(!isInHumanHalf) { humanScore++; } startNewSet(); return; } velocity.y *= -1.0; velocity.y *= damping; velocity.x *= damping; // refresh position of ball ball.y = 10 * screenHeight / 12 - groundSickness / 2 - ball.r; noCollision(); } // this method simulate when ball pass racket void boundaryCollision() { if(ball.x - ball.r <= 0 && hitGround == 1) { robotScore++; } else if(ball.x + ball.r > screenWidth && hitGround == 1) { humanScore++; } else if(ball.x - ball.r <= 0) { humanScore++; } else if(ball.x + ball.r > screenWidth) { robotScore++; } startNewSet(); } // this method simulate ball projectile movement void noCollision() { float ballPreviousX = ball.x; float ballPreviousY = ball.y; ball.x += velocity.x; float ballCurrentX = ball.x; float ballCurrentY = ball.y; // check for net collision in fast speeds if(ballPreviousY > 9 * screenHeight / 12 && (ballPreviousX - screenWidth / 2) / (ballCurrentX - screenWidth / 2) < 0){ netCollision(); } velocity.y += gravity; ball.y += velocity.y; // determine if ball is in human half or not if(ball.x < screenWidth / 2) { isInHumanHalf = true; } else if(ball.x > screenWidth / 2) { isInHumanHalf = false; } // determine if ball has passed the net if(ballPreviousX < screenWidth / 2 && ballCurrentX > screenWidth / 2 || ballPreviousX > screenWidth / 2 && ballCurrentX < screenWidth / 2) { hasPassedNet = true; hitGround = 0; } } // this method simulate ball and racket collision void racketCollision(boolean isUP) { if(isUP && hasPassedNet && ball.y > 5 * screenHeight / 12) { if (velocity.y <= 0) { velocity.y -= 3; } else { velocity.y *= -1; velocity.y -= 3; } velocity.x *= - 0.5 * abs(7.0 / velocity.x); hasPassedNet = false; println("up"); } else if(!isUP && hasPassedNet && ball.y > 5 * screenHeight / 12) { velocity.y *= -0.01; velocity.x *= -2; hasPassedNet = false; println("down"); } } // this method control robot player actions // it is very simple simulation void robotPlayer() { float rnd = random(ball.x); if (rnd < (ball.x - 4 * screenWidth / 5)) { if(ball.y < 7 * screenHeight / 12 && ball.y > 5 * screenHeight / 12) { racketCollision(false); } else if(ball.y > 7 * screenHeight / 12 && ball.y > 5 * screenHeight / 12) { racketCollision(true); } } } //////////////////////// // Event Handler Methods //////////////////////// // this method handle events if player click mouse buttons void mouseClicked() { if(mouseButton == LEFT) { racketCollision(true); } else if(mouseButton == RIGHT) { racketCollision(false); } } // this method handle events if player press keyboard keys void keyPressed() { if(key == 'n' || key == 'N') { startNewGame(); } } /////////// // Classes ////////// class Ball{ float x, y, r; // Default constructor Ball() { } Ball(float x, float y, float r) { this.x = x; this.y = y; this.r = r; } }