Friday, October 16, 2015

ZenPong

This is an example from Pro ScalaFX, with some modifications, i.e. the paddle movement is now limited within the scene.

package proscalafx.ch02.zenpong

import javafx.animation.Animation.Status

import scalafx.Includes._
import scalafx.animation.{KeyFrame, Timeline}
import scalafx.application.JFXApp
import scalafx.application.JFXApp.PrimaryStage
import scalafx.beans.property.{BooleanProperty, DoubleProperty}
import scalafx.event.ActionEvent
import scalafx.scene.control.Button
import scalafx.scene.input.{KeyCode, KeyEvent, MouseEvent}
import scalafx.scene.paint.{Color, CycleMethod, LinearGradient, Stop}
import scalafx.scene.shape.{Circle, Rectangle}
import scalafx.scene.{Cursor, Group, Scene}

object ZenPongMain extends JFXApp {

  val sceneHeight, sceneWidth = 500
  val paddleHeight = 60
  val maxYTranslate = sceneHeight - paddleHeight
  val minYTranslate = 0

  val paddleWidth = 20
  val leftPaddleX = 0
  val rightPaddleX = sceneWidth - paddleWidth

  /** The center points of the moving ball */
  val centerBallX = new DoubleProperty
  val centerBallY = new DoubleProperty

  /** The Y coordinate of the left paddle */
  val leftPaddleY = new DoubleProperty

  /** The Y coordinate of the right paddle */
  val rightPaddleY = new DoubleProperty

  /** The drag anchor for left and right paddles */
  var leftPaddleDragAnchorY: Double = _
  var rightPaddleDragAnchorY: Double = _

  /** Controls whether the ball is moving right */
  var movingRight = true

  /** Controls whether the ball is moving down */
  var movingDown = true

  /** The initial translateY property for the left and right paddles */
  var initLeftPaddleTranslateY: Double = _
  var initRightPaddleTranslateY: Double = _

  /** The moving ball */
  val ball = new Circle {
    radius = 5.0
    fill = Color.Black
    centerX <== centerBallX
    centerY <== centerBallY
  }

  /** The left and right paddles */
  val leftPaddle: Rectangle = new Rectangle {
    x = leftPaddleX
    width = paddleWidth
    height = paddleHeight
    fill = Color.LightBlue
    cursor = Cursor.HAND
    translateY <== leftPaddleY
    onMousePressed = (me: MouseEvent) => {
      initLeftPaddleTranslateY = leftPaddle.translateY()
      leftPaddleDragAnchorY = me.sceneY
    }
    onMouseDragged = (me: MouseEvent) => {
      val dragY = me.sceneY - leftPaddleDragAnchorY
      var totalYTranslate = initLeftPaddleTranslateY + dragY
      if(totalYTranslate > maxYTranslate) {
        totalYTranslate = maxYTranslate
      } else if(totalYTranslate < minYTranslate) {
        totalYTranslate = minYTranslate
      }
      leftPaddleY() = totalYTranslate
    }
  }

  val rightPaddle: Rectangle = new Rectangle {
    x = rightPaddleX
    width = paddleWidth
    height = paddleHeight
    fill = Color.LightBlue
    cursor = Cursor.HAND
    translateY <== rightPaddleY
    onMousePressed = (me: MouseEvent) => {
      initRightPaddleTranslateY = rightPaddle.getTranslateY
      rightPaddleDragAnchorY = me.getSceneY
    }
    onMouseDragged = (me: MouseEvent) => {
      val dragY = me.sceneY - rightPaddleDragAnchorY
      var totalTranslate = initRightPaddleTranslateY + dragY
      if(totalTranslate > maxYTranslate) {
        totalTranslate = maxYTranslate
      } else if(totalTranslate < minYTranslate) {
        totalTranslate = minYTranslate
      }
      rightPaddleY() = totalTranslate
    }
  }

  /** The walls */
  val topWall = new Rectangle {
    x = 0
    y = 0
    width = sceneWidth
    height = 1
  }
  val rightWall = new Rectangle {
    x = rightPaddleX + 2
    y = 0
    width = 1
    height = sceneHeight
  }
  val leftWall = new Rectangle {
    x = leftPaddleX + paddleWidth - 2
    y = 0
    width = 1
    height = sceneHeight
  }
  val bottomWall = new Rectangle {
    x = 0
    y = sceneHeight
    width = sceneWidth
    height = 1
  }

  /**
   * Controls whether the startButton is visible
   */
  val startVisible = BooleanProperty(true)

  /** The animation of the ball */
  val keyFrame = KeyFrame(10 ms, onFinished = {
    event: ActionEvent =>
      checkForCollision()
      val horzPixels = if (movingRight) 1 else -1
      val vertPixels = if (movingDown) 1 else -1
      centerBallX() = centerBallX.value + horzPixels
      centerBallY() = centerBallY.value + vertPixels
  })
  val pongAnimation = new Timeline {
    keyFrames = Seq(keyFrame)
    cycleCount = Timeline.Indefinite
  }

  val startButton = new Button {
    layoutX() = 225
    layoutY() = 470
    text = "Start!"
    visible <== startVisible
    onAction = {
      event: ActionEvent =>
        startVisible() = false
        pongAnimation.playFromStart()
        pongComponents.requestFocus()
    }
  }

  /** The Group containing all of the walls, paddles, and ball. This also allows
    * us to requestFocus for KeyEvents on the Group
    */
  var pongComponents: Group = new Group {
    focusTraversable = true
    children = List(
      ball,
      topWall,
      leftWall,
      rightWall,
      bottomWall,
      leftPaddle,
      rightPaddle,
      startButton
    )
    onKeyPressed = (k: KeyEvent) => k.code match {
      case KeyCode.SPACE if pongAnimation.status() == Status.STOPPED =>
      rightPaddleY() = rightPaddleY.value - 6
      case KeyCode.L if !rightPaddle.boundsInParent().intersects(topWall.boundsInLocal()) =>
        rightPaddleY() = rightPaddleY.value - 6
      case KeyCode.COMMA if !rightPaddle.boundsInParent().intersects(bottomWall.boundsInLocal()) =>
        rightPaddleY() = rightPaddleY.value + 6
      case KeyCode.A if !leftPaddle.boundsInParent().intersects(topWall.boundsInLocal()) =>
        leftPaddleY() = leftPaddleY.value - 6
      case KeyCode.Z if !leftPaddle.boundsInParent().intersects(bottomWall.boundsInLocal()) =>
        leftPaddleY() = leftPaddleY.value + 6
      case _ =>
    }
  }

  /** Sets the initial starting positions of the ball and paddles */
  def initialize() {
    centerBallX() = 250
    centerBallY() = 250
    leftPaddleY() = 235.0
    rightPaddleY() = 235
    startVisible() = true
    pongComponents.requestFocus()
  }

  /** Checks whether or not the ball has collided with either the paddles,
    * topWall, or bottomWall.  If the ball hits the wall behind the paddles,
    * the game is over.
    */
  def checkForCollision() {
    if (ball.intersects(rightWall.boundsInLocal()) || ball.intersects(leftWall.boundsInLocal())) {
      pongAnimation.stop()
      initialize()
    } else if (ball.intersects(bottomWall.boundsInLocal()) ||
      ball.intersects(topWall.boundsInLocal())) {
      movingDown = !movingDown
    } else if (ball.intersects(leftPaddle.boundsInParent()) && !movingRight) {
      movingRight = !movingRight
    } else if (ball.intersects(rightPaddle.boundsInParent()) && movingRight) {
      movingRight = !movingRight
    }
  }

  stage = new PrimaryStage {
    title = "ZenPong Example"
    scene = new Scene(sceneWidth, sceneHeight) {
      content = pongComponents
    }
  }

  initialize()
}

0 comments: