diff options
author | Haoyi Li <haoyi@haoyi-mbp.corp.dropbox.com> | 2014-11-26 00:45:31 -0800 |
---|---|---|
committer | Haoyi Li <haoyi@haoyi-mbp.corp.dropbox.com> | 2014-11-26 00:45:31 -0800 |
commit | 2c4b142503bd2d871e6818b5cab8c38627d9e4a0 (patch) | |
tree | 6ba33d2980a1a7a1286100202a695c6631bd240e /examples/reversi/Reversi.scala | |
download | hands-on-scala-js-2c4b142503bd2d871e6818b5cab8c38627d9e4a0.tar.gz hands-on-scala-js-2c4b142503bd2d871e6818b5cab8c38627d9e4a0.tar.bz2 hands-on-scala-js-2c4b142503bd2d871e6818b5cab8c38627d9e4a0.zip |
Squashed 'examples/scala-js/' content from commit 47311ba
git-subtree-dir: examples/scala-js
git-subtree-split: 47311ba693f949f204f27ea9475bb63425fbd4f3
Diffstat (limited to 'examples/reversi/Reversi.scala')
-rw-r--r-- | examples/reversi/Reversi.scala | 266 |
1 files changed, 266 insertions, 0 deletions
diff --git a/examples/reversi/Reversi.scala b/examples/reversi/Reversi.scala new file mode 100644 index 0000000..b4a34a4 --- /dev/null +++ b/examples/reversi/Reversi.scala @@ -0,0 +1,266 @@ +/* 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() + } +} |