├── .gitignore ├── README.md ├── build.sbt ├── client ├── .gitignore └── src │ └── main │ └── scala │ └── pairs │ └── client │ ├── PairsClient.scala │ └── phaser │ ├── Phaser.scala │ └── pixi │ └── Sprite.scala ├── project ├── build.properties └── plugins.sbt └── server ├── .gitignore └── src └── main ├── resources └── assets │ ├── 0.png │ ├── 1.png │ ├── 10.png │ ├── 2.png │ ├── 3.png │ ├── 4.png │ ├── 5.png │ ├── 6.png │ ├── 7.png │ ├── 8.png │ ├── 9.png │ ├── back.png │ ├── phaser.js │ └── phaser.min.js └── scala └── pairs └── server └── Server.scala /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | .cache 3 | .cache-main 4 | .classpath 5 | .project 6 | .settings/ 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Demo with Scala.js and Phaser 2 | 3 | This is a small demo of a game written in [Scala.js](http://www.scala-js.org/) with the [Phaser](http://phaser.io/) library. 4 | -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | val commonSettings = Seq( 2 | version := "0.1.0-SNAPSHOT", 3 | scalaVersion := "2.12.4", 4 | scalacOptions ++= Seq("-deprecation", "-feature", "-encoding", "utf-8", "-Xfatal-warnings") 5 | ) 6 | 7 | lazy val `pairs-client` = project.in(file("client")). 8 | enablePlugins(ScalaJSPlugin). 9 | settings(commonSettings). 10 | settings( 11 | scalacOptions += "-P:scalajs:sjsDefinedByDefault", 12 | libraryDependencies ++= Seq( 13 | "org.scala-js" %%% "scalajs-dom" % "0.9.3" 14 | ), 15 | scalaJSUseMainModuleInitializer := true 16 | ) 17 | 18 | lazy val `pairs-server` = project.in(file("server")). 19 | settings(commonSettings). 20 | settings(Revolver.settings). 21 | settings( 22 | libraryDependencies ++= Seq( 23 | "org.scala-lang.modules" %% "scala-xml" % "1.0.6", 24 | "com.typesafe.akka" %% "akka-http" % "10.0.10", 25 | "com.typesafe.akka" %% "akka-actor" % "2.5.4", 26 | "com.typesafe.akka" %% "akka-stream" % "2.5.4" 27 | ) 28 | ) 29 | -------------------------------------------------------------------------------- /client/.gitignore: -------------------------------------------------------------------------------- 1 | /bin/ 2 | -------------------------------------------------------------------------------- /client/src/main/scala/pairs/client/PairsClient.scala: -------------------------------------------------------------------------------- 1 | package pairs.client 2 | 3 | import scala.scalajs.js 4 | import js.annotation._ 5 | import js.JSConverters._ 6 | import org.scalajs.dom 7 | 8 | import pairs.client.phaser._ 9 | 10 | class Square(val row: Int, val col: Int, val card: Int, 11 | val front: Sprite, val back: Sprite) 12 | 13 | class GameState extends State { 14 | private var firstClick: Option[Square] = None 15 | private var secondClick: Option[Square] = None 16 | 17 | private var score: Int = 0 18 | private var scoreText: js.Dynamic = null 19 | private var scoreGraphics: Graphics = null 20 | 21 | override def preload(): Unit = { 22 | load.image("back", "assets/back.png") 23 | for (i <- 0 to 9) 24 | load.image(i.toString(), s"assets/$i.png") 25 | } 26 | 27 | override def create(): Unit = { 28 | val allCards = 29 | for (i <- 0 to 9; _ <- 1 to 2) yield i // two copies of each card 30 | val shuffledCards = scala.util.Random.shuffle(allCards) 31 | 32 | val allPositions = 33 | for (row <- 0 until 4; col <- 0 until 5) yield (row, col) 34 | 35 | for (((row, col), card) <- allPositions zip shuffledCards) yield { 36 | val TileSize = 130 37 | val (x, y) = (col * TileSize, row * TileSize) 38 | val front = game.add.sprite(x, y, key = card.toString()) 39 | val back = game.add.sprite(x, y, key = "back") 40 | 41 | // Initially, the back is visible 42 | front.visible = false 43 | 44 | // Setup click event 45 | val square = new Square(row, col, card, front, back) 46 | back.inputEnabled = true 47 | back.events.onInputDown.add((sprite: Sprite) => doClick(square)) 48 | } 49 | 50 | scoreText = game.asInstanceOf[js.Dynamic].add.text( 51 | 660, 20, "Score: 0", 52 | js.Dynamic.literal(fontSize = "24px", fill = "#fff")) 53 | 54 | scoreGraphics = game.add.graphics(660, 50) 55 | } 56 | 57 | private def doClick(square: Square): Unit = { 58 | (firstClick, secondClick) match { 59 | case (None, _) => 60 | // First click of a pair 61 | firstClick = Some(square) 62 | 63 | case (Some(first), None) if first.card == square.card => 64 | // Found a pair 65 | firstClick = None 66 | score += 50 67 | 68 | case (Some(_), None) => 69 | // Missing a pair, need to hide it later 70 | secondClick = Some(square) 71 | score -= 5 72 | js.timers.setTimeout(1000) { 73 | assert(firstClick.isDefined && secondClick.isDefined) 74 | for (square <- Seq(firstClick.get, secondClick.get)) { 75 | square.front.visible = false 76 | square.back.visible = true 77 | } 78 | firstClick = None 79 | secondClick = None 80 | } 81 | 82 | case (Some(_), Some(_)) => 83 | // Third click, cancel (have to wait for the deadline to elapse) 84 | return 85 | } 86 | 87 | square.back.visible = false 88 | square.front.visible = true 89 | 90 | scoreText.text = s"Score: $score" 91 | 92 | scoreGraphics.clear() 93 | for (i <- 0 until score / 100) { 94 | val offset = i * 24 95 | def pt(x0: Double, y0: Double): PointLike = new PointLike { 96 | val x = x0 97 | val y = y0 98 | } 99 | 100 | val points = for (i <- (0 until 10).toJSArray) yield { 101 | val angle = 2*Math.PI/10 * i + Math.PI/2 102 | val len = if (i % 2 == 0) 10 else 4 103 | pt(offset + 10 + len*Math.cos(angle), 10 - len*Math.sin(angle)) 104 | } 105 | 106 | scoreGraphics.beginFill(0xFFD700) 107 | scoreGraphics.drawPolygon(points) 108 | scoreGraphics.endFill() 109 | } 110 | } 111 | } 112 | 113 | object PairsClient { 114 | def main(args: Array[String]): Unit = { 115 | val game = new Game(width = 800, height = 520, parent = "pairs-container") 116 | game.state.add("game", new GameState) 117 | game.state.start("game") 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /client/src/main/scala/pairs/client/phaser/Phaser.scala: -------------------------------------------------------------------------------- 1 | package pairs.client.phaser 2 | 3 | import scala.scalajs.js 4 | import js.annotation._ 5 | import js.| 6 | import org.scalajs.dom.html 7 | 8 | @js.native 9 | @JSGlobal 10 | object Phaser extends js.Object { 11 | val AUTO: Int = js.native 12 | } 13 | 14 | @js.native 15 | @JSGlobal("Phaser.Game") 16 | class Game( 17 | width: Double | String = 800, 18 | height: Double | String = 600, 19 | renderer: Int = Phaser.AUTO, 20 | parent: String | html.Element = "") extends js.Object { 21 | 22 | val state: StateManager = js.native 23 | 24 | val add: GameObjectFactory = js.native 25 | } 26 | 27 | @js.native 28 | @JSGlobal("Phaser.StateManager") 29 | class StateManager(val game: Game) extends js.Object { 30 | def add(key: String, state: State, 31 | autoStart: Boolean = false): Unit = js.native 32 | 33 | def start(key: String): Unit = js.native 34 | } 35 | 36 | @js.native 37 | @JSGlobal("Phaser.State") 38 | abstract class State extends js.Object { 39 | protected final def game: Game = js.native 40 | 41 | protected final def load: Loader = js.native 42 | 43 | def preload(): Unit = js.native 44 | 45 | def create(): Unit = js.native 46 | } 47 | 48 | @js.native 49 | @JSGlobal("Phaser.Loader") 50 | class Loader extends js.Object { 51 | def image(key: String, url: String = js.native, 52 | overwrite: Boolean = false): this.type = js.native 53 | } 54 | 55 | @js.native 56 | @JSGlobal("Phaser.GameObjectFactory") 57 | class GameObjectFactory(game: Game) extends js.Object { 58 | def sprite(x: Double = 0, y: Double = 0, 59 | key: String = js.native): Sprite = js.native 60 | 61 | def graphics(x: Double = 0, y: Double = 0): Graphics = js.native 62 | } 63 | 64 | @js.native 65 | @JSGlobal("Phaser.Sprite") 66 | class Sprite protected () extends pixi.Sprite 67 | with ComponentCore with InputEnabled { 68 | 69 | } 70 | 71 | @js.native 72 | trait ComponentCore extends js.Object { 73 | val events: Events = js.native 74 | } 75 | 76 | @js.native 77 | trait InputEnabled extends js.Object { 78 | def inputEnabled: Boolean = js.native 79 | def inputEnabled_=(value: Boolean): Unit = js.native 80 | } 81 | 82 | @js.native 83 | @JSGlobal("Phaser.Events") 84 | class Events(sprite: Sprite) extends js.Object { 85 | val onInputDown: Signal[js.Function1[Sprite, _]] = js.native 86 | } 87 | 88 | @js.native 89 | @JSGlobal("Phaser.Signal") 90 | class Signal[ListenerType <: js.Function] extends js.Object { 91 | def add(listener: ListenerType): Unit = js.native 92 | } 93 | 94 | @js.native 95 | @JSGlobal("Phaser.Graphics") 96 | class Graphics protected () extends js.Object { 97 | def clear(): Unit = js.native 98 | def beginFill(color: Int): Unit = js.native 99 | def endFill(): Unit = js.native 100 | def drawPolygon(path: js.Array[PointLike]): Unit = js.native 101 | } 102 | 103 | trait PointLike extends js.Object { 104 | def x: Double 105 | def y: Double 106 | } 107 | -------------------------------------------------------------------------------- /client/src/main/scala/pairs/client/phaser/pixi/Sprite.scala: -------------------------------------------------------------------------------- 1 | package pairs.client.phaser.pixi 2 | 3 | import scala.scalajs.js 4 | import js.annotation._ 5 | 6 | @js.native 7 | @JSGlobal("PIXI.Sprite") 8 | class Sprite protected () extends js.Object { 9 | var x: Double = js.native 10 | var y: Double = js.native 11 | 12 | var visible: Boolean = js.native 13 | } 14 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.16 2 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.21") 2 | addSbtPlugin("io.spray" % "sbt-revolver" % "0.7.2") 3 | -------------------------------------------------------------------------------- /server/.gitignore: -------------------------------------------------------------------------------- 1 | /bin/ 2 | -------------------------------------------------------------------------------- /server/src/main/resources/assets/0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sjrd/scalajs-phaser-demo/94d1a98580f651ec73b93acc52d2b4566136e633/server/src/main/resources/assets/0.png -------------------------------------------------------------------------------- /server/src/main/resources/assets/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sjrd/scalajs-phaser-demo/94d1a98580f651ec73b93acc52d2b4566136e633/server/src/main/resources/assets/1.png -------------------------------------------------------------------------------- /server/src/main/resources/assets/10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sjrd/scalajs-phaser-demo/94d1a98580f651ec73b93acc52d2b4566136e633/server/src/main/resources/assets/10.png -------------------------------------------------------------------------------- /server/src/main/resources/assets/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sjrd/scalajs-phaser-demo/94d1a98580f651ec73b93acc52d2b4566136e633/server/src/main/resources/assets/2.png -------------------------------------------------------------------------------- /server/src/main/resources/assets/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sjrd/scalajs-phaser-demo/94d1a98580f651ec73b93acc52d2b4566136e633/server/src/main/resources/assets/3.png -------------------------------------------------------------------------------- /server/src/main/resources/assets/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sjrd/scalajs-phaser-demo/94d1a98580f651ec73b93acc52d2b4566136e633/server/src/main/resources/assets/4.png -------------------------------------------------------------------------------- /server/src/main/resources/assets/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sjrd/scalajs-phaser-demo/94d1a98580f651ec73b93acc52d2b4566136e633/server/src/main/resources/assets/5.png -------------------------------------------------------------------------------- /server/src/main/resources/assets/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sjrd/scalajs-phaser-demo/94d1a98580f651ec73b93acc52d2b4566136e633/server/src/main/resources/assets/6.png -------------------------------------------------------------------------------- /server/src/main/resources/assets/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sjrd/scalajs-phaser-demo/94d1a98580f651ec73b93acc52d2b4566136e633/server/src/main/resources/assets/7.png -------------------------------------------------------------------------------- /server/src/main/resources/assets/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sjrd/scalajs-phaser-demo/94d1a98580f651ec73b93acc52d2b4566136e633/server/src/main/resources/assets/8.png -------------------------------------------------------------------------------- /server/src/main/resources/assets/9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sjrd/scalajs-phaser-demo/94d1a98580f651ec73b93acc52d2b4566136e633/server/src/main/resources/assets/9.png -------------------------------------------------------------------------------- /server/src/main/resources/assets/back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sjrd/scalajs-phaser-demo/94d1a98580f651ec73b93acc52d2b4566136e633/server/src/main/resources/assets/back.png -------------------------------------------------------------------------------- /server/src/main/scala/pairs/server/Server.scala: -------------------------------------------------------------------------------- 1 | package pairs.server 2 | 3 | import akka.actor.ActorSystem 4 | import akka.http.scaladsl.Http 5 | import akka.http.scaladsl.model._ 6 | import akka.http.scaladsl.server.Directives._ 7 | import akka.stream.ActorMaterializer 8 | import scala.io.StdIn 9 | 10 | import scala.util.Properties 11 | 12 | object Server { 13 | private val Index = """ 14 | 15 |
16 |