summaryrefslogtreecommitdiff
path: root/examples/scala-js/examples/reversi
diff options
context:
space:
mode:
Diffstat (limited to 'examples/scala-js/examples/reversi')
-rw-r--r--examples/scala-js/examples/reversi/JSTypes.scala87
-rw-r--r--examples/scala-js/examples/reversi/Reversi.scala266
-rw-r--r--examples/scala-js/examples/reversi/reversi-2.10-fastopt.html30
-rw-r--r--examples/scala-js/examples/reversi/reversi-2.10.html30
-rw-r--r--examples/scala-js/examples/reversi/reversi-2.11-fastopt.html30
-rw-r--r--examples/scala-js/examples/reversi/reversi-2.11.html30
6 files changed, 473 insertions, 0 deletions
diff --git a/examples/scala-js/examples/reversi/JSTypes.scala b/examples/scala-js/examples/reversi/JSTypes.scala
new file mode 100644
index 0000000..cc0e5a4
--- /dev/null
+++ b/examples/scala-js/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/scala-js/examples/reversi/Reversi.scala b/examples/scala-js/examples/reversi/Reversi.scala
new file mode 100644
index 0000000..b4a34a4
--- /dev/null
+++ b/examples/scala-js/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()
+ }
+}
diff --git a/examples/scala-js/examples/reversi/reversi-2.10-fastopt.html b/examples/scala-js/examples/reversi/reversi-2.10-fastopt.html
new file mode 100644
index 0000000..46cd1c7
--- /dev/null
+++ b/examples/scala-js/examples/reversi/reversi-2.10-fastopt.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Reversi - Scala.js example</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+</head>
+<body>
+
+<h1>Reversi - Scala.js example</h1>
+
+<p>Somewhat inspired by
+<a href="http://davidbau.com/reversi/">http://davidbau.com/reversi/</a></p>
+
+<div id="playground">
+</div>
+
+<script type="text/javascript" src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
+
+<script type="text/javascript" src="./target/scala-2.10/reversi-fastopt.js"></script>
+
+<script type="text/javascript">
+$(function() {
+ var mainInstance = new Reversi(
+ jQuery, jQuery("#playground"));
+ mainInstance.startGame();
+});
+</script>
+
+</body>
+</html>
diff --git a/examples/scala-js/examples/reversi/reversi-2.10.html b/examples/scala-js/examples/reversi/reversi-2.10.html
new file mode 100644
index 0000000..5f7b696
--- /dev/null
+++ b/examples/scala-js/examples/reversi/reversi-2.10.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Reversi - Scala.js example</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+</head>
+<body>
+
+<h1>Reversi - Scala.js example</h1>
+
+<p>Somewhat inspired by
+<a href="http://davidbau.com/reversi/">http://davidbau.com/reversi/</a></p>
+
+<div id="playground">
+</div>
+
+<script type="text/javascript" src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
+
+<script type="text/javascript" src="./target/scala-2.10/reversi-opt.js"></script>
+
+<script type="text/javascript">
+$(function() {
+ var mainInstance = new Reversi(
+ jQuery, jQuery("#playground"));
+ mainInstance.startGame();
+});
+</script>
+
+</body>
+</html>
diff --git a/examples/scala-js/examples/reversi/reversi-2.11-fastopt.html b/examples/scala-js/examples/reversi/reversi-2.11-fastopt.html
new file mode 100644
index 0000000..524e716
--- /dev/null
+++ b/examples/scala-js/examples/reversi/reversi-2.11-fastopt.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Reversi - Scala.js example</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+</head>
+<body>
+
+<h1>Reversi - Scala.js example</h1>
+
+<p>Somewhat inspired by
+<a href="http://davidbau.com/reversi/">http://davidbau.com/reversi/</a></p>
+
+<div id="playground">
+</div>
+
+<script type="text/javascript" src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
+
+<script type="text/javascript" src="./target/scala-2.11/reversi-fastopt.js"></script>
+
+<script type="text/javascript">
+$(function() {
+ var mainInstance = new Reversi(
+ jQuery, jQuery("#playground"));
+ mainInstance.startGame();
+});
+</script>
+
+</body>
+</html>
diff --git a/examples/scala-js/examples/reversi/reversi-2.11.html b/examples/scala-js/examples/reversi/reversi-2.11.html
new file mode 100644
index 0000000..b1a6d08
--- /dev/null
+++ b/examples/scala-js/examples/reversi/reversi-2.11.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Reversi - Scala.js example</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+</head>
+<body>
+
+<h1>Reversi - Scala.js example</h1>
+
+<p>Somewhat inspired by
+<a href="http://davidbau.com/reversi/">http://davidbau.com/reversi/</a></p>
+
+<div id="playground">
+</div>
+
+<script type="text/javascript" src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
+
+<script type="text/javascript" src="./target/scala-2.11/reversi-opt.js"></script>
+
+<script type="text/javascript">
+$(function() {
+ var mainInstance = new Reversi(
+ jQuery, jQuery("#playground"));
+ mainInstance.startGame();
+});
+</script>
+
+</body>
+</html>