summaryrefslogtreecommitdiff
path: root/examples/reversi/Reversi.scala
diff options
context:
space:
mode:
Diffstat (limited to 'examples/reversi/Reversi.scala')
-rw-r--r--examples/reversi/Reversi.scala266
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()
+ }
+}