/* 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("", js.Dynamic.literal( `type` = "button", value = "Reset" )).click(reset _) } def createPassButton() = { jQuery("", js.Dynamic.literal( `type` = "button", value = "Pass" )).click(pass _) } def createStatus() = { jQuery("") } 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( "") val domCanvas = boardCanvas.get(0).asInstanceOf[HTMLCanvasElement] val context = domCanvas.getContext("2d").asInstanceOf[CanvasRenderingContext2D] playground.append(jQuery("
").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("

") 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() } }