├── client
├── .gitignore
└── src
│ └── main
│ └── scala
│ └── pairs
│ └── client
│ ├── phaser
│ ├── pixi
│ │ └── Sprite.scala
│ └── Phaser.scala
│ └── PairsClient.scala
├── 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
│ └── scala
│ └── pairs
│ └── server
│ └── Server.scala
├── project
├── build.properties
└── plugins.sbt
├── .gitignore
└── README.md
/client/.gitignore:
--------------------------------------------------------------------------------
1 | /bin/
2 |
--------------------------------------------------------------------------------
/server/.gitignore:
--------------------------------------------------------------------------------
1 | /bin/
2 |
--------------------------------------------------------------------------------
/project/build.properties:
--------------------------------------------------------------------------------
1 | sbt.version=0.13.16
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | target/
2 | .cache
3 | .cache-main
4 | .classpath
5 | .project
6 | .settings/
7 |
--------------------------------------------------------------------------------
/project/plugins.sbt:
--------------------------------------------------------------------------------
1 | addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.21")
2 | addSbtPlugin("io.spray" % "sbt-revolver" % "0.7.2")
3 |
--------------------------------------------------------------------------------
/server/src/main/resources/assets/0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sjrd/scalajs-phaser-demo/HEAD/server/src/main/resources/assets/0.png
--------------------------------------------------------------------------------
/server/src/main/resources/assets/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sjrd/scalajs-phaser-demo/HEAD/server/src/main/resources/assets/1.png
--------------------------------------------------------------------------------
/server/src/main/resources/assets/10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sjrd/scalajs-phaser-demo/HEAD/server/src/main/resources/assets/10.png
--------------------------------------------------------------------------------
/server/src/main/resources/assets/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sjrd/scalajs-phaser-demo/HEAD/server/src/main/resources/assets/2.png
--------------------------------------------------------------------------------
/server/src/main/resources/assets/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sjrd/scalajs-phaser-demo/HEAD/server/src/main/resources/assets/3.png
--------------------------------------------------------------------------------
/server/src/main/resources/assets/4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sjrd/scalajs-phaser-demo/HEAD/server/src/main/resources/assets/4.png
--------------------------------------------------------------------------------
/server/src/main/resources/assets/5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sjrd/scalajs-phaser-demo/HEAD/server/src/main/resources/assets/5.png
--------------------------------------------------------------------------------
/server/src/main/resources/assets/6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sjrd/scalajs-phaser-demo/HEAD/server/src/main/resources/assets/6.png
--------------------------------------------------------------------------------
/server/src/main/resources/assets/7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sjrd/scalajs-phaser-demo/HEAD/server/src/main/resources/assets/7.png
--------------------------------------------------------------------------------
/server/src/main/resources/assets/8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sjrd/scalajs-phaser-demo/HEAD/server/src/main/resources/assets/8.png
--------------------------------------------------------------------------------
/server/src/main/resources/assets/9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sjrd/scalajs-phaser-demo/HEAD/server/src/main/resources/assets/9.png
--------------------------------------------------------------------------------
/server/src/main/resources/assets/back.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sjrd/scalajs-phaser-demo/HEAD/server/src/main/resources/assets/back.png
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 | Pairs
17 |
18 |
19 | Pairs
20 |
21 |
22 |
23 |
24 |
25 | """
26 |
27 | def main(args: Array[String]): Unit = {
28 | implicit val system = ActorSystem("my-system")
29 | implicit val materializer = ActorMaterializer()
30 | // needed for the future flatMap/onComplete in the end
31 | implicit val executionContext = system.dispatcher
32 |
33 | val route = {
34 | pathPrefix("js") { //fileName =>
35 | //getFromFile("../client/target/scala-2.11/" + fileName)
36 | getFromDirectory("./client/target/scala-2.12")
37 | } ~
38 | pathPrefix("assets") {
39 | getFromResourceDirectory("assets")
40 | } ~
41 | pathSingleSlash {
42 | get {
43 | complete(HttpEntity(ContentTypes.`text/html(UTF-8)`, Index))
44 | } ~
45 | getFromResourceDirectory("")
46 | }
47 | }
48 |
49 | val bindingFuture = Http().bindAndHandle(route, "localhost", 8080)
50 |
51 | println(s"Server online at http://localhost:8080/\nPress RETURN to stop...")
52 | StdIn.readLine() // let it run until user presses return
53 | bindingFuture
54 | .flatMap(_.unbind()) // trigger unbinding from the port
55 | .onComplete(_ => system.terminate()) // and shutdown when done
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------