feat(scala): Scala.js browser platform, snake and pong pages, build-web.sh

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Lucas Winther
2026-10-29 00:17:02 +01:00
co-authored by Claude Opus 5.5
parent f4b671678e
commit 183821737e
5 changed files with 123 additions and 0 deletions
+50
View File
@@ -0,0 +1,50 @@
package snake
package web
import scala.scalajs.js
import scala.scalajs.js.Dynamic.global
import scala.scalajs.js.typedarray.Uint32Array
/**
* Browser glue: turns a canvas into a Platform. Impure by nature; not part of the exercises' logic.
* `js.Dynamic` lets us call browser APIs by name without type facades (and without extra libraries).
*/
final class BrowserPlatform(canvas: js.Dynamic) extends Platform:
private val ctx = canvas.getContext("2d")
private val window = global.window
// Keys that scroll the page should only steer the game.
private val prevent = Set("ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", " ")
private def listen(kind: String, cb: String => Unit): Unit =
val handler: js.Function1[js.Dynamic, Unit] = e =>
val key = e.key.asInstanceOf[String]
if prevent(key) then e.preventDefault()
cb(key)
window.addEventListener(kind, handler)
def fillRect(x: Int, y: Int, w: Int, h: Int, color: String): Unit =
ctx.fillStyle = color
ctx.fillRect(x, y, w, h)
def fillText(x: Int, y: Int, text: String, color: String, size: Int, align: String): Unit =
ctx.fillStyle = color
ctx.font = s"${size}px monospace"
ctx.textAlign = align
ctx.fillText(text, x, y)
def randomSeed(): Long =
val buf = new Uint32Array(1)
global.crypto.getRandomValues(buf)
buf(0).toLong
// t is the number of milliseconds since the first animation frame.
def startClock(onTime: Long => Unit): Unit =
def frame(start: Option[Double]): js.Function1[Double, Unit] = now =>
val t0 = start.getOrElse(now)
onTime((now - t0).toLong)
window.requestAnimationFrame(frame(Some(t0)))
window.requestAnimationFrame(frame(None))
def onKeyDown(cb: String => Unit): Unit = listen("keydown", cb)
def onKeyUp(cb: String => Unit): Unit = listen("keyup", cb)
+16
View File
@@ -0,0 +1,16 @@
package snake
package web
import scala.scalajs.js.Dynamic.global
// Two programs, one runtime: build-web.sh picks the entry point with --main-class.
private def canvas() = global.document.getElementById("game")
/** scala/index.html: Snake. */
object SnakeMain:
def main(args: Array[String]): Unit = Runtime.runApp(App.main, BrowserPlatform(canvas()))
/** scala/pong.html: Pong (m11). */
object PongMain:
def main(args: Array[String]): Unit = Runtime.runApp(pong.PongApp.pongMain, BrowserPlatform(canvas()))