Last active
August 29, 2015 13:57
-
-
Save lihaoyi/9759723 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@JSExport | |
object ScalaJSExample{ | |
@JSExport | |
def main(args: Array[String]): Unit = { | |
val xs = Seq(1, 2, 3) | |
println(xs) | |
val ys = Seq(4, 5, 6) | |
println(ys) | |
val zs = for{ | |
x <- xs | |
y <- ys | |
} yield x * y | |
println(zs) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import org.scalajs.dom | |
import scala.util.Random | |
import scala.scalajs.js | |
case class Point(x: Double, y: Double){ | |
def +(p: Point) = Point(x + p.x, y + p.y) | |
def -(p: Point) = Point(x - p.x, y - p.y) | |
def *(d: Double) = Point(x * d, y * d) | |
def /(d: Double) = Point(x / d, y / d) | |
def length = Math.sqrt(x * x + y * y) | |
} | |
class Enemy(var pos: Point, var vel: Point) | |
@JSExport | |
object ScalaJSExample { | |
var startTime = js.Date.now() | |
var player = Point(dom.innerWidth.toInt/2, dom.innerHeight.toInt/2) | |
var enemies = Seq.empty[Enemy] | |
var death: Option[(String, Int)] = None | |
def run() = { | |
enemies = enemies.filter(e => | |
e.pos.x >= 0 && e.pos.x <= canvas.width && | |
e.pos.y >= 0 && e.pos.y <= canvas.height | |
) | |
def randSpeed = Random.nextInt(5) - 3 | |
enemies = enemies ++ Seq.fill(20 - enemies.length)( | |
new Enemy( | |
Point(Random.nextInt(canvas.width.toInt), 0), | |
Point(randSpeed, randSpeed) | |
) | |
) | |
for(enemy <- enemies){ | |
enemy.pos = enemy.pos + enemy.vel | |
val delta = player - enemy.pos | |
enemy.vel = enemy.vel + delta / delta.length / 100 | |
} | |
if(enemies.exists(e => (e.pos - player).length < 20)){ | |
death = Some((s"You lasted $deltaT seconds", 100)) | |
enemies = enemies.filter(e => (e.pos - player).length > 20) | |
} | |
} | |
def deltaT = ((js.Date.now() - startTime) / 1000).toInt | |
def draw() = { | |
renderer.clearRect(0, 0, canvas.width, canvas.height) | |
death match{ | |
case None => | |
renderer.fillStyle = "white" | |
renderer.fillRect(player.x - 10, player.y - 10, 20, 20) | |
renderer.fillText("player", player.x - 15, player.y - 30) | |
renderer.fillStyle = "red" | |
for (enemy <- enemies){ | |
renderer.fillRect(enemy.pos.x - 10, enemy.pos.y - 10, 20, 20) | |
} | |
renderer.fillStyle = "white" | |
renderer.fillText(s"$deltaT seconds", canvas.width / 2 - 100, canvas.height / 5) | |
case Some((msg, time)) => | |
renderer.fillStyle = "white" | |
renderer.fillText(msg, canvas.width / 2 - 100, canvas.height / 2) | |
if (time - 1 == 0){ | |
death = None | |
startTime = js.Date.now() | |
}else{ | |
death = Option((msg, time - 1)) | |
} | |
} | |
} | |
@JSExport | |
def main(args: Array[String] ): Unit = { | |
dom.document.onmousemove = { (e: dom.MouseEvent) => | |
player = Point(e.clientX.toInt, e.clientY.toInt) | |
(): js.Any | |
} | |
dom.setInterval(() => {run(); draw()}, 20) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import org.scalajs.dom | |
import scala.scalajs.js | |
/** | |
* A spiritual clone of Flappy Bird. Click to jump, don't hit the white barriers! | |
* | |
* High scores are saved using HTML5 Local Storage. | |
*/ | |
@JSExport | |
object ScalaJSExample{ | |
var game = new Game() | |
@JSExport | |
def main(args: Array[String]): Unit = { | |
dom.setInterval(() => game.run(), 20 * 800 / canvas.width) | |
canvas.onclick = { (e: dom.MouseEvent) => | |
game.v += canvas.height / 200 | |
} | |
} | |
class Game{ | |
renderer.fillStyle = "black" | |
renderer.fillRect(0, 0, canvas.width, canvas.height) | |
var i = 0.0 | |
var h = canvas.height / 2 | |
var v = 0.0 | |
val a = -0.05 | |
def run() = { | |
clear() | |
println("Score: " + i.toInt) | |
val highScore = Option(dom.localStorage.getItem("highScore")).fold(0)(_.toString.toInt) | |
println("High Score: " + highScore) | |
i += 1 | |
dom.localStorage.setItem("highScore", Math.max(i, highScore).toString) | |
v += a | |
h += v | |
val spotData = renderer.getImageData(canvas.width/2 + 3, canvas.height - h, 1, 1).data | |
if (spotData.take(3).map(_.toInt).sum != 0 || h > canvas.height || h < 0) { | |
game = new Game() | |
} | |
else{ | |
val imageData = renderer.getImageData(1, 0, canvas.width-1, canvas.height) | |
renderer.putImageData(imageData, 0, 0) | |
renderer.fillStyle = "black" | |
renderer.fillRect(canvas.width-1, 0, 1, canvas.height) | |
if (i.toInt % (canvas.width / 3).toInt == 0){ | |
val gapHeight = canvas.height / 5 | |
val gap = util.Random.nextInt(canvas.height.toInt - gapHeight.toInt) | |
renderer.fillStyle = "white" | |
renderer.fillRect(canvas.width-2, 0, 2, gap) | |
renderer.fillRect(canvas.width-2, gap + gapHeight, 2, canvas.height - gap - gapHeight) | |
} | |
val r = (h / canvas.height * 255).toInt | |
val b = (255 / (1 + Math.pow(Math.E, -v))).toInt | |
renderer.fillStyle = s"rgb($r, 255, $b)" | |
renderer.fillRect(canvas.width / 2, canvas.height - h, 2, 2) | |
} | |
} | |
} | |
} | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Simple, zero-dependency, self-contained JSON parser, serializer and AST | |
* taken from | |
* | |
* https://github.com/nestorpersist/json | |
* | |
* Notably, does not use the browser's native JSON capability, but re-implements | |
* it all in order to be 100% compatible with both Scala-JS and Scala-JVM | |
*/ | |
@JSExport | |
object ScalaJSExample{ | |
@JSExport | |
def main(args: Array[String]): Unit = { | |
val parsed = Json.read(ugly) | |
println(parsed(0)) | |
println(parsed(8)("real")) | |
println(parsed(8)("comment")) | |
println(parsed(8)("jsontext")) | |
println(Json.write(parsed(8))) | |
} | |
val ugly = | |
""" | |
|[ | |
| "JSON Test Pattern pass1", | |
| {"object with 1 member":["array with 1 element"]}, | |
| {}, | |
| [], | |
| -42, | |
| true, | |
| false, | |
| null, | |
| { | |
| "integer": 1234567890, | |
| "real": -9876.543210, | |
| "e": 0.123456789e-12, | |
| "E": 1.234567890E+34, | |
| "": 23456789012E66, | |
| "zero": 0, | |
| "one": 1, | |
| "space": " ", | |
| "quote": "\"", | |
| "backslash": "\\", | |
| "controls": "\b\f\n\r\t", | |
| "slash": "/ & \/", | |
| "alpha": "abcdefghijklmnopqrstuvwyz", | |
| "ALPHA": "ABCDEFGHIJKLMNOPQRSTUVWYZ", | |
| "digit": "0123456789", | |
| "0123456789": "digit", | |
| "special": "`1~!@#$%^&*()_+-={':[,]}|;.</>?", | |
| "hex": "\u0123\u4567\u89AB\uCDEF\uabcd\uef4A", | |
| "true": true, | |
| "false": false, | |
| "null": null, | |
| "array":[ ], | |
| "object":{ }, | |
| "address": "50 St. James Street", | |
| "url": "http://www.JSON.org/", | |
| "comment": "// /* <!-- --", | |
| "# -- --> */": " ", | |
| " s p a c e d " :[1,2 , 3 | |
| | |
|, | |
| | |
|4 , 5 , 6 ,7 ],"compact":[1,2,3,4,5,6,7], | |
| "jsontext": "{\"object with 1 member\":[\"array with 1 element\"]}", | |
| "quotes": "" \u005Cu0022 %22 0x22 034 "", | |
| "\/\\\"\uCAFE\uBABE\uAB98\uFCDE\ubcda\uef4A\b\f\n\r\t`1~!@#$%^&*()_+-=[]{}|;:',./<>?" | |
|: "A key can be any string" | |
| }, | |
| 0.5 ,98.6 | |
|, | |
|99.44 | |
|, | |
| | |
|1066, | |
|1e1, | |
|0.1e1, | |
|1e-1, | |
|1e00,2e+00,2e-00 | |
|,"rosebud"] | |
""".stripMargin | |
} | |
object Js { | |
sealed trait Value{ | |
def value: Any | |
def apply(i: Int): Value = this.asInstanceOf[Array].value(i) | |
def apply(s: java.lang.String): Value = this.asInstanceOf[Object].value.find(_._1 == s).get._2 | |
} | |
case class String(value: java.lang.String) extends Value | |
case class Object(value: Seq[(java.lang.String, Value)]) extends Value | |
case class Array(value: Seq[Value]) extends Value | |
case class Number(value: java.lang.String) extends Value | |
case object False extends Value{ | |
def value = true | |
} | |
case object True extends Value{ | |
def value = false | |
} | |
case object Null extends Value{ | |
def value = null | |
} | |
} | |
object Json { | |
def write(v: Js.Value): String = v match { | |
case Js.String(s) => | |
val out = s.flatMap { | |
case '\\' => "\\\\" | |
case '"' => "\\\"" | |
case '/' => "\\/" | |
case '\b' => "\\b" | |
case '\t' => "\\t" | |
case '\n' => "\\n" | |
case '\f' => "\\f" | |
case '\r' => "\\r" | |
case c if c < ' ' => | |
val t = "000" + Integer.toHexString(c) | |
"\\u" + t.takeRight(4) | |
case c => c.toString | |
} | |
'"' + out + '"' | |
case Js.Object(kv) => | |
val contents = kv.toIterator.map{ | |
case (k, v) => s"${write(Js.String(k))}: ${write(v)}" | |
}.mkString(", ") | |
s"{$contents}" | |
case Js.Array(vs) => s"[${vs.map(write).mkString(", ")}]" | |
case Js.Number(d) => d | |
case Js.False => "false" | |
case Js.True => "true" | |
case Js.Null => "null" | |
} | |
/** | |
* Self-contained JSON parser adapted from | |
* | |
* https://github.com/nestorpersist/json | |
*/ | |
def read(s: String): Js.Value = { | |
// *** Character Kinds | |
type CharKind = Int | |
val Letter = 0 | |
val Digit = 1 | |
val Minus = 2 | |
val Quote = 3 | |
val Colon = 4 | |
val Comma = 5 | |
val Lbra = 6 | |
val Rbra = 7 | |
val Larr = 8 | |
val Rarr = 9 | |
val Blank = 10 | |
val Other = 11 | |
val Eof = 12 | |
val Slash = 13 | |
// *** Token Kinds | |
type TokenKind = Int | |
val ID = 0 | |
val STRING = 1 | |
val NUMBER = 2 | |
val BIGNUMBER = 3 | |
val FLOATNUMBER = 4 | |
val COLON = 5 | |
val COMMA = 6 | |
val LOBJ = 7 | |
val ROBJ = 8 | |
val LARR = 9 | |
val RARR = 10 | |
val BLANK = 11 | |
val EOF = 12 | |
// *** Character => CharKind Map *** | |
val charKind = (0 to 255).toArray.map { | |
case c if 'a'.toInt <= c && c <= 'z'.toInt => Letter | |
case c if 'A'.toInt <= c && c <= 'Z'.toInt => Letter | |
case c if '0'.toInt <= c && c <= '9'.toInt => Digit | |
case '-' => Minus | |
case ',' => Comma | |
case '"' => Quote | |
case ':' => Colon | |
case '{' => Lbra | |
case '}' => Rbra | |
case '[' => Larr | |
case ']' => Rarr | |
case ' ' => Blank | |
case '\t' => Blank | |
case '\n' => Blank | |
case '\r' => Blank | |
case '/' => Slash | |
case _ => Other | |
} | |
// *** Character Escapes | |
val escapeMap = Map[Int, String]( | |
'\\'.toInt -> "\\", | |
'/'.toInt -> "/", | |
'\"'.toInt -> "\"", | |
'b'.toInt -> "\b", | |
'f'.toInt -> "\f", | |
'n'.toInt -> "\n", | |
'r'.toInt -> "\r", | |
't'.toInt -> "\t" | |
) | |
// *** Import Shared Data *** | |
// *** INPUT STRING *** | |
// array faster than accessing string directly using charAt | |
//final val s1 = s.toCharArray() | |
val size = s.size | |
// *** CHARACTERS *** | |
var pos = 0 | |
var ch: Int = 0 | |
var chKind: CharKind = 0 | |
var chLinePos: Int = 0 | |
var chCharPos: Int = 0 | |
def chNext() = { | |
if (pos < size) { | |
//ch = s1(pos).toInt | |
ch = s.charAt(pos) | |
chKind = if (ch < 255) { | |
charKind(ch) | |
} else { | |
Other | |
} | |
pos += 1 | |
if (ch == '\n'.toInt) { | |
chLinePos += 1 | |
chCharPos = 1 | |
} else { | |
chCharPos += 1 | |
} | |
} else { | |
ch = -1 | |
pos = size + 1 | |
chKind = Eof | |
} | |
} | |
def chError(msg: String): Nothing = { | |
throw new Json.Exception(msg, s, chLinePos, chCharPos) | |
} | |
def chMark = pos - 1 | |
def chSubstr(first: Int, delta: Int = 0) = { | |
s.substring(first, pos - 1 - delta) | |
} | |
// *** LEXER *** | |
var tokenKind = BLANK | |
var tokenValue = "" | |
var linePos = 1 | |
var charPos = 1 | |
def getDigits() = { | |
while (chKind == Digit) chNext() | |
} | |
def handleDigit() { | |
val first = chMark | |
getDigits() | |
val k1 = if (ch == '.'.toInt) { | |
chNext() | |
getDigits() | |
BIGNUMBER | |
} else { | |
NUMBER | |
} | |
val k2 = if (ch == 'E'.toInt || ch == 'e'.toInt) { | |
chNext() | |
if (ch == '+'.toInt) { | |
chNext() | |
} else if (ch == '-'.toInt) { | |
chNext() | |
} | |
getDigits() | |
FLOATNUMBER | |
} else { | |
k1 | |
} | |
tokenKind = k2 | |
tokenValue = chSubstr(first) | |
} | |
def handleRaw() { | |
chNext() | |
val first = chMark | |
var state = 0 | |
do { | |
if (chKind == Eof) chError("EOF encountered in raw string") | |
state = (ch, state) match { | |
case ('}', _) => 1 | |
case ('"', 1) => 2 | |
case ('"', 2) => 3 | |
case ('"', 3) => 0 | |
case _ => 0 | |
} | |
chNext() | |
} while (state != 3) | |
tokenKind = STRING | |
tokenValue = chSubstr(first, 3) | |
} | |
def handle(i: Int) = { | |
chNext() | |
tokenKind = i | |
tokenValue = "" | |
} | |
def tokenNext() { | |
do { | |
linePos = chLinePos | |
charPos = chCharPos | |
val kind: Int = chKind | |
kind match { | |
case Letter => | |
val first = chMark | |
while (chKind == Letter || chKind == Digit) { | |
chNext() | |
} | |
tokenKind = ID | |
tokenValue = chSubstr(first) | |
case Digit => handleDigit() | |
case Minus => | |
chNext() | |
handleDigit() | |
tokenValue = "-" + tokenValue | |
case Quote => | |
val sb = new StringBuilder(50) | |
chNext() | |
var first = chMark | |
while (ch != '"'.toInt && ch >= 32) { | |
if (ch == '\\'.toInt) { | |
sb.append(chSubstr(first)) | |
chNext() | |
escapeMap.get(ch) match { | |
case Some(s) => | |
sb.append(s) | |
chNext() | |
case None => | |
if (ch != 'u'.toInt) chError("Illegal escape") | |
chNext() | |
var code = 0 | |
for (i <- 1 to 4) { | |
val ch1 = ch.toChar.toString | |
val i = "0123456789abcdef".indexOf(ch1.toLowerCase) | |
if (i == -1) chError("Illegal hex character") | |
code = code * 16 + i | |
chNext() | |
} | |
sb.append(code.toChar.toString) | |
} | |
first = chMark | |
} else { | |
chNext() | |
} | |
} | |
if (ch != '"') chError("Unexpected string character: " + ch.toChar) | |
sb.append(chSubstr(first)) | |
tokenKind = STRING | |
tokenValue = sb.toString() | |
chNext() | |
if (tokenValue.length() == 0 && ch == '{') { | |
handleRaw() | |
} | |
case Colon => handle(COLON) | |
case Comma => handle(COMMA) | |
case Lbra => handle(LOBJ) | |
case Rbra => handle(ROBJ) | |
case Larr => handle(LARR) | |
case Rarr => handle(RARR) | |
case Blank => | |
do chNext() while (chKind == Blank) | |
tokenKind = BLANK | |
tokenValue = "" | |
case Other => chError("Unexpected character: " + ch.toChar + " " + ch) | |
case Eof => | |
chNext() | |
tokenKind = EOF | |
tokenValue = "" | |
case Slash => | |
if (chKind != Slash) chError("Expecting Slash") | |
do chNext() while (ch != '\n' && chKind != Eof) | |
tokenKind = BLANK | |
tokenValue = "" | |
} | |
} while (tokenKind == BLANK) | |
} | |
def tokenError(msg: String): Nothing = { | |
throw new Json.Exception(msg, s, linePos, charPos) | |
} | |
// *** PARSER *** | |
def handleEof() = tokenError("Unexpected eof") | |
def handleUnexpected(i: String) = tokenError(s"Unexpected input: [$i]") | |
def handleArray(): Js.Array = { | |
tokenNext() | |
var result = List.empty[Js.Value] | |
while (tokenKind != RARR) { | |
result = getJson() :: result | |
tokenKind match{ | |
case COMMA => tokenNext() | |
case RARR => // do nothing | |
case _ => tokenError("Expecting , or ]") | |
} | |
} | |
tokenNext() | |
Js.Array(result.reverse) | |
} | |
def handleObject(): Js.Object = { | |
tokenNext() | |
var result = List.empty[(String, Js.Value)] | |
while (tokenKind != ROBJ) { | |
if (tokenKind != STRING && tokenKind != ID) tokenError("Expecting string or name") | |
val name = tokenValue | |
tokenNext() | |
if (tokenKind != COLON) tokenError("Expecting :") | |
tokenNext() | |
result = (name -> getJson()) :: result | |
tokenKind match{ | |
case COMMA => tokenNext() | |
case ROBJ => // do nothing | |
case _ => tokenError("Expecting , or }") | |
} | |
} | |
tokenNext() | |
Js.Object(result.reverse) | |
} | |
def handleNumber(name: String, f: String => Unit) = { | |
val v = try { | |
f(tokenValue) | |
} catch { | |
case _: Throwable => tokenError("Bad " + name) | |
} | |
val old = tokenValue | |
tokenNext() | |
Js.Number(old) | |
} | |
def getJson(): Js.Value = { | |
val kind: Int = tokenKind | |
val result: Js.Value = kind match { | |
case ID => | |
val result = tokenValue match { | |
case "true" => Js.True | |
case "false" => Js.False | |
case "null" => Js.Null | |
case _ => tokenError("Not true, false, or null") | |
} | |
tokenNext() | |
result | |
case STRING => | |
val result = tokenValue | |
tokenNext() | |
Js.String(result) | |
case NUMBER => handleNumber("NUMBER", _.toLong) | |
case BIGNUMBER => handleNumber("BIGNUMBER", _.toDouble) | |
case FLOATNUMBER => handleNumber("FLOATNUMBER", _.toDouble) | |
case COLON => handleUnexpected(":") | |
case COMMA => handleUnexpected(",") | |
case LOBJ => handleObject() | |
case ROBJ => handleUnexpected("}") | |
case LARR => handleArray() | |
case RARR => handleUnexpected("]") | |
case EOF => handleEof() | |
} | |
result | |
} | |
def parse(): Js.Value = { | |
chNext() | |
tokenNext() | |
val result = getJson | |
if (tokenKind != EOF) tokenError("Excess input") | |
result | |
} | |
parse() | |
} | |
class Exception(val msg: String, | |
val input: String, | |
val line: Int, | |
val char: Int) | |
extends scala.Exception(s"JsonParse Error: $msg line $line [$char] in $input") | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import org.scalajs.dom | |
case class Pt(x: Double, y: Double) | |
/** | |
* Splash page for www.scala-js-fiddle.com. Uses Scalatags to render a HTML page | |
* containing the text on the page, while using a HTML5 Canvas element to slowly | |
* render a Sierpinski Triangle behind the text pixel by pixel. | |
*/ | |
@JSExport | |
object ScalaJSExample{ | |
val sandbox = dom.document.getElementById("sandbox") | |
clear() | |
def render() = { | |
import scalatags.all._ | |
import scalatags._ | |
renderln( | |
div( | |
padding:="10px", | |
id:="page", | |
h1( | |
marginTop:="0px", | |
img(src:="/Shield.svg", height:="40px", marginBottom:="-10px"), | |
" Scala.jsFiddle" | |
), | |
p( | |
"Scala.jsFiddle is an online scratchpad to try out your ", | |
a(href:="http://www.scala-js.org/", "Scala.js "), | |
"snippets. Enter Scala code on the left and see the results on the right, ", | |
"after the Scala is compiled to Javascript using the Scala.js compiler." | |
), | |
ul( | |
li(green("Ctrl/Cmd-Enter"), ": compile and execute"), | |
li(green("Ctrl/Cmd-Alt-Enter"), ": compile and execute using ", b("preoptimize")), | |
li(green("Ctrl/Cmd-Shift-Enter"), ": compile and execute using ", b("optimize")), | |
li(green("Ctrl/Cmd-Space"), ": autocomplete at caret"), | |
li(green("Ctrl/Cmd-J"), ": show the generated Javascript code"), | |
li(green("Ctrl/Cmd-Alt-J"), ": show the generated code, packaged with dependencies"), | |
li(green("Ctrl/Cmd-Shift-J"), ": show the generated code, optimized by Google Closure"), | |
li(green("Ctrl/Cmd-S"), ": save the code to a public Gist"), | |
li(green("Ctrl/Cmd-E"), ": export your code to a stand-alone web page") | |
), | |
p( | |
"To load the code from an existing gist, simply go to " | |
), | |
ul(li(green("www.scala-js-fiddle.com/gist/<gist-id>"))), | |
p( | |
"Where ",green("<gist-id>"), " is the last section of the gist's URL.", | |
"You can also go to:" | |
), | |
ul(li(green("www.scala-js-fiddle.com/gist/<gist-id>/<file-name>"))), | |
p( | |
"If you want a particular file in a multi file gist." | |
), | |
p( | |
"If you need ideas of things you can make using Scala.jsFiddle, check ", | |
"out some of our examples:" | |
), | |
ul( | |
for { | |
(file, name) <- Seq( | |
"BasicOperations.scala" -> "Basic Operations", | |
"SierpinskiTriangle.scala" -> "Sierpinski Triangle", | |
"Turmites.scala" -> "Turmites", | |
"Oscilloscope.scala" -> "Oscilloscope", | |
"FlappyLine.scala" -> "Flappy Line", | |
"SquareRoot.scala" -> "Square Root solver", | |
"DodgeTheDots.scala" -> "Dodge the Dots", | |
"SpaceInvaders.scala" -> "Space Invaders", | |
"SquareRootRx.scala" -> "Square Root solver with Scala.rx", | |
"TodoMVC.scala" -> "TodoMVC", | |
"JsonParser.scala" -> "JSON Parser", | |
"ScalaAsyncPaintbrush.scala" -> "Scala Async Paintbrush", | |
"RayTracer.scala" -> "RayTracer" | |
) | |
} yield li( | |
a(href:=(dom.document.location.origin + "/gist/9759723/" + file), name) | |
) | |
), | |
p("Scala.jsFiddle provides some build-in variables to give you started quickly:"), | |
ul( | |
li( | |
blue("canvas"), ": a ", blue("dom.HTMLCanvasElement"), " that lets you draw ", | |
"on the right pane" | |
), | |
li( | |
blue("renderer"), ": a ", blue("dom.CanvasRenderingContext2D"), " that ", | |
"lets you draw on the ", blue("canvas") | |
), | |
li( | |
blue("println(s: Any*)"), ": prints the given values to the right pane as ", | |
"a string." | |
), | |
li( | |
blue("renderln(s: Any*)"), ": prints the given values to the right pane as ", | |
"HTML." | |
), | |
li(blue("clear()"), ": Removes all printed output from the right pane"), | |
li( | |
blue("scroll(px: Int)"), ": Scrolls the right pane up or down by ", | |
"the given number of pixels" | |
), | |
li( | |
"The colors ", red("red"), ", ", green("green"), " and ", blue("blue"), | |
" to help prettify your output" | |
) | |
), | |
p("Apart from these inbuilt helpers, available libraries include:"), | |
ul( | |
li(a(href:="https://github.com/lihaoyi/scalatags", "scalatags")), | |
li(a(href:="https://github.com/lihaoyi/scala.rx", "scala.rx")), | |
li(a(href:="https://github.com/scala-js/scala-js-dom", "scala-js-dom")) | |
), | |
p( | |
"Scala.jsFiddle is made by ", a(href:="https://github.com/lihaoyi")("Li Haoyi"), | |
" and can be found on ", a(href:="https://github.com/lihaoyi/scala-js-fiddle")("Github") | |
) | |
) | |
) | |
// scroll to top so if the users screen is too short, he sees the top of the | |
// rendered page rather than the bottom | |
scroll(-1000) | |
} | |
val corners = Seq( | |
Pt(canvas.width/2, 0), | |
Pt(0, canvas.height), | |
Pt(canvas.width, canvas.height) | |
) | |
var pt = corners(0) | |
val (w, h) = (canvas.height.toDouble, canvas.height.toDouble) | |
@JSExport | |
def main(args: Array[String]): Unit = { | |
render() | |
dom.setInterval(() => { | |
val c = corners(util.Random.nextInt(3)) | |
pt = Pt((pt.x + c.x) / 2, (pt.y + c.y) / 2) | |
val m = (pt.y / h) | |
val r = 255 - (pt.x / w * m * 255).toInt | |
val g = 255 - ((w-pt.x) / w * m * 255).toInt | |
val b = 255 - ((h - pt.y) / h * 255).toInt | |
renderer.fillStyle = s"rgb($r, $g, $b)" | |
renderer.fillRect(pt.x, pt.y, 1, 1) | |
}, 20) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import org.scalajs.dom | |
import Math._ | |
@JSExport | |
object ScalaJSExample { | |
@JSExport | |
def main(): Unit = { | |
val (h, w) = (canvas.height, canvas.width) | |
var x = 0 | |
val graphs = Seq[(String, Double => Double)]( | |
("red", sin), | |
("green", x => 2 - abs(x % 8 - 4)), | |
("blue", x => 3 * pow(sin(x / 12), 2) * sin(x)) | |
).zipWithIndex | |
dom.setInterval(() => { | |
x = (x + 1) % w.toInt | |
if (x == 0) renderer.clearRect(0, 0, w, h) | |
else for (((color, func), i) <- graphs) { | |
val y = func(x/w * 75) * h/40 + h/3 * (i+0.5) | |
renderer.fillStyle = color | |
renderer.fillRect(x, y, 3, 3) | |
} | |
}, 10) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import scala.scalajs.js.annotation.JSExport | |
import org.scalajs.dom | |
import math._ | |
import scala.language.postfixOps | |
import scala.scalajs.concurrent.JSExecutionContext.Implicits.queue | |
import ScalaJSExample.{Color, Epsilon} | |
import scala.math._ | |
import scala.async.Async._ | |
import scala.concurrent.Future | |
import scalaxy.loops._ | |
import scala.language.postfixOps | |
/** | |
* A simple ray tracer, taken from the PyPy benchmarks | |
* | |
* https://bitbucket.org/pypy/benchmarks/src/846fa56a282b/own/raytrace-simple.py?at=default | |
* | |
* Half the lines of code | |
*/ | |
@JSExport | |
object ScalaJSExample { | |
val Epsilon = 0.00001 | |
type Color = Vec | |
val Color = Vec | |
@JSExport | |
def main(): Unit = { | |
val r = new util.Random(16314302) | |
val spiral = for (i <- 0 until 11) yield { | |
val theta = i * (i + 5) * Pi / 100 + 0.3 | |
val center = (0 - 4 * sin(theta), 1.5 - i / 2.0, 0 - 4 * cos(theta)) | |
val form = Sphere(center, 0.3 + i * 0.1) | |
val surface = Flat((i / 6.0, 1 - i / 6.0, 0.5)) | |
(form, surface) | |
} | |
def rand(d: Double) = (r.nextDouble() - 0.5) * d * 2 | |
val drops = Array( | |
Sphere((2.5, 2.5, -8), 0.3), | |
Sphere((1.5, 2.2, -7), 0.25), | |
Sphere((-1.3, 0.8, -8.5), 0.15), | |
Sphere((0.5, -2.5, -7.5), 0.2), | |
Sphere((-1.8, 2.3, -7.5), 0.3), | |
Sphere((-1.8, -2.3, -7.5), 0.3), | |
Sphere((1.3, 0.0, -8), 0.25) | |
).map(_ -> Refractor()) | |
val s = new Scene( | |
objects = Array( | |
Sphere((0, 0, 0), 2) -> Flat((1, 1, 1), specularC = 0.6, lambertC = 0.4), | |
Plane((0, 4, 0), (0, 1, 0)) -> Checked(), | |
Plane((0, -4, 0), (0, 1, 0)) -> Flat((0.9, 1, 1)), | |
Plane((6, 0, 0), (1, 0, 0)) -> Flat((1, 0.9, 1)), | |
Plane((-6, 0, 0), (1, 0, 0)) -> Flat((1, 1, 0.9)), | |
Plane((0, 0, 6), (0, 0, 1)) -> Flat((0.9, 0.9, 1)) | |
) ++ spiral ++ drops, | |
lightPoints = Array( | |
Light((0, -3, 0), (3, 3, 0)), | |
Light((3, 3, 0), (0, 3, 3)), | |
Light((-3, 3, 0), (3, 0, 3)) | |
), | |
position = (0, 0, -15), | |
lookingAt = (0, 0, 0), | |
fieldOfView = 45.0 | |
) | |
val c = new Canvas{ | |
val width = min(canvas.width.toInt, canvas.height.toInt) | |
val height = min(canvas.width.toInt, canvas.height.toInt) | |
val data = renderer.getImageData(0, 0, canvas.width, canvas.height) | |
def save(y: Int): Unit = { | |
renderer.putImageData(data, 0, 0, 0, y-1, width, 1) | |
} | |
def plot(x: Int, y: Int, rgb: ScalaJSExample.Color): Unit = { | |
val index = (y * data.width + x) * 4 | |
data.data(index+0) = (rgb.x * 255).toInt | |
data.data(index+1) = (rgb.y * 255).toInt | |
data.data(index+2) = (rgb.z * 255).toInt | |
data.data(index+3) = 255 | |
} | |
} | |
s.render(c) | |
} | |
} | |
final case class Vec(x: Double, y: Double, z: Double){ | |
def magnitude = sqrt(this dot this) | |
def +(o: Vec) = Vec(x + o.x, y + o.y, z + o.z) | |
def -(o: Vec) = Vec(x - o.x, y - o.y, z - o.z) | |
def *(o: Vec) = Vec(x * o.x, y * o.y, z * o.z) | |
def *(f: Double) = Vec(x * f, y * f, z * f) | |
def /(f: Double) = Vec(x / f, y / f, z / f) | |
def dot(o: Vec) = x * o.x + y * o.y + z * o.z | |
def cross(o: Vec) = Vec( | |
y * o.z - z * o.y, | |
z * o.x - x * o.z, | |
x * o.y - y * o.x | |
) | |
def normalized = this / magnitude | |
def reflectThrough(normal: Vec) = this - normal * (this dot normal) * 2 | |
} | |
object Vec{ | |
case class Unit(x: Double, y: Double, z: Double) | |
implicit def normalizer(v: Vec) = { | |
val l = v.magnitude | |
new Unit(v.x / l, v.y / l, v.z / l) | |
} | |
implicit def denormalizer(v: Vec.Unit) = new Vec(v.x, v.y, v.z) | |
implicit def pointify[X: Numeric, Y: Numeric, Z: Numeric](x: (X, Y, Z)): Vec = Vec( | |
implicitly[Numeric[X]].toDouble(x._1), | |
implicitly[Numeric[Y]].toDouble(x._2), | |
implicitly[Numeric[Z]].toDouble(x._3) | |
) | |
implicit def pointify2[X: Numeric, Y: Numeric, Z: Numeric](x: (X, Y, Z)): Vec.Unit = Vec.normalizer(x) | |
} | |
abstract class Form{ | |
def intersectionTime(ray: Ray): Double | |
def normalAt(p: Vec): Vec | |
} | |
case class Sphere(center: Vec, radius: Double) extends Form{ | |
def intersectionTime(ray: Ray) = { | |
val cp = center - ray.point | |
val v = cp dot ray.vector | |
val d = radius * radius - ((cp dot cp) - v * v) | |
if (d < 0) -1 | |
else v - sqrt(d) | |
} | |
def normalAt(p: Vec) = (p - center).normalized | |
} | |
case class Plane(point: Vec, normal: Vec.Unit) extends Form{ | |
def intersectionTime(ray: Ray) = { | |
val v = ray.vector dot normal | |
if (v != 0) ((point - ray.point) dot normal) / v | |
else -1 | |
} | |
def normalAt(p: Vec) = normal | |
} | |
case class Ray(point: Vec, vector: Vec.Unit){ | |
def pointAtTime(t: Double) = point + vector * t | |
} | |
case class Light(center: Vec, color: Color) | |
abstract class Surface{ | |
def colorAt(scene: Scene, ray: Ray, p: Vec, normal: Vec.Unit, depth: Int): Color | |
} | |
abstract class SolidSurface extends Surface{ | |
def baseColorAt(p: Vec): Color | |
def specularC: Double | |
def lambertC: Double | |
val ambientC = 1.0 - specularC - lambertC | |
def colorAt(scene: Scene, ray: Ray, p: Vec, normal: Vec.Unit, depth: Int): Color = { | |
val b = baseColorAt(p) | |
val specular = { | |
val reflectedRay = Ray(p, ray.vector.reflectThrough(normal)) | |
val reflectedColor = scene.rayColor(reflectedRay, depth) | |
reflectedColor * specularC | |
} | |
val lambert = { | |
var lambertAmount = Vec(0, 0, 0) | |
for (i <- 0 until scene.lightPoints.length optimized) { | |
val light = scene.lightPoints(i) | |
if (scene.lightIsVisible(light.center, p)) { | |
val d = p - light.center | |
val dLength = d.magnitude | |
val contribution = light.color * abs(d dot normal / (dLength * dLength)) | |
lambertAmount += contribution | |
} | |
} | |
b * lambertAmount * lambertC | |
} | |
val ambient = b * ambientC | |
specular + lambert + ambient | |
} | |
} | |
case class Refractor(refractiveIndex: Double = 0.5) extends Surface{ | |
def colorAt(scene: Scene, ray: Ray, p: Vec, normal: Vec.Unit, depth: Int): Color = { | |
val r = if ((normal dot ray.vector) < 0) | |
refractiveIndex | |
else | |
1.0 / refractiveIndex | |
val c = (normal * -1) dot ray.vector | |
val sqrtValue = 1 - r * r * (1 - c * c) | |
if (sqrtValue > 0){ | |
val refracted = ray.vector * r + normal * (r * c - sqrt(sqrtValue)) | |
scene.rayColor(Ray(p, refracted), depth) | |
}else{ | |
val perp = ray.vector dot normal | |
val reflected: Vec = Vec.denormalizer(ray.vector) + normal * 2 * perp | |
scene.rayColor(Ray(p, reflected), depth) | |
} | |
} | |
} | |
case class Flat(baseColor: Color = Color(1, 1, 1), | |
specularC: Double = 0.3, | |
lambertC: Double = 0.6) extends SolidSurface{ | |
def baseColorAt(p: Vec) = baseColor | |
} | |
case class Checked(baseColor: Color = Color(1, 1, 1), | |
specularC: Double = 0.3, | |
lambertC: Double = 0.6, | |
otherColor: Color = (0, 0, 0), | |
checkSize: Double = 1) extends SolidSurface{ | |
override def baseColorAt(p: Vec) = { | |
val v = p * (1.0 / checkSize) | |
def f(x: Double) = (abs(x) + 0.5).toInt | |
if ((f(v.x) + f(v.y) + f(v.z)) % 2 == 1) otherColor | |
else baseColor | |
} | |
} | |
abstract class Canvas{ | |
def width: Int | |
def height: Int | |
def save(y: Int): Unit | |
def plot(x: Int, y: Int, rgb: Color) | |
} | |
class Scene(objects: Array[(Form, Surface)], | |
val lightPoints: Array[Light], | |
position: Vec, | |
lookingAt: Vec, | |
fieldOfView: Double){ | |
def lightIsVisible(l: Vec, p: Vec) = { | |
val ray = Ray(p, l - p) | |
val length = (l - p).magnitude | |
var visible = true | |
for (i <- 0 until objects.length optimized){ | |
val (o, s) = objects(i) | |
val t = o.intersectionTime(ray) | |
if (t > Epsilon && t < length - Epsilon){ | |
visible = false | |
} | |
} | |
visible | |
} | |
def rayColor(ray: Ray, depth: Int): Color = { | |
if (depth > 3) (0, 0, 0) | |
else{ | |
var (minT, minO, minS) = (-1.0, null: Form, null: Surface) | |
for(i <- 0 until objects.length optimized){ | |
val (o, s) = objects(i) | |
val t = o.intersectionTime(ray) | |
if (t > Epsilon && (t < minT || minT < 0)){ | |
minT = t | |
minO = o | |
minS = s | |
} | |
} | |
minT match{ | |
case -1 => (0, 0, 0) | |
case t => | |
val p = ray.pointAtTime(minT) | |
minS.colorAt(this, ray, p, minO.normalAt(p), depth + 1) | |
} | |
} | |
} | |
def render(canvas: Canvas) = async{ | |
val fovRadians = Pi * (fieldOfView / 2.0) / 180.0 | |
val halfWidth = tan(fovRadians) | |
val halfHeight = halfWidth | |
val width = halfWidth * 2 | |
val height= halfHeight * 2 | |
val pixelWidth = width / (canvas.width - 1) | |
val pixelHeight = height / (canvas.height - 1) | |
val eye = Ray(position, lookingAt - position) | |
val vpRight = eye.vector.cross((0, 1, 0)).normalized | |
val vpUp = vpRight.cross(eye.vector).normalized | |
for(y <- 0 until canvas.height optimized){ | |
await(Future()) | |
canvas.save(y) | |
for (x <- 0 until canvas.width optimized){ | |
val xcomp = vpRight * (x * pixelWidth - halfWidth) | |
val ycomp = vpUp * (y * pixelHeight - halfHeight) | |
val ray = Ray(eye.point, xcomp + ycomp + eye.vector) | |
val color = rayColor(ray, 0) | |
canvas.plot(x, y, color) | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import org.scalajs.dom | |
import concurrent._ | |
import async.Async._ | |
import scalajs.concurrent.JSExecutionContext.Implicits.queue | |
/** | |
* Re-implementation of the mouse-drag example from Deprecating the Observer | |
* Pattern | |
* | |
* http://lampwww.epfl.ch/~imaier/pub/DeprecatingObserversTR2010.pdf | |
* | |
* Using Scala.Async instead of Scala.React and continuations. Click and drag | |
* around the canvas on the right to see pretty shapes appear. | |
*/ | |
@JSExport | |
object ScalaJSExample{ | |
@JSExport | |
def main(args: Array[String]): Unit = { | |
val command = Channel[dom.MouseEvent]() | |
canvas.onmousemove = command.update _ | |
canvas.onmouseup = command.update _ | |
canvas.onmousedown = command.update _ | |
renderer.lineWidth = 5 | |
renderer.strokeStyle = "red" | |
renderer.fillStyle = "cyan" | |
val flow = async{ | |
while(true){ | |
renderer.beginPath() | |
val start = await(command.filter(_.`type` == "mousedown")()) | |
renderer.moveTo(start.clientX - canvas.width, start.clientY) | |
var m = await(command()) | |
while(m.`type` == "mousemove"){ | |
renderer.lineTo(m.clientX - canvas.width, m.clientY) | |
renderer.stroke() | |
m = await(command()) | |
} | |
renderer.fill() | |
} | |
} | |
} | |
} | |
case class Channel[T](){ | |
private[this] var value: Promise[T] = null | |
def apply(): Future[T] = { | |
value = Promise[T]() | |
value.future | |
} | |
def update(t: T) = { | |
if (value != null && !value.isCompleted) value.success(t) | |
} | |
def filter(p: T => Boolean) = { | |
val filtered = Channel[T]() | |
async{ | |
while(true){ | |
val t = await(this()) | |
if (p(t)) filtered() = t | |
} | |
} | |
filtered | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import org.scalajs.dom | |
case class Pt(x: Double, y: Double) | |
@JSExport | |
object ScalaJSExample{ | |
println("Hello!!") | |
val corners = Seq( | |
Pt(canvas.width/2, 0), | |
Pt(0, canvas.height), | |
Pt(canvas.width, canvas.height) | |
) | |
var p = corners(0) | |
val (w, h) = (canvas.height.toDouble, canvas.height.toDouble) | |
@JSExport | |
def main(args: Array[String]): Unit = { | |
dom.setInterval(() => for(_ <- 0 until 10){ | |
val c = corners(util.Random.nextInt(3)) | |
p = Pt((p.x + c.x) / 2, (p.y + c.y) / 2) | |
val m = (p.y / h) | |
val r = 255 - (p.x / w * m * 255).toInt | |
val g = 255 - ((w-p.x) / w * m * 255).toInt | |
val b = 255 - ((h - p.y) / h * 255).toInt | |
renderer.fillStyle = s"rgb($r, $g, $b)" | |
renderer.fillRect(p.x, p.y, 1, 1) | |
}, 10) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import org.scalajs.dom | |
@JSExport | |
object ScalaJSExample{ | |
@JSExport | |
def main(args: Array[String]): Unit = { | |
var i = 0 | |
dom.setInterval(() => for(_ <- 0 until 5){ | |
i += 1 | |
val x = i % canvas.width | |
val y = Math.sin(i / canvas.width * 10) * canvas.height / 4 + canvas.height / 2 | |
output.clear() | |
val r = math.abs(((y / canvas.height - 0.5) * 255 * 4).toInt) | |
println(x) | |
println(y) | |
renderer.fillStyle = s"rgb($r, 255, 255)" | |
renderer.fillRect(x, y, 2, 2) | |
}, 10) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import org.scalajs.dom | |
case class Point(x: Double, y: Double){ | |
def +(p: Point) = Point(x + p.x, y + p.y) | |
def -(p: Point) = Point(x - p.x, y - p.y) | |
def /(d: Double) = Point(x / d, y / d) | |
def *(d: Double) = Point(x * d, y * d) | |
def length = Math.sqrt(x * x + y * y) | |
} | |
@JSExport | |
object ScalaJSExample { | |
var count = 0 | |
var player = Point(dom.innerWidth / 2, dom.innerHeight / 2) | |
val corners = Seq(Point(255, 255), Point(0, 255), Point(128, 0)) | |
var bullets = Seq.empty[Point] | |
var enemies = Seq.empty[Point] | |
var wave = 1 | |
def run = { | |
count += 1 | |
bullets = bullets.map( | |
p => Point(p.x, p.y - 5) | |
) | |
if (enemies.isEmpty){ | |
enemies = for{ | |
x <- (0 until canvas.width.toInt by 50) | |
y <- 0 until wave | |
} yield { | |
Point(x, 50 + y * 50) | |
} | |
wave += 1 | |
} | |
enemies = enemies.filter( e => | |
!bullets.exists(b => | |
(e - b).length < 5 | |
) | |
) | |
enemies = enemies.map{ e => | |
val i = count % 200 | |
if (i < 50) e.copy(x = e.x - 0.2) | |
else if (i < 100) e.copy(y = e.y + 0.2) | |
else if (i < 150) e.copy(x = e.x + 0.2) | |
else e.copy(y = e.y + 0.2) | |
} | |
if (keysDown(38)) player += Point(0, -2) | |
if (keysDown(37)) player += Point(-2, 0) | |
if (keysDown(39)) player += Point(2, 0) | |
if (keysDown(40)) player += Point(0, 2) | |
} | |
def draw = { | |
renderer.clearRect(0, 0, canvas.width, canvas.height) | |
renderer.fillStyle = "white" | |
renderer.fillRect(player.x - 5, player.y - 5, 10, 10) | |
renderer.fillStyle = "yellow" | |
for (enemy <- enemies){ | |
renderer.fillRect(enemy.x - 5, enemy.y - 5, 10, 10) | |
} | |
renderer.fillStyle = "red" | |
for (bullet <- bullets){ | |
renderer.fillRect(bullet.x - 2, bullet.y - 2, 4, 4) | |
} | |
} | |
val keysDown = collection.mutable.Set.empty[Int] | |
@JSExport | |
def main(args: Array[String]): Unit = { | |
dom.onkeypress = {(e: dom.KeyboardEvent) => | |
if (e.keyCode.toInt == 32) bullets = player +: bullets | |
} | |
dom.onkeydown = {(e: dom.KeyboardEvent) => | |
keysDown.add(e.keyCode.toInt) | |
} | |
dom.onkeyup = {(e: dom.KeyboardEvent) => | |
keysDown.remove(e.keyCode.toInt) | |
} | |
dom.setInterval(() => {run; draw}, 20) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* A simple square root solver, one step at a time | |
*/ | |
import org.scalajs.dom | |
import scala.scalajs.js | |
@JSExport | |
object ScalaJSExample{ | |
@JSExport | |
def main(args: Array[String]): Unit = { | |
val n = 1000.0 | |
var guess = 1.0 | |
lazy val id: js.Number = dom.setInterval(() => { | |
val newGuess = guess - (guess * guess - n) / (2 * guess) | |
if (newGuess == guess) dom.clearInterval(id) | |
else guess = newGuess | |
println(guess) | |
}, 1000) | |
id | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* A simple Scala.Rx dataflow graph which looks like: | |
* | |
* guess -> error | |
* ^ | | |
* | v | |
* --------o | |
* | |
* Where the changes propagate around and around until | |
* the magnitude of `error` drops below `epsilon` | |
* | |
*/ | |
@JSExport | |
object ScalaJSExample{ | |
@JSExport | |
def main(args: Array[String]): Unit = { | |
import rx._ | |
val n = 10.0 | |
val guess = Var(1.0) | |
val epsilon = 0.0000000001 | |
val error = Rx{ guess() * guess() - n } | |
val o = Obs(error){ | |
println("Guess: " + guess()) | |
if (math.abs(error()) > epsilon) { | |
guess() = guess() - error() / (2 * guess()) | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Port of the Scala.js TodoMVC example application | |
* | |
* http://lihaoyi.github.io/workbench-example-app/todo.html | |
* | |
* To Scala.jsFiddle. Mostly involved sticking the two source files into one | |
* (since Scala.jsFiddle doesn't support multiple files) and inlining/tweaking | |
* the CSS so it looks right | |
*/ | |
import scala.collection.mutable | |
import scalatags.{HtmlTag, Attr, Modifier} | |
import scala.util.Random | |
import scalatags.all._ | |
import scalatags.HtmlTag | |
import rx._ | |
import rx.core.{Propagator, Obs} | |
import org.scalajs.dom | |
import org.scalajs.dom.DOMParser | |
import scala.scalajs.js | |
import scalatags.HtmlTag | |
/** | |
* A minimal binding between Scala.Rx and Scalatags and Scala-Js-Dom | |
*/ | |
object Framework { | |
/** | |
* Wraps reactive strings in spans, so they can be referenced/replaced | |
* when the Rx changes. | |
*/ | |
implicit def RxStr(r: Rx[String]): Modifier = { | |
new RxMod(Rx(span(r()))) | |
} | |
/** | |
* Sticks an ID to a HtmlTag if it doesnt already have one, so we can refer | |
* to it later. | |
*/ | |
class DomRef[T](r0: HtmlTag) extends Modifier{ | |
val elemId = r0.attrs.getOrElse("id", ""+Random.nextInt()) | |
val r = r0(id := elemId) | |
def transform(tag: HtmlTag): HtmlTag = { | |
tag(r) | |
} | |
} | |
implicit def derefDomRef[T](d: DomRef[T]) = { | |
dom.document.getElementById(d.elemId).asInstanceOf[T] | |
} | |
/** | |
* Sticks some Rx into a Scalatags fragment, which means hooking up an Obs | |
* to propapgate changes into the DOM via the element's ID. Monkey-patches | |
* the Obs onto the element itself so we have a reference to kill it when | |
* the element leaves the DOM (e.g. it gets deleted). | |
*/ | |
implicit class RxMod(r: Rx[HtmlTag]) extends Modifier{ | |
val elemId = r.now.attrs.getOrElse("id", ""+Random.nextInt()) | |
lazy val obs: Obs = Obs(r, skipInitial = true){ | |
dom.console.log("Obs fire!", elemId) | |
val target = dom.document.getElementById(elemId) | |
val element = dom.document.createElement("div") | |
element.innerHTML = r.now( | |
id := elemId | |
).toString() | |
if (target != null){ | |
target.parentElement.replaceChild(element.children(0), target) | |
}else{ | |
obs.kill() | |
r.kill() | |
} | |
} | |
dom.setTimeout(() => { | |
target.asInstanceOf[js.Dynamic].obs = obs.asInstanceOf[js.Dynamic] | |
}, 10) | |
def transform(tag: HtmlTag): HtmlTag = { | |
tag( | |
r.now( | |
id := elemId | |
) | |
) | |
} | |
} | |
/** | |
* Lets you stick Scala callbacks onto onclick and other onXXXX | |
* attributes using Scala callbacks, monkey-patching the callback | |
* object onto the element itself to avoid it getting prematurely | |
* garbage-collected. | |
*/ | |
implicit class Transformable(a: Attr){ | |
class CallbackModifier(a: Attr, func: () => Unit) extends Modifier{ | |
def transform(tag: HtmlTag): HtmlTag = { | |
val elemId = tag.attrs.getOrElse("id", ""+Random.nextInt()) | |
val funcName = a.name + "Func" | |
dom.setTimeout(() => { | |
val target = dom.document | |
.getElementById(elemId) | |
.asInstanceOf[js.Dynamic] | |
if (target != null) | |
target.updateDynamic(funcName)(func: js.Function0[Unit]) | |
}, 10) | |
tag(id:=elemId, a:=s"this.$funcName(); return false;") | |
} | |
} | |
def <~ (func: => Unit) = new CallbackModifier(a, () => func) | |
} | |
} | |
import org.scalajs.dom | |
import scalatags.all._ | |
import scalatags.Tags2.section | |
import scalatags.ExtendedString | |
import rx._ | |
import rx.core.Propagator | |
case class Task(txt: Var[String], done: Var[Boolean]) | |
@JSExport | |
object ScalaJSExample { | |
import Framework._ | |
val editing = Var[Option[Task]](None) | |
val tasks = Var( | |
Seq( | |
Task(Var("TodoMVC Task A"), Var(true)), | |
Task(Var("TodoMVC Task B"), Var(false)), | |
Task(Var("TodoMVC Task C"), Var(false)) | |
) | |
) | |
val filters: Map[String, Task => Boolean] = Map( | |
("All", t => true), | |
("Active", !_.done()), | |
("Completed", _.done()) | |
) | |
val filter = Var("All") | |
val inputBox = new DomRef[dom.HTMLInputElement](input( | |
id:="new-todo", | |
placeholder:="What needs to be done?", | |
autofocus:=true | |
)) | |
@JSExport | |
def main(args: Array[String]): Unit = { | |
renderln(div( | |
scalatags.Tags2.style(inlineCss), | |
section(id:="todoapp", color:="black")( | |
header(id:="header")( | |
h1("todos"), | |
form( | |
inputBox, | |
onsubmit <~ { | |
tasks() = Task(Var(inputBox.value), Var(false)) +: tasks() | |
inputBox.value = "" | |
} | |
) | |
), | |
section(id:="main")( | |
input( | |
id:="toggle-all", | |
`type`:="checkbox", | |
cursor:="pointer", | |
onclick <~ { | |
val target = tasks().exists(_.done() == false) | |
Var.set(tasks().map(_.done -> target): _*) | |
} | |
), | |
label(`for`:="toggle-all", "Mark all as complete"), | |
Rx{ | |
dom.console.log("A") | |
ul(id:="todo-list")( | |
for(task <- tasks() if filters(filter())(task)) yield { | |
dom.console.log("B", task.txt()) | |
val inputRef = new DomRef[dom.HTMLInputElement]( | |
input(`class`:="edit", value:=task.txt()) | |
) | |
li(if(task.done()) `class`:="completed" else (), if(editing() == Some(task)) `class`:="editing" else ())( | |
div(`class`:="view")( | |
"ondblclick".attr <~ {editing() = Some(task)}, | |
input( | |
`class`:="toggle", | |
`type`:="checkbox", | |
cursor:="pointer", | |
onchange <~ {task.done() = !task.done()}, | |
if(task.done()) checked:=true else () | |
), | |
label(task.txt()), | |
button( | |
`class`:="destroy", | |
cursor:="pointer", | |
onclick <~ (tasks() = tasks().filter(_ != task)) | |
) | |
), | |
form( | |
onsubmit <~ { | |
task.txt() = inputRef.value | |
editing() = None | |
}, | |
inputRef | |
) | |
) | |
} | |
) | |
}, | |
footer(id:="footer")( | |
span(id:="todo-count")(strong(Rx(tasks().count(!_.done()).toString)), " item left"), | |
Rx{ | |
ul(id:="filters")( | |
for ((name, pred) <- filters.toSeq) yield { | |
li(a( | |
if(name == filter()) `class`:="selected" else (), | |
name, | |
href:="#", | |
onclick <~ (filter() = name) | |
)) | |
} | |
) | |
}, | |
button( | |
id:="clear-completed", | |
onclick <~ {tasks() = tasks().filter(!_.done())}, | |
"Clear completed (", Rx(tasks().count(_.done()).toString), ")" | |
) | |
) | |
), | |
footer(id:="info")( | |
p("Double-click to edit a todo"), | |
p(a(href:="https://github.com/lihaoyi/workbench-example-app/blob/todomvc/src/main/scala/example/ScalaJSExample.scala")("Source Code")), | |
p("Created by ", a(href:="http://github.com/lihaoyi")("Li Haoyi")) | |
) | |
) | |
)) | |
} | |
def inlineCss = """ | |
html, | |
body { | |
margin: 0; | |
padding: 0; | |
} | |
button { | |
margin: 0; | |
padding: 0; | |
border: 0; | |
background: none; | |
font-size: 100%; | |
vertical-align: baseline; | |
font-family: inherit; | |
color: inherit; | |
-webkit-appearance: none; | |
-ms-appearance: none; | |
-o-appearance: none; | |
appearance: none; | |
} | |
body{ | |
background: url('https://raw.github.com/lihaoyi/workbench-example-app/todomvc/src/main/resources/css/bg.png'); | |
} | |
#sandbox { | |
font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif; | |
line-height: 1.4em; | |
background: url('https://raw.github.com/lihaoyi/workbench-example-app/todomvc/src/main/resources/css/bg.png'); | |
color: #4d4d4d; | |
width: 550px; | |
margin: 0 auto; | |
-webkit-font-smoothing: antialiased; | |
-moz-font-smoothing: antialiased; | |
-ms-font-smoothing: antialiased; | |
-o-font-smoothing: antialiased; | |
font-smoothing: antialiased; | |
} | |
button, | |
input[type="checkbox"] { | |
outline: none; | |
} | |
#todoapp { | |
background: #fff; | |
background: rgba(255, 255, 255, 0.9); | |
margin: 130px 0 40px 0; | |
border: 1px solid #ccc; | |
position: relative; | |
border-top-left-radius: 2px; | |
border-top-right-radius: 2px; | |
box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.2), | |
0 25px 50px 0 rgba(0, 0, 0, 0.15); | |
} | |
#todoapp:before { | |
content: ''; | |
border-left: 1px solid #f5d6d6; | |
border-right: 1px solid #f5d6d6; | |
width: 2px; | |
position: absolute; | |
top: 0; | |
left: 40px; | |
height: 100%; | |
} | |
#todoapp input::-webkit-input-placeholder { | |
font-style: italic; | |
} | |
#todoapp input::-moz-placeholder { | |
font-style: italic; | |
color: #a9a9a9; | |
} | |
#todoapp h1 { | |
position: absolute; | |
top: -120px; | |
width: 100%; | |
font-size: 70px; | |
font-weight: bold; | |
text-align: center; | |
color: #b3b3b3; | |
color: rgba(255, 255, 255, 0.3); | |
text-shadow: -1px -1px rgba(0, 0, 0, 0.2); | |
-webkit-text-rendering: optimizeLegibility; | |
-moz-text-rendering: optimizeLegibility; | |
-ms-text-rendering: optimizeLegibility; | |
-o-text-rendering: optimizeLegibility; | |
text-rendering: optimizeLegibility; | |
} | |
#header { | |
padding-top: 15px; | |
border-radius: inherit; | |
} | |
#header:before { | |
content: ''; | |
position: absolute; | |
top: 0; | |
right: 0; | |
left: 0; | |
height: 15px; | |
z-index: 2; | |
border-bottom: 1px solid #6c615c; | |
background: #8d7d77; | |
background: -webkit-gradient(linear, left top, left bottom, from(rgba(132, 110, 100, 0.8)),to(rgba(101, 84, 76, 0.8))); | |
background: -webkit-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8)); | |
background: linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8)); | |
filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr='#9d8b83', EndColorStr='#847670'); | |
border-top-left-radius: 1px; | |
border-top-right-radius: 1px; | |
} | |
#new-todo, | |
.edit { | |
position: relative; | |
margin: 0; | |
width: 100%; | |
font-size: 24px; | |
font-family: inherit; | |
line-height: 1.4em; | |
border: 0; | |
outline: none; | |
color: inherit; | |
padding: 6px; | |
border: 1px solid #999; | |
box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2); | |
-moz-box-sizing: border-box; | |
-ms-box-sizing: border-box; | |
-o-box-sizing: border-box; | |
box-sizing: border-box; | |
-webkit-font-smoothing: antialiased; | |
-moz-font-smoothing: antialiased; | |
-ms-font-smoothing: antialiased; | |
-o-font-smoothing: antialiased; | |
font-smoothing: antialiased; | |
} | |
#new-todo { | |
padding: 16px 16px 16px 60px; | |
border: none; | |
background: rgba(0, 0, 0, 0.02); | |
z-index: 100; | |
box-shadow: none; | |
} | |
#main { | |
position: relative; | |
z-index: 2; | |
border-top: 1px dotted #adadad; | |
} | |
label[for='toggle-all'] { | |
display: none; | |
} | |
#toggle-all { | |
position: absolute; | |
top: -42px; | |
left: -4px; | |
width: 40px; | |
text-align: center; | |
/* Mobile Safari */ | |
border: none; | |
} | |
#toggle-all:before { | |
content: '»'; | |
font-size: 28px; | |
color: #d9d9d9; | |
padding: 0 25px 7px; | |
} | |
#toggle-all:checked:before { | |
color: #737373; | |
} | |
#todo-list { | |
margin: 0; | |
padding: 0; | |
list-style: none; | |
} | |
#todo-list li { | |
position: relative; | |
font-size: 24px; | |
border-bottom: 1px dotted #ccc; | |
} | |
#todo-list li:last-child { | |
border-bottom: none; | |
} | |
#todo-list li.editing { | |
border-bottom: none; | |
padding: 0; | |
} | |
#todo-list li.editing .edit { | |
display: block; | |
width: 506px; | |
padding: 13px 17px 12px 17px; | |
margin: 0 0 0 43px; | |
} | |
#todo-list li.editing .view { | |
display: none; | |
} | |
#todo-list li .toggle { | |
text-align: center; | |
width: 40px; | |
/* auto, since non-WebKit browsers doesn't support input styling */ | |
height: auto; | |
position: absolute; | |
top: 0; | |
bottom: 0; | |
margin: auto 0; | |
/* Mobile Safari */ | |
border: none; | |
-webkit-appearance: none; | |
-ms-appearance: none; | |
-o-appearance: none; | |
appearance: none; | |
} | |
#todo-list li .toggle:after { | |
content: '✔'; | |
/* 40 + a couple of pixels visual adjustment */ | |
line-height: 43px; | |
font-size: 20px; | |
color: #d9d9d9; | |
text-shadow: 0 -1px 0 #bfbfbf; | |
} | |
#todo-list li .toggle:checked:after { | |
color: #85ada7; | |
text-shadow: 0 1px 0 #669991; | |
bottom: 1px; | |
position: relative; | |
} | |
#todo-list li label { | |
white-space: pre; | |
word-break: break-word; | |
padding: 15px 60px 15px 15px; | |
margin-left: 45px; | |
display: block; | |
line-height: 1.2; | |
-webkit-transition: color 0.4s; | |
transition: color 0.4s; | |
} | |
#todo-list li.completed label { | |
color: #a9a9a9; | |
text-decoration: line-through; | |
} | |
#todo-list li .destroy { | |
display: none; | |
position: absolute; | |
top: 0; | |
right: 10px; | |
bottom: 0; | |
width: 40px; | |
height: 40px; | |
margin: auto 0; | |
font-size: 22px; | |
color: #a88a8a; | |
-webkit-transition: all 0.2s; | |
transition: all 0.2s; | |
} | |
#todo-list li .destroy:hover { | |
text-shadow: 0 0 1px #000, | |
0 0 10px rgba(199, 107, 107, 0.8); | |
-webkit-transform: scale(1.3); | |
-ms-transform: scale(1.3); | |
transform: scale(1.3); | |
} | |
#todo-list li .destroy:after { | |
content: '✖'; | |
} | |
#todo-list li:hover .destroy { | |
display: block; | |
} | |
#todo-list li .edit { | |
display: none; | |
} | |
#todo-list li.editing:last-child { | |
margin-bottom: -1px; | |
} | |
#footer { | |
color: #777; | |
padding: 0 15px; | |
position: absolute; | |
right: 0; | |
bottom: -31px; | |
left: 0; | |
height: 20px; | |
text-align: center; | |
} | |
#footer:before { | |
content: ''; | |
position: absolute; | |
right: 0; | |
bottom: 31px; | |
left: 0; | |
height: 50px; | |
z-index: -1; | |
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.3), | |
0 6px 0 -3px rgba(255, 255, 255, 0.8), | |
0 7px 1px -3px rgba(0, 0, 0, 0.3), | |
0 43px 0 -6px rgba(255, 255, 255, 0.8), | |
0 44px 2px -6px rgba(0, 0, 0, 0.2); | |
} | |
#todo-count { | |
float: left; | |
text-align: left; | |
} | |
#filters { | |
margin: 0; | |
padding: 0; | |
list-style: none; | |
position: absolute; | |
right: 0; | |
left: 0; | |
} | |
#filters li { | |
display: inline; | |
} | |
#filters li a { | |
color: #83756f; | |
margin: 2px; | |
text-decoration: none; | |
} | |
#filters li a.selected { | |
font-weight: bold; | |
} | |
#clear-completed { | |
float: right; | |
position: relative; | |
line-height: 20px; | |
text-decoration: none; | |
background: rgba(0, 0, 0, 0.1); | |
font-size: 11px; | |
padding: 0 10px; | |
border-radius: 3px; | |
box-shadow: 0 -1px 0 0 rgba(0, 0, 0, 0.2); | |
} | |
#clear-completed:hover { | |
background: rgba(0, 0, 0, 0.15); | |
box-shadow: 0 -1px 0 0 rgba(0, 0, 0, 0.3); | |
} | |
#info { | |
margin: 65px auto 0; | |
color: #a6a6a6; | |
font-size: 12px; | |
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.7); | |
text-align: center; | |
} | |
#info a { | |
color: inherit; | |
} | |
/* | |
Hack to remove background from Mobile Safari. | |
Can't use it globally since it destroys checkboxes in Firefox and Opera | |
*/ | |
@media screen and (-webkit-min-device-pixel-ratio:0) { | |
#toggle-all, | |
#todo-list li .toggle { | |
background: none; | |
} | |
#todo-list li .toggle { | |
height: 40px; | |
} | |
#toggle-all { | |
top: -56px; | |
left: -15px; | |
width: 65px; | |
height: 41px; | |
-webkit-transform: rotate(90deg); | |
-ms-transform: rotate(90deg); | |
transform: rotate(90deg); | |
-webkit-appearance: none; | |
appearance: none; | |
} | |
} | |
.hidden { | |
display: none; | |
} | |
hr { | |
margin: 20px 0; | |
border: 0; | |
border-top: 1px dashed #C5C5C5; | |
border-bottom: 1px dashed #F7F7F7; | |
} | |
.learn a { | |
font-weight: normal; | |
text-decoration: none; | |
color: #b83f45; | |
} | |
.learn a:hover { | |
text-decoration: underline; | |
color: #787e7e; | |
} | |
.learn h3, | |
.learn h4, | |
.learn h5 { | |
margin: 10px 0; | |
font-weight: 500; | |
line-height: 1.2; | |
color: #000; | |
} | |
.learn h3 { | |
font-size: 24px; | |
} | |
.learn h4 { | |
font-size: 18px; | |
} | |
.learn h5 { | |
margin-bottom: 0; | |
font-size: 14px; | |
} | |
.learn ul { | |
padding: 0; | |
margin: 0 0 30px 25px; | |
} | |
.learn li { | |
line-height: 20px; | |
} | |
.learn p { | |
font-size: 15px; | |
font-weight: 300; | |
line-height: 1.3; | |
margin-top: 0; | |
margin-bottom: 0; | |
} | |
.quote { | |
border: none; | |
margin: 20px 0 60px 0; | |
} | |
.quote p { | |
font-style: italic; | |
} | |
.quote p:before { | |
content: '“'; | |
font-size: 50px; | |
opacity: .15; | |
position: absolute; | |
top: -20px; | |
left: 3px; | |
} | |
.quote p:after { | |
content: '”'; | |
font-size: 50px; | |
opacity: .15; | |
position: absolute; | |
bottom: -42px; | |
right: 3px; | |
} | |
.quote footer { | |
position: absolute; | |
bottom: -40px; | |
right: 0; | |
} | |
.quote footer img { | |
border-radius: 3px; | |
} | |
.quote footer a { | |
margin-left: 5px; | |
vertical-align: middle; | |
} | |
.speech-bubble { | |
position: relative; | |
padding: 10px; | |
background: rgba(0, 0, 0, .04); | |
border-radius: 5px; | |
} | |
.speech-bubble:after { | |
content: ''; | |
position: absolute; | |
top: 100%; | |
right: 30px; | |
border: 13px solid transparent; | |
border-top-color: rgba(0, 0, 0, .04); | |
} | |
.learn-bar > .learn { | |
position: absolute; | |
width: 272px; | |
top: 8px; | |
left: -300px; | |
padding: 10px; | |
border-radius: 5px; | |
background-color: rgba(255, 255, 255, .6); | |
-webkit-transition-property: left; | |
transition-property: left; | |
-webkit-transition-duration: 500ms; | |
transition-duration: 500ms; | |
} | |
@media (min-width: 899px) { | |
.learn-bar { | |
width: auto; | |
margin: 0 0 0 300px; | |
} | |
.learn-bar > .learn { | |
left: 8px; | |
} | |
.learn-bar #todoapp { | |
width: 550px; | |
margin: 130px auto 40px auto; | |
} | |
} | |
""" | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import org.scalajs.dom | |
case class Pt(x: Double, y: Double) | |
@JSExport | |
object ScalaJSExample{ | |
import scalatags.all.{a, href, h1} | |
println( | |
"An implementation of ", a(href:="http://en.wikipedia.org/wiki/Turmites")("Turmites"), | |
", in particular ", a(href:="http://en.wikipedia.org/wiki/Langton's_ant")("Langton's Ant"), | |
", on the HTML canvas. Click anywhere to create more turmites." | |
) | |
val directions = Seq( | |
Pt(0, 1), | |
Pt(1, 0), | |
Pt(0, -1), | |
Pt(-1, 0) | |
) | |
val (w, h) = (canvas.width, canvas.height) | |
var turmites = Seq( | |
Pt(w / 2, h / 2) -> 0 | |
) | |
@JSExport | |
def main(args: Array[String]): Unit = { | |
dom.setInterval(() => for(_ <- 0 until 10){ | |
turmites = for((oldP, facing) <- turmites) yield { | |
val d = directions(facing) | |
val p = Pt((oldP.x + d.x + w) % w, (oldP.y + d.y + h) % h) | |
val data = renderer.getImageData(p.x, p.y, 1, 1).data | |
val newFacing = if (data(0).toInt == 0){ | |
val m = (p.y / h) | |
val r = 255 - (p.x / w * m * 255).toInt | |
val g = 255 - ((w - p.x) / w * m * 255).toInt | |
val b = 255 - ((h - p.y) / h * 255).toInt | |
renderer.fillStyle = s"rgb($r, $g, $b)" | |
(facing + 3) % 4 | |
}else{ | |
renderer.fillStyle = "black" | |
(facing + 1) % 4 | |
} | |
renderer.fillRect(p.x, p.y, 1, 1) | |
p -> newFacing | |
} | |
}, 20) | |
canvas.onclick = { (e: dom.MouseEvent) => | |
val rect = canvas.getBoundingClientRect() | |
val newP = Pt(e.clientX.toInt - rect.left, e.clientY.toInt - rect.top) | |
turmites = turmites :+ (newP -> util.Random.nextInt(4)) | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment