From 2c4b142503bd2d871e6818b5cab8c38627d9e4a0 Mon Sep 17 00:00:00 2001 From: Haoyi Li Date: Wed, 26 Nov 2014 00:45:31 -0800 Subject: Squashed 'examples/scala-js/' content from commit 47311ba git-subtree-dir: examples/scala-js git-subtree-split: 47311ba693f949f204f27ea9475bb63425fbd4f3 --- examples/reversi/JSTypes.scala | 87 ++++++++++ examples/reversi/Reversi.scala | 266 +++++++++++++++++++++++++++++ examples/reversi/reversi-2.10-fastopt.html | 30 ++++ examples/reversi/reversi-2.10.html | 30 ++++ examples/reversi/reversi-2.11-fastopt.html | 30 ++++ examples/reversi/reversi-2.11.html | 30 ++++ 6 files changed, 473 insertions(+) create mode 100644 examples/reversi/JSTypes.scala create mode 100644 examples/reversi/Reversi.scala create mode 100644 examples/reversi/reversi-2.10-fastopt.html create mode 100644 examples/reversi/reversi-2.10.html create mode 100644 examples/reversi/reversi-2.11-fastopt.html create mode 100644 examples/reversi/reversi-2.11.html (limited to 'examples/reversi') diff --git a/examples/reversi/JSTypes.scala b/examples/reversi/JSTypes.scala new file mode 100644 index 0000000..cc0e5a4 --- /dev/null +++ b/examples/reversi/JSTypes.scala @@ -0,0 +1,87 @@ +/* Scala.js example code + * Public domain + * @author Sébastien Doeraene + */ + +package reversi + +import scala.scalajs.js + +trait Window extends js.Object { + val document: DOMDocument = js.native + + def alert(msg: String): Unit = js.native +} + +trait DOMDocument extends js.Object { + def getElementById(id: String): DOMElement = js.native + def createElement(tag: String): DOMElement = js.native +} + +trait DOMElement extends js.Object { + var innerHTML: String = js.native + + def appendChild(child: DOMElement): Unit = js.native +} + +trait JQueryStatic extends js.Object { + def apply(arg: js.Any): JQuery = js.native + def apply(arg: js.Any, attributes: js.Any): JQuery = js.native +} + +trait JQuery extends js.Object { + def get(index: Int): DOMElement = js.native + + def text(value: String): JQuery = js.native + def text(): String = js.native + + def html(value: String): JQuery = js.native + def html(): String = js.native + + def prop(property: String): js.Any = js.native + def prop(property: String, value: js.Any): JQuery = js.native + + def offset(): JQueryOffset = js.native + + def appendTo(parent: JQuery): JQuery = js.native + def append(children: JQuery): JQuery = js.native + + def addClass(classes: String): JQuery = js.native + def removeClass(classes: String): JQuery = js.native + + def each[U](callback: js.Function2[Int, js.Dynamic, U]): JQuery = js.native + + def click[U](handler: js.Function0[U]): JQuery = js.native + def click[U](handler: js.Function1[JQueryEvent, U]): JQuery = js.native +} + +trait JQueryOffset extends js.Object { + val top: Double = js.native + val left: Double = js.native +} + +trait JQueryEvent extends js.Object { + val pageX: Double = js.native + val pageY: Double = js.native +} + +trait HTMLCanvasElement extends DOMElement { + def getContext(kind: String): js.Any = js.native // depends on the kind +} + +trait CanvasRenderingContext2D extends js.Object { + val canvas: HTMLCanvasElement = js.native + + var fillStyle: String = js.native + var lineWidth: Double = js.native + + def fillRect(x: Double, y: Double, w: Double, h: Double): Unit = js.native + def strokeRect(x: Double, y: Double, w: Double, h: Double): Unit = js.native + + def beginPath(): Unit = js.native + def fill(): Unit = js.native + def stroke(): Unit = js.native + + def arc(x: Double, y: Double, radius: Double, startAngle: Double, + endAngle: Double, anticlockwise: Boolean): Unit = js.native +} 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("", 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() + } +} diff --git a/examples/reversi/reversi-2.10-fastopt.html b/examples/reversi/reversi-2.10-fastopt.html new file mode 100644 index 0000000..46cd1c7 --- /dev/null +++ b/examples/reversi/reversi-2.10-fastopt.html @@ -0,0 +1,30 @@ + + + + Reversi - Scala.js example + + + + +

Reversi - Scala.js example

+ +

Somewhat inspired by +http://davidbau.com/reversi/

+ +
+
+ + + + + + + + + diff --git a/examples/reversi/reversi-2.10.html b/examples/reversi/reversi-2.10.html new file mode 100644 index 0000000..5f7b696 --- /dev/null +++ b/examples/reversi/reversi-2.10.html @@ -0,0 +1,30 @@ + + + + Reversi - Scala.js example + + + + +

Reversi - Scala.js example

+ +

Somewhat inspired by +http://davidbau.com/reversi/

+ +
+
+ + + + + + + + + diff --git a/examples/reversi/reversi-2.11-fastopt.html b/examples/reversi/reversi-2.11-fastopt.html new file mode 100644 index 0000000..524e716 --- /dev/null +++ b/examples/reversi/reversi-2.11-fastopt.html @@ -0,0 +1,30 @@ + + + + Reversi - Scala.js example + + + + +

Reversi - Scala.js example

+ +

Somewhat inspired by +http://davidbau.com/reversi/

+ +
+
+ + + + + + + + + diff --git a/examples/reversi/reversi-2.11.html b/examples/reversi/reversi-2.11.html new file mode 100644 index 0000000..b1a6d08 --- /dev/null +++ b/examples/reversi/reversi-2.11.html @@ -0,0 +1,30 @@ + + + + Reversi - Scala.js example + + + + +

Reversi - Scala.js example

+ +

Somewhat inspired by +http://davidbau.com/reversi/

+ +
+
+ + + + + + + + + -- cgit v1.2.3