Pong

Due: Monday, Sep 15 11:59PM

Introduction

According to Wikipedia, Pong was one of the first video games to go mainstream. You should first play the game to understand how Pong works.

In this assignment, you will write a program that allows two players to play Pong. Both players will control their paddles using the keyboard:

To encourage cooperation, your game won’t keep score. But, you will implement Pong physics (i.e., basic collision detection).

Part 0: Preliminaries

We are using the course graphics API. In addition, the cmpsci22.hw.pong package defines the Vector2D type and some geometry functions that will help with the last part of this assignment.

Save your work in a file called pong.scala. It should begin with these imports:

import cmpsci220._
import cmpsci220.graphics._
import cmpsci220.hw.pong._

Part 1: Paddle Motion

In this part, you’ll ignore the ball and focus on the paddles. We’ll guide you through implementing the following behavior:

Use these types to represent the game state. For now, we are ignoring the ball:

// Assume that width > 0 and height > 0
case class Table(width: Int, height: Int)

// (x,y) is the bottom coordinate and (x, y + height) is the top coordinate
case class Paddle(pos: Vector2D, height: Double)

case class Game(table: Table,
                paddle1: Paddle,
                paddle2: Paddle,
                velocity1: Vector2D,
                velocity2: Vector2D)

Define a value initGame that represents the start state:

val initGame : Game

Pick a size for the table and positions for the paddles that make sense for your computer screen. However, both initial velocities should be zeroed.

A paddle must fit within the table. However, since the table’s dimensions are not fixed, we can only determine if a paddle is valid relative to a given table. Write the following function to do so:

def isValidPaddle(paddle: Paddle, table: Table): Boolean

Write a function to move a paddle:

def movePaddle(paddle: Paddle, table: Table, velocity: Vector2D): Option[Paddle]

movePaddle should assume that isValidPaddle(paddle, table) holds. The function should produce a valid paddle that is offset by (velocity.dx, velocity.dy) or None.

Hint: See the API documentation for Vector2D. It has some useful methods.

Since players will control their paddles with the keyboard, we have to respond to keys being pressed and released as follows:

Note: when players press keys, you need to update their paddle velocity, but do not change the paddle position. You will write that separately.

First, write a function that responds to key-presses:

def keyPressed(key: String, game: Game): Game = key match {
  case "a" => ... // change Player 1 paddle velocity to move up
  case "z" => ... // change Player 1 paddle velocity to move down
  case "k" => ... // change Player 2 paddle velocity to move up
  case "m" => ... // change Player 2 paddle velocity to move up
  case _ => game  // ignore the key
}

You can use the same template to respond to key-releases:

def keyReleased(key: String, game: Game): Game

Now that you have functions to update paddle velocity, you need to write a function to update paddle positions, based on their velocity:

// Do not update velocities
def moveBothPaddles(game: Game): Game

You should use the movePaddle function that you defined above.

Write a function to draw the table and both paddles:

// No need to write tests cases for this function
def drawGame(game: Game): Image

Finally, you can put all these pieces together with animate:

animate(init = initGame,
        draw = drawGame,
        width = initGame.table.width,
        height = initGame.table.height,
        tick = moveBothPaddles,
        keyPressed = keyPressed,
        keyReleased = keyReleased)

If you’re written and tested all the functions correctly, you should be able to control both paddles with the keyboard. Of course, you’re missing the ball, which you’ll implement next.

Check Your Work: From the command-line, run the command:

check220 check pong step1

Part 2: Refactoring to add a Ball

In this part, you’ll add the ball to the game-state. You’ll focus on editing the code your wrote in Part 1 and drawing the ball. But, you won’t implement ball motion or collision detection yet.

Use the following datatype to represent the size, position and velocity of the ball:

case class Ball(radius: Double, position: Vector2D, velocity: Vector2D)

Change the Game type to include a ball:

case class Game(table: Table,
                paddle1: Paddle,
                paddle2: Paddle,
                velocity1: Vector2D,
                velocity2: Vector2D,
                ball: Ball)

You have to modify the existing code. You should update drawGame to draw the ball, but do not make the ball move yet. Simply ensure that all existing tests pass.

Check Your Work: From the command-line, run the command:

check220 check pong step2

Part 3: Ball Motion

The challenge with moving the ball is to implement collision detection correctly. First, write functions to determine if the ball has collided with the table edge or a paddle:

def hasHitTable(ball: Ball, table: Table): Boolean

def hasHitPaddle(ball: Ball, paddle: Paddle): Boolean

In principle, both functions need to calculate the distance between a point (the center of the ball) and a line segment (a paddle or an edge of the table). You have to account for the radius of the ball too. Vector2D has some methods that will help you. If you want to do the math yourself, you can use the law of cosines.

Write the following function:

// Assumes that ball is not currently in a collision
def moveBall(game: Game): Game

This function should move the ball along its current trajectory if no collision will occur. If a collision would occur, it should leave the position of the ball unchanged, but update the velocity to simulate a bounce.

Finally, update the call to animate to move both the ball and the paddles:

animate(init = initGame,
        draw = drawGame,
        width = initGame.table.width,
        height = initGame.table.height,
        tick = (game: Game) => { moveBall(moveBothPaddles(game)) },
        keyPressed = keyPressed,
        keyReleased = keyReleased)

At this point, the game should be playable.

Check Your Work: From the command-line, run the command:

check220 check pong final

Hand In

From the command-line, run the command:

check220 tar pong final

This command will create the file submission-pong-final.tgz. Submit this file using Moodle.