summaryrefslogblamecommitdiff
path: root/examples/reversi/Reversi.scala
blob: b4a34a46de58a1a082ca8cf17122a772772a9b11 (plain) (tree)









































































































































































































































































                                                                                   
/* Scala.js example code
 * Public domain
 * @author  Sébastien Doeraene
 */

package reversi

import scala.annotation.tailrec
import scala.scalajs.js
import scala.scalajs.js.annotation.JSExport

sealed abstract class OptPlayer

sealed abstract class Player extends OptPlayer {
  val opponent: Player
}

case object NoPlayer extends OptPlayer

case object White extends Player {
  val opponent = Black
}

case object Black extends Player {
  val opponent = White
}

@JSExport("Reversi")
class Reversi(jQuery: JQueryStatic, playground: JQuery) {

  // The Model -----------------------------------------------------------------

  val BoardSize = 8 // size of a Reversi board

  def inBounds(index: Int): Boolean = index >= 0 && index < BoardSize
  def inBounds(x: Int, y: Int): Boolean = inBounds(x) && inBounds(y)

  class Square(val x: Int, val y: Int) {
    private var _owner: OptPlayer = NoPlayer

    var onOwnerChange: (OptPlayer, OptPlayer) => Unit = (oldP, newP) => ()

    def owner = _owner
    def owner_=(value: OptPlayer) {
      val previous = _owner
      if (value != previous) {
        _owner = value
        onOwnerChange(previous, value)
      }
    }

    override def toString() = "Square("+x+", "+y+", "+owner+")"
  }

  val board = Array.tabulate[Square](BoardSize, BoardSize)(new Square(_, _))
  val allSquares = board.flatten
  var currentPlayer: Player = White // Irrelevant, set again in startGame()

  // The GUI -------------------------------------------------------------------

  val resetButton = createResetButton()
  val passButton = createPassButton()
  val status = createStatus()
  buildUI()

  def createResetButton() = {
    jQuery("<input>", js.Dynamic.literal(
        `type` = "button", value = "Reset"
    )).click(reset _)
  }

  def createPassButton() = {
    jQuery("<input>", js.Dynamic.literal(
        `type` = "button", value = "Pass"
    )).click(pass _)
  }

  def createStatus() = {
    jQuery("<span>")
  }

  def buildUI() {
    // Some dimensions
    val SquareSizePx = 48
    val HalfSquareSizePx = SquareSizePx/2
    val PawnRadiusPx = HalfSquareSizePx-4
    val BoardSizePx = BoardSize*SquareSizePx + 3

    // Creat the board canvas
    val boardCanvas = jQuery(
        "<canvas width='"+BoardSizePx+"' height='"+BoardSizePx+"'></canvas>")
    val domCanvas = boardCanvas.get(0).asInstanceOf[HTMLCanvasElement]
    val context = domCanvas.getContext("2d").asInstanceOf[CanvasRenderingContext2D]

    playground.append(jQuery("<div>").append(boardCanvas))

    /** Draw the specified square on the board canvas */
    def drawSquare(square: Square) {
      val x = square.x * SquareSizePx
      val y = square.y * SquareSizePx

      // Background
      context.fillStyle = "green"
      context.fillRect(x, y, SquareSizePx, SquareSizePx)

      // Border
      context.fillStyle = "black"
      context.lineWidth = 3
      context.strokeRect(x, y, SquareSizePx, SquareSizePx)

      // Pawn
      if (square.owner != NoPlayer) {
        context.fillStyle = if (square.owner == White) "white" else "black"
        context.beginPath()
        context.arc(x+HalfSquareSizePx, y+HalfSquareSizePx, PawnRadiusPx,
            0, 2*Math.PI, true)
        context.fill()
      }
    }

    // Draw squares now, and everytime they change ownership
    for (square <- allSquares) {
      drawSquare(square)
      square.onOwnerChange = { (prevOwner, newOwner) =>
        drawSquare(square)
      }
    }

    // Configure clicks on the board
    boardCanvas.click { (event: JQueryEvent) =>
      val offsetX = event.pageX - boardCanvas.offset().left
      val offsetY = event.pageY - boardCanvas.offset().top
      val x = offsetX.toInt / SquareSizePx
      val y = offsetY.toInt / SquareSizePx

      if (inBounds(x, y))
        clickSquare(board(x)(y))
    }

    // Build the status bar
    val statusBar = jQuery("<p>")
    statusBar.append(resetButton)
    statusBar.append(status)
    statusBar.append(passButton)
    playground.append(statusBar)
  }

  // The Game ------------------------------------------------------------------

  def reset() {
    startGame()
  }

  @JSExport
  def startGame() {
    // Set up the board
    allSquares foreach (_.owner = NoPlayer)
    board(3)(3).owner = White
    board(3)(4).owner = Black
    board(4)(3).owner = Black
    board(4)(4).owner = White

    // White begins
    currentPlayer = White

    // Let's go!
    startTurn()
  }

  def startTurn() {
    val (scoreWhite, scoreBlack) = computeScore()
    status.text(currentPlayer+"'s turn -- White: "+scoreWhite+
        " -- Black: "+scoreBlack)

    passButton.prop("disabled", true)

    if (!existsValidMove()) {
      // Test if the other player can do something
      currentPlayer = currentPlayer.opponent
      val opponentCanDoSomething = existsValidMove()
      currentPlayer = currentPlayer.opponent

      if (opponentCanDoSomething) {
        passButton.prop("disabled", false)
      } else {
        // End of game
        val winnerText =
          if (scoreWhite > scoreBlack) "White won!"
          else if (scoreBlack > scoreWhite) "Black won!"
          else "Draw"
        status.text("Game finished -- White: "+scoreWhite+
            " -- Black: "+scoreBlack+" -- "+winnerText)
      }
    }
  }

  def clickSquare(square: Square) {
    val toFlip = computeFlips(square)
    if (!toFlip.isEmpty) {
      (square :: toFlip) foreach (_.owner = currentPlayer)
      nextTurn()
    }
  }

  def pass() {
    assert(!existsValidMove())
    nextTurn()
  }

  def existsValidMove(): Boolean = {
    allSquares.exists(isValidMove)
  }

  def isValidMove(square: Square): Boolean = {
    !computeFlips(square).isEmpty
  }

  def computeFlips(square: Square): List[Square] = {
    if (square.owner != NoPlayer) Nil
    else {
      for {
        i <- (-1 to 1).toList
        j <- -1 to 1
        if i != 0 || j != 0
        flip <- computeFlipsInDirection(square.x, square.y, i, j)
      } yield flip
    }
  }

  def computeFlipsInDirection(x: Int, y: Int,
      dirx: Int, diry: Int): List[Square] = {

    val allInDir = allSquaresInDirection(x, y, dirx, diry)
    val (toFlip, remaining) =
      allInDir.span(_.owner == currentPlayer.opponent)

    val success = remaining.headOption.exists(_.owner == currentPlayer)
    if (success) toFlip
    else Nil
  }

  def allSquaresInDirection(fromx: Int, fromy: Int,
      dirx: Int, diry: Int): List[Square] = {
    val nextx = fromx + dirx
    val nexty = fromy + diry
    if (inBounds(nextx, nexty))
      board(nextx)(nexty) :: allSquaresInDirection(nextx, nexty, dirx, diry)
    else
      Nil
  }

  def computeScore(): (Int, Int) = {
    allSquares.foldLeft((0, 0)) { case ((white, black), square) =>
      square.owner match {
        case White => (white+1, black)
        case Black => (white, black+1)
        case NoPlayer => (white, black)
      }
    }
  }

  def nextTurn() {
    currentPlayer = currentPlayer.opponent
    startTurn()
  }
}