├── testcommitfile.txt
├── gowasm
├── Makefile
├── Dockerfile
├── input.go
├── main.go
└── wasm_exec.js
├── Makefile
├── .gitignore
├── images
├── favicon.ico
├── screenshot.png
└── favicon.svg
├── test
├── jasmine
│ ├── jasmine_favicon.png
│ ├── console.js
│ ├── boot.js
│ └── jasmine-html.js
├── index.html
└── performance.html
├── font-awesome-4.1-1.0
└── fonts
│ ├── FontAwesome.otf
│ ├── fontawesome-webfont.eot
│ ├── fontawesome-webfont.ttf
│ └── fontawesome-webfont.woff
├── autopublish.sh
├── cmd
└── server
│ └── server.go
├── fitnessworker.js
├── README.md
├── LICENSE.txt
├── floor.js
├── libs
├── highlight
│ ├── styles
│ │ ├── solarized_dark.css
│ │ ├── solarized_light.css
│ │ └── default.css
│ └── highlight.pack.js
├── riot.js
├── unobservable.js
├── codemirror
│ ├── themes
│ │ └── solarized.css
│ ├── mode
│ │ └── go
│ │ │ └── go.js
│ ├── addon
│ │ └── edit
│ │ │ └── closebrackets.js
│ └── codemirror.css
└── spark-md5.min.js
├── gowasmbuilder.js
├── user.js
├── base.js
├── fitness.js
├── interfaces.js
├── api
└── server.go
├── movable.js
├── challenges.js
├── presenters.js
├── style.css
├── index.html
├── app.js
├── elevator.js
├── world.js
└── documentation.html
/testcommitfile.txt:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/gowasm/Makefile:
--------------------------------------------------------------------------------
1 | docker-build:
2 | docker build . -t didil/gowasmbuilder
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | run-api:
2 | DOCKER_API_VERSION=1.39.0 go run cmd/server/server.go
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 |
3 | .vscode
4 |
5 | gowasm/app.wasm
6 |
7 | nginx.conf
--------------------------------------------------------------------------------
/images/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/didil/gowasm-elevatorsaga/HEAD/images/favicon.ico
--------------------------------------------------------------------------------
/images/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/didil/gowasm-elevatorsaga/HEAD/images/screenshot.png
--------------------------------------------------------------------------------
/test/jasmine/jasmine_favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/didil/gowasm-elevatorsaga/HEAD/test/jasmine/jasmine_favicon.png
--------------------------------------------------------------------------------
/font-awesome-4.1-1.0/fonts/FontAwesome.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/didil/gowasm-elevatorsaga/HEAD/font-awesome-4.1-1.0/fonts/FontAwesome.otf
--------------------------------------------------------------------------------
/autopublish.sh:
--------------------------------------------------------------------------------
1 | git checkout gh-pages;
2 | git pull --rebase origin gh-pages;
3 | git merge master;
4 | git push origin gh-pages;
5 | git checkout master;
--------------------------------------------------------------------------------
/font-awesome-4.1-1.0/fonts/fontawesome-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/didil/gowasm-elevatorsaga/HEAD/font-awesome-4.1-1.0/fonts/fontawesome-webfont.eot
--------------------------------------------------------------------------------
/font-awesome-4.1-1.0/fonts/fontawesome-webfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/didil/gowasm-elevatorsaga/HEAD/font-awesome-4.1-1.0/fonts/fontawesome-webfont.ttf
--------------------------------------------------------------------------------
/font-awesome-4.1-1.0/fonts/fontawesome-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/didil/gowasm-elevatorsaga/HEAD/font-awesome-4.1-1.0/fonts/fontawesome-webfont.woff
--------------------------------------------------------------------------------
/gowasm/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM golang:1.11.5-alpine
2 |
3 | WORKDIR /app/
4 |
5 | ENV GOARCH wasm
6 | ENV GOOS js
7 |
8 | CMD ["go", "build", "-o", "app.wasm" , "."]
--------------------------------------------------------------------------------
/cmd/server/server.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 |
6 | "github.com/didil/gowasm-elevatorsaga/api"
7 | )
8 |
9 | func main() {
10 | log.Fatal(api.Start())
11 | }
12 |
--------------------------------------------------------------------------------
/fitnessworker.js:
--------------------------------------------------------------------------------
1 | importScripts("libs/lodash.min.js", "libs/riot.js", "libs/unobservable.js")
2 | importScripts("base.js", "movable.js", "floor.js", "user.js", "elevator.js", "interfaces.js", "world.js", "fitness.js");
3 |
4 |
5 | onmessage = function(msg) {
6 | // Assume it is a code object that should be fitness-tested
7 | var codeStr = msg.data;
8 | var results = doFitnessSuite(codeStr, 6);
9 | console.log("Posting message back", results);
10 | postMessage(results);
11 | };
--------------------------------------------------------------------------------
/gowasm/input.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "syscall/js"
5 | )
6 |
7 | // Init corresponds to the init function from http://play.elevatorsaga.com/documentation.html#docs
8 | func Init(args []js.Value) {
9 | elevators := args[0]
10 | // floors := args[1]
11 |
12 | elevator := elevators.Index(0) // Let's use the first elevator
13 |
14 | // Whenever the elevator is idle (has no more queued destinations) ...
15 | idleCb := js.NewCallback(func(args []js.Value) {
16 | elevator.Call("goToFloor", 0)
17 | elevator.Call("goToFloor", 1)
18 | })
19 |
20 | // Attach callback
21 | elevator.Call("on", "idle", idleCb)
22 | }
23 |
24 | // Update corresponds to the update function from http://play.elevatorsaga.com/documentation.html#docs
25 | func Update(args []js.Value) {
26 | // We normally don't need to do anything here
27 | }
28 |
--------------------------------------------------------------------------------
/gowasm/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "syscall/js"
5 | )
6 |
7 | func main() {
8 | // exit channel
9 | ch := make(chan struct{})
10 |
11 | js.Global().Get("console").Call("log", "Starting Wasm module ...")
12 |
13 | // init game callback
14 | init := js.NewCallback(Init)
15 | defer init.Release()
16 | // update game callback
17 | update := js.NewCallback(Update)
18 | defer update.Release()
19 |
20 | // exit callback
21 | exitWasm := js.NewCallback(func(args []js.Value) {
22 | ch <- struct{}{}
23 | })
24 | defer exitWasm.Release()
25 |
26 | // create js objects
27 | c := make(map[string]interface{})
28 | c["init"] = init
29 | c["update"] = update
30 | js.Global().Get("GoWasmBuilder").Set("codeObj", c)
31 | js.Global().Get("GoWasmBuilder").Set("exitWasm", exitWasm)
32 |
33 | // wait for exit signal
34 | <-ch
35 | js.Global().Get("console").Call("log", "Exiting Wasm module ...")
36 | }
37 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | GO WASM Elevator Saga
2 | ===================
3 | The [elevator programming game](https://github.com/magwo/elevatorsaga), modified to accept GO WASM input:
4 | - The user submits his go code
5 | - A POST request is sent to a REST API endpoint with the user code + a hash
6 | - The reverse proxy checks the cache, in case of a cache HIT the next step is skipped
7 | - The Go API server builds the wasm binary in a docker container
8 | - The wasm binary is returned to the browser and loaded
9 | - Frontend JS calls the wasm code and runs the solution
10 |
11 | Intro Article: [Practice your Go WebAssembly with a Game](https://medium.com/@didil/practice-your-go-webassembly-with-a-game-7195dabbfc44)
12 |
13 | **Only tested on Chrome v71+ !**
14 |
15 | TODO:
16 | - try out tinygo
17 |
18 | [Play it now!](https://didil.github.io/gowasm-elevatorsaga/)
19 |
20 | 
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2019 Adil H
4 |
5 | Copyright (c) 2015 Magnus Wolffelt
6 |
7 | Permission is hereby granted, free of charge, to any person obtaining a copy
8 | of this software and associated documentation files (the "Software"), to deal
9 | in the Software without restriction, including without limitation the rights
10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | copies of the Software, and to permit persons to whom the Software is
12 | furnished to do so, subject to the following conditions:
13 |
14 | The above copyright notice and this permission notice shall be included in all
15 | copies or substantial portions of the Software.
16 |
17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23 | SOFTWARE.
24 |
--------------------------------------------------------------------------------
/test/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Elevator Saga tests
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/floor.js:
--------------------------------------------------------------------------------
1 |
2 | var asFloor = function(obj, floorLevel, yPosition, errorHandler) {
3 | var floor = riot.observable(obj);
4 |
5 | floor.level = floorLevel;
6 | floor.yPosition = yPosition;
7 | floor.buttonStates = {up: "", down: ""};
8 |
9 | // TODO: Ideally the floor should have a facade where tryTrigger is done
10 | var tryTrigger = function(event, arg1, arg2, arg3, arg4) {
11 | try {
12 | floor.trigger(event, arg1, arg2, arg3, arg4);
13 | } catch(e) { errorHandler(e); }
14 | };
15 |
16 | floor.pressUpButton = function() {
17 | var prev = floor.buttonStates.up;
18 | floor.buttonStates.up = "activated";
19 | if(prev !== floor.buttonStates.up) {
20 | tryTrigger("buttonstate_change", floor.buttonStates);
21 | tryTrigger("up_button_pressed", floor);
22 | }
23 | };
24 |
25 | floor.pressDownButton = function() {
26 | var prev = floor.buttonStates.down;
27 | floor.buttonStates.down = "activated";
28 | if(prev !== floor.buttonStates.down) {
29 | tryTrigger("buttonstate_change", floor.buttonStates);
30 | tryTrigger("down_button_pressed", floor);
31 | }
32 | };
33 |
34 | floor.elevatorAvailable = function(elevator) {
35 | if(elevator.goingUpIndicator && floor.buttonStates.up) {
36 | floor.buttonStates.up = "";
37 | tryTrigger("buttonstate_change", floor.buttonStates);
38 | }
39 | if(elevator.goingDownIndicator && floor.buttonStates.down) {
40 | floor.buttonStates.down = "";
41 | tryTrigger("buttonstate_change", floor.buttonStates);
42 | }
43 | };
44 |
45 | floor.getSpawnPosY = function() {
46 | return floor.yPosition + 30;
47 | };
48 |
49 | floor.floorNum = function() {
50 | return floor.level;
51 | };
52 |
53 | return floor;
54 | };
55 |
--------------------------------------------------------------------------------
/libs/highlight/styles/solarized_dark.css:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | Orginal Style from ethanschoonover.com/solarized (c) Jeremy Hull
4 |
5 | */
6 |
7 | .hljs {
8 | display: block;
9 | overflow-x: auto;
10 | padding: 0.5em;
11 | background: #002b36;
12 | color: #839496;
13 | -webkit-text-size-adjust: none;
14 | }
15 |
16 | .hljs-comment,
17 | .hljs-template_comment,
18 | .diff .hljs-header,
19 | .hljs-doctype,
20 | .hljs-pi,
21 | .lisp .hljs-string,
22 | .hljs-javadoc {
23 | color: #586e75;
24 | }
25 |
26 | /* Solarized Green */
27 | .hljs-keyword,
28 | .hljs-winutils,
29 | .method,
30 | .hljs-addition,
31 | .css .hljs-tag,
32 | .hljs-request,
33 | .hljs-status,
34 | .nginx .hljs-title {
35 | color: #859900;
36 | }
37 |
38 | /* Solarized Cyan */
39 | .hljs-number,
40 | .hljs-command,
41 | .hljs-string,
42 | .hljs-tag .hljs-value,
43 | .hljs-rules .hljs-value,
44 | .hljs-phpdoc,
45 | .hljs-dartdoc,
46 | .tex .hljs-formula,
47 | .hljs-regexp,
48 | .hljs-hexcolor,
49 | .hljs-link_url {
50 | color: #2aa198;
51 | }
52 |
53 | /* Solarized Blue */
54 | .hljs-title,
55 | .hljs-localvars,
56 | .hljs-chunk,
57 | .hljs-decorator,
58 | .hljs-built_in,
59 | .hljs-identifier,
60 | .vhdl .hljs-literal,
61 | .hljs-id,
62 | .css .hljs-function {
63 | color: #268bd2;
64 | }
65 |
66 | /* Solarized Yellow */
67 | .hljs-attribute,
68 | .hljs-variable,
69 | .lisp .hljs-body,
70 | .smalltalk .hljs-number,
71 | .hljs-constant,
72 | .hljs-class .hljs-title,
73 | .hljs-parent,
74 | .hljs-type,
75 | .hljs-link_reference {
76 | color: #b58900;
77 | }
78 |
79 | /* Solarized Orange */
80 | .hljs-preprocessor,
81 | .hljs-preprocessor .hljs-keyword,
82 | .hljs-pragma,
83 | .hljs-shebang,
84 | .hljs-symbol,
85 | .hljs-symbol .hljs-string,
86 | .diff .hljs-change,
87 | .hljs-special,
88 | .hljs-attr_selector,
89 | .hljs-subst,
90 | .hljs-cdata,
91 | .css .hljs-pseudo,
92 | .hljs-header {
93 | color: #cb4b16;
94 | }
95 |
96 | /* Solarized Red */
97 | .hljs-deletion,
98 | .hljs-important {
99 | color: #dc322f;
100 | }
101 |
102 | /* Solarized Violet */
103 | .hljs-link_label {
104 | color: #6c71c4;
105 | }
106 |
107 | .tex .hljs-formula {
108 | background: #073642;
109 | }
110 |
--------------------------------------------------------------------------------
/libs/highlight/styles/solarized_light.css:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | Orginal Style from ethanschoonover.com/solarized (c) Jeremy Hull
4 |
5 | */
6 |
7 | .hljs {
8 | display: block;
9 | overflow-x: auto;
10 | padding: 0.5em;
11 | background: #fdf6e3;
12 | color: #657b83;
13 | -webkit-text-size-adjust: none;
14 | }
15 |
16 | .hljs-comment,
17 | .hljs-template_comment,
18 | .diff .hljs-header,
19 | .hljs-doctype,
20 | .hljs-pi,
21 | .lisp .hljs-string,
22 | .hljs-javadoc {
23 | color: #93a1a1;
24 | }
25 |
26 | /* Solarized Green */
27 | .hljs-keyword,
28 | .hljs-winutils,
29 | .method,
30 | .hljs-addition,
31 | .css .hljs-tag,
32 | .hljs-request,
33 | .hljs-status,
34 | .nginx .hljs-title {
35 | color: #859900;
36 | }
37 |
38 | /* Solarized Cyan */
39 | .hljs-number,
40 | .hljs-command,
41 | .hljs-string,
42 | .hljs-tag .hljs-value,
43 | .hljs-rules .hljs-value,
44 | .hljs-phpdoc,
45 | .hljs-dartdoc,
46 | .tex .hljs-formula,
47 | .hljs-regexp,
48 | .hljs-hexcolor,
49 | .hljs-link_url {
50 | color: #2aa198;
51 | }
52 |
53 | /* Solarized Blue */
54 | .hljs-title,
55 | .hljs-localvars,
56 | .hljs-chunk,
57 | .hljs-decorator,
58 | .hljs-built_in,
59 | .hljs-identifier,
60 | .vhdl .hljs-literal,
61 | .hljs-id,
62 | .css .hljs-function {
63 | color: #268bd2;
64 | }
65 |
66 | /* Solarized Yellow */
67 | .hljs-attribute,
68 | .hljs-variable,
69 | .lisp .hljs-body,
70 | .smalltalk .hljs-number,
71 | .hljs-constant,
72 | .hljs-class .hljs-title,
73 | .hljs-parent,
74 | .hljs-type,
75 | .hljs-link_reference {
76 | color: #b58900;
77 | }
78 |
79 | /* Solarized Orange */
80 | .hljs-preprocessor,
81 | .hljs-preprocessor .hljs-keyword,
82 | .hljs-pragma,
83 | .hljs-shebang,
84 | .hljs-symbol,
85 | .hljs-symbol .hljs-string,
86 | .diff .hljs-change,
87 | .hljs-special,
88 | .hljs-attr_selector,
89 | .hljs-subst,
90 | .hljs-cdata,
91 | .css .hljs-pseudo,
92 | .hljs-header {
93 | color: #cb4b16;
94 | }
95 |
96 | /* Solarized Red */
97 | .hljs-deletion,
98 | .hljs-important {
99 | color: #dc322f;
100 | }
101 |
102 | /* Solarized Violet */
103 | .hljs-link_label {
104 | color: #6c71c4;
105 | }
106 |
107 | .tex .hljs-formula {
108 | background: #eee8d5;
109 | }
110 |
--------------------------------------------------------------------------------
/gowasmbuilder.js:
--------------------------------------------------------------------------------
1 | window.GoWasmBuilder = {
2 | exitWasm: null,
3 | codeObj: null,
4 | mod: null,
5 | inst: null,
6 | apiRoot: null,
7 | async init(bytes) {
8 | // load the go module
9 | GoWasmBuilder.go = new Go();
10 |
11 | let result = await WebAssembly.instantiate(bytes, GoWasmBuilder.go.importObject);
12 | GoWasmBuilder.mod = result.module;
13 | GoWasmBuilder.inst = result.instance;
14 | },
15 | run() {
16 | // run the go module
17 | GoWasmBuilder.go.run(GoWasmBuilder.inst)
18 | },
19 | async getCodeObjFromCode(code) {
20 | // build json input
21 | let json = JSON.stringify({ compiler: "go1.11.5", input: code, })
22 | // hash json input
23 | let hash = SparkMD5.hash(json);
24 | // perform POST request
25 | let resp = await fetch(GoWasmBuilder.apiRoot + "/api/v1/compile", {
26 | method: 'POST',
27 | headers: {
28 | 'Accept': 'application/json, application/xml, text/plain, text/html, *.*',
29 | 'Accept-Encoding': 'gzip',
30 | 'Content-Type': 'application/json',
31 | 'Code-Hash': hash,
32 | },
33 | body: json
34 | })
35 |
36 | if (!resp.ok){
37 | let text = await resp.text()
38 | throw new Error(text)
39 | }
40 |
41 | let bytes = await resp.arrayBuffer()
42 |
43 | if (GoWasmBuilder.exitWasm) {
44 | GoWasmBuilder.exitWasm()
45 | GoWasmBuilder.exitWasm = null
46 | }
47 |
48 | // init module and run
49 | await GoWasmBuilder.init(bytes)
50 | GoWasmBuilder.run()
51 |
52 | // get result object
53 | let codeObj = GoWasmBuilder.codeObj
54 | if (typeof codeObj.init !== "function") {
55 | throw new Error("Code must contain an init function");
56 | }
57 | if (typeof codeObj.update !== "function") {
58 | throw new Error("Code must contain an update function");
59 | }
60 |
61 | return codeObj
62 | }
63 | }
64 |
65 | if (window.location.href.includes("localhost")){
66 | GoWasmBuilder.apiRoot = "http://localhost:3000";
67 | }
68 | else {
69 | GoWasmBuilder.apiRoot = "https://gowasm-elevatorsaga.leclouddev.com";
70 | }
--------------------------------------------------------------------------------
/user.js:
--------------------------------------------------------------------------------
1 | function User(weight) {
2 | newGuard(this, User);
3 | Movable.call(this);
4 | var user = this;
5 | user.weight = weight;
6 | user.currentFloor = 0;
7 | user.destinationFloor = 0;
8 | user.done = false;
9 | user.removeMe = false;
10 | };
11 | User.prototype = Object.create(Movable.prototype);
12 |
13 |
14 | User.prototype.appearOnFloor = function(floor, destinationFloorNum) {
15 | var floorPosY = floor.getSpawnPosY();
16 | this.currentFloor = floor.level;
17 | this.destinationFloor = destinationFloorNum;
18 | this.moveTo(null, floorPosY);
19 | this.pressFloorButton(floor);
20 | };
21 |
22 | User.prototype.pressFloorButton = function(floor) {
23 | if(this.destinationFloor < this.currentFloor) {
24 | floor.pressDownButton();
25 | } else {
26 | floor.pressUpButton();
27 | }
28 | };
29 |
30 | User.prototype.handleExit = function(floorNum, elevator) {
31 | if(elevator.currentFloor === this.destinationFloor) {
32 | elevator.userExiting(this);
33 | this.currentFloor = elevator.currentFloor;
34 | this.setParent(null);
35 | var destination = this.x + 100;
36 | this.done = true;
37 | this.trigger("exited_elevator", elevator);
38 | this.trigger("new_state");
39 | this.trigger("new_display_state");
40 | var self = this;
41 | this.moveToOverTime(destination, null, 1 + Math.random()*0.5, linearInterpolate, function lastMove() {
42 | self.removeMe = true;
43 | self.trigger("removed");
44 | self.off("*");
45 | });
46 |
47 | elevator.off("exit_available", this.exitAvailableHandler);
48 | }
49 | };
50 |
51 | User.prototype.elevatorAvailable = function(elevator, floor) {
52 | if(this.done || this.parent !== null || this.isBusy()) {
53 | return;
54 | }
55 |
56 | if(!elevator.isSuitableForTravelBetween(this.currentFloor, this.destinationFloor)) {
57 | // Not suitable for travel - don't use this elevator
58 | return;
59 | }
60 |
61 | var pos = elevator.userEntering(this);
62 | if(pos) {
63 | // Success
64 | this.setParent(elevator);
65 | this.trigger("entered_elevator", elevator);
66 | var self = this;
67 | this.moveToOverTime(pos[0], pos[1], 1, undefined, function() {
68 | elevator.pressFloorButton(self.destinationFloor);
69 | });
70 | this.exitAvailableHandler = function (floorNum, elevator) { self.handleExit(elevator.currentFloor, elevator); };
71 | elevator.on("exit_available", this.exitAvailableHandler);
72 | } else {
73 | this.pressFloorButton(floor);
74 | }
75 | };
76 |
--------------------------------------------------------------------------------
/base.js:
--------------------------------------------------------------------------------
1 | // Console shim
2 | (function () {
3 | var f = function () { };
4 | if (!console) {
5 | console = {
6 | log: f, info: f, warn: f, debug: f, error: f
7 | };
8 | }
9 | }());
10 |
11 | var limitNumber = function (num, min, max) {
12 | return Math.min(max, Math.max(num, min));
13 | };
14 |
15 | var epsilonEquals = function (a, b) {
16 | return Math.abs(a - b) < 0.00000001;
17 | };
18 |
19 | // Polyfill from MDN
20 | var sign = function (x) {
21 | x = +x; // convert to a number
22 | if (x === 0 || isNaN(x)) {
23 | return x;
24 | }
25 | return x > 0 ? 1 : -1;
26 | };
27 | if (typeof Math.sign === "undefined") {
28 | Math.sign = sign;
29 | }
30 |
31 | var deprecationWarning = function (name) {
32 | console.warn("You are using a deprecated feature scheduled for removal: " + name);
33 | };
34 |
35 | var newGuard = function (obj, type) {
36 | if (!(obj instanceof type)) { throw "Incorrect instantiation, got " + typeof obj + " but expected " + type; }
37 | }
38 |
39 | var createBoolPassthroughFunction = function (owner, obj, objPropertyName) {
40 | return function (val) {
41 | if (typeof val !== "undefined") {
42 | obj[objPropertyName] = val ? true : false;
43 | obj.trigger("change:" + objPropertyName, obj[objPropertyName]);
44 | return owner;
45 | } else {
46 | return obj[objPropertyName];
47 | }
48 | };
49 | };
50 |
51 | distanceNeededToAchieveSpeed = function (currentSpeed, targetSpeed, acceleration) {
52 | // v² = u² + 2a * d
53 | var requiredDistance = (Math.pow(targetSpeed, 2) - Math.pow(currentSpeed, 2)) / (2 * acceleration);
54 | return requiredDistance;
55 | };
56 | accelerationNeededToAchieveChangeDistance = function (currentSpeed, targetSpeed, distance) {
57 | // v² = u² + 2a * d
58 | var requiredAcceleration = 0.5 * ((Math.pow(targetSpeed, 2) - Math.pow(currentSpeed, 2)) / distance);
59 | return requiredAcceleration;
60 | };
61 |
62 | // Fake frame requester helper used for testing and fitness simulations
63 | var createFrameRequester = function (timeStep) {
64 | var currentCb = null;
65 | var requester = {};
66 | requester.currentT = 0.0;
67 | requester.register = function (cb) { currentCb = cb; };
68 | requester.trigger = function () { requester.currentT += timeStep; if (currentCb !== null) { currentCb(requester.currentT); } };
69 | return requester;
70 | };
71 |
72 | var getCodeObjFromCode = function (code) {
73 | if (code.trim().substr(0, 1) == "{" && code.trim().substr(-1, 1) == "}") {
74 | code = "(" + code + ")";
75 | }
76 | /* jslint evil:true */
77 | obj = eval(code);
78 | /* jshint evil:false */
79 | if (typeof obj.init !== "function") {
80 | throw "Code must contain an init function";
81 | }
82 | if (typeof obj.update !== "function") {
83 | throw "Code must contain an update function";
84 | }
85 | return obj;
86 | }
87 |
88 |
--------------------------------------------------------------------------------
/images/favicon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/libs/highlight/styles/default.css:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | Original style from softwaremaniacs.org (c) Ivan Sagalaev
4 |
5 | */
6 |
7 | .hljs {
8 | display: block;
9 | overflow-x: auto;
10 | padding: 0.5em;
11 | background: #f0f0f0;
12 | -webkit-text-size-adjust: none;
13 | }
14 |
15 | .hljs,
16 | .hljs-subst,
17 | .hljs-tag .hljs-title,
18 | .nginx .hljs-title {
19 | color: black;
20 | }
21 |
22 | .hljs-string,
23 | .hljs-title,
24 | .hljs-constant,
25 | .hljs-parent,
26 | .hljs-tag .hljs-value,
27 | .hljs-rules .hljs-value,
28 | .hljs-preprocessor,
29 | .hljs-pragma,
30 | .haml .hljs-symbol,
31 | .ruby .hljs-symbol,
32 | .ruby .hljs-symbol .hljs-string,
33 | .hljs-template_tag,
34 | .django .hljs-variable,
35 | .smalltalk .hljs-class,
36 | .hljs-addition,
37 | .hljs-flow,
38 | .hljs-stream,
39 | .bash .hljs-variable,
40 | .apache .hljs-tag,
41 | .apache .hljs-cbracket,
42 | .tex .hljs-command,
43 | .tex .hljs-special,
44 | .erlang_repl .hljs-function_or_atom,
45 | .asciidoc .hljs-header,
46 | .markdown .hljs-header,
47 | .coffeescript .hljs-attribute {
48 | color: #800;
49 | }
50 |
51 | .smartquote,
52 | .hljs-comment,
53 | .hljs-annotation,
54 | .hljs-template_comment,
55 | .diff .hljs-header,
56 | .hljs-chunk,
57 | .asciidoc .hljs-blockquote,
58 | .markdown .hljs-blockquote {
59 | color: #888;
60 | }
61 |
62 | .hljs-number,
63 | .hljs-date,
64 | .hljs-regexp,
65 | .hljs-literal,
66 | .hljs-hexcolor,
67 | .smalltalk .hljs-symbol,
68 | .smalltalk .hljs-char,
69 | .go .hljs-constant,
70 | .hljs-change,
71 | .lasso .hljs-variable,
72 | .makefile .hljs-variable,
73 | .asciidoc .hljs-bullet,
74 | .markdown .hljs-bullet,
75 | .asciidoc .hljs-link_url,
76 | .markdown .hljs-link_url {
77 | color: #080;
78 | }
79 |
80 | .hljs-label,
81 | .hljs-javadoc,
82 | .ruby .hljs-string,
83 | .hljs-decorator,
84 | .hljs-filter .hljs-argument,
85 | .hljs-localvars,
86 | .hljs-array,
87 | .hljs-attr_selector,
88 | .hljs-important,
89 | .hljs-pseudo,
90 | .hljs-pi,
91 | .haml .hljs-bullet,
92 | .hljs-doctype,
93 | .hljs-deletion,
94 | .hljs-envvar,
95 | .hljs-shebang,
96 | .apache .hljs-sqbracket,
97 | .nginx .hljs-built_in,
98 | .tex .hljs-formula,
99 | .erlang_repl .hljs-reserved,
100 | .hljs-prompt,
101 | .asciidoc .hljs-link_label,
102 | .markdown .hljs-link_label,
103 | .vhdl .hljs-attribute,
104 | .clojure .hljs-attribute,
105 | .asciidoc .hljs-attribute,
106 | .lasso .hljs-attribute,
107 | .coffeescript .hljs-property,
108 | .hljs-phony {
109 | color: #88f;
110 | }
111 |
112 | .hljs-keyword,
113 | .hljs-id,
114 | .hljs-title,
115 | .hljs-built_in,
116 | .css .hljs-tag,
117 | .hljs-javadoctag,
118 | .hljs-phpdoc,
119 | .hljs-dartdoc,
120 | .hljs-yardoctag,
121 | .smalltalk .hljs-class,
122 | .hljs-winutils,
123 | .bash .hljs-variable,
124 | .apache .hljs-tag,
125 | .hljs-type,
126 | .hljs-typename,
127 | .tex .hljs-command,
128 | .asciidoc .hljs-strong,
129 | .markdown .hljs-strong,
130 | .hljs-request,
131 | .hljs-status {
132 | font-weight: bold;
133 | }
134 |
135 | .asciidoc .hljs-emphasis,
136 | .markdown .hljs-emphasis {
137 | font-style: italic;
138 | }
139 |
140 | .nginx .hljs-built_in {
141 | font-weight: normal;
142 | }
143 |
144 | .coffeescript .javascript,
145 | .javascript .xml,
146 | .lasso .markup,
147 | .tex .hljs-formula,
148 | .xml .javascript,
149 | .xml .vbscript,
150 | .xml .css,
151 | .xml .hljs-cdata {
152 | opacity: 0.5;
153 | }
154 |
--------------------------------------------------------------------------------
/test/performance.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Elevator Saga performance tests
5 |
6 |
7 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | Movable
29 |
30 | Elevator
31 |
32 | User
33 |
34 | Floor
35 |
36 |
37 |
38 |
88 |
89 |
--------------------------------------------------------------------------------
/libs/riot.js:
--------------------------------------------------------------------------------
1 | /* Riot 1.0.2, @license MIT, (c) 2014 Muut Inc + contributors */
2 | (function(riot) { "use strict";
3 |
4 | riot.observable = function(el) {
5 | var callbacks = {}, slice = [].slice;
6 |
7 | el.on = function(events, fn) {
8 | if (typeof fn === "function") {
9 | events.replace(/[^\s]+/g, function(name, pos) {
10 | (callbacks[name] = callbacks[name] || []).push(fn);
11 | fn.typed = pos > 0;
12 | });
13 | }
14 | return el;
15 | };
16 |
17 | el.off = function(events, fn) {
18 | if (events === "*") callbacks = {};
19 | else if (fn) {
20 | var arr = callbacks[events];
21 | for (var i = 0, cb; (cb = arr && arr[i]); ++i) {
22 | if (cb === fn) arr.splice(i, 1);
23 | }
24 | } else {
25 | events.replace(/[^\s]+/g, function(name) {
26 | callbacks[name] = [];
27 | });
28 | }
29 | return el;
30 | };
31 |
32 | // only single event supported
33 | el.one = function(name, fn) {
34 | if (fn) fn.one = true;
35 | return el.on(name, fn);
36 | };
37 |
38 | el.trigger = function(name) {
39 | var args = slice.call(arguments, 1),
40 | fns = callbacks[name] || [];
41 |
42 | for (var i = 0, fn; (fn = fns[i]); ++i) {
43 | if (!fn.busy) {
44 | fn.busy = true;
45 | fn.apply(el, fn.typed ? [name].concat(args) : args);
46 | if (fn.one) { fns.splice(i, 1); i--; }
47 | else if(fns[i] && fns[i] !== fn) { i-- } // Makes self-removal possible during iteration
48 | fn.busy = false;
49 | }
50 | }
51 |
52 | return el;
53 | };
54 |
55 | return el;
56 | };
57 |
58 | var FN = {}, // Precompiled templates (JavaScript functions)
59 | template_escape = {"\\": "\\\\", "\n": "\\n", "\r": "\\r", "'": "\\'"},
60 | render_escape = {'&': '&', '"': '"', '<': '<', '>': '>'};
61 |
62 | function default_escape_fn(str, key) {
63 | return str == null ? '' : (str+'').replace(/[&\"<>]/g, function(char) {
64 | return render_escape[char];
65 | });
66 | }
67 |
68 | riot.render = function(tmpl, data, escape_fn) {
69 | if (escape_fn === true) escape_fn = default_escape_fn;
70 | tmpl = tmpl || '';
71 |
72 | return (FN[tmpl] = FN[tmpl] || new Function("_", "e", "return '" +
73 | tmpl.replace(/[\\\n\r']/g, function(char) {
74 | return template_escape[char];
75 | }).replace(/{\s*([\w\.]+)\s*}/g, "' + (e?e(_.$1,'$1'):_.$1||(_.$1==null?'':_.$1)) + '") + "'")
76 | )(data, escape_fn);
77 | };
78 | /* Cross browser popstate */
79 | (function () {
80 | // for browsers only
81 | if (typeof window === "undefined") return;
82 |
83 | var currentHash,
84 | pops = riot.observable({}),
85 | listen = window.addEventListener,
86 | doc = document;
87 |
88 | function pop(hash) {
89 | hash = hash.type ? location.hash : hash;
90 | if (hash !== currentHash) pops.trigger("pop", hash);
91 | currentHash = hash;
92 | }
93 |
94 | /* Always fire pop event upon page load (normalize behaviour across browsers) */
95 |
96 | // standard browsers
97 | if (listen) {
98 | listen("popstate", pop, false);
99 | doc.addEventListener("DOMContentLoaded", pop, false);
100 |
101 | // IE
102 | } else {
103 | doc.attachEvent("onreadystatechange", function() {
104 | if (doc.readyState === "complete") pop("");
105 | });
106 | }
107 |
108 | /* Change the browser URL or listen to changes on the URL */
109 | riot.route = function(to) {
110 | // listen
111 | if (typeof to === "function") return pops.on("pop", to);
112 |
113 | // fire
114 | if (history.pushState) history.pushState(0, 0, to);
115 | pop(to);
116 |
117 | };
118 | })();
119 | })(typeof window !== "undefined" ? window.riot = {} : (typeof exports !== "undefined" ? exports : self.riot = {}));
120 |
--------------------------------------------------------------------------------
/fitness.js:
--------------------------------------------------------------------------------
1 |
2 | var requireNothing = function() {
3 | return {
4 | description: "No requirement",
5 | evaluate: function() { return null; }
6 | };
7 | };
8 |
9 | var fitnessChallenges = [
10 | {options: {description: "Small scenario", floorCount: 4, elevatorCount: 2, spawnRate: 0.6}, condition: requireNothing()}
11 | ,{options: {description: "Medium scenario", floorCount: 6, elevatorCount: 3, spawnRate: 1.5, elevatorCapacities: [5]}, condition: requireNothing()}
12 | ,{options: {description: "Large scenario", floorCount: 18, elevatorCount: 6, spawnRate: 1.9, elevatorCapacities: [8]}, condition: requireNothing()}
13 | ]
14 |
15 | // Simulation without visualisation
16 | function calculateFitness(challenge, codeObj, stepSize, stepsToSimulate) {
17 | var controller = createWorldController(stepSize);
18 | var result = {};
19 |
20 | var worldCreator = createWorldCreator();
21 | var world = worldCreator.createWorld(challenge.options);
22 | var frameRequester = createFrameRequester(stepSize);
23 |
24 | controller.on("usercode_error", function(e) {
25 | result.error = e;
26 | });
27 | world.on("stats_changed", function() {
28 | result.transportedPerSec = world.transportedPerSec;
29 | result.avgWaitTime = world.avgWaitTime;
30 | result.transportedCount = world.transportedCounter;
31 | });
32 |
33 | controller.start(world, codeObj, frameRequester.register, true);
34 |
35 | for(var stepCount=0; stepCount < stepsToSimulate && !controller.isPaused; stepCount++) {
36 | frameRequester.trigger();
37 | }
38 | return result;
39 | };
40 |
41 |
42 | function makeAverageResult(results) {
43 | var averagedResult = {};
44 | _.forOwn(results[0].result, function(value, resultProperty) {
45 | var sum = _.sum(_.pluck(_.pluck(results, "result"), resultProperty));
46 | averagedResult[resultProperty] = sum / results.length;
47 |
48 | });
49 | return { options: results[0].options, result: averagedResult };
50 | };
51 |
52 |
53 | function doFitnessSuite(codeStr, runCount) {
54 | try {
55 | var codeObj = getCodeObjFromCode(codeStr);
56 | } catch(e) {
57 | return {error: "" + e};
58 | }
59 | console.log("Fitness testing code", codeObj);
60 | var error = null;
61 |
62 | var testruns = [];
63 | _.times(runCount, function() {
64 | var results = _.map(fitnessChallenges, function(challenge) {
65 | var fitness = calculateFitness(challenge, codeObj, 1000.0/60.0, 12000);
66 | if(fitness.error) { error = fitness.error; return };
67 | return { options: challenge.options, result: fitness }
68 | });
69 | if(error) { return; }
70 | testruns.push(results);
71 | });
72 | if(error) {
73 | return { error: "" + error }
74 | }
75 |
76 | // Now do averaging over all properties for each challenge's test runs
77 | var averagedResults = _.map(_.range(testruns[0].length), function(n) { return makeAverageResult(_.pluck(testruns, n)) });
78 |
79 | return averagedResults;
80 | }
81 |
82 | function fitnessSuite(codeStr, preferWorker, callback) {
83 | if(!!Worker && preferWorker) {
84 | // Web workers are available, neat.
85 | try {
86 | var w = new Worker("fitnessworker.js");
87 | w.postMessage(codeStr);
88 | w.onmessage = function(msg) {
89 | console.log("Got message from fitness worker", msg);
90 | var results = msg.data;
91 | callback(results);
92 | };
93 | return;
94 | } catch(e) {
95 | console.log("Fitness worker creation failed, falling back to normal", e);
96 | }
97 | }
98 | // Fall back do synch calculation without web worker
99 | var results = doFitnessSuite(codeStr, 2);
100 | callback(results);
101 | };
--------------------------------------------------------------------------------
/interfaces.js:
--------------------------------------------------------------------------------
1 |
2 | // Interface that hides actual elevator object behind a more robust facade,
3 | // while also exposing relevant events, and providing some helper queue
4 | // functions that allow programming without async logic.
5 | var asElevatorInterface = function(obj, elevator, floorCount, errorHandler) {
6 | var elevatorInterface = riot.observable(obj);
7 |
8 | elevatorInterface.destinationQueue = [];
9 |
10 | var tryTrigger = function(event, arg1, arg2, arg3, arg4) {
11 | try {
12 | elevatorInterface.trigger(event, arg1, arg2, arg3, arg4);
13 | } catch(e) { errorHandler(e); }
14 | };
15 |
16 | elevatorInterface.checkDestinationQueue = function() {
17 | if(!elevator.isBusy()) {
18 | if(elevatorInterface.destinationQueue.length) {
19 | elevator.goToFloor(_.first(elevatorInterface.destinationQueue));
20 | } else {
21 | tryTrigger("idle");
22 | }
23 | }
24 | };
25 |
26 | // TODO: Write tests for this queueing logic
27 | elevatorInterface.goToFloor = function(floorNum, forceNow) {
28 | floorNum = limitNumber(Number(floorNum), 0, floorCount - 1);
29 | // Auto-prevent immediately duplicate destinations
30 | if(elevatorInterface.destinationQueue.length) {
31 | var adjacentElement = forceNow ? _.first(elevatorInterface.destinationQueue) : _.last(elevatorInterface.destinationQueue);
32 | if(epsilonEquals(floorNum, adjacentElement)) {
33 | return;
34 | }
35 | }
36 | elevatorInterface.destinationQueue[(forceNow ? "unshift" : "push")](floorNum);
37 | elevatorInterface.checkDestinationQueue();
38 | };
39 |
40 | elevatorInterface.stop = function() {
41 | elevatorInterface.destinationQueue = [];
42 | if(!elevator.isBusy()) {
43 | elevator.goToFloor(elevator.getExactFutureFloorIfStopped());
44 | }
45 | };
46 |
47 | elevatorInterface.getFirstPressedFloor = function() { return elevator.getFirstPressedFloor(); }; // Undocumented and deprecated, will be removed
48 | elevatorInterface.getPressedFloors = function() { return elevator.getPressedFloors(); };
49 | elevatorInterface.currentFloor = function() { return elevator.currentFloor; };
50 | elevatorInterface.maxPassengerCount = function() { return elevator.maxUsers; };
51 | elevatorInterface.loadFactor = function() { return elevator.getLoadFactor(); };
52 | elevatorInterface.destinationDirection = function() {
53 | if(elevator.destinationY === elevator.y) { return "stopped"; }
54 | return elevator.destinationY > elevator.y ? "down" : "up";
55 | }
56 | elevatorInterface.goingUpIndicator = createBoolPassthroughFunction(elevatorInterface, elevator, "goingUpIndicator");
57 | elevatorInterface.goingDownIndicator = createBoolPassthroughFunction(elevatorInterface, elevator, "goingDownIndicator");
58 |
59 | elevator.on("stopped", function(position) {
60 | if(elevatorInterface.destinationQueue.length && epsilonEquals(_.first(elevatorInterface.destinationQueue), position)) {
61 | // Reached the destination, so remove element at front of queue
62 | elevatorInterface.destinationQueue = _.rest(elevatorInterface.destinationQueue);
63 | if(elevator.isOnAFloor()) {
64 | elevator.wait(1, function() {
65 | elevatorInterface.checkDestinationQueue();
66 | });
67 | } else {
68 | elevatorInterface.checkDestinationQueue();
69 | }
70 | }
71 | });
72 |
73 | elevator.on("passing_floor", function(floorNum, direction) {
74 | tryTrigger("passing_floor", floorNum, direction);
75 | });
76 |
77 | elevator.on("stopped_at_floor", function(floorNum) {
78 | tryTrigger("stopped_at_floor", floorNum);
79 | });
80 | elevator.on("floor_button_pressed", function(floorNum) {
81 | tryTrigger("floor_button_pressed", floorNum);
82 | });
83 |
84 | return elevatorInterface;
85 | };
86 |
--------------------------------------------------------------------------------
/api/server.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import (
4 | "context"
5 | "encoding/json"
6 | "fmt"
7 | "io"
8 | "io/ioutil"
9 | "net/http"
10 | "os"
11 | "path/filepath"
12 |
13 | "github.com/docker/docker/api/types"
14 | "github.com/docker/docker/api/types/container"
15 | "github.com/docker/docker/api/types/mount"
16 | "github.com/docker/docker/client"
17 | "github.com/rs/cors"
18 | )
19 |
20 | // Start the api server
21 | func Start() error {
22 | mux := http.NewServeMux()
23 | mux.HandleFunc("/api/v1/compile", handleCompile)
24 |
25 | // cors
26 | c := cors.New(cors.Options{
27 | AllowedOrigins: []string{"*"},
28 | AllowCredentials: true,
29 | })
30 | router := c.Handler(mux)
31 |
32 | fmt.Println("Listening on port 3000")
33 | return http.ListenAndServe(":3000", router)
34 | }
35 |
36 | type compileRequest struct {
37 | Compiler string `json:"compiler,omitempty"`
38 | Input string `json:"input,omitempty"`
39 | }
40 |
41 | // http handler
42 | func handleCompile(w http.ResponseWriter, r *http.Request) {
43 | cRequest := &compileRequest{}
44 | err := json.NewDecoder(r.Body).Decode(cRequest)
45 | if err != nil {
46 | handleErr(w, err)
47 | return
48 | }
49 |
50 | dir, err := createSourceFiles(cRequest)
51 | if err != nil {
52 | handleErr(w, err)
53 | return
54 | }
55 |
56 | err = runCompile(dir)
57 | if err != nil {
58 | handleErr(w, err)
59 | return
60 | }
61 |
62 | w.Header().Set("Content-Type", "application/wasm")
63 |
64 | wasmF, err := os.Open(filepath.Join(dir, "app.wasm"))
65 | if err != nil {
66 | handleErr(w, err)
67 | return
68 | }
69 | defer wasmF.Close()
70 |
71 | io.Copy(w, wasmF)
72 | }
73 |
74 | func handleErr(w http.ResponseWriter, err error) {
75 | http.Error(w, err.Error(), http.StatusInternalServerError)
76 | }
77 |
78 | // create source files
79 | func createSourceFiles(cRequest *compileRequest) (string, error) {
80 | dir, err := ioutil.TempDir("/tmp/", "gowasmbuilder-")
81 | if err != nil {
82 | return "", err
83 | }
84 |
85 | fmt.Println("Saving data to ", dir)
86 |
87 | mainF, err := os.Open("./gowasm/main.go")
88 | if err != nil {
89 | return "", err
90 | }
91 | defer mainF.Close()
92 |
93 | mainFCopy, err := os.Create(filepath.Join(dir, "main.go"))
94 | if err != nil {
95 | return "", err
96 | }
97 | defer mainFCopy.Close()
98 |
99 | _, err = io.Copy(mainFCopy, mainF)
100 | if err != nil {
101 | return "", err
102 | }
103 |
104 | inputF, err := os.Create(filepath.Join(dir, "input.go"))
105 | if err != nil {
106 | return "", err
107 | }
108 | defer inputF.Close()
109 |
110 | _, err = inputF.WriteString(cRequest.Input)
111 | if err != nil {
112 | return "", err
113 | }
114 |
115 | return dir, nil
116 | }
117 |
118 | // compile wasm using Docker API
119 | func runCompile(dir string) error {
120 | ctx := context.Background()
121 | cli, err := client.NewEnvClient()
122 | if err != nil {
123 | return err
124 | }
125 |
126 | resp, err := cli.ContainerCreate(ctx, &container.Config{
127 | Image: "didil/gowasmbuilder",
128 | }, &container.HostConfig{
129 | Mounts: []mount.Mount{
130 | mount.Mount{Type: mount.TypeBind, Source: dir, Target: "/app/"},
131 | },
132 | }, nil, "")
133 | if err != nil {
134 | return err
135 | }
136 |
137 | if err := cli.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{}); err != nil {
138 | return err
139 | }
140 |
141 | statusCh, errCh := cli.ContainerWait(ctx, resp.ID, container.WaitConditionNotRunning)
142 | select {
143 | case err := <-errCh:
144 | if err != nil {
145 | return err
146 | }
147 | case status := <-statusCh:
148 | if status.StatusCode != 0 {
149 | out, err := cli.ContainerLogs(ctx, resp.ID, types.ContainerLogsOptions{ShowStdout: true, ShowStderr: true})
150 | if err != nil {
151 | return err
152 | }
153 |
154 | logs, err := ioutil.ReadAll(out)
155 | if err != nil {
156 | return err
157 | }
158 |
159 | return fmt.Errorf("Container exited with code %d\nlogs: %v", status.StatusCode, string(logs))
160 | }
161 | }
162 | return nil
163 | }
164 |
--------------------------------------------------------------------------------
/libs/unobservable.js:
--------------------------------------------------------------------------------
1 | (function(unobservable) { "use strict";
2 |
3 | // Black magic stuff
4 | function CustomArray(numPreallocated) {
5 | this.arr = new Array(numPreallocated);
6 | this.len = 0;
7 | }
8 | CustomArray.prototype.push = function(e) {
9 | this.arr[this.len++] = e;
10 | };
11 | CustomArray.prototype.removeAt = function(index) {
12 | for(var j=index+1; j 1) {
41 | var name = events.slice(i, i2);
42 | count++;
43 | i = i2;
44 | }
45 | if(name.length > 0) {
46 | (this.callbacks[name] = this.callbacks[name] || new CustomArray()).push(fn);
47 | }
48 | }
49 | fn.typed = count > 1;
50 | };
51 |
52 | obj.off = function(events, fn) {
53 | if (events === "*") this.callbacks = {};
54 | else if (fn) {
55 | var fns = this.callbacks[events];
56 | for (var i = 0, len=fns.len; i 1) {
72 | var name = events.slice(i, i2);
73 | i = i2;
74 | }
75 | if(name.length > 0) {
76 | this.callbacks[name] = undefined;
77 | }
78 | }
79 | }
80 | return this;
81 | };
82 |
83 | // Only single event supported
84 | obj.one = function(name, fn) {
85 | fn.one = true;
86 | return this.on(name, fn);
87 | };
88 |
89 | obj.trigger = function(name, arg1, arg2, arg3, arg4) {
90 | // Just using bogus args is much faster than manipulating the arguments array
91 | var fns = this.callbacks[name];
92 | if(!fns) { return this; }
93 |
94 | for (var i=0; i millis) {
73 | self.currentTask = null;
74 | if(cb) { cb(); }
75 | }
76 | };
77 | };
78 |
79 | Movable.prototype.moveToOverTime = function(newX, newY, timeToSpend, interpolator, cb) {
80 | this.makeSureNotBusy();
81 | this.currentTask = true;
82 | if(newX === null) { newX = this.x; }
83 | if(newY === null) { newY = this.y; }
84 | if(typeof interpolator === "undefined") { interpolator = DEFAULT_INTERPOLATOR; }
85 | var origX = this.x;
86 | var origY = this.y;
87 | var timeSpent = 0.0;
88 | var self = this;
89 | self.currentTask = function moveToOverTimeTask(dt) {
90 | timeSpent = Math.min(timeToSpend, timeSpent + dt);
91 | if(timeSpent === timeToSpend) { // Epsilon issues possibly?
92 | self.moveToFast(newX, newY);
93 | self.currentTask = null;
94 | if(cb) { cb(); }
95 | } else {
96 | var factor = timeSpent / timeToSpend;
97 | self.moveToFast(interpolator(origX, newX, factor), interpolator(origY, newY, factor));
98 | }
99 | };
100 | };
101 |
102 | Movable.prototype.update = function(dt) {
103 | if(this.currentTask !== null) {
104 | this.currentTask(dt);
105 | }
106 | };
107 |
108 | Movable.prototype.getWorldPosition = function(storage) {
109 | var resultX = this.x;
110 | var resultY = this.y;
111 | var currentParent = this.parent;
112 | while(currentParent !== null) {
113 | resultX += currentParent.x;
114 | resultY += currentParent.y;
115 | currentParent = currentParent.parent;
116 | }
117 | storage[0] = resultX;
118 | storage[1] = resultY;
119 | };
120 |
121 | Movable.prototype.setParent = function(movableParent) {
122 | var objWorld = [0,0];
123 | if(movableParent === null) {
124 | if(this.parent !== null) {
125 | this.getWorldPosition(objWorld);
126 | this.parent = null;
127 | this.moveToFast(objWorld[0], objWorld[1]);
128 | }
129 | } else {
130 | // Parent is being set a non-null movable
131 | this.getWorldPosition(objWorld);
132 | var parentWorld = [0,0];
133 | movableParent.getWorldPosition(parentWorld);
134 | this.parent = movableParent;
135 | this.moveToFast(objWorld[0] - parentWorld[0], objWorld[1] - parentWorld[1]);
136 | }
137 | };
138 |
--------------------------------------------------------------------------------
/test/jasmine/console.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2008-2014 Pivotal Labs
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining
5 | a copy of this software and associated documentation files (the
6 | "Software"), to deal in the Software without restriction, including
7 | without limitation the rights to use, copy, modify, merge, publish,
8 | distribute, sublicense, and/or sell copies of the Software, and to
9 | permit persons to whom the Software is furnished to do so, subject to
10 | the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be
13 | included in all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 | */
23 | function getJasmineRequireObj() {
24 | if (typeof module !== 'undefined' && module.exports) {
25 | return exports;
26 | } else {
27 | window.jasmineRequire = window.jasmineRequire || {};
28 | return window.jasmineRequire;
29 | }
30 | }
31 |
32 | getJasmineRequireObj().console = function(jRequire, j$) {
33 | j$.ConsoleReporter = jRequire.ConsoleReporter();
34 | };
35 |
36 | getJasmineRequireObj().ConsoleReporter = function() {
37 |
38 | var noopTimer = {
39 | start: function(){},
40 | elapsed: function(){ return 0; }
41 | };
42 |
43 | function ConsoleReporter(options) {
44 | var print = options.print,
45 | showColors = options.showColors || false,
46 | onComplete = options.onComplete || function() {},
47 | timer = options.timer || noopTimer,
48 | specCount,
49 | failureCount,
50 | failedSpecs = [],
51 | pendingCount,
52 | ansi = {
53 | green: '\x1B[32m',
54 | red: '\x1B[31m',
55 | yellow: '\x1B[33m',
56 | none: '\x1B[0m'
57 | };
58 |
59 | this.jasmineStarted = function() {
60 | specCount = 0;
61 | failureCount = 0;
62 | pendingCount = 0;
63 | print('Started');
64 | printNewline();
65 | timer.start();
66 | };
67 |
68 | this.jasmineDone = function() {
69 | printNewline();
70 | for (var i = 0; i < failedSpecs.length; i++) {
71 | specFailureDetails(failedSpecs[i]);
72 | }
73 |
74 | if(specCount > 0) {
75 | printNewline();
76 |
77 | var specCounts = specCount + ' ' + plural('spec', specCount) + ', ' +
78 | failureCount + ' ' + plural('failure', failureCount);
79 |
80 | if (pendingCount) {
81 | specCounts += ', ' + pendingCount + ' pending ' + plural('spec', pendingCount);
82 | }
83 |
84 | print(specCounts);
85 | } else {
86 | print('No specs found');
87 | }
88 |
89 | printNewline();
90 | var seconds = timer.elapsed() / 1000;
91 | print('Finished in ' + seconds + ' ' + plural('second', seconds));
92 |
93 | printNewline();
94 |
95 | onComplete(failureCount === 0);
96 | };
97 |
98 | this.specDone = function(result) {
99 | specCount++;
100 |
101 | if (result.status == 'pending') {
102 | pendingCount++;
103 | print(colored('yellow', '*'));
104 | return;
105 | }
106 |
107 | if (result.status == 'passed') {
108 | print(colored('green', '.'));
109 | return;
110 | }
111 |
112 | if (result.status == 'failed') {
113 | failureCount++;
114 | failedSpecs.push(result);
115 | print(colored('red', 'F'));
116 | }
117 | };
118 |
119 | return this;
120 |
121 | function printNewline() {
122 | print('\n');
123 | }
124 |
125 | function colored(color, str) {
126 | return showColors ? (ansi[color] + str + ansi.none) : str;
127 | }
128 |
129 | function plural(str, count) {
130 | return count == 1 ? str : str + 's';
131 | }
132 |
133 | function repeat(thing, times) {
134 | var arr = [];
135 | for (var i = 0; i < times; i++) {
136 | arr.push(thing);
137 | }
138 | return arr;
139 | }
140 |
141 | function indent(str, spaces) {
142 | var lines = (str || '').split('\n');
143 | var newArr = [];
144 | for (var i = 0; i < lines.length; i++) {
145 | newArr.push(repeat(' ', spaces).join('') + lines[i]);
146 | }
147 | return newArr.join('\n');
148 | }
149 |
150 | function specFailureDetails(result) {
151 | printNewline();
152 | print(result.fullName);
153 |
154 | for (var i = 0; i < result.failedExpectations.length; i++) {
155 | var failedExpectation = result.failedExpectations[i];
156 | printNewline();
157 | print(indent(failedExpectation.message, 2));
158 | print(indent(failedExpectation.stack, 2));
159 | }
160 |
161 | printNewline();
162 | }
163 | }
164 |
165 | return ConsoleReporter;
166 | };
167 |
--------------------------------------------------------------------------------
/test/jasmine/boot.js:
--------------------------------------------------------------------------------
1 | /**
2 | Starting with version 2.0, this file "boots" Jasmine, performing all of the necessary initialization before executing the loaded environment and all of a project's specs. This file should be loaded after `jasmine.js`, but before any project source files or spec files are loaded. Thus this file can also be used to customize Jasmine for a project.
3 |
4 | If a project is using Jasmine via the standalone distribution, this file can be customized directly. If a project is using Jasmine via the [Ruby gem][jasmine-gem], this file can be copied into the support directory via `jasmine copy_boot_js`. Other environments (e.g., Python) will have different mechanisms.
5 |
6 | The location of `boot.js` can be specified and/or overridden in `jasmine.yml`.
7 |
8 | [jasmine-gem]: http://github.com/pivotal/jasmine-gem
9 | */
10 |
11 | (function() {
12 |
13 | /**
14 | * ## Require & Instantiate
15 | *
16 | * Require Jasmine's core files. Specifically, this requires and attaches all of Jasmine's code to the `jasmine` reference.
17 | */
18 | window.jasmine = jasmineRequire.core(jasmineRequire);
19 |
20 | /**
21 | * Since this is being run in a browser and the results should populate to an HTML page, require the HTML-specific Jasmine code, injecting the same reference.
22 | */
23 | jasmineRequire.html(jasmine);
24 |
25 | /**
26 | * Create the Jasmine environment. This is used to run all specs in a project.
27 | */
28 | var env = jasmine.getEnv();
29 |
30 | /**
31 | * ## The Global Interface
32 | *
33 | * Build up the functions that will be exposed as the Jasmine public interface. A project can customize, rename or alias any of these functions as desired, provided the implementation remains unchanged.
34 | */
35 | var jasmineInterface = jasmineRequire.interface(jasmine, env);
36 |
37 | /**
38 | * Add all of the Jasmine global/public interface to the proper global, so a project can use the public interface directly. For example, calling `describe` in specs instead of `jasmine.getEnv().describe`.
39 | */
40 | if (typeof window == "undefined" && typeof exports == "object") {
41 | extend(exports, jasmineInterface);
42 | } else {
43 | extend(window, jasmineInterface);
44 | }
45 |
46 | /**
47 | * ## Runner Parameters
48 | *
49 | * More browser specific code - wrap the query string in an object and to allow for getting/setting parameters from the runner user interface.
50 | */
51 |
52 | var queryString = new jasmine.QueryString({
53 | getWindowLocation: function() { return window.location; }
54 | });
55 |
56 | var catchingExceptions = queryString.getParam("catch");
57 | env.catchExceptions(typeof catchingExceptions === "undefined" ? true : catchingExceptions);
58 |
59 | /**
60 | * ## Reporters
61 | * The `HtmlReporter` builds all of the HTML UI for the runner page. This reporter paints the dots, stars, and x's for specs, as well as all spec names and all failures (if any).
62 | */
63 | var htmlReporter = new jasmine.HtmlReporter({
64 | env: env,
65 | onRaiseExceptionsClick: function() { queryString.setParam("catch", !env.catchingExceptions()); },
66 | getContainer: function() { return document.body; },
67 | createElement: function() { return document.createElement.apply(document, arguments); },
68 | createTextNode: function() { return document.createTextNode.apply(document, arguments); },
69 | timer: new jasmine.Timer()
70 | });
71 |
72 | /**
73 | * The `jsApiReporter` also receives spec results, and is used by any environment that needs to extract the results from JavaScript.
74 | */
75 | env.addReporter(jasmineInterface.jsApiReporter);
76 | env.addReporter(htmlReporter);
77 |
78 | /**
79 | * Filter which specs will be run by matching the start of the full name against the `spec` query param.
80 | */
81 | var specFilter = new jasmine.HtmlSpecFilter({
82 | filterString: function() { return queryString.getParam("spec"); }
83 | });
84 |
85 | env.specFilter = function(spec) {
86 | return specFilter.matches(spec.getFullName());
87 | };
88 |
89 | /**
90 | * Setting up timing functions to be able to be overridden. Certain browsers (Safari, IE 8, phantomjs) require this hack.
91 | */
92 | window.setTimeout = window.setTimeout;
93 | window.setInterval = window.setInterval;
94 | window.clearTimeout = window.clearTimeout;
95 | window.clearInterval = window.clearInterval;
96 |
97 | /**
98 | * ## Execution
99 | *
100 | * Replace the browser window's `onload`, ensure it's called, and then run all of the loaded specs. This includes initializing the `HtmlReporter` instance and then executing the loaded Jasmine environment. All of this will happen after all of the specs are loaded.
101 | */
102 | var currentWindowOnload = window.onload;
103 |
104 | window.onload = function() {
105 | if (currentWindowOnload) {
106 | currentWindowOnload();
107 | }
108 | htmlReporter.initialize();
109 | env.execute();
110 | };
111 |
112 | /**
113 | * Helper function for readability above.
114 | */
115 | function extend(destination, source) {
116 | for (var property in source) destination[property] = source[property];
117 | return destination;
118 | }
119 |
120 | }());
121 |
--------------------------------------------------------------------------------
/challenges.js:
--------------------------------------------------------------------------------
1 |
2 | var requireUserCountWithinTime = function(userCount, timeLimit) {
3 | return {
4 | description: "Transport " + userCount + " people in " + timeLimit.toFixed(0) + " seconds or less",
5 | evaluate: function(world) {
6 | if(world.elapsedTime >= timeLimit || world.transportedCounter >= userCount) {
7 | return world.elapsedTime <= timeLimit && world.transportedCounter >= userCount;
8 | } else {
9 | return null;
10 | }
11 | }
12 | };
13 | };
14 |
15 | var requireUserCountWithMaxWaitTime = function(userCount, maxWaitTime) {
16 | return {
17 | description: "Transport " + userCount + " people and let no one wait more than " + maxWaitTime.toFixed(1) + " seconds",
18 | evaluate: function(world) {
19 | if(world.maxWaitTime >= maxWaitTime || world.transportedCounter >= userCount) {
20 | return world.maxWaitTime <= maxWaitTime && world.transportedCounter >= userCount;
21 | } else {
22 | return null;
23 | }
24 | }
25 | };
26 | };
27 |
28 | var requireUserCountWithinTimeWithMaxWaitTime = function(userCount, timeLimit, maxWaitTime) {
29 | return {
30 | description: "Transport " + userCount + " people in " + timeLimit.toFixed(0) + " seconds or less and let no one wait more than " + maxWaitTime.toFixed(1) + " seconds",
31 | evaluate: function(world) {
32 | if(world.elapsedTime >= timeLimit || world.maxWaitTime >= maxWaitTime || world.transportedCounter >= userCount) {
33 | return world.elapsedTime <= timeLimit && world.maxWaitTime <= maxWaitTime && world.transportedCounter >= userCount;
34 | } else {
35 | return null;
36 | }
37 | }
38 | };
39 | };
40 |
41 | var requireUserCountWithinMoves = function(userCount, moveLimit) {
42 | return {
43 | description: "Transport " + userCount + " people using " + moveLimit + " elevator moves or less",
44 | evaluate: function(world) {
45 | if(world.moveCount >= moveLimit || world.transportedCounter >= userCount) {
46 | return world.moveCount <= moveLimit && world.transportedCounter >= userCount;
47 | } else {
48 | return null;
49 | }
50 | }
51 | };
52 | };
53 |
54 | var requireDemo = function() {
55 | return {
56 | description: "Perpetual demo",
57 | evaluate: function() { return null; }
58 | };
59 | };
60 |
61 | /* jshint laxcomma:true */
62 | var challenges = [
63 | {options: {floorCount: 3, elevatorCount: 1, spawnRate: 0.3}, condition: requireUserCountWithinTime(15, 60)}
64 | ,{options: {floorCount: 5, elevatorCount: 1, spawnRate: 0.4}, condition: requireUserCountWithinTime(20, 60)}
65 | ,{options: {floorCount: 5, elevatorCount: 1, spawnRate: 0.5, elevatorCapacities: [6]}, condition: requireUserCountWithinTime(23, 60)}
66 | ,{options: {floorCount: 8, elevatorCount: 2, spawnRate: 0.6}, condition: requireUserCountWithinTime(28, 60)}
67 | ,{options: {floorCount: 6, elevatorCount: 4, spawnRate: 1.7}, condition: requireUserCountWithinTime(100, 68)}
68 | ,{options: {floorCount: 4, elevatorCount: 2, spawnRate: 0.8}, condition: requireUserCountWithinMoves(40, 60)}
69 | ,{options: {floorCount: 3, elevatorCount: 3, spawnRate: 3.0}, condition: requireUserCountWithinMoves(100, 63)}
70 | ,{options: {floorCount: 6, elevatorCount: 2, spawnRate: 0.4, elevatorCapacities: [5]}, condition: requireUserCountWithMaxWaitTime(50, 21)}
71 | ,{options: {floorCount: 7, elevatorCount: 3, spawnRate: 0.6}, condition: requireUserCountWithMaxWaitTime(50, 20)}
72 |
73 | ,{options: {floorCount: 13, elevatorCount: 2, spawnRate: 1.1, elevatorCapacities: [4,10]}, condition: requireUserCountWithinTime(50, 70)}
74 |
75 | ,{options: {floorCount: 9, elevatorCount: 5, spawnRate: 1.1}, condition: requireUserCountWithMaxWaitTime(60, 19)}
76 | ,{options: {floorCount: 9, elevatorCount: 5, spawnRate: 1.1}, condition: requireUserCountWithMaxWaitTime(80, 17)}
77 | ,{options: {floorCount: 9, elevatorCount: 5, spawnRate: 1.1, elevatorCapacities: [5]}, condition: requireUserCountWithMaxWaitTime(100, 15)}
78 | ,{options: {floorCount: 9, elevatorCount: 5, spawnRate: 1.0, elevatorCapacities: [6]}, condition: requireUserCountWithMaxWaitTime(110, 15)}
79 | ,{options: {floorCount: 8, elevatorCount: 6, spawnRate: 0.9}, condition: requireUserCountWithMaxWaitTime(120, 14)}
80 |
81 | ,{options: {floorCount: 12, elevatorCount: 4, spawnRate: 1.4, elevatorCapacities: [5,10]}, condition: requireUserCountWithinTime(70, 80)}
82 | ,{options: {floorCount: 21, elevatorCount: 5, spawnRate: 1.9, elevatorCapacities: [10]}, condition: requireUserCountWithinTime(110, 80)}
83 |
84 | ,{options: {floorCount: 21, elevatorCount: 8, spawnRate: 1.5, elevatorCapacities: [6,8]}, condition: requireUserCountWithinTimeWithMaxWaitTime(2675, 1800, 45)}
85 |
86 | ,{options: {floorCount: 21, elevatorCount: 8, spawnRate: 1.5, elevatorCapacities: [6,8]}, condition: requireDemo()}
87 | ];
88 | /* jshint laxcomma:false */
89 |
--------------------------------------------------------------------------------
/libs/codemirror/themes/solarized.css:
--------------------------------------------------------------------------------
1 | /*
2 | Solarized theme for code-mirror
3 | http://ethanschoonover.com/solarized
4 | */
5 |
6 | /*
7 | Solarized color palette
8 | http://ethanschoonover.com/solarized/img/solarized-palette.png
9 | */
10 |
11 | .solarized.base03 { color: #002b36; }
12 | .solarized.base02 { color: #073642; }
13 | .solarized.base01 { color: #586e75; }
14 | .solarized.base00 { color: #657b83; }
15 | .solarized.base0 { color: #839496; }
16 | .solarized.base1 { color: #93a1a1; }
17 | .solarized.base2 { color: #eee8d5; }
18 | .solarized.base3 { color: #fdf6e3; }
19 | .solarized.solar-yellow { color: #b58900; }
20 | .solarized.solar-orange { color: #cb4b16; }
21 | .solarized.solar-red { color: #dc322f; }
22 | .solarized.solar-magenta { color: #d33682; }
23 | .solarized.solar-violet { color: #6c71c4; }
24 | .solarized.solar-blue { color: #268bd2; }
25 | .solarized.solar-cyan { color: #2aa198; }
26 | .solarized.solar-green { color: #859900; }
27 |
28 | /* Color scheme for code-mirror */
29 |
30 | .cm-s-solarized {
31 | line-height: 1.45em;
32 | color-profile: sRGB;
33 | rendering-intent: auto;
34 | }
35 | .cm-s-solarized.cm-s-dark {
36 | color: #839496;
37 | background-color: #002b36;
38 | text-shadow: #002b36 0 1px;
39 | }
40 | .cm-s-solarized.cm-s-light {
41 | background-color: #fdf6e3;
42 | color: #657b83;
43 | text-shadow: #eee8d5 0 1px;
44 | }
45 |
46 | .cm-s-solarized .CodeMirror-widget {
47 | text-shadow: none;
48 | }
49 |
50 | .cm-s-solarized .cm-header { color: #586e75; }
51 | .cm-s-solarized .cm-quote { color: #93a1a1; }
52 |
53 | .cm-s-solarized .cm-keyword { color: #cb4b16; }
54 | .cm-s-solarized .cm-atom { color: #d33682; }
55 | .cm-s-solarized .cm-number { color: #d33682; }
56 | .cm-s-solarized .cm-def { color: #2aa198; }
57 |
58 | .cm-s-solarized .cm-variable { color: #839496; }
59 | .cm-s-solarized .cm-variable-2 { color: #b58900; }
60 | .cm-s-solarized .cm-variable-3, .cm-s-solarized .cm-type { color: #6c71c4; }
61 |
62 | .cm-s-solarized .cm-property { color: #2aa198; }
63 | .cm-s-solarized .cm-operator { color: #6c71c4; }
64 |
65 | .cm-s-solarized .cm-comment { color: #586e75; font-style:italic; }
66 |
67 | .cm-s-solarized .cm-string { color: #859900; }
68 | .cm-s-solarized .cm-string-2 { color: #b58900; }
69 |
70 | .cm-s-solarized .cm-meta { color: #859900; }
71 | .cm-s-solarized .cm-qualifier { color: #b58900; }
72 | .cm-s-solarized .cm-builtin { color: #d33682; }
73 | .cm-s-solarized .cm-bracket { color: #cb4b16; }
74 | .cm-s-solarized .CodeMirror-matchingbracket { color: #859900; }
75 | .cm-s-solarized .CodeMirror-nonmatchingbracket { color: #dc322f; }
76 | .cm-s-solarized .cm-tag { color: #93a1a1; }
77 | .cm-s-solarized .cm-attribute { color: #2aa198; }
78 | .cm-s-solarized .cm-hr {
79 | color: transparent;
80 | border-top: 1px solid #586e75;
81 | display: block;
82 | }
83 | .cm-s-solarized .cm-link { color: #93a1a1; cursor: pointer; }
84 | .cm-s-solarized .cm-special { color: #6c71c4; }
85 | .cm-s-solarized .cm-em {
86 | color: #999;
87 | text-decoration: underline;
88 | text-decoration-style: dotted;
89 | }
90 | .cm-s-solarized .cm-error,
91 | .cm-s-solarized .cm-invalidchar {
92 | color: #586e75;
93 | border-bottom: 1px dotted #dc322f;
94 | }
95 |
96 | .cm-s-solarized.cm-s-dark div.CodeMirror-selected { background: #073642; }
97 | .cm-s-solarized.cm-s-dark.CodeMirror ::selection { background: rgba(7, 54, 66, 0.99); }
98 | .cm-s-solarized.cm-s-dark .CodeMirror-line::-moz-selection, .cm-s-dark .CodeMirror-line > span::-moz-selection, .cm-s-dark .CodeMirror-line > span > span::-moz-selection { background: rgba(7, 54, 66, 0.99); }
99 |
100 | .cm-s-solarized.cm-s-light div.CodeMirror-selected { background: #eee8d5; }
101 | .cm-s-solarized.cm-s-light .CodeMirror-line::selection, .cm-s-light .CodeMirror-line > span::selection, .cm-s-light .CodeMirror-line > span > span::selection { background: #eee8d5; }
102 | .cm-s-solarized.cm-s-light .CodeMirror-line::-moz-selection, .cm-s-ligh .CodeMirror-line > span::-moz-selection, .cm-s-ligh .CodeMirror-line > span > span::-moz-selection { background: #eee8d5; }
103 |
104 | /* Editor styling */
105 |
106 |
107 |
108 | /* Little shadow on the view-port of the buffer view */
109 | .cm-s-solarized.CodeMirror {
110 | -moz-box-shadow: inset 7px 0 12px -6px #000;
111 | -webkit-box-shadow: inset 7px 0 12px -6px #000;
112 | box-shadow: inset 7px 0 12px -6px #000;
113 | }
114 |
115 | /* Remove gutter border */
116 | .cm-s-solarized .CodeMirror-gutters {
117 | border-right: 0;
118 | }
119 |
120 | /* Gutter colors and line number styling based of color scheme (dark / light) */
121 |
122 | /* Dark */
123 | .cm-s-solarized.cm-s-dark .CodeMirror-gutters {
124 | background-color: #073642;
125 | }
126 |
127 | .cm-s-solarized.cm-s-dark .CodeMirror-linenumber {
128 | color: #586e75;
129 | text-shadow: #021014 0 -1px;
130 | }
131 |
132 | /* Light */
133 | .cm-s-solarized.cm-s-light .CodeMirror-gutters {
134 | background-color: #eee8d5;
135 | }
136 |
137 | .cm-s-solarized.cm-s-light .CodeMirror-linenumber {
138 | color: #839496;
139 | }
140 |
141 | /* Common */
142 | .cm-s-solarized .CodeMirror-linenumber {
143 | padding: 0 5px;
144 | }
145 | .cm-s-solarized .CodeMirror-guttermarker-subtle { color: #586e75; }
146 | .cm-s-solarized.cm-s-dark .CodeMirror-guttermarker { color: #ddd; }
147 | .cm-s-solarized.cm-s-light .CodeMirror-guttermarker { color: #cb4b16; }
148 |
149 | .cm-s-solarized .CodeMirror-gutter .CodeMirror-gutter-text {
150 | color: #586e75;
151 | }
152 |
153 | /* Cursor */
154 | .cm-s-solarized .CodeMirror-cursor { border-left: 1px solid #819090; }
155 |
156 | /* Fat cursor */
157 | .cm-s-solarized.cm-s-light.cm-fat-cursor .CodeMirror-cursor { background: #77ee77; }
158 | .cm-s-solarized.cm-s-light .cm-animate-fat-cursor { background-color: #77ee77; }
159 | .cm-s-solarized.cm-s-dark.cm-fat-cursor .CodeMirror-cursor { background: #586e75; }
160 | .cm-s-solarized.cm-s-dark .cm-animate-fat-cursor { background-color: #586e75; }
161 |
162 | /* Active line */
163 | .cm-s-solarized.cm-s-dark .CodeMirror-activeline-background {
164 | background: rgba(255, 255, 255, 0.06);
165 | }
166 | .cm-s-solarized.cm-s-light .CodeMirror-activeline-background {
167 | background: rgba(0, 0, 0, 0.06);
168 | }
169 |
--------------------------------------------------------------------------------
/libs/codemirror/mode/go/go.js:
--------------------------------------------------------------------------------
1 | // CodeMirror, copyright (c) by Marijn Haverbeke and others
2 | // Distributed under an MIT license: https://codemirror.net/LICENSE
3 |
4 | (function(mod) {
5 | if (typeof exports == "object" && typeof module == "object") // CommonJS
6 | mod(require("../../lib/codemirror"));
7 | else if (typeof define == "function" && define.amd) // AMD
8 | define(["../../lib/codemirror"], mod);
9 | else // Plain browser env
10 | mod(CodeMirror);
11 | })(function(CodeMirror) {
12 | "use strict";
13 |
14 | CodeMirror.defineMode("go", function(config) {
15 | var indentUnit = config.indentUnit;
16 |
17 | var keywords = {
18 | "break":true, "case":true, "chan":true, "const":true, "continue":true,
19 | "default":true, "defer":true, "else":true, "fallthrough":true, "for":true,
20 | "func":true, "go":true, "goto":true, "if":true, "import":true,
21 | "interface":true, "map":true, "package":true, "range":true, "return":true,
22 | "select":true, "struct":true, "switch":true, "type":true, "var":true,
23 | "bool":true, "byte":true, "complex64":true, "complex128":true,
24 | "float32":true, "float64":true, "int8":true, "int16":true, "int32":true,
25 | "int64":true, "string":true, "uint8":true, "uint16":true, "uint32":true,
26 | "uint64":true, "int":true, "uint":true, "uintptr":true, "error": true,
27 | "rune":true
28 | };
29 |
30 | var atoms = {
31 | "true":true, "false":true, "iota":true, "nil":true, "append":true,
32 | "cap":true, "close":true, "complex":true, "copy":true, "delete":true, "imag":true,
33 | "len":true, "make":true, "new":true, "panic":true, "print":true,
34 | "println":true, "real":true, "recover":true
35 | };
36 |
37 | var isOperatorChar = /[+\-*&^%:=<>!|\/]/;
38 |
39 | var curPunc;
40 |
41 | function tokenBase(stream, state) {
42 | var ch = stream.next();
43 | if (ch == '"' || ch == "'" || ch == "`") {
44 | state.tokenize = tokenString(ch);
45 | return state.tokenize(stream, state);
46 | }
47 | if (/[\d\.]/.test(ch)) {
48 | if (ch == ".") {
49 | stream.match(/^[0-9]+([eE][\-+]?[0-9]+)?/);
50 | } else if (ch == "0") {
51 | stream.match(/^[xX][0-9a-fA-F]+/) || stream.match(/^0[0-7]+/);
52 | } else {
53 | stream.match(/^[0-9]*\.?[0-9]*([eE][\-+]?[0-9]+)?/);
54 | }
55 | return "number";
56 | }
57 | if (/[\[\]{}\(\),;\:\.]/.test(ch)) {
58 | curPunc = ch;
59 | return null;
60 | }
61 | if (ch == "/") {
62 | if (stream.eat("*")) {
63 | state.tokenize = tokenComment;
64 | return tokenComment(stream, state);
65 | }
66 | if (stream.eat("/")) {
67 | stream.skipToEnd();
68 | return "comment";
69 | }
70 | }
71 | if (isOperatorChar.test(ch)) {
72 | stream.eatWhile(isOperatorChar);
73 | return "operator";
74 | }
75 | stream.eatWhile(/[\w\$_\xa1-\uffff]/);
76 | var cur = stream.current();
77 | if (keywords.propertyIsEnumerable(cur)) {
78 | if (cur == "case" || cur == "default") curPunc = "case";
79 | return "keyword";
80 | }
81 | if (atoms.propertyIsEnumerable(cur)) return "atom";
82 | return "variable";
83 | }
84 |
85 | function tokenString(quote) {
86 | return function(stream, state) {
87 | var escaped = false, next, end = false;
88 | while ((next = stream.next()) != null) {
89 | if (next == quote && !escaped) {end = true; break;}
90 | escaped = !escaped && quote != "`" && next == "\\";
91 | }
92 | if (end || !(escaped || quote == "`"))
93 | state.tokenize = tokenBase;
94 | return "string";
95 | };
96 | }
97 |
98 | function tokenComment(stream, state) {
99 | var maybeEnd = false, ch;
100 | while (ch = stream.next()) {
101 | if (ch == "/" && maybeEnd) {
102 | state.tokenize = tokenBase;
103 | break;
104 | }
105 | maybeEnd = (ch == "*");
106 | }
107 | return "comment";
108 | }
109 |
110 | function Context(indented, column, type, align, prev) {
111 | this.indented = indented;
112 | this.column = column;
113 | this.type = type;
114 | this.align = align;
115 | this.prev = prev;
116 | }
117 | function pushContext(state, col, type) {
118 | return state.context = new Context(state.indented, col, type, null, state.context);
119 | }
120 | function popContext(state) {
121 | if (!state.context.prev) return;
122 | var t = state.context.type;
123 | if (t == ")" || t == "]" || t == "}")
124 | state.indented = state.context.indented;
125 | return state.context = state.context.prev;
126 | }
127 |
128 | // Interface
129 |
130 | return {
131 | startState: function(basecolumn) {
132 | return {
133 | tokenize: null,
134 | context: new Context((basecolumn || 0) - indentUnit, 0, "top", false),
135 | indented: 0,
136 | startOfLine: true
137 | };
138 | },
139 |
140 | token: function(stream, state) {
141 | var ctx = state.context;
142 | if (stream.sol()) {
143 | if (ctx.align == null) ctx.align = false;
144 | state.indented = stream.indentation();
145 | state.startOfLine = true;
146 | if (ctx.type == "case") ctx.type = "}";
147 | }
148 | if (stream.eatSpace()) return null;
149 | curPunc = null;
150 | var style = (state.tokenize || tokenBase)(stream, state);
151 | if (style == "comment") return style;
152 | if (ctx.align == null) ctx.align = true;
153 |
154 | if (curPunc == "{") pushContext(state, stream.column(), "}");
155 | else if (curPunc == "[") pushContext(state, stream.column(), "]");
156 | else if (curPunc == "(") pushContext(state, stream.column(), ")");
157 | else if (curPunc == "case") ctx.type = "case";
158 | else if (curPunc == "}" && ctx.type == "}") popContext(state);
159 | else if (curPunc == ctx.type) popContext(state);
160 | state.startOfLine = false;
161 | return style;
162 | },
163 |
164 | indent: function(state, textAfter) {
165 | if (state.tokenize != tokenBase && state.tokenize != null) return CodeMirror.Pass;
166 | var ctx = state.context, firstChar = textAfter && textAfter.charAt(0);
167 | if (ctx.type == "case" && /^(?:case|default)\b/.test(textAfter)) {
168 | state.context.type = "}";
169 | return ctx.indented;
170 | }
171 | var closing = firstChar == ctx.type;
172 | if (ctx.align) return ctx.column + (closing ? 0 : 1);
173 | else return ctx.indented + (closing ? 0 : indentUnit);
174 | },
175 |
176 | electricChars: "{}):",
177 | closeBrackets: "()[]{}''\"\"``",
178 | fold: "brace",
179 | blockCommentStart: "/*",
180 | blockCommentEnd: "*/",
181 | lineComment: "//"
182 | };
183 | });
184 |
185 | CodeMirror.defineMIME("text/x-go", "go");
186 |
187 | });
188 |
--------------------------------------------------------------------------------
/presenters.js:
--------------------------------------------------------------------------------
1 |
2 | function clearAll($elems) {
3 | _.each($elems, function($elem) {
4 | $elem.empty();
5 | });
6 | };
7 |
8 | function setTransformPos(elem, x, y) {
9 | var style = "translate(" + x + "px," + y + "px) translateZ(0)";
10 | elem.style.transform = style;
11 | elem.style["-ms-transform"] = style;
12 | elem.style["-webkit-transform"] = style;
13 | };
14 |
15 | function updateUserState($user, elem_user, user) {
16 | setTransformPos(elem_user, user.worldX, user.worldY);
17 | if(user.done) { $user.addClass("leaving"); }
18 | };
19 |
20 |
21 | function presentStats($parent, world) {
22 |
23 | var elem_transportedcounter = $parent.find(".transportedcounter").get(0),
24 | elem_elapsedtime = $parent.find(".elapsedtime").get(0),
25 | elem_transportedpersec = $parent.find(".transportedpersec").get(0),
26 | elem_avgwaittime = $parent.find(".avgwaittime").get(0),
27 | elem_maxwaittime = $parent.find(".maxwaittime").get(0),
28 | elem_movecount = $parent.find(".movecount").get(0);
29 |
30 | world.on("stats_display_changed", function updateStats() {
31 | elem_transportedcounter.textContent = world.transportedCounter;
32 | elem_elapsedtime.textContent = world.elapsedTime.toFixed(0) + "s";
33 | elem_transportedpersec.textContent = world.transportedPerSec.toPrecision(3);
34 | elem_avgwaittime.textContent = world.avgWaitTime.toFixed(1) + "s";
35 | elem_maxwaittime.textContent = world.maxWaitTime.toFixed(1) + "s";
36 | elem_movecount.textContent = world.moveCount;
37 | });
38 | world.trigger("stats_display_changed");
39 | };
40 |
41 | function presentChallenge($parent, challenge, app, world, worldController, challengeNum, challengeTempl) {
42 | var $challenge = $(riot.render(challengeTempl, {
43 | challenge: challenge,
44 | num: challengeNum,
45 | timeScale: worldController.timeScale.toFixed(0) + "x",
46 | startButtonText: world.challengeEnded ? " Restart" : (worldController.isPaused ? "Start" : "Pause")
47 | }));
48 | $parent.html($challenge);
49 |
50 | $parent.find(".startstop").on("click", function() {
51 | app.startStopOrRestart();
52 | });
53 | $parent.find(".timescale_increase").on("click", function(e) {
54 | e.preventDefault();
55 | if(worldController.timeScale < 40) {
56 | var timeScale = Math.round(worldController.timeScale * 1.618);
57 | worldController.setTimeScale(timeScale);
58 | }
59 | });
60 | $parent.find(".timescale_decrease").on("click", function(e) {
61 | e.preventDefault();
62 | var timeScale = Math.round(worldController.timeScale / 1.618);
63 | worldController.setTimeScale(timeScale);
64 | });
65 | };
66 |
67 | function presentFeedback($parent, feedbackTempl, world, title, message, url) {
68 | $parent.html(riot.render(feedbackTempl, {title: title, message: message, url: url, paddingTop: world.floors.length * world.floorHeight * 0.2}));
69 | if(!url) {
70 | $parent.find("a").remove();
71 | }
72 | };
73 |
74 | function presentWorld($world, world, floorTempl, elevatorTempl, elevatorButtonTempl, userTempl) {
75 | $world.css("height", world.floorHeight * world.floors.length);
76 |
77 | $world.append(_.map(world.floors, function(f) {
78 | var $floor = $(riot.render(floorTempl, f));
79 | var $up = $floor.find(".up");
80 | var $down = $floor.find(".down");
81 | f.on("buttonstate_change", function(buttonStates) {
82 | $up.toggleClass("activated", buttonStates.up !== "");
83 | $down.toggleClass("activated", buttonStates.down !== "");
84 | });
85 | $up.on("click", function() {
86 | f.pressUpButton();
87 | });
88 | $down.on("click", function() {
89 | f.pressDownButton();
90 | });
91 | return $floor;
92 | }));
93 | $world.find(".floor").first().find(".down").addClass("invisible");
94 | $world.find(".floor").last().find(".up").addClass("invisible");
95 |
96 | function renderElevatorButtons(states) {
97 | // This is a rarely executed inner-inner loop, does not need efficiency
98 | return _.map(states, function(b, i) {
99 | return riot.render(elevatorButtonTempl, {floorNum: i});
100 | }).join("");
101 | };
102 |
103 | function setUpElevator(e) {
104 | var $elevator = $(riot.render(elevatorTempl, {e: e}));
105 | var elem_elevator = $elevator.get(0);
106 | $elevator.find(".buttonindicator").html(renderElevatorButtons(e.buttonStates));
107 | var $buttons = _.map($elevator.find(".buttonindicator").children(), function(c) { return $(c); });
108 | var elem_floorindicator = $elevator.find(".floorindicator > span").get(0);
109 |
110 | $elevator.on("click", ".buttonpress", function() {
111 | e.pressFloorButton(parseInt($(this).text()));
112 | });
113 | e.on("new_display_state", function updateElevatorPosition() {
114 | setTransformPos(elem_elevator, e.worldX, e.worldY);
115 | });
116 | e.on("new_current_floor", function update_current_floor(floor) {
117 | elem_floorindicator.textContent = floor;
118 | });
119 | e.on("floor_buttons_changed", function update_floor_buttons(states, indexChanged) {
120 | $buttons[indexChanged].toggleClass("activated", states[indexChanged]);
121 | });
122 | e.on("indicatorstate_change", function indicatorstate_change(indicatorStates) {
123 | $elevator.find(".up").toggleClass("activated", indicatorStates.up);
124 | $elevator.find(".down").toggleClass("activated", indicatorStates.down);
125 | });
126 | e.trigger("new_state", e);
127 | e.trigger("new_display_state", e);
128 | e.trigger("new_current_floor", e.currentFloor);
129 | return $elevator;
130 | }
131 |
132 | $world.append(_.map(world.elevators, function(e) {
133 | return setUpElevator(e);
134 | }));
135 |
136 | world.on("new_user", function(user) {
137 | var $user = $(riot.render(userTempl, {u: user, state: user.done ? "leaving" : ""}));
138 | var elem_user = $user.get(0);
139 |
140 | user.on("new_display_state", function() { updateUserState($user, elem_user, user); })
141 | user.on("removed", function() {
142 | $user.remove();
143 | });
144 | $world.append($user);
145 | });
146 | };
147 |
148 |
149 | function presentCodeStatus($parent, templ, error) {
150 | console.log(error);
151 | var errorDisplay = error ? "block" : "none";
152 | var successDisplay = error ? "none" : "block";
153 | var errorMessage = error;
154 | if(error && error.stack) {
155 | errorMessage = error.stack;
156 | errorMessage = errorMessage.replace(/\n/g, " ");
157 | }
158 | var status = riot.render(templ, {errorMessage: errorMessage, errorDisplay: errorDisplay, successDisplay: successDisplay});
159 | $parent.html(status);
160 | };
161 |
162 | function makeDemoFullscreen() {
163 | $("body .container > *").not(".world").css("visibility", "hidden");
164 | $("html, body, body .container, .world").css({width: "100%", margin: "0", "padding": 0});
165 | };
166 |
--------------------------------------------------------------------------------
/libs/codemirror/addon/edit/closebrackets.js:
--------------------------------------------------------------------------------
1 | // CodeMirror, copyright (c) by Marijn Haverbeke and others
2 | // Distributed under an MIT license: https://codemirror.net/LICENSE
3 |
4 | (function(mod) {
5 | if (typeof exports == "object" && typeof module == "object") // CommonJS
6 | mod(require("../../lib/codemirror"));
7 | else if (typeof define == "function" && define.amd) // AMD
8 | define(["../../lib/codemirror"], mod);
9 | else // Plain browser env
10 | mod(CodeMirror);
11 | })(function(CodeMirror) {
12 | var defaults = {
13 | pairs: "()[]{}''\"\"",
14 | triples: "",
15 | explode: "[]{}"
16 | };
17 |
18 | var Pos = CodeMirror.Pos;
19 |
20 | CodeMirror.defineOption("autoCloseBrackets", false, function(cm, val, old) {
21 | if (old && old != CodeMirror.Init) {
22 | cm.removeKeyMap(keyMap);
23 | cm.state.closeBrackets = null;
24 | }
25 | if (val) {
26 | ensureBound(getOption(val, "pairs"))
27 | cm.state.closeBrackets = val;
28 | cm.addKeyMap(keyMap);
29 | }
30 | });
31 |
32 | function getOption(conf, name) {
33 | if (name == "pairs" && typeof conf == "string") return conf;
34 | if (typeof conf == "object" && conf[name] != null) return conf[name];
35 | return defaults[name];
36 | }
37 |
38 | var keyMap = {Backspace: handleBackspace, Enter: handleEnter};
39 | function ensureBound(chars) {
40 | for (var i = 0; i < chars.length; i++) {
41 | var ch = chars.charAt(i), key = "'" + ch + "'"
42 | if (!keyMap[key]) keyMap[key] = handler(ch)
43 | }
44 | }
45 | ensureBound(defaults.pairs + "`")
46 |
47 | function handler(ch) {
48 | return function(cm) { return handleChar(cm, ch); };
49 | }
50 |
51 | function getConfig(cm) {
52 | var deflt = cm.state.closeBrackets;
53 | if (!deflt || deflt.override) return deflt;
54 | var mode = cm.getModeAt(cm.getCursor());
55 | return mode.closeBrackets || deflt;
56 | }
57 |
58 | function handleBackspace(cm) {
59 | var conf = getConfig(cm);
60 | if (!conf || cm.getOption("disableInput")) return CodeMirror.Pass;
61 |
62 | var pairs = getOption(conf, "pairs");
63 | var ranges = cm.listSelections();
64 | for (var i = 0; i < ranges.length; i++) {
65 | if (!ranges[i].empty()) return CodeMirror.Pass;
66 | var around = charsAround(cm, ranges[i].head);
67 | if (!around || pairs.indexOf(around) % 2 != 0) return CodeMirror.Pass;
68 | }
69 | for (var i = ranges.length - 1; i >= 0; i--) {
70 | var cur = ranges[i].head;
71 | cm.replaceRange("", Pos(cur.line, cur.ch - 1), Pos(cur.line, cur.ch + 1), "+delete");
72 | }
73 | }
74 |
75 | function handleEnter(cm) {
76 | var conf = getConfig(cm);
77 | var explode = conf && getOption(conf, "explode");
78 | if (!explode || cm.getOption("disableInput")) return CodeMirror.Pass;
79 |
80 | var ranges = cm.listSelections();
81 | for (var i = 0; i < ranges.length; i++) {
82 | if (!ranges[i].empty()) return CodeMirror.Pass;
83 | var around = charsAround(cm, ranges[i].head);
84 | if (!around || explode.indexOf(around) % 2 != 0) return CodeMirror.Pass;
85 | }
86 | cm.operation(function() {
87 | var linesep = cm.lineSeparator() || "\n";
88 | cm.replaceSelection(linesep + linesep, null);
89 | cm.execCommand("goCharLeft");
90 | ranges = cm.listSelections();
91 | for (var i = 0; i < ranges.length; i++) {
92 | var line = ranges[i].head.line;
93 | cm.indentLine(line, null, true);
94 | cm.indentLine(line + 1, null, true);
95 | }
96 | });
97 | }
98 |
99 | function contractSelection(sel) {
100 | var inverted = CodeMirror.cmpPos(sel.anchor, sel.head) > 0;
101 | return {anchor: new Pos(sel.anchor.line, sel.anchor.ch + (inverted ? -1 : 1)),
102 | head: new Pos(sel.head.line, sel.head.ch + (inverted ? 1 : -1))};
103 | }
104 |
105 | function handleChar(cm, ch) {
106 | var conf = getConfig(cm);
107 | if (!conf || cm.getOption("disableInput")) return CodeMirror.Pass;
108 |
109 | var pairs = getOption(conf, "pairs");
110 | var pos = pairs.indexOf(ch);
111 | if (pos == -1) return CodeMirror.Pass;
112 | var triples = getOption(conf, "triples");
113 |
114 | var identical = pairs.charAt(pos + 1) == ch;
115 | var ranges = cm.listSelections();
116 | var opening = pos % 2 == 0;
117 |
118 | var type;
119 | for (var i = 0; i < ranges.length; i++) {
120 | var range = ranges[i], cur = range.head, curType;
121 | var next = cm.getRange(cur, Pos(cur.line, cur.ch + 1));
122 | if (opening && !range.empty()) {
123 | curType = "surround";
124 | } else if ((identical || !opening) && next == ch) {
125 | if (identical && stringStartsAfter(cm, cur))
126 | curType = "both";
127 | else if (triples.indexOf(ch) >= 0 && cm.getRange(cur, Pos(cur.line, cur.ch + 3)) == ch + ch + ch)
128 | curType = "skipThree";
129 | else
130 | curType = "skip";
131 | } else if (identical && cur.ch > 1 && triples.indexOf(ch) >= 0 &&
132 | cm.getRange(Pos(cur.line, cur.ch - 2), cur) == ch + ch) {
133 | if (cur.ch > 2 && /\bstring/.test(cm.getTokenTypeAt(Pos(cur.line, cur.ch - 2)))) return CodeMirror.Pass;
134 | curType = "addFour";
135 | } else if (identical) {
136 | var prev = cur.ch == 0 ? " " : cm.getRange(Pos(cur.line, cur.ch - 1), cur)
137 | if (!CodeMirror.isWordChar(next) && prev != ch && !CodeMirror.isWordChar(prev)) curType = "both";
138 | else return CodeMirror.Pass;
139 | } else if (opening) {
140 | curType = "both";
141 | } else {
142 | return CodeMirror.Pass;
143 | }
144 | if (!type) type = curType;
145 | else if (type != curType) return CodeMirror.Pass;
146 | }
147 |
148 | var left = pos % 2 ? pairs.charAt(pos - 1) : ch;
149 | var right = pos % 2 ? ch : pairs.charAt(pos + 1);
150 | cm.operation(function() {
151 | if (type == "skip") {
152 | cm.execCommand("goCharRight");
153 | } else if (type == "skipThree") {
154 | for (var i = 0; i < 3; i++)
155 | cm.execCommand("goCharRight");
156 | } else if (type == "surround") {
157 | var sels = cm.getSelections();
158 | for (var i = 0; i < sels.length; i++)
159 | sels[i] = left + sels[i] + right;
160 | cm.replaceSelections(sels, "around");
161 | sels = cm.listSelections().slice();
162 | for (var i = 0; i < sels.length; i++)
163 | sels[i] = contractSelection(sels[i]);
164 | cm.setSelections(sels);
165 | } else if (type == "both") {
166 | cm.replaceSelection(left + right, null);
167 | cm.triggerElectric(left + right);
168 | cm.execCommand("goCharLeft");
169 | } else if (type == "addFour") {
170 | cm.replaceSelection(left + left + left + left, "before");
171 | cm.execCommand("goCharRight");
172 | }
173 | });
174 | }
175 |
176 | function charsAround(cm, pos) {
177 | var str = cm.getRange(Pos(pos.line, pos.ch - 1),
178 | Pos(pos.line, pos.ch + 1));
179 | return str.length == 2 ? str : null;
180 | }
181 |
182 | function stringStartsAfter(cm, pos) {
183 | var token = cm.getTokenAt(Pos(pos.line, pos.ch + 1))
184 | return /\bstring/.test(token.type) && token.start == pos.ch &&
185 | (pos.ch == 0 || !/\bstring/.test(cm.getTokenTypeAt(pos)))
186 | }
187 | });
188 |
--------------------------------------------------------------------------------
/style.css:
--------------------------------------------------------------------------------
1 |
2 |
3 | html, body {
4 | background-color: #BFBD9F;
5 | color: white;
6 | margin: 0;
7 | padding: 0;
8 | }
9 |
10 | body {
11 | font-family: Oswald, Arial, Helvetica, sans-serif;
12 | }
13 | h1, h2, h3, h4, h5 {
14 | margin-top: 0.4em;
15 | margin-bottom: 0.3em;
16 | }
17 |
18 | h1, h2, h3, h4, h5, p {
19 | color: #555;
20 | }
21 |
22 | .emphasis-color {
23 | color: #F1F2D8;
24 | text-shadow: 0 2px 0.4pt #555;
25 | }
26 |
27 | .error-color {
28 | color: #d54;
29 | }
30 |
31 | a {
32 | color: #444;
33 | font-weight: bold;
34 | text-decoration: none;
35 | }
36 | a:hover {
37 | text-decoration: underline;
38 | }
39 |
40 | h2, h3, h4, h5 {
41 | font-weight: normal;
42 | }
43 |
44 | dl {
45 | color: #555;
46 | }
47 | dl dd {
48 | font: 15px Arial, Helvetica, sans-serif;
49 | margin-bottom: 1em;
50 | margin-left: 1em;
51 | width: 40%;
52 | }
53 |
54 |
55 | .container {
56 | width: 1220px;
57 | margin: 0 auto;
58 | padding: 20px 0 40px 0;
59 | background-color: #BFBD9F;
60 | }
61 |
62 | .unselectable {
63 | -webkit-touch-callout: none;
64 | -webkit-user-select: none;
65 | -khtml-user-select: none;
66 | -moz-user-select: none;
67 | -ms-user-select: none;
68 | user-select: none;
69 | }
70 |
71 | button {
72 | height: 30px;
73 | line-height: 16px;
74 | font-size: 16px;
75 | font-weight: bold;
76 | padding-left: 10px;
77 | padding-right: 10px;
78 | color: #333;
79 | background-color: #777;
80 | text-shadow: 0 0 3px #aaa;
81 | border: 1px solid #666;
82 | border-radius: 5px;
83 | margin-right: 5px;
84 | }
85 |
86 | button.right {
87 | margin-left: 5px;
88 | margin-right: 0;
89 | }
90 |
91 | .blink {
92 | animation: blink 0.5s steps(3, start) infinite;
93 | -webkit-animation: blink 0.5s steps(3, start) infinite;
94 | }
95 | @keyframes blink {
96 | to { visibility: hidden; }
97 | }
98 | @-webkit-keyframes blink {
99 | to { visibility: hidden; }
100 | }
101 |
102 | .invisible {
103 | visibility: hidden;
104 | }
105 |
106 | .faded {
107 | opacity: 0.4;
108 | }
109 |
110 | #save_message, #fitness_message, #building_message{
111 | color: #555;
112 | font-size: 12px;
113 | line-height: 30px;
114 | margin: 0 20px;
115 | }
116 |
117 |
118 | .left {
119 | float: left;
120 | }
121 |
122 | .right {
123 | float: right;
124 | }
125 |
126 | .header {
127 | height: 60px;
128 | }
129 |
130 | .header h1 {
131 | line-height: 50px;
132 | margin: 0;
133 | padding: 0;
134 | }
135 |
136 | .header a {
137 | display: inline-block;
138 | margin-left: 15px;
139 | font-size: 20px;
140 | line-height: 50px;
141 | padding: 0 4px;
142 | }
143 |
144 |
145 | .timescale_decrease, .timescale_increase {
146 | padding: 4px;
147 | line-height: 20px;
148 | cursor: pointer;
149 | }
150 |
151 | .timescale_increase {
152 | margin-right: 10px;
153 | }
154 |
155 |
156 | .codestatus {
157 | padding-top: 10px;
158 | }
159 |
160 | .CodeMirror {
161 | font: 14px Consolas, Monaco, monospace;
162 | width: 1220px;
163 | margin: 10px auto;
164 | height: 380px;
165 | }
166 |
167 | .world {
168 | position: relative;
169 | width: 1220px;
170 | background-color: #333;
171 | padding: 0;
172 | overflow: hidden;
173 | }
174 |
175 | .innerworld {
176 | position: relative;
177 | height: 100%;
178 | width: 938px;
179 | border-right: 1px solid black;
180 | border-left: 1px solid black;
181 | font-family: Arial, Helvetica, sans-serif;
182 | }
183 |
184 | .statscontainer {
185 | font: 12px Consolas, Monaco, monospace;
186 | line-height: 10px;
187 | color: #999;
188 | position: absolute;
189 | top: 0;
190 | right: 0px;
191 | width: 240px;
192 | height: 200px;
193 | padding: 20px;
194 | z-index: 1;
195 | }
196 |
197 | .statscontainer div {
198 | border-bottom: 1px solid #444;
199 | }
200 |
201 | .statscontainer div, .statscontainer span {
202 | position: absolute;
203 | display: block;
204 | width: 240px;
205 | height: 10px;
206 | }
207 |
208 | .statscontainer .value {
209 | text-align: right;
210 | }
211 |
212 |
213 | .feedbackcontainer {
214 | position: absolute;
215 | }
216 |
217 | .feedback {
218 | position: absolute;
219 | width: 1280px;
220 | height: 2000px;
221 | padding-top: 20px;
222 | line-height: 20px;
223 | text-align: center;
224 | background-color: rgba(44,44,44, 0.6);
225 | z-index: 5;
226 | overflow: hidden;
227 | }
228 |
229 | .movable {
230 | position: absolute;
231 | top: 0;
232 | left: 0;
233 | display: block;
234 | }
235 |
236 | .user {
237 | color: white;
238 | text-shadow: 0 1px 3px black;
239 | z-index: 2;
240 | }
241 |
242 | .user.happy {
243 | }
244 |
245 | .user.frustrated {
246 | color: yellow;
247 | }
248 |
249 | .user.disappointed {
250 | color: red;
251 | }
252 |
253 | .user.leaving {
254 | color: #eee;
255 | }
256 |
257 | .elevator {
258 | background-color: #4F8686;
259 | border: 2px solid white;
260 | height: 46px;
261 | z-index: 1;
262 | }
263 |
264 | .elevator .directionindicator {
265 | position: absolute;
266 | font-size: 10px;
267 | top: 3px;
268 | }
269 |
270 | .elevator .directionindicator.directionindicatorup {
271 | left: 2px;
272 | }
273 |
274 | .elevator .directionindicator.directionindicatordown {
275 | right: 2px;
276 | }
277 |
278 | .elevator .directionindicator .fa{
279 | color: rgba(255, 255, 255, 0.2);
280 | }
281 |
282 | .elevator .directionindicator .fa.activated{
283 | color: #33ff44;
284 | }
285 |
286 | .elevator .floorindicator {
287 | display: inline-block;
288 | position: absolute;
289 | width: 100%;
290 | font-size: 15px;
291 | text-align: center;
292 | color: rgba(255, 255, 255, 0.3);
293 | }
294 |
295 |
296 | .elevator .buttonindicator {
297 | display: inline-block;
298 | position: absolute;
299 | top: 15px;
300 | width: 95%;
301 | text-align: center;
302 | font-size: 8px;
303 | line-height: 8px;
304 | color: rgba(255, 255, 255, 0.3);
305 | }
306 | .elevator .buttonpress {
307 | cursor: pointer;
308 | margin: 0;
309 | display: inline-block;
310 | }
311 | .elevator .buttonpress.activated {
312 | color: #33ff44;
313 | }
314 |
315 | .floor {
316 | position: absolute;
317 | width: 100%;
318 | height: 49px;
319 | background-color: rgba(255, 255, 255, 0.1);
320 | background: linear-gradient(rgba(255,255,255,0.1), rgba(255,255,255,0.2), rgba(255,255,255,0.24), rgba(255,255,255,0.1)); /* Standard syntax */
321 | border-bottom: 1px solid #333;
322 | }
323 |
324 | .floor .buttonindicator {
325 | cursor: pointer;
326 | line-height: 50px;
327 | padding-left: 50px;
328 | color: rgba(255, 255, 255, 0.2);
329 | }
330 | .floor .buttonindicator .activated {
331 | color: rgba(55, 255, 55, 1.0);
332 | }
333 |
334 | .floor .floornumber {
335 | position: absolute;
336 | color: rgba(255, 255, 255, 0.15);
337 | font-size: 32px;
338 | line-height: 50px;
339 | padding-left: 10px;
340 | }
341 |
342 |
343 | .footer {
344 | margin-top: 20px;
345 | }
346 | .footer h4 {
347 | font-size: 13px;
348 | }
349 |
350 | .help {
351 | margin-top: 20px;
352 | margin-bottom: 30px;
353 | }
354 |
355 | .help p {
356 | max-width: 75%;
357 | }
358 |
359 | .doctable {
360 | margin-top: 10px;
361 | margin-bottom: 20px;
362 | color: #555;
363 | border: none;
364 | }
365 |
366 | .doctable th, .doctable td {
367 | text-align: left;
368 | border-bottom: 1px solid rgba(0, 0, 0, 0.1);
369 | }
370 |
371 | .doctable tr td:first-child {
372 | padding-right: 5px;
373 | color: #333;
374 | }
375 |
376 | .doctable td {
377 | font: 13px Consolas, Monaco, monospace;
378 | }
379 |
--------------------------------------------------------------------------------
/libs/highlight/highlight.pack.js:
--------------------------------------------------------------------------------
1 | var hljs=new function(){function e(e){return e.replace(/&/gm,"&").replace(//gm,">")}function t(e){return e.nodeName.toLowerCase()}function n(e,t){var n=e&&e.exec(t);return n&&0==n.index}function r(e){var t=(e.className+" "+(e.parentNode?e.parentNode.className:"")).split(/\s+/);return t=t.map(function(e){return e.replace(/^lang(uage)?-/,"")}),t.filter(function(e){return m(e)||/no(-?)highlight/.test(e)})[0]}function i(e,t){var n={};for(var r in e)n[r]=e[r];if(t)for(var r in t)n[r]=t[r];return n}function a(e){var n=[];return function r(e,i){for(var a=e.firstChild;a;a=a.nextSibling)3==a.nodeType?i+=a.nodeValue.length:1==a.nodeType&&(n.push({event:"start",offset:i,node:a}),i=r(a,i),t(a).match(/br|hr|img|input/)||n.push({event:"stop",offset:i,node:a}));return i}(e,0),n}function s(n,r,i){function a(){return n.length&&r.length?n[0].offset!=r[0].offset?n[0].offset"}function o(e){l+=""+t(e)+">"}function c(e){("start"==e.event?s:o)(e.node)}for(var u=0,l="",f=[];n.length||r.length;){var h=a();if(l+=e(i.substr(u,h[0].offset-u)),u=h[0].offset,h==n){f.reverse().forEach(o);do c(h.splice(0,1)[0]),h=a();while(h==n&&h.length&&h[0].offset==u);f.reverse().forEach(s)}else"start"==h[0].event?f.push(h[0].node):f.pop(),c(h.splice(0,1)[0])}return l+e(i.substr(u))}function o(e){function t(e){return e&&e.source||e}function n(n,r){return RegExp(t(n),"m"+(e.cI?"i":"")+(r?"g":""))}function r(a,s){if(!a.compiled){if(a.compiled=!0,a.k=a.k||a.bK,a.k){var o={},c=function(t,n){e.cI&&(n=n.toLowerCase()),n.split(" ").forEach(function(e){var n=e.split("|");o[n[0]]=[t,n[1]?Number(n[1]):1]})};"string"==typeof a.k?c("keyword",a.k):Object.keys(a.k).forEach(function(e){c(e,a.k[e])}),a.k=o}a.lR=n(a.l||/\b[A-Za-z0-9_]+\b/,!0),s&&(a.bK&&(a.b="\\b("+a.bK.split(" ").join("|")+")\\b"),a.b||(a.b=/\B|\b/),a.bR=n(a.b),a.e||a.eW||(a.e=/\B|\b/),a.e&&(a.eR=n(a.e)),a.tE=t(a.e)||"",a.eW&&s.tE&&(a.tE+=(a.e?"|":"")+s.tE)),a.i&&(a.iR=n(a.i)),void 0===a.r&&(a.r=1),a.c||(a.c=[]);var u=[];a.c.forEach(function(e){e.v?e.v.forEach(function(t){u.push(i(e,t))}):u.push("self"==e?a:e)}),a.c=u,a.c.forEach(function(e){r(e,a)}),a.starts&&r(a.starts,s);var l=a.c.map(function(e){return e.bK?"\\.?("+e.b+")\\.?":e.b}).concat([a.tE,a.i]).map(t).filter(Boolean);a.t=l.length?n(l.join("|"),!0):{exec:function(){return null}}}}r(e)}function c(t,r,i,a){function s(e,t){for(var r=0;r";return a+=e+'">',a+t+s}function p(){if(!w.k)return e(B);var t="",n=0;w.lR.lastIndex=0;for(var r=w.lR.exec(B);r;){t+=e(B.substr(n,r.index-n));var i=h(w,r);i?(y+=i[1],t+=g(i[0],e(r[0]))):t+=e(r[0]),n=w.lR.lastIndex,r=w.lR.exec(B)}return t+e(B.substr(n))}function v(){if(w.sL&&!E[w.sL])return e(B);var t=w.sL?c(w.sL,B,!0,L[w.sL]):u(B);return w.r>0&&(y+=t.r),"continuous"==w.subLanguageMode&&(L[w.sL]=t.top),g(t.language,t.value,!1,!0)}function b(){return void 0!==w.sL?v():p()}function d(t,n){var r=t.cN?g(t.cN,"",!0):"";t.rB?(M+=r,B=""):t.eB?(M+=e(n)+r,B=""):(M+=r,B=n),w=Object.create(t,{parent:{value:w}})}function R(t,n){if(B+=t,void 0===n)return M+=b(),0;var r=s(n,w);if(r)return M+=b(),d(r,n),r.rB?0:n.length;var i=l(w,n);if(i){var a=w;a.rE||a.eE||(B+=n),M+=b();do w.cN&&(M+=""),y+=w.r,w=w.parent;while(w!=i.parent);return a.eE&&(M+=e(n)),B="",i.starts&&d(i.starts,""),a.rE?0:n.length}if(f(n,w))throw new Error('Illegal lexeme "'+n+'" for mode "'+(w.cN||"")+'"');return B+=n,n.length||1}var x=m(t);if(!x)throw new Error('Unknown language: "'+t+'"');o(x);for(var w=a||x,L={},M="",k=w;k!=x;k=k.parent)k.cN&&(M=g(k.cN,"",!0)+M);var B="",y=0;try{for(var C,I,j=0;;){if(w.t.lastIndex=j,C=w.t.exec(r),!C)break;I=R(r.substr(j,C.index-j),C[0]),j=C.index+I}R(r.substr(j));for(var k=w;k.parent;k=k.parent)k.cN&&(M+="");return{r:y,value:M,language:t,top:w}}catch(A){if(-1!=A.message.indexOf("Illegal"))return{r:0,value:e(r)};throw A}}function u(t,n){n=n||N.languages||Object.keys(E);var r={r:0,value:e(t)},i=r;return n.forEach(function(e){if(m(e)){var n=c(e,t,!1);n.language=e,n.r>i.r&&(i=n),n.r>r.r&&(i=r,r=n)}}),i.language&&(r.second_best=i),r}function l(e){return N.tabReplace&&(e=e.replace(/^((<[^>]+>|\t)+)/gm,function(e,t){return t.replace(/\t/g,N.tabReplace)})),N.useBR&&(e=e.replace(/\n/g," ")),e}function f(e,t,n){var r=t?R[t]:n,i=[e.trim()];return e.match(/(\s|^)hljs(\s|$)/)||i.push("hljs"),r&&i.push(r),i.join(" ").trim()}function h(e){var t=r(e);if(!/no(-?)highlight/.test(t)){var n;N.useBR?(n=document.createElementNS("http://www.w3.org/1999/xhtml","div"),n.innerHTML=e.innerHTML.replace(/\n/g,"").replace(/ /g,"\n")):n=e;var i=n.textContent,o=t?c(t,i,!0):u(i),h=a(n);if(h.length){var g=document.createElementNS("http://www.w3.org/1999/xhtml","div");g.innerHTML=o.value,o.value=s(h,a(g),i)}o.value=l(o.value),e.innerHTML=o.value,e.className=f(e.className,t,o.language),e.result={language:o.language,re:o.r},o.second_best&&(e.second_best={language:o.second_best.language,re:o.second_best.r})}}function g(e){N=i(N,e)}function p(){if(!p.called){p.called=!0;var e=document.querySelectorAll("pre code");Array.prototype.forEach.call(e,h)}}function v(){addEventListener("DOMContentLoaded",p,!1),addEventListener("load",p,!1)}function b(e,t){var n=E[e]=t(this);n.aliases&&n.aliases.forEach(function(t){R[t]=e})}function d(){return Object.keys(E)}function m(e){return E[e]||E[R[e]]}var N={classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:void 0},E={},R={};this.highlight=c,this.highlightAuto=u,this.fixMarkup=l,this.highlightBlock=h,this.configure=g,this.initHighlighting=p,this.initHighlightingOnLoad=v,this.registerLanguage=b,this.listLanguages=d,this.getLanguage=m,this.inherit=i,this.IR="[a-zA-Z][a-zA-Z0-9_]*",this.UIR="[a-zA-Z_][a-zA-Z0-9_]*",this.NR="\\b\\d+(\\.\\d+)?",this.CNR="(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",this.BNR="\\b(0b[01]+)",this.RSR="!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",this.BE={b:"\\\\[\\s\\S]",r:0},this.ASM={cN:"string",b:"'",e:"'",i:"\\n",c:[this.BE]},this.QSM={cN:"string",b:'"',e:'"',i:"\\n",c:[this.BE]},this.PWM={b:/\b(a|an|the|are|I|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such)\b/},this.CLCM={cN:"comment",b:"//",e:"$",c:[this.PWM]},this.CBCM={cN:"comment",b:"/\\*",e:"\\*/",c:[this.PWM]},this.HCM={cN:"comment",b:"#",e:"$",c:[this.PWM]},this.NM={cN:"number",b:this.NR,r:0},this.CNM={cN:"number",b:this.CNR,r:0},this.BNM={cN:"number",b:this.BNR,r:0},this.CSSNM={cN:"number",b:this.NR+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",r:0},this.RM={cN:"regexp",b:/\//,e:/\/[gimuy]*/,i:/\n/,c:[this.BE,{b:/\[/,e:/\]/,r:0,c:[this.BE]}]},this.TM={cN:"title",b:this.IR,r:0},this.UTM={cN:"title",b:this.UIR,r:0}};hljs.registerLanguage("javascript",function(r){return{aliases:["js"],k:{keyword:"in if for while finally var new function do return void else break catch instanceof with throw case default try this switch continue typeof delete let yield const class",literal:"true false null undefined NaN Infinity",built_in:"eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent encodeURI encodeURIComponent escape unescape Object Function Boolean Error EvalError InternalError RangeError ReferenceError StopIteration SyntaxError TypeError URIError Number Math Date String RegExp Array Float32Array Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array Uint8Array Uint8ClampedArray ArrayBuffer DataView JSON Intl arguments require module console window document"},c:[{cN:"pi",b:/^\s*('|")use strict('|")/,r:10},r.ASM,r.QSM,r.CLCM,r.CBCM,r.CNM,{b:"("+r.RSR+"|\\b(case|return|throw)\\b)\\s*",k:"return throw case",c:[r.CLCM,r.CBCM,r.RM,{b:/,e:/>;/,r:0,sL:"xml"}],r:0},{cN:"function",bK:"function",e:/\{/,eE:!0,c:[r.inherit(r.TM,{b:/[A-Za-z$_][0-9A-Za-z$_]*/}),{cN:"params",b:/\(/,e:/\)/,c:[r.CLCM,r.CBCM],i:/["'\(]/}],i:/\[|%/},{b:/\$[(.]/},{b:"\\."+r.IR,r:0}]}});
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Go Wasm Elevator Saga - the elevator programming game
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
44 |
45 |
54 |
55 |
63 |
64 |
67 |
68 |
75 |
76 |
87 |
88 |
91 |
92 |
120 |
148 |
149 |
150 |
151 |
152 |
153 |
159 |
160 |
161 | Your browser does not appear to support JavaScript. This page contains a browser-based programming game implemented in JavaScript.
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
Transported
173 |
Elapsed time
174 |
Transported/s
175 |
Avg waiting time
176 |
Max waiting time
177 |
Moves
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
Reset
186 |
Undo reset
187 |
Apply
188 |
Save
189 |
190 |
191 |
192 |
193 |
194 |
197 |
202 |
203 |
204 |
205 |
--------------------------------------------------------------------------------
/libs/spark-md5.min.js:
--------------------------------------------------------------------------------
1 | (function(factory){if(typeof exports==="object"){module.exports=factory()}else if(typeof define==="function"&&define.amd){define(factory)}else{var glob;try{glob=window}catch(e){glob=self}glob.SparkMD5=factory()}})(function(undefined){"use strict";var add32=function(a,b){return a+b&4294967295},hex_chr=["0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f"];function cmn(q,a,b,x,s,t){a=add32(add32(a,q),add32(x,t));return add32(a<>>32-s,b)}function md5cycle(x,k){var a=x[0],b=x[1],c=x[2],d=x[3];a+=(b&c|~b&d)+k[0]-680876936|0;a=(a<<7|a>>>25)+b|0;d+=(a&b|~a&c)+k[1]-389564586|0;d=(d<<12|d>>>20)+a|0;c+=(d&a|~d&b)+k[2]+606105819|0;c=(c<<17|c>>>15)+d|0;b+=(c&d|~c&a)+k[3]-1044525330|0;b=(b<<22|b>>>10)+c|0;a+=(b&c|~b&d)+k[4]-176418897|0;a=(a<<7|a>>>25)+b|0;d+=(a&b|~a&c)+k[5]+1200080426|0;d=(d<<12|d>>>20)+a|0;c+=(d&a|~d&b)+k[6]-1473231341|0;c=(c<<17|c>>>15)+d|0;b+=(c&d|~c&a)+k[7]-45705983|0;b=(b<<22|b>>>10)+c|0;a+=(b&c|~b&d)+k[8]+1770035416|0;a=(a<<7|a>>>25)+b|0;d+=(a&b|~a&c)+k[9]-1958414417|0;d=(d<<12|d>>>20)+a|0;c+=(d&a|~d&b)+k[10]-42063|0;c=(c<<17|c>>>15)+d|0;b+=(c&d|~c&a)+k[11]-1990404162|0;b=(b<<22|b>>>10)+c|0;a+=(b&c|~b&d)+k[12]+1804603682|0;a=(a<<7|a>>>25)+b|0;d+=(a&b|~a&c)+k[13]-40341101|0;d=(d<<12|d>>>20)+a|0;c+=(d&a|~d&b)+k[14]-1502002290|0;c=(c<<17|c>>>15)+d|0;b+=(c&d|~c&a)+k[15]+1236535329|0;b=(b<<22|b>>>10)+c|0;a+=(b&d|c&~d)+k[1]-165796510|0;a=(a<<5|a>>>27)+b|0;d+=(a&c|b&~c)+k[6]-1069501632|0;d=(d<<9|d>>>23)+a|0;c+=(d&b|a&~b)+k[11]+643717713|0;c=(c<<14|c>>>18)+d|0;b+=(c&a|d&~a)+k[0]-373897302|0;b=(b<<20|b>>>12)+c|0;a+=(b&d|c&~d)+k[5]-701558691|0;a=(a<<5|a>>>27)+b|0;d+=(a&c|b&~c)+k[10]+38016083|0;d=(d<<9|d>>>23)+a|0;c+=(d&b|a&~b)+k[15]-660478335|0;c=(c<<14|c>>>18)+d|0;b+=(c&a|d&~a)+k[4]-405537848|0;b=(b<<20|b>>>12)+c|0;a+=(b&d|c&~d)+k[9]+568446438|0;a=(a<<5|a>>>27)+b|0;d+=(a&c|b&~c)+k[14]-1019803690|0;d=(d<<9|d>>>23)+a|0;c+=(d&b|a&~b)+k[3]-187363961|0;c=(c<<14|c>>>18)+d|0;b+=(c&a|d&~a)+k[8]+1163531501|0;b=(b<<20|b>>>12)+c|0;a+=(b&d|c&~d)+k[13]-1444681467|0;a=(a<<5|a>>>27)+b|0;d+=(a&c|b&~c)+k[2]-51403784|0;d=(d<<9|d>>>23)+a|0;c+=(d&b|a&~b)+k[7]+1735328473|0;c=(c<<14|c>>>18)+d|0;b+=(c&a|d&~a)+k[12]-1926607734|0;b=(b<<20|b>>>12)+c|0;a+=(b^c^d)+k[5]-378558|0;a=(a<<4|a>>>28)+b|0;d+=(a^b^c)+k[8]-2022574463|0;d=(d<<11|d>>>21)+a|0;c+=(d^a^b)+k[11]+1839030562|0;c=(c<<16|c>>>16)+d|0;b+=(c^d^a)+k[14]-35309556|0;b=(b<<23|b>>>9)+c|0;a+=(b^c^d)+k[1]-1530992060|0;a=(a<<4|a>>>28)+b|0;d+=(a^b^c)+k[4]+1272893353|0;d=(d<<11|d>>>21)+a|0;c+=(d^a^b)+k[7]-155497632|0;c=(c<<16|c>>>16)+d|0;b+=(c^d^a)+k[10]-1094730640|0;b=(b<<23|b>>>9)+c|0;a+=(b^c^d)+k[13]+681279174|0;a=(a<<4|a>>>28)+b|0;d+=(a^b^c)+k[0]-358537222|0;d=(d<<11|d>>>21)+a|0;c+=(d^a^b)+k[3]-722521979|0;c=(c<<16|c>>>16)+d|0;b+=(c^d^a)+k[6]+76029189|0;b=(b<<23|b>>>9)+c|0;a+=(b^c^d)+k[9]-640364487|0;a=(a<<4|a>>>28)+b|0;d+=(a^b^c)+k[12]-421815835|0;d=(d<<11|d>>>21)+a|0;c+=(d^a^b)+k[15]+530742520|0;c=(c<<16|c>>>16)+d|0;b+=(c^d^a)+k[2]-995338651|0;b=(b<<23|b>>>9)+c|0;a+=(c^(b|~d))+k[0]-198630844|0;a=(a<<6|a>>>26)+b|0;d+=(b^(a|~c))+k[7]+1126891415|0;d=(d<<10|d>>>22)+a|0;c+=(a^(d|~b))+k[14]-1416354905|0;c=(c<<15|c>>>17)+d|0;b+=(d^(c|~a))+k[5]-57434055|0;b=(b<<21|b>>>11)+c|0;a+=(c^(b|~d))+k[12]+1700485571|0;a=(a<<6|a>>>26)+b|0;d+=(b^(a|~c))+k[3]-1894986606|0;d=(d<<10|d>>>22)+a|0;c+=(a^(d|~b))+k[10]-1051523|0;c=(c<<15|c>>>17)+d|0;b+=(d^(c|~a))+k[1]-2054922799|0;b=(b<<21|b>>>11)+c|0;a+=(c^(b|~d))+k[8]+1873313359|0;a=(a<<6|a>>>26)+b|0;d+=(b^(a|~c))+k[15]-30611744|0;d=(d<<10|d>>>22)+a|0;c+=(a^(d|~b))+k[6]-1560198380|0;c=(c<<15|c>>>17)+d|0;b+=(d^(c|~a))+k[13]+1309151649|0;b=(b<<21|b>>>11)+c|0;a+=(c^(b|~d))+k[4]-145523070|0;a=(a<<6|a>>>26)+b|0;d+=(b^(a|~c))+k[11]-1120210379|0;d=(d<<10|d>>>22)+a|0;c+=(a^(d|~b))+k[2]+718787259|0;c=(c<<15|c>>>17)+d|0;b+=(d^(c|~a))+k[9]-343485551|0;b=(b<<21|b>>>11)+c|0;x[0]=a+x[0]|0;x[1]=b+x[1]|0;x[2]=c+x[2]|0;x[3]=d+x[3]|0}function md5blk(s){var md5blks=[],i;for(i=0;i<64;i+=4){md5blks[i>>2]=s.charCodeAt(i)+(s.charCodeAt(i+1)<<8)+(s.charCodeAt(i+2)<<16)+(s.charCodeAt(i+3)<<24)}return md5blks}function md5blk_array(a){var md5blks=[],i;for(i=0;i<64;i+=4){md5blks[i>>2]=a[i]+(a[i+1]<<8)+(a[i+2]<<16)+(a[i+3]<<24)}return md5blks}function md51(s){var n=s.length,state=[1732584193,-271733879,-1732584194,271733878],i,length,tail,tmp,lo,hi;for(i=64;i<=n;i+=64){md5cycle(state,md5blk(s.substring(i-64,i)))}s=s.substring(i-64);length=s.length;tail=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];for(i=0;i>2]|=s.charCodeAt(i)<<(i%4<<3)}tail[i>>2]|=128<<(i%4<<3);if(i>55){md5cycle(state,tail);for(i=0;i<16;i+=1){tail[i]=0}}tmp=n*8;tmp=tmp.toString(16).match(/(.*?)(.{0,8})$/);lo=parseInt(tmp[2],16);hi=parseInt(tmp[1],16)||0;tail[14]=lo;tail[15]=hi;md5cycle(state,tail);return state}function md51_array(a){var n=a.length,state=[1732584193,-271733879,-1732584194,271733878],i,length,tail,tmp,lo,hi;for(i=64;i<=n;i+=64){md5cycle(state,md5blk_array(a.subarray(i-64,i)))}a=i-64>2]|=a[i]<<(i%4<<3)}tail[i>>2]|=128<<(i%4<<3);if(i>55){md5cycle(state,tail);for(i=0;i<16;i+=1){tail[i]=0}}tmp=n*8;tmp=tmp.toString(16).match(/(.*?)(.{0,8})$/);lo=parseInt(tmp[2],16);hi=parseInt(tmp[1],16)||0;tail[14]=lo;tail[15]=hi;md5cycle(state,tail);return state}function rhex(n){var s="",j;for(j=0;j<4;j+=1){s+=hex_chr[n>>j*8+4&15]+hex_chr[n>>j*8&15]}return s}function hex(x){var i;for(i=0;i>16)+(y>>16)+(lsw>>16);return msw<<16|lsw&65535}}if(typeof ArrayBuffer!=="undefined"&&!ArrayBuffer.prototype.slice){(function(){function clamp(val,length){val=val|0||0;if(val<0){return Math.max(val+length,0)}return Math.min(val,length)}ArrayBuffer.prototype.slice=function(from,to){var length=this.byteLength,begin=clamp(from,length),end=length,num,target,targetArray,sourceArray;if(to!==undefined){end=clamp(to,length)}if(begin>end){return new ArrayBuffer(0)}num=end-begin;target=new ArrayBuffer(num);targetArray=new Uint8Array(target);sourceArray=new Uint8Array(this,begin,num);targetArray.set(sourceArray);return target}})()}function toUtf8(str){if(/[\u0080-\uFFFF]/.test(str)){str=unescape(encodeURIComponent(str))}return str}function utf8Str2ArrayBuffer(str,returnUInt8Array){var length=str.length,buff=new ArrayBuffer(length),arr=new Uint8Array(buff),i;for(i=0;i>2]|=buff.charCodeAt(i)<<(i%4<<3)}this._finish(tail,length);ret=hex(this._hash);if(raw){ret=hexToBinaryString(ret)}this.reset();return ret};SparkMD5.prototype.reset=function(){this._buff="";this._length=0;this._hash=[1732584193,-271733879,-1732584194,271733878];return this};SparkMD5.prototype.getState=function(){return{buff:this._buff,length:this._length,hash:this._hash}};SparkMD5.prototype.setState=function(state){this._buff=state.buff;this._length=state.length;this._hash=state.hash;return this};SparkMD5.prototype.destroy=function(){delete this._hash;delete this._buff;delete this._length};SparkMD5.prototype._finish=function(tail,length){var i=length,tmp,lo,hi;tail[i>>2]|=128<<(i%4<<3);if(i>55){md5cycle(this._hash,tail);for(i=0;i<16;i+=1){tail[i]=0}}tmp=this._length*8;tmp=tmp.toString(16).match(/(.*?)(.{0,8})$/);lo=parseInt(tmp[2],16);hi=parseInt(tmp[1],16)||0;tail[14]=lo;tail[15]=hi;md5cycle(this._hash,tail)};SparkMD5.hash=function(str,raw){return SparkMD5.hashBinary(toUtf8(str),raw)};SparkMD5.hashBinary=function(content,raw){var hash=md51(content),ret=hex(hash);return raw?hexToBinaryString(ret):ret};SparkMD5.ArrayBuffer=function(){this.reset()};SparkMD5.ArrayBuffer.prototype.append=function(arr){var buff=concatenateArrayBuffers(this._buff.buffer,arr,true),length=buff.length,i;this._length+=arr.byteLength;for(i=64;i<=length;i+=64){md5cycle(this._hash,md5blk_array(buff.subarray(i-64,i)))}this._buff=i-64>2]|=buff[i]<<(i%4<<3)}this._finish(tail,length);ret=hex(this._hash);if(raw){ret=hexToBinaryString(ret)}this.reset();return ret};SparkMD5.ArrayBuffer.prototype.reset=function(){this._buff=new Uint8Array(0);this._length=0;this._hash=[1732584193,-271733879,-1732584194,271733878];return this};SparkMD5.ArrayBuffer.prototype.getState=function(){var state=SparkMD5.prototype.getState.call(this);state.buff=arrayBuffer2Utf8Str(state.buff);return state};SparkMD5.ArrayBuffer.prototype.setState=function(state){state.buff=utf8Str2ArrayBuffer(state.buff,true);return SparkMD5.prototype.setState.call(this,state)};SparkMD5.ArrayBuffer.prototype.destroy=SparkMD5.prototype.destroy;SparkMD5.ArrayBuffer.prototype._finish=SparkMD5.prototype._finish;SparkMD5.ArrayBuffer.hash=function(arr,raw){var hash=md51_array(new Uint8Array(arr)),ret=hex(hash);return raw?hexToBinaryString(ret):ret};return SparkMD5});
2 |
--------------------------------------------------------------------------------
/libs/codemirror/codemirror.css:
--------------------------------------------------------------------------------
1 | /* BASICS */
2 |
3 | .CodeMirror {
4 | /* Set height, width, borders, and global font properties here */
5 | font-family: monospace;
6 | height: 300px;
7 | color: black;
8 | direction: ltr;
9 | }
10 |
11 | /* PADDING */
12 |
13 | .CodeMirror-lines {
14 | padding: 4px 0; /* Vertical padding around content */
15 | }
16 | .CodeMirror pre {
17 | padding: 0 4px; /* Horizontal padding of content */
18 | }
19 |
20 | .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
21 | background-color: white; /* The little square between H and V scrollbars */
22 | }
23 |
24 | /* GUTTER */
25 |
26 | .CodeMirror-gutters {
27 | border-right: 1px solid #ddd;
28 | background-color: #f7f7f7;
29 | white-space: nowrap;
30 | }
31 | .CodeMirror-linenumbers {}
32 | .CodeMirror-linenumber {
33 | padding: 0 3px 0 5px;
34 | min-width: 20px;
35 | text-align: right;
36 | color: #999;
37 | white-space: nowrap;
38 | }
39 |
40 | .CodeMirror-guttermarker { color: black; }
41 | .CodeMirror-guttermarker-subtle { color: #999; }
42 |
43 | /* CURSOR */
44 |
45 | .CodeMirror-cursor {
46 | border-left: 1px solid black;
47 | border-right: none;
48 | width: 0;
49 | }
50 | /* Shown when moving in bi-directional text */
51 | .CodeMirror div.CodeMirror-secondarycursor {
52 | border-left: 1px solid silver;
53 | }
54 | .cm-fat-cursor .CodeMirror-cursor {
55 | width: auto;
56 | border: 0 !important;
57 | background: #7e7;
58 | }
59 | .cm-fat-cursor div.CodeMirror-cursors {
60 | z-index: 1;
61 | }
62 | .cm-fat-cursor-mark {
63 | background-color: rgba(20, 255, 20, 0.5);
64 | -webkit-animation: blink 1.06s steps(1) infinite;
65 | -moz-animation: blink 1.06s steps(1) infinite;
66 | animation: blink 1.06s steps(1) infinite;
67 | }
68 | .cm-animate-fat-cursor {
69 | width: auto;
70 | border: 0;
71 | -webkit-animation: blink 1.06s steps(1) infinite;
72 | -moz-animation: blink 1.06s steps(1) infinite;
73 | animation: blink 1.06s steps(1) infinite;
74 | background-color: #7e7;
75 | }
76 | @-moz-keyframes blink {
77 | 0% {}
78 | 50% { background-color: transparent; }
79 | 100% {}
80 | }
81 | @-webkit-keyframes blink {
82 | 0% {}
83 | 50% { background-color: transparent; }
84 | 100% {}
85 | }
86 | @keyframes blink {
87 | 0% {}
88 | 50% { background-color: transparent; }
89 | 100% {}
90 | }
91 |
92 | /* Can style cursor different in overwrite (non-insert) mode */
93 | .CodeMirror-overwrite .CodeMirror-cursor {}
94 |
95 | .cm-tab { display: inline-block; text-decoration: inherit; }
96 |
97 | .CodeMirror-rulers {
98 | position: absolute;
99 | left: 0; right: 0; top: -50px; bottom: -20px;
100 | overflow: hidden;
101 | }
102 | .CodeMirror-ruler {
103 | border-left: 1px solid #ccc;
104 | top: 0; bottom: 0;
105 | position: absolute;
106 | }
107 |
108 | /* DEFAULT THEME */
109 |
110 | .cm-s-default .cm-header {color: blue;}
111 | .cm-s-default .cm-quote {color: #090;}
112 | .cm-negative {color: #d44;}
113 | .cm-positive {color: #292;}
114 | .cm-header, .cm-strong {font-weight: bold;}
115 | .cm-em {font-style: italic;}
116 | .cm-link {text-decoration: underline;}
117 | .cm-strikethrough {text-decoration: line-through;}
118 |
119 | .cm-s-default .cm-keyword {color: #708;}
120 | .cm-s-default .cm-atom {color: #219;}
121 | .cm-s-default .cm-number {color: #164;}
122 | .cm-s-default .cm-def {color: #00f;}
123 | .cm-s-default .cm-variable,
124 | .cm-s-default .cm-punctuation,
125 | .cm-s-default .cm-property,
126 | .cm-s-default .cm-operator {}
127 | .cm-s-default .cm-variable-2 {color: #05a;}
128 | .cm-s-default .cm-variable-3, .cm-s-default .cm-type {color: #085;}
129 | .cm-s-default .cm-comment {color: #a50;}
130 | .cm-s-default .cm-string {color: #a11;}
131 | .cm-s-default .cm-string-2 {color: #f50;}
132 | .cm-s-default .cm-meta {color: #555;}
133 | .cm-s-default .cm-qualifier {color: #555;}
134 | .cm-s-default .cm-builtin {color: #30a;}
135 | .cm-s-default .cm-bracket {color: #997;}
136 | .cm-s-default .cm-tag {color: #170;}
137 | .cm-s-default .cm-attribute {color: #00c;}
138 | .cm-s-default .cm-hr {color: #999;}
139 | .cm-s-default .cm-link {color: #00c;}
140 |
141 | .cm-s-default .cm-error {color: #f00;}
142 | .cm-invalidchar {color: #f00;}
143 |
144 | .CodeMirror-composing { border-bottom: 2px solid; }
145 |
146 | /* Default styles for common addons */
147 |
148 | div.CodeMirror span.CodeMirror-matchingbracket {color: #0b0;}
149 | div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #a22;}
150 | .CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); }
151 | .CodeMirror-activeline-background {background: #e8f2ff;}
152 |
153 | /* STOP */
154 |
155 | /* The rest of this file contains styles related to the mechanics of
156 | the editor. You probably shouldn't touch them. */
157 |
158 | .CodeMirror {
159 | position: relative;
160 | overflow: hidden;
161 | background: white;
162 | }
163 |
164 | .CodeMirror-scroll {
165 | overflow: scroll !important; /* Things will break if this is overridden */
166 | /* 30px is the magic margin used to hide the element's real scrollbars */
167 | /* See overflow: hidden in .CodeMirror */
168 | margin-bottom: -30px; margin-right: -30px;
169 | padding-bottom: 30px;
170 | height: 100%;
171 | outline: none; /* Prevent dragging from highlighting the element */
172 | position: relative;
173 | }
174 | .CodeMirror-sizer {
175 | position: relative;
176 | border-right: 30px solid transparent;
177 | }
178 |
179 | /* The fake, visible scrollbars. Used to force redraw during scrolling
180 | before actual scrolling happens, thus preventing shaking and
181 | flickering artifacts. */
182 | .CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
183 | position: absolute;
184 | z-index: 6;
185 | display: none;
186 | }
187 | .CodeMirror-vscrollbar {
188 | right: 0; top: 0;
189 | overflow-x: hidden;
190 | overflow-y: scroll;
191 | }
192 | .CodeMirror-hscrollbar {
193 | bottom: 0; left: 0;
194 | overflow-y: hidden;
195 | overflow-x: scroll;
196 | }
197 | .CodeMirror-scrollbar-filler {
198 | right: 0; bottom: 0;
199 | }
200 | .CodeMirror-gutter-filler {
201 | left: 0; bottom: 0;
202 | }
203 |
204 | .CodeMirror-gutters {
205 | position: absolute; left: 0; top: 0;
206 | min-height: 100%;
207 | z-index: 3;
208 | }
209 | .CodeMirror-gutter {
210 | white-space: normal;
211 | height: 100%;
212 | display: inline-block;
213 | vertical-align: top;
214 | margin-bottom: -30px;
215 | }
216 | .CodeMirror-gutter-wrapper {
217 | position: absolute;
218 | z-index: 4;
219 | background: none !important;
220 | border: none !important;
221 | }
222 | .CodeMirror-gutter-background {
223 | position: absolute;
224 | top: 0; bottom: 0;
225 | z-index: 4;
226 | }
227 | .CodeMirror-gutter-elt {
228 | position: absolute;
229 | cursor: default;
230 | z-index: 4;
231 | }
232 | .CodeMirror-gutter-wrapper ::selection { background-color: transparent }
233 | .CodeMirror-gutter-wrapper ::-moz-selection { background-color: transparent }
234 |
235 | .CodeMirror-lines {
236 | cursor: text;
237 | min-height: 1px; /* prevents collapsing before first draw */
238 | }
239 | .CodeMirror pre {
240 | /* Reset some styles that the rest of the page might have set */
241 | -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0;
242 | border-width: 0;
243 | background: transparent;
244 | font-family: inherit;
245 | font-size: inherit;
246 | margin: 0;
247 | white-space: pre;
248 | word-wrap: normal;
249 | line-height: inherit;
250 | color: inherit;
251 | z-index: 2;
252 | position: relative;
253 | overflow: visible;
254 | -webkit-tap-highlight-color: transparent;
255 | -webkit-font-variant-ligatures: contextual;
256 | font-variant-ligatures: contextual;
257 | }
258 | .CodeMirror-wrap pre {
259 | word-wrap: break-word;
260 | white-space: pre-wrap;
261 | word-break: normal;
262 | }
263 |
264 | .CodeMirror-linebackground {
265 | position: absolute;
266 | left: 0; right: 0; top: 0; bottom: 0;
267 | z-index: 0;
268 | }
269 |
270 | .CodeMirror-linewidget {
271 | position: relative;
272 | z-index: 2;
273 | padding: 0.1px; /* Force widget margins to stay inside of the container */
274 | }
275 |
276 | .CodeMirror-widget {}
277 |
278 | .CodeMirror-rtl pre { direction: rtl; }
279 |
280 | .CodeMirror-code {
281 | outline: none;
282 | }
283 |
284 | /* Force content-box sizing for the elements where we expect it */
285 | .CodeMirror-scroll,
286 | .CodeMirror-sizer,
287 | .CodeMirror-gutter,
288 | .CodeMirror-gutters,
289 | .CodeMirror-linenumber {
290 | -moz-box-sizing: content-box;
291 | box-sizing: content-box;
292 | }
293 |
294 | .CodeMirror-measure {
295 | position: absolute;
296 | width: 100%;
297 | height: 0;
298 | overflow: hidden;
299 | visibility: hidden;
300 | }
301 |
302 | .CodeMirror-cursor {
303 | position: absolute;
304 | pointer-events: none;
305 | }
306 | .CodeMirror-measure pre { position: static; }
307 |
308 | div.CodeMirror-cursors {
309 | visibility: hidden;
310 | position: relative;
311 | z-index: 3;
312 | }
313 | div.CodeMirror-dragcursors {
314 | visibility: visible;
315 | }
316 |
317 | .CodeMirror-focused div.CodeMirror-cursors {
318 | visibility: visible;
319 | }
320 |
321 | .CodeMirror-selected { background: #d9d9d9; }
322 | .CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }
323 | .CodeMirror-crosshair { cursor: crosshair; }
324 | .CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; }
325 | .CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; }
326 |
327 | .cm-searching {
328 | background-color: #ffa;
329 | background-color: rgba(255, 255, 0, .4);
330 | }
331 |
332 | /* Used to force a border model for a node */
333 | .cm-force-border { padding-right: .1px; }
334 |
335 | @media print {
336 | /* Hide the cursor when printing */
337 | .CodeMirror div.CodeMirror-cursors {
338 | visibility: hidden;
339 | }
340 | }
341 |
342 | /* See issue #2901 */
343 | .cm-tab-wrap-hack:after { content: ''; }
344 |
345 | /* Help users use markselection to safely style text background */
346 | span.CodeMirror-selectedtext { background: none; }
347 |
--------------------------------------------------------------------------------
/app.js:
--------------------------------------------------------------------------------
1 |
2 | var createEditor = function () {
3 | var lsKey = "elevatorCrushCode_v5";
4 |
5 | var cm = CodeMirror.fromTextArea(document.getElementById("code"), {
6 | lineNumbers: true,
7 | indentUnit: 4,
8 | indentWithTabs: false,
9 | theme: "solarized light",
10 | mode: "go",
11 | autoCloseBrackets: true,
12 | extraKeys: {
13 | // the following Tab key mapping is from http://codemirror.net/doc/manual.html#keymaps
14 | Tab: function (cm) {
15 | var spaces = new Array(cm.getOption("indentUnit") + 1).join(" ");
16 | cm.replaceSelection(spaces);
17 | }
18 | }
19 | });
20 |
21 | // reindent on paste (adapted from https://github.com/ahuth/brackets-paste-and-indent/blob/master/main.js)
22 | cm.on("change", function (codeMirror, change) {
23 | if (change.origin !== "paste") {
24 | return;
25 | }
26 |
27 | var lineFrom = change.from.line;
28 | var lineTo = change.from.line + change.text.length;
29 |
30 | function reindentLines(codeMirror, lineFrom, lineTo) {
31 | codeMirror.operation(function () {
32 | codeMirror.eachLine(lineFrom, lineTo, function (lineHandle) {
33 | codeMirror.indentLine(lineHandle.lineNo(), "smart");
34 | });
35 | });
36 | }
37 |
38 | reindentLines(codeMirror, lineFrom, lineTo);
39 | });
40 |
41 | var reset = function () {
42 | cm.setValue($("#default-elev-implementation").text().trim());
43 | };
44 | var saveCode = function () {
45 | localStorage.setItem(lsKey, cm.getValue());
46 | $("#save_message").text("Code saved " + new Date().toTimeString());
47 | returnObj.trigger("change");
48 | };
49 |
50 | var existingCode = localStorage.getItem(lsKey);
51 | if (existingCode) {
52 | cm.setValue(existingCode);
53 | } else {
54 | reset();
55 | }
56 |
57 | $("#button_save").click(function () {
58 | saveCode();
59 | cm.focus();
60 | });
61 |
62 | $("#button_reset").click(function () {
63 | if (confirm("Do you really want to reset to the default implementation?")) {
64 | localStorage.setItem("develevateBackupCode", cm.getValue());
65 | reset();
66 | }
67 | cm.focus();
68 | });
69 |
70 | $("#button_resetundo").click(function () {
71 | if (confirm("Do you want to bring back the code as before the last reset?")) {
72 | cm.setValue(localStorage.getItem("develevateBackupCode") || "");
73 | }
74 | cm.focus();
75 | });
76 |
77 | var returnObj = riot.observable({});
78 | var autoSaver = _.debounce(saveCode, 1000);
79 | cm.on("change", function () {
80 | autoSaver();
81 | });
82 |
83 | returnObj.getCodeObj = function () {
84 | console.log("Getting code...");
85 | var code = cm.getValue();
86 |
87 | return GoWasmBuilder.getCodeObjFromCode(code).then((codeObj) => {
88 | returnObj.trigger("code_success");
89 | return codeObj;
90 | }).catch((err) => {
91 | returnObj.trigger("usercode_error", err);
92 | return Promise.reject(err)
93 | })
94 | };
95 | returnObj.setCode = function (code) {
96 | cm.setValue(code);
97 | };
98 | returnObj.getCode = function () {
99 | return cm.getValue();
100 | }
101 | returnObj.setDevTestCode = function () {
102 | cm.setValue($("#devtest-elev-implementation").text().trim());
103 | }
104 |
105 | $("#button_apply").click(function () {
106 | $(this).attr("disabled", true)
107 | $("#building_message").text("Building Code ...");
108 | returnObj.trigger("apply_code");
109 | });
110 | return returnObj;
111 | };
112 |
113 |
114 | var createParamsUrl = function (current, overrides) {
115 | return "#" + _.map(_.merge(current, overrides), function (val, key) {
116 | return key + "=" + val;
117 | }).join(",");
118 | };
119 |
120 |
121 |
122 | $(function () {
123 | var tsKey = "elevatorTimeScale";
124 | var editor = createEditor();
125 |
126 | var params = {};
127 |
128 | var $world = $(".innerworld");
129 | var $stats = $(".statscontainer");
130 | var $feedback = $(".feedbackcontainer");
131 | var $challenge = $(".challenge");
132 | var $codestatus = $(".codestatus");
133 |
134 | var floorTempl = document.getElementById("floor-template").innerHTML.trim();
135 | var elevatorTempl = document.getElementById("elevator-template").innerHTML.trim();
136 | var elevatorButtonTempl = document.getElementById("elevatorbutton-template").innerHTML.trim();
137 | var userTempl = document.getElementById("user-template").innerHTML.trim();
138 | var challengeTempl = document.getElementById("challenge-template").innerHTML.trim();
139 | var feedbackTempl = document.getElementById("feedback-template").innerHTML.trim();
140 | var codeStatusTempl = document.getElementById("codestatus-template").innerHTML.trim();
141 |
142 | var app = riot.observable({});
143 | app.worldController = createWorldController(1.0 / 60.0);
144 | app.worldController.on("usercode_error", function (e) {
145 | console.log("World raised code error", e);
146 | editor.trigger("usercode_error", e);
147 | });
148 |
149 | console.log(app.worldController);
150 | app.worldCreator = createWorldCreator();
151 | app.world = undefined;
152 |
153 | app.currentChallengeIndex = 0;
154 |
155 | app.startStopOrRestart = function () {
156 | if (app.world.challengeEnded) {
157 | app.startChallenge(app.currentChallengeIndex);
158 | } else {
159 | app.worldController.setPaused(!app.worldController.isPaused);
160 | }
161 | };
162 |
163 | app.startChallenge = async function (challengeIndex, autoStart) {
164 | if (typeof app.world !== "undefined") {
165 | app.world.unWind();
166 | // TODO: Investigate if memory leaks happen here
167 | }
168 | app.currentChallengeIndex = challengeIndex;
169 | app.world = app.worldCreator.createWorld(challenges[challengeIndex].options);
170 | window.world = app.world;
171 |
172 | clearAll([$world, $feedback]);
173 | presentStats($stats, app.world);
174 | presentChallenge($challenge, challenges[challengeIndex], app, app.world, app.worldController, challengeIndex + 1, challengeTempl);
175 | presentWorld($world, app.world, floorTempl, elevatorTempl, elevatorButtonTempl, userTempl);
176 |
177 | app.worldController.on("timescale_changed", function () {
178 | localStorage.setItem(tsKey, app.worldController.timeScale);
179 | presentChallenge($challenge, challenges[challengeIndex], app, app.world, app.worldController, challengeIndex + 1, challengeTempl);
180 | });
181 |
182 | app.world.on("stats_changed", function () {
183 | var challengeStatus = challenges[challengeIndex].condition.evaluate(app.world);
184 | if (challengeStatus !== null) {
185 | app.world.challengeEnded = true;
186 | app.worldController.setPaused(true);
187 | if (challengeStatus) {
188 | presentFeedback($feedback, feedbackTempl, app.world, "Success!", "Challenge completed", createParamsUrl(params, { challenge: (challengeIndex + 2) }));
189 | } else {
190 | presentFeedback($feedback, feedbackTempl, app.world, "Challenge failed", "Maybe your program needs an improvement?", "");
191 | }
192 | }
193 | });
194 |
195 | if (autoStart) {
196 | try {
197 | let codeObj = await editor.getCodeObj()
198 | $("#button_apply").attr("disabled", false)
199 | $("#building_message").text("");
200 | console.log("Starting...");
201 | app.worldController.start(app.world, codeObj, window.requestAnimationFrame, autoStart);
202 | }
203 | catch (err) {
204 | $("#button_apply").attr("disabled", false)
205 | $("#building_message").text("");
206 | throw err
207 | }
208 | }
209 | else {
210 | app.worldController.start(app.world, {}, window.requestAnimationFrame, autoStart);
211 | }
212 | };
213 |
214 | editor.on("apply_code", function () {
215 | app.startChallenge(app.currentChallengeIndex, true);
216 | });
217 | editor.on("code_success", function () {
218 | presentCodeStatus($codestatus, codeStatusTempl);
219 | });
220 | editor.on("usercode_error", function (error) {
221 | presentCodeStatus($codestatus, codeStatusTempl, error);
222 | });
223 | editor.on("change", function () {
224 | $("#fitness_message").addClass("faded");
225 | var codeStr = editor.getCode();
226 | // fitnessSuite(codeStr, true, function(results) {
227 | // var message = "";
228 | // if(!results.error) {
229 | // message = "Fitness avg wait times: " + _.map(results, function(r){ return r.options.description + ": " + r.result.avgWaitTime.toPrecision(3) + "s" }).join("   ");
230 | // } else {
231 | // message = "Could not compute fitness due to error: " + results.error;
232 | // }
233 | // $("#fitness_message").html(message).removeClass("faded");
234 | // });
235 | });
236 | editor.trigger("change");
237 |
238 | riot.route(function (path) {
239 | params = _.reduce(path.split(","), function (result, p) {
240 | var match = p.match(/(\w+)=(\w+$)/);
241 | if (match) { result[match[1]] = match[2]; } return result;
242 | }, {});
243 | var requestedChallenge = 0;
244 | var autoStart = false;
245 | var timeScale = parseFloat(localStorage.getItem(tsKey)) || 2.0;
246 | _.each(params, function (val, key) {
247 | if (key === "challenge") {
248 | requestedChallenge = _.parseInt(val) - 1;
249 | if (requestedChallenge < 0 || requestedChallenge >= challenges.length) {
250 | console.log("Invalid challenge index", requestedChallenge);
251 | console.log("Defaulting to first challenge");
252 | requestedChallenge = 0;
253 | }
254 | } else if (key === "autostart") {
255 | autoStart = val === "false" ? false : true;
256 | } else if (key === "timescale") {
257 | timeScale = parseFloat(val);
258 | } else if (key === "devtest") {
259 | editor.setDevTestCode();
260 | } else if (key === "fullscreen") {
261 | makeDemoFullscreen();
262 | }
263 | });
264 | app.worldController.setTimeScale(timeScale);
265 | app.startChallenge(requestedChallenge, autoStart);
266 | });
267 | });
268 |
--------------------------------------------------------------------------------
/elevator.js:
--------------------------------------------------------------------------------
1 | function newElevStateHandler(elevator) { elevator.handleNewState(); }
2 |
3 | function Elevator(speedFloorsPerSec, floorCount, floorHeight, maxUsers) {
4 | newGuard(this, Elevator);
5 | Movable.call(this);
6 | var elevator = this;
7 |
8 | elevator.ACCELERATION = floorHeight * 2.1;
9 | elevator.DECELERATION = floorHeight * 2.6;
10 | elevator.MAXSPEED = floorHeight * speedFloorsPerSec;
11 | elevator.floorCount = floorCount;
12 | elevator.floorHeight = floorHeight;
13 | elevator.maxUsers = maxUsers || 4;
14 | elevator.destinationY = 0.0;
15 | elevator.velocityY = 0.0;
16 | // isMoving flag is needed when going to same floor again - need to re-raise events
17 | elevator.isMoving = false;
18 |
19 | elevator.goingDownIndicator = true;
20 | elevator.goingUpIndicator = true;
21 |
22 | elevator.currentFloor = 0;
23 | elevator.previousTruncFutureFloorIfStopped = 0;
24 | elevator.buttonStates = _.map(_.range(floorCount), function(e, i){ return false; });
25 | elevator.moveCount = 0;
26 | elevator.removed = false;
27 | elevator.userSlots = _.map(_.range(elevator.maxUsers), function(user, i) {
28 | return { pos: [2 + (i * 10), 30], user: null};
29 | });
30 | elevator.width = elevator.maxUsers * 10;
31 | elevator.destinationY = elevator.getYPosOfFloor(elevator.currentFloor);
32 |
33 | elevator.on("new_state", newElevStateHandler);
34 |
35 | elevator.on("change:goingUpIndicator", function(value){
36 | elevator.trigger("indicatorstate_change", {up: elevator.goingUpIndicator, down: elevator.goingDownIndicator});
37 | });
38 |
39 | elevator.on("change:goingDownIndicator", function(value){
40 | elevator.trigger("indicatorstate_change", {up: elevator.goingUpIndicator, down: elevator.goingDownIndicator});
41 | });
42 | };
43 | Elevator.prototype = Object.create(Movable.prototype);
44 |
45 | Elevator.prototype.setFloorPosition = function(floor) {
46 | var destination = this.getYPosOfFloor(floor);
47 | this.currentFloor = floor;
48 | this.previousTruncFutureFloorIfStopped = floor;
49 | this.moveTo(null, destination);
50 | };
51 |
52 | Elevator.prototype.userEntering = function(user) {
53 | var randomOffset = _.random(this.userSlots.length - 1);
54 | for(var i=0; i toFloorNum) { return this.goingDownIndicator; }
177 | if(fromFloorNum < toFloorNum) { return this.goingUpIndicator; }
178 | return true;
179 | };
180 |
181 | Elevator.prototype.getYPosOfFloor = function(floorNum) {
182 | return (this.floorCount - 1) * this.floorHeight - floorNum * this.floorHeight;
183 | };
184 |
185 | Elevator.prototype.getExactFloorOfYPos = function(y) {
186 | return ((this.floorCount - 1) * this.floorHeight - y) / this.floorHeight;
187 | };
188 |
189 | Elevator.prototype.getExactCurrentFloor = function() {
190 | return this.getExactFloorOfYPos(this.y);
191 | };
192 |
193 | Elevator.prototype.getDestinationFloor = function() {
194 | return this.getExactFloorOfYPos(this.destinationY);
195 | };
196 |
197 | Elevator.prototype.getRoundedCurrentFloor = function() {
198 | return Math.round(this.getExactCurrentFloor());
199 | };
200 |
201 | Elevator.prototype.getExactFutureFloorIfStopped = function() {
202 | var distanceNeededToStop = distanceNeededToAchieveSpeed(this.velocityY, 0.0, this.DECELERATION);
203 | return this.getExactFloorOfYPos(this.y - Math.sign(this.velocityY) * distanceNeededToStop);
204 | };
205 |
206 | Elevator.prototype.isApproachingFloor = function(floorNum) {
207 | var floorYPos = this.getYPosOfFloor(floorNum);
208 | var elevToFloor = floorYPos - this.y;
209 | return this.velocityY !== 0.0 && (Math.sign(this.velocityY) === Math.sign(elevToFloor));
210 | };
211 |
212 | Elevator.prototype.isOnAFloor = function() {
213 | return epsilonEquals(this.getExactCurrentFloor(), this.getRoundedCurrentFloor());
214 | };
215 |
216 | Elevator.prototype.getLoadFactor = function() {
217 | var load = _.reduce(this.userSlots, function(sum, slot) { return sum + (slot.user ? slot.user.weight : 0); }, 0);
218 | return load / (this.maxUsers * 100);
219 | };
220 |
221 | Elevator.prototype.isFull = function() {
222 | for(var i=0; i 0.0 ? "down" : "up";
253 | this.trigger("passing_floor", floorBeingPassed, direction);
254 | }
255 | }
256 | this.previousTruncFutureFloorIfStopped = futureTruncFloorIfStopped;
257 | };
258 |
--------------------------------------------------------------------------------
/world.js:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | var createWorldCreator = function() {
5 | var creator = {};
6 |
7 | creator.createFloors = function(floorCount, floorHeight, errorHandler) {
8 | var floors = _.map(_.range(floorCount), function(e, i) {
9 | var yPos = (floorCount - 1 - i) * floorHeight;
10 | var floor = asFloor({}, i, yPos, errorHandler);
11 | return floor;
12 | });
13 | return floors;
14 | };
15 | creator.createElevators = function(elevatorCount, floorCount, floorHeight, elevatorCapacities) {
16 | elevatorCapacities = elevatorCapacities || [4];
17 | var currentX = 200.0;
18 | var elevators = _.map(_.range(elevatorCount), function(e, i) {
19 | var elevator = new Elevator(2.6, floorCount, floorHeight, elevatorCapacities[i%elevatorCapacities.length]);
20 |
21 | // Move to right x position
22 | elevator.moveTo(currentX, null);
23 | elevator.setFloorPosition(0);
24 | elevator.updateDisplayPosition();
25 | currentX += (20 + elevator.width);
26 | return elevator;
27 | });
28 | return elevators;
29 | };
30 |
31 | creator.createRandomUser = function() {
32 | var weight = _.random(55, 100);
33 | var user = new User(weight);
34 | if(_.random(40) === 0) {
35 | user.displayType = "child";
36 | } else if(_.random(1) === 0) {
37 | user.displayType = "female";
38 | } else {
39 | user.displayType = "male";
40 | }
41 | return user;
42 | };
43 |
44 | creator.spawnUserRandomly = function(floorCount, floorHeight, floors) {
45 | var user = creator.createRandomUser();
46 | user.moveTo(105+_.random(40), 0);
47 | var currentFloor = _.random(1) === 0 ? 0 : _.random(floorCount - 1);
48 | var destinationFloor;
49 | if(currentFloor === 0) {
50 | // Definitely going up
51 | destinationFloor = _.random(1, floorCount - 1);
52 | } else {
53 | // Usually going down, but sometimes not
54 | if(_.random(10) === 0) {
55 | destinationFloor = (currentFloor + _.random(1, floorCount - 1)) % floorCount;
56 | } else {
57 | destinationFloor = 0;
58 | }
59 | }
60 | user.appearOnFloor(floors[currentFloor], destinationFloor);
61 | return user;
62 | };
63 |
64 | creator.createWorld = function(options) {
65 | console.log("Creating world with options", options);
66 | var defaultOptions = { floorHeight: 50, floorCount: 4, elevatorCount: 2, spawnRate: 0.5 };
67 | options = _.defaults(_.clone(options), defaultOptions);
68 | var world = {floorHeight: options.floorHeight, transportedCounter: 0};
69 | riot.observable(world);
70 |
71 | var handleUserCodeError = function(e) {
72 | world.trigger("usercode_error", e);
73 | }
74 |
75 | world.floors = creator.createFloors(options.floorCount, world.floorHeight, handleUserCodeError);
76 | world.elevators = creator.createElevators(options.elevatorCount, options.floorCount, world.floorHeight, options.elevatorCapacities);
77 | world.elevatorInterfaces = _.map(world.elevators, function(e) { return asElevatorInterface({}, e, options.floorCount, handleUserCodeError); });
78 | world.users = [];
79 | world.transportedCounter = 0;
80 | world.transportedPerSec = 0.0;
81 | world.moveCount = 0;
82 | world.elapsedTime = 0.0;
83 | world.maxWaitTime = 0.0;
84 | world.avgWaitTime = 0.0;
85 | world.challengeEnded = false;
86 |
87 | var recalculateStats = function() {
88 | world.transportedPerSec = world.transportedCounter / world.elapsedTime;
89 | // TODO: Optimize this loop?
90 | world.moveCount = _.reduce(world.elevators, function(sum, elevator) { return sum+elevator.moveCount; }, 0);
91 | world.trigger("stats_changed");
92 | };
93 |
94 | var registerUser = function(user) {
95 | world.users.push(user);
96 | user.updateDisplayPosition(true);
97 | user.spawnTimestamp = world.elapsedTime;
98 | world.trigger("new_user", user);
99 | user.on("exited_elevator", function() {
100 | world.transportedCounter++;
101 | world.maxWaitTime = Math.max(world.maxWaitTime, world.elapsedTime - user.spawnTimestamp);
102 | world.avgWaitTime = (world.avgWaitTime * (world.transportedCounter - 1) + (world.elapsedTime - user.spawnTimestamp)) / world.transportedCounter;
103 | recalculateStats();
104 | });
105 | user.updateDisplayPosition(true);
106 | };
107 |
108 | var handleElevAvailability = function(elevator) {
109 | // Use regular loops for memory/performance reasons
110 | // Notify floors first because overflowing users
111 | // will press buttons again.
112 | for(var i=0, len=world.floors.length; i 1.0/options.spawnRate) {
165 | elapsedSinceSpawn -= 1.0/options.spawnRate;
166 | registerUser(creator.spawnUserRandomly(options.floorCount, world.floorHeight, world.floors));
167 | }
168 |
169 | // Use regular for loops for performance and memory friendlyness
170 | for(var i=0, len=world.elevators.length; i < len; ++i) {
171 | var e = world.elevators[i];
172 | e.update(dt);
173 | e.updateElevatorMovement(dt);
174 | }
175 | for(var users=world.users, i=0, len=users.length; i < len; ++i) {
176 | var u = users[i];
177 | u.update(dt);
178 | world.maxWaitTime = Math.max(world.maxWaitTime, world.elapsedTime - u.spawnTimestamp);
179 | };
180 |
181 | for(var users=world.users, i=world.users.length-1; i>=0; i--) {
182 | var u = users[i];
183 | if(u.removeMe) {
184 | users.splice(i, 1);
185 | }
186 | }
187 |
188 | recalculateStats();
189 | };
190 |
191 | world.updateDisplayPositions = function() {
192 | for(var i=0, len=world.elevators.length; i < len; ++i) {
193 | world.elevators[i].updateDisplayPosition();
194 | }
195 | for(var users=world.users, i=0, len=users.length; i < len; ++i) {
196 | users[i].updateDisplayPosition();
197 | }
198 | };
199 |
200 |
201 | world.unWind = function() {
202 | console.log("Unwinding", world);
203 | _.each(world.elevators.concat(world.elevatorInterfaces).concat(world.users).concat(world.floors).concat([world]), function(obj) {
204 | obj.off("*");
205 | });
206 | world.challengeEnded = true;
207 | world.elevators = world.elevatorInterfaces = world.users = world.floors = [];
208 | };
209 |
210 | world.init = function() {
211 | // Checking the floor queue of the elevators triggers the idle event here
212 | for(var i=0; i < world.elevatorInterfaces.length; ++i) {
213 | world.elevatorInterfaces[i].checkDestinationQueue();
214 | }
215 | };
216 |
217 | return world;
218 | };
219 |
220 | return creator;
221 | };
222 |
223 |
224 | var createWorldController = function(dtMax) {
225 | var controller = riot.observable({});
226 | controller.timeScale = 1.0;
227 | controller.isPaused = true;
228 | controller.start = function(world, codeObj, animationFrameRequester, autoStart) {
229 | controller.isPaused = true;
230 | var lastT = null;
231 | var firstUpdate = true;
232 | world.on("usercode_error", controller.handleUserCodeError);
233 | var updater = function(t) {
234 | if(!controller.isPaused && !world.challengeEnded && lastT !== null) {
235 | if(firstUpdate) {
236 | firstUpdate = false;
237 | // This logic prevents infite loops in usercode from breaking the page permanently - don't evaluate user code until game is unpaused.
238 | try {
239 | codeObj.init(world.elevatorInterfaces, world.floors);
240 | world.init();
241 | } catch(e) { controller.handleUserCodeError(e); }
242 | }
243 |
244 | var dt = (t - lastT);
245 | var scaledDt = dt * 0.001 * controller.timeScale;
246 | scaledDt = Math.min(scaledDt, dtMax * 3 * controller.timeScale); // Limit to prevent unhealthy substepping
247 | try {
248 | codeObj.update(scaledDt, world.elevatorInterfaces, world.floors);
249 | } catch(e) { controller.handleUserCodeError(e); }
250 | while(scaledDt > 0.0 && !world.challengeEnded) {
251 | var thisDt = Math.min(dtMax, scaledDt);
252 | world.update(thisDt);
253 | scaledDt -= dtMax;
254 | }
255 | world.updateDisplayPositions();
256 | world.trigger("stats_display_changed"); // TODO: Trigger less often for performance reasons etc
257 | }
258 | lastT = t;
259 | if(!world.challengeEnded) {
260 | animationFrameRequester(updater);
261 | }
262 | };
263 | if(autoStart) {
264 | controller.setPaused(false);
265 | }
266 | animationFrameRequester(updater);
267 | };
268 |
269 | controller.handleUserCodeError = function(e) {
270 | controller.setPaused(true);
271 | console.log("Usercode error on update", e);
272 | controller.trigger("usercode_error", e);
273 | };
274 |
275 | controller.setPaused = function(paused) {
276 | controller.isPaused = paused;
277 | controller.trigger("timescale_changed");
278 | };
279 | controller.setTimeScale = function(timeScale) {
280 | controller.timeScale = timeScale;
281 | controller.trigger("timescale_changed");
282 | };
283 |
284 | return controller;
285 | };
286 |
--------------------------------------------------------------------------------
/documentation.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Elevator Saga - help and API documentation
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
21 |
22 |
23 |
About the game
24 |
25 | This is a game of programming!
26 | Your task is to program the movement of elevators, by writing a program in JavaScript .
27 |
28 |
29 | The goal is to transport people in an efficient manner.
30 | Depending on how well you do it, you can progress through the ever more difficult challenges.
31 | Only the very best programs will be able to complete all the challenges.
32 |
33 |
34 |
How to play
35 |
36 | Enter your code in the input window below the game view, and press the Apply button to start the challenge.
37 | You can increase or decrease the speed of time by pressing the and buttons.
38 |
39 |
40 | If your program contains an error, you can use the developer tools in your web browser to try and debug it.
41 | If you want to start over with the code, press the Reset button. This will revert the code to a working but simplistic implementation.
42 | If you have a favorite text editor, such as Sublime Text , feel free to edit the code there and paste it into the game editor.
43 | Your code is automatically saved in your local storage, so don't worry - it doesn't disappear if you accidentally close the browser.
44 |
45 |
Basics
46 |
47 | Your code must declare an object containing at least two functions called init and update . Like this:
48 |
49 |
{
50 | init: function(elevators, floors) {
51 | // Do stuff with the elevators and floors, which are both arrays of objects
52 | },
53 | update: function(dt, elevators, floors) {
54 | // Do more stuff with the elevators and floors
55 | // dt is the number of game seconds that passed since the last time update was called
56 | }
57 | }
58 |
These functions will then be called by the game during the challenge.
59 | init will be called when the challenge starts, and update repeatedly during the challenge.
60 |
61 |
62 | Normally you will put most of your code in the init function, to set up event listeners and logic.
63 |
64 |
Code examples
65 |
How to control an elevator
66 |
67 | elevator.goToFloor(1);
68 | Tell the elevator to move to floor 1 after completing other tasks, if any. Note that this will have no effect if the elevator is already queued to go to that floor.
69 | if(elevator.currentFloor() > 2) { ... }
70 | Calling currentFloor gets the floor number that the elevator currently is on. Note that this is a rounded number and does not necessarily mean the elevator is in a stopped state.
71 |
72 |
Listening for events
73 |
It is possible to listen for events, like when stopping at a floor, or a button has been pressed.
74 |
75 | elevator.on("idle", function() { elevator.goToFloor(0); });
76 | Listen for the "idle" event issued by the elevator, when the task queue has been emptied and the elevator is doing nothing. In this example we tell it to move to floor 0.
77 | elevator.on("floor_button_pressed", function(floorNum) { ... } );
78 | Listen for the "floor_button_pressed" event, issued when a passenger pressed a button inside the elevator. This indicates that the passenger wants to go to that floor.
79 | floor.on("up_button_pressed", function() { ... } );
80 | Listen for the "up_button_pressed" event, issued when a passenger pressed the up button on the floor they are waiting on. This indicates that the passenger wants to go to another floor.
81 |
82 |
83 |
API documentation
84 |
Elevator object
85 |
86 | Property Type Explanation Example
87 |
88 | goToFloor function Queue the elevator to go to specified floor number. If you specify true as second argument, the elevator will go to that floor directly, and then go to any other queued floors. elevator.goToFloor(3); // Do it after anything else
89 | elevator.goToFloor(2, true); // Do it before anything else
90 | stop function Clear the destination queue and stop the elevator if it is moving. Note that you normally don't need to stop elevators - it is intended for advanced solutions with in-transit rescheduling logic. Also, note that the elevator will probably not stop at a floor, so passengers will not get out. elevator.stop();
91 | currentFloor function Gets the floor number that the elevator currently is on. if(elevator.currentFloor() === 0) {
92 | // Do something special?
93 | }
94 | goingUpIndicator function Gets or sets the going up indicator, which will affect passenger behaviour when stopping at floors. if(elevator.goingUpIndicator()) {
95 | elevator.goingDownIndicator(false);
96 | }
97 | goingDownIndicator function Gets or sets the going down indicator, which will affect passenger behaviour when stopping at floors. if(elevator.goingDownIndicator()) {
98 | elevator.goingUpIndicator(false);
99 | }
100 | maxPassengerCount function Gets the maximum number of passengers that can occupy the elevator at the same time. if(elevator.maxPassengerCount() > 5) {
101 | // Use this elevator for something special, because it's big
102 | }
103 | loadFactor function Gets the load factor of the elevator. 0 means empty, 1 means full. Varies with passenger weights, which vary - not an exact measure. if(elevator.loadFactor() < 0.4) {
104 | // Maybe use this elevator, since it's not full yet?
105 | }
106 | destinationDirection function Gets the direction the elevator is currently going to move toward. Can be "up", "down" or "stopped".
107 | destinationQueue array The current destination queue, meaning the floor numbers the elevator is scheduled to go to. Can be modified and emptied if desired. Note that you need to call checkDestinationQueue() for the change to take effect immediately. elevator.destinationQueue = [];
108 | elevator.checkDestinationQueue();
109 | checkDestinationQueue function Checks the destination queue for any new destinations to go to. Note that you only need to call this if you modify the destination queue explicitly. elevator.checkDestinationQueue();
110 | getPressedFloors function Gets the currently pressed floor numbers as an array. if(elevator.getPressedFloors().length > 0) {
111 | // Maybe go to some chosen floor first?
112 | }
113 |
114 |
115 |
116 | Event Explanation Example
117 |
118 | idle Triggered when the elevator has completed all its tasks and is not doing anything. elevator.on("idle", function() { ... });
119 | floor_button_pressed Triggered when a passenger has pressed a button inside the elevator. elevator.on("floor_button_pressed", function(floorNum) {
120 | // Maybe tell the elevator to go to that floor?
121 | })
122 | passing_floor Triggered slightly before the elevator will pass a floor. A good time to decide whether to stop at that floor. Note that this event is not triggered for the destination floor. Direction is either "up" or "down". elevator.on("passing_floor", function(floorNum, direction) { ... });
123 | stopped_at_floor Triggered when the elevator has arrived at a floor. elevator.on("stopped_at_floor", function(floorNum) {
124 | // Maybe decide where to go next?
125 | })
126 |
127 |
128 |
Floor object
129 |
130 | Property Type Explanation Example
131 |
132 | floorNum function Gets the floor number of the floor object. if(floor.floorNum() > 3) { ... }
133 |
134 |
135 |
136 | Event Explanation Example
137 |
138 | up_button_pressed Triggered when someone has pressed the up button at a floor. Note that passengers will press the button again if they fail to enter an elevator. floor.on("up_button_pressed", function() {
139 | // Maybe tell an elevator to go to this floor?
140 | })
141 | down_button_pressed Triggered when someone has pressed the down button at a floor. Note that passengers will press the button again if they fail to enter an elevator. floor.on("down_button_pressed", function() {
142 | // Maybe tell an elevator to go to this floor?
143 | })
144 |
145 |
146 |
147 |
148 |
153 |
154 |
155 |
156 |
--------------------------------------------------------------------------------
/gowasm/wasm_exec.js:
--------------------------------------------------------------------------------
1 | // Copyright 2018 The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | (() => {
6 | // Map web browser API and Node.js API to a single common API (preferring web standards over Node.js API).
7 | const isNodeJS = typeof process !== "undefined";
8 | if (isNodeJS) {
9 | global.require = require;
10 | global.fs = require("fs");
11 |
12 | const nodeCrypto = require("crypto");
13 | global.crypto = {
14 | getRandomValues(b) {
15 | nodeCrypto.randomFillSync(b);
16 | },
17 | };
18 |
19 | global.performance = {
20 | now() {
21 | const [sec, nsec] = process.hrtime();
22 | return sec * 1000 + nsec / 1000000;
23 | },
24 | };
25 |
26 | const util = require("util");
27 | global.TextEncoder = util.TextEncoder;
28 | global.TextDecoder = util.TextDecoder;
29 | } else {
30 | if (typeof window !== "undefined") {
31 | window.global = window;
32 | } else if (typeof self !== "undefined") {
33 | self.global = self;
34 | } else {
35 | throw new Error("cannot export Go (neither window nor self is defined)");
36 | }
37 |
38 | let outputBuf = "";
39 | global.fs = {
40 | constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1 }, // unused
41 | writeSync(fd, buf) {
42 | outputBuf += decoder.decode(buf);
43 | const nl = outputBuf.lastIndexOf("\n");
44 | if (nl != -1) {
45 | console.log(outputBuf.substr(0, nl));
46 | outputBuf = outputBuf.substr(nl + 1);
47 | }
48 | return buf.length;
49 | },
50 | openSync(path, flags, mode) {
51 | const err = new Error("not implemented");
52 | err.code = "ENOSYS";
53 | throw err;
54 | },
55 | };
56 | }
57 |
58 | const encoder = new TextEncoder("utf-8");
59 | const decoder = new TextDecoder("utf-8");
60 |
61 | global.Go = class {
62 | constructor() {
63 | this.argv = ["js"];
64 | this.env = {};
65 | this.exit = (code) => {
66 | if (code !== 0) {
67 | console.warn("exit code:", code);
68 | }
69 | };
70 | this._callbackTimeouts = new Map();
71 | this._nextCallbackTimeoutID = 1;
72 |
73 | const mem = () => {
74 | // The buffer may change when requesting more memory.
75 | return new DataView(this._inst.exports.mem.buffer);
76 | }
77 |
78 | const setInt64 = (addr, v) => {
79 | mem().setUint32(addr + 0, v, true);
80 | mem().setUint32(addr + 4, Math.floor(v / 4294967296), true);
81 | }
82 |
83 | const getInt64 = (addr) => {
84 | const low = mem().getUint32(addr + 0, true);
85 | const high = mem().getInt32(addr + 4, true);
86 | return low + high * 4294967296;
87 | }
88 |
89 | const loadValue = (addr) => {
90 | const f = mem().getFloat64(addr, true);
91 | if (!isNaN(f)) {
92 | return f;
93 | }
94 |
95 | const id = mem().getUint32(addr, true);
96 | return this._values[id];
97 | }
98 |
99 | const storeValue = (addr, v) => {
100 | const nanHead = 0x7FF80000;
101 |
102 | if (typeof v === "number") {
103 | if (isNaN(v)) {
104 | mem().setUint32(addr + 4, nanHead, true);
105 | mem().setUint32(addr, 0, true);
106 | return;
107 | }
108 | mem().setFloat64(addr, v, true);
109 | return;
110 | }
111 |
112 | switch (v) {
113 | case undefined:
114 | mem().setUint32(addr + 4, nanHead, true);
115 | mem().setUint32(addr, 1, true);
116 | return;
117 | case null:
118 | mem().setUint32(addr + 4, nanHead, true);
119 | mem().setUint32(addr, 2, true);
120 | return;
121 | case true:
122 | mem().setUint32(addr + 4, nanHead, true);
123 | mem().setUint32(addr, 3, true);
124 | return;
125 | case false:
126 | mem().setUint32(addr + 4, nanHead, true);
127 | mem().setUint32(addr, 4, true);
128 | return;
129 | }
130 |
131 | let ref = this._refs.get(v);
132 | if (ref === undefined) {
133 | ref = this._values.length;
134 | this._values.push(v);
135 | this._refs.set(v, ref);
136 | }
137 | let typeFlag = 0;
138 | switch (typeof v) {
139 | case "string":
140 | typeFlag = 1;
141 | break;
142 | case "symbol":
143 | typeFlag = 2;
144 | break;
145 | case "function":
146 | typeFlag = 3;
147 | break;
148 | }
149 | mem().setUint32(addr + 4, nanHead | typeFlag, true);
150 | mem().setUint32(addr, ref, true);
151 | }
152 |
153 | const loadSlice = (addr) => {
154 | const array = getInt64(addr + 0);
155 | const len = getInt64(addr + 8);
156 | return new Uint8Array(this._inst.exports.mem.buffer, array, len);
157 | }
158 |
159 | const loadSliceOfValues = (addr) => {
160 | const array = getInt64(addr + 0);
161 | const len = getInt64(addr + 8);
162 | const a = new Array(len);
163 | for (let i = 0; i < len; i++) {
164 | a[i] = loadValue(array + i * 8);
165 | }
166 | return a;
167 | }
168 |
169 | const loadString = (addr) => {
170 | const saddr = getInt64(addr + 0);
171 | const len = getInt64(addr + 8);
172 | return decoder.decode(new DataView(this._inst.exports.mem.buffer, saddr, len));
173 | }
174 |
175 | const timeOrigin = Date.now() - performance.now();
176 | this.importObject = {
177 | go: {
178 | // func wasmExit(code int32)
179 | "runtime.wasmExit": (sp) => {
180 | const code = mem().getInt32(sp + 8, true);
181 | this.exited = true;
182 | delete this._inst;
183 | delete this._values;
184 | delete this._refs;
185 | this.exit(code);
186 | },
187 |
188 | // func wasmWrite(fd uintptr, p unsafe.Pointer, n int32)
189 | "runtime.wasmWrite": (sp) => {
190 | const fd = getInt64(sp + 8);
191 | const p = getInt64(sp + 16);
192 | const n = mem().getInt32(sp + 24, true);
193 | fs.writeSync(fd, new Uint8Array(this._inst.exports.mem.buffer, p, n));
194 | },
195 |
196 | // func nanotime() int64
197 | "runtime.nanotime": (sp) => {
198 | setInt64(sp + 8, (timeOrigin + performance.now()) * 1000000);
199 | },
200 |
201 | // func walltime() (sec int64, nsec int32)
202 | "runtime.walltime": (sp) => {
203 | const msec = (new Date).getTime();
204 | setInt64(sp + 8, msec / 1000);
205 | mem().setInt32(sp + 16, (msec % 1000) * 1000000, true);
206 | },
207 |
208 | // func scheduleCallback(delay int64) int32
209 | "runtime.scheduleCallback": (sp) => {
210 | const id = this._nextCallbackTimeoutID;
211 | this._nextCallbackTimeoutID++;
212 | this._callbackTimeouts.set(id, setTimeout(
213 | () => { this._resolveCallbackPromise(); },
214 | getInt64(sp + 8) + 1, // setTimeout has been seen to fire up to 1 millisecond early
215 | ));
216 | mem().setInt32(sp + 16, id, true);
217 | },
218 |
219 | // func clearScheduledCallback(id int32)
220 | "runtime.clearScheduledCallback": (sp) => {
221 | const id = mem().getInt32(sp + 8, true);
222 | clearTimeout(this._callbackTimeouts.get(id));
223 | this._callbackTimeouts.delete(id);
224 | },
225 |
226 | // func getRandomData(r []byte)
227 | "runtime.getRandomData": (sp) => {
228 | crypto.getRandomValues(loadSlice(sp + 8));
229 | },
230 |
231 | // func stringVal(value string) ref
232 | "syscall/js.stringVal": (sp) => {
233 | storeValue(sp + 24, loadString(sp + 8));
234 | },
235 |
236 | // func valueGet(v ref, p string) ref
237 | "syscall/js.valueGet": (sp) => {
238 | storeValue(sp + 32, Reflect.get(loadValue(sp + 8), loadString(sp + 16)));
239 | },
240 |
241 | // func valueSet(v ref, p string, x ref)
242 | "syscall/js.valueSet": (sp) => {
243 | Reflect.set(loadValue(sp + 8), loadString(sp + 16), loadValue(sp + 32));
244 | },
245 |
246 | // func valueIndex(v ref, i int) ref
247 | "syscall/js.valueIndex": (sp) => {
248 | storeValue(sp + 24, Reflect.get(loadValue(sp + 8), getInt64(sp + 16)));
249 | },
250 |
251 | // valueSetIndex(v ref, i int, x ref)
252 | "syscall/js.valueSetIndex": (sp) => {
253 | Reflect.set(loadValue(sp + 8), getInt64(sp + 16), loadValue(sp + 24));
254 | },
255 |
256 | // func valueCall(v ref, m string, args []ref) (ref, bool)
257 | "syscall/js.valueCall": (sp) => {
258 | try {
259 | const v = loadValue(sp + 8);
260 | const m = Reflect.get(v, loadString(sp + 16));
261 | const args = loadSliceOfValues(sp + 32);
262 | storeValue(sp + 56, Reflect.apply(m, v, args));
263 | mem().setUint8(sp + 64, 1);
264 | } catch (err) {
265 | storeValue(sp + 56, err);
266 | mem().setUint8(sp + 64, 0);
267 | }
268 | },
269 |
270 | // func valueInvoke(v ref, args []ref) (ref, bool)
271 | "syscall/js.valueInvoke": (sp) => {
272 | try {
273 | const v = loadValue(sp + 8);
274 | const args = loadSliceOfValues(sp + 16);
275 | storeValue(sp + 40, Reflect.apply(v, undefined, args));
276 | mem().setUint8(sp + 48, 1);
277 | } catch (err) {
278 | storeValue(sp + 40, err);
279 | mem().setUint8(sp + 48, 0);
280 | }
281 | },
282 |
283 | // func valueNew(v ref, args []ref) (ref, bool)
284 | "syscall/js.valueNew": (sp) => {
285 | try {
286 | const v = loadValue(sp + 8);
287 | const args = loadSliceOfValues(sp + 16);
288 | storeValue(sp + 40, Reflect.construct(v, args));
289 | mem().setUint8(sp + 48, 1);
290 | } catch (err) {
291 | storeValue(sp + 40, err);
292 | mem().setUint8(sp + 48, 0);
293 | }
294 | },
295 |
296 | // func valueLength(v ref) int
297 | "syscall/js.valueLength": (sp) => {
298 | setInt64(sp + 16, parseInt(loadValue(sp + 8).length));
299 | },
300 |
301 | // valuePrepareString(v ref) (ref, int)
302 | "syscall/js.valuePrepareString": (sp) => {
303 | const str = encoder.encode(String(loadValue(sp + 8)));
304 | storeValue(sp + 16, str);
305 | setInt64(sp + 24, str.length);
306 | },
307 |
308 | // valueLoadString(v ref, b []byte)
309 | "syscall/js.valueLoadString": (sp) => {
310 | const str = loadValue(sp + 8);
311 | loadSlice(sp + 16).set(str);
312 | },
313 |
314 | // func valueInstanceOf(v ref, t ref) bool
315 | "syscall/js.valueInstanceOf": (sp) => {
316 | mem().setUint8(sp + 24, loadValue(sp + 8) instanceof loadValue(sp + 16));
317 | },
318 |
319 | "debug": (value) => {
320 | console.log(value);
321 | },
322 | }
323 | };
324 | }
325 |
326 | async run(instance) {
327 | this._inst = instance;
328 | this._values = [ // TODO: garbage collection
329 | NaN,
330 | undefined,
331 | null,
332 | true,
333 | false,
334 | global,
335 | this._inst.exports.mem,
336 | this,
337 | ];
338 | this._refs = new Map();
339 | this._callbackShutdown = false;
340 | this.exited = false;
341 |
342 | const mem = new DataView(this._inst.exports.mem.buffer)
343 |
344 | // Pass command line arguments and environment variables to WebAssembly by writing them to the linear memory.
345 | let offset = 4096;
346 |
347 | const strPtr = (str) => {
348 | let ptr = offset;
349 | new Uint8Array(mem.buffer, offset, str.length + 1).set(encoder.encode(str + "\0"));
350 | offset += str.length + (8 - (str.length % 8));
351 | return ptr;
352 | };
353 |
354 | const argc = this.argv.length;
355 |
356 | const argvPtrs = [];
357 | this.argv.forEach((arg) => {
358 | argvPtrs.push(strPtr(arg));
359 | });
360 |
361 | const keys = Object.keys(this.env).sort();
362 | argvPtrs.push(keys.length);
363 | keys.forEach((key) => {
364 | argvPtrs.push(strPtr(`${key}=${this.env[key]}`));
365 | });
366 |
367 | const argv = offset;
368 | argvPtrs.forEach((ptr) => {
369 | mem.setUint32(offset, ptr, true);
370 | mem.setUint32(offset + 4, 0, true);
371 | offset += 8;
372 | });
373 |
374 | while (true) {
375 | const callbackPromise = new Promise((resolve) => {
376 | this._resolveCallbackPromise = () => {
377 | if (this.exited) {
378 | throw new Error("bad callback: Go program has already exited");
379 | }
380 | setTimeout(resolve, 0); // make sure it is asynchronous
381 | };
382 | });
383 | this._inst.exports.run(argc, argv);
384 | if (this.exited) {
385 | break;
386 | }
387 | await callbackPromise;
388 | }
389 | }
390 |
391 | static _makeCallbackHelper(id, pendingCallbacks, go) {
392 | return function() {
393 | pendingCallbacks.push({ id: id, args: arguments });
394 | go._resolveCallbackPromise();
395 | };
396 | }
397 |
398 | static _makeEventCallbackHelper(preventDefault, stopPropagation, stopImmediatePropagation, fn) {
399 | return function(event) {
400 | if (preventDefault) {
401 | event.preventDefault();
402 | }
403 | if (stopPropagation) {
404 | event.stopPropagation();
405 | }
406 | if (stopImmediatePropagation) {
407 | event.stopImmediatePropagation();
408 | }
409 | fn(event);
410 | };
411 | }
412 | }
413 |
414 | if (isNodeJS) {
415 | if (process.argv.length < 3) {
416 | process.stderr.write("usage: go_js_wasm_exec [wasm binary] [arguments]\n");
417 | process.exit(1);
418 | }
419 |
420 | const go = new Go();
421 | go.argv = process.argv.slice(2);
422 | go.env = process.env;
423 | go.exit = process.exit;
424 | WebAssembly.instantiate(fs.readFileSync(process.argv[2]), go.importObject).then((result) => {
425 | process.on("exit", (code) => { // Node.js exits if no callback is pending
426 | if (code === 0 && !go.exited) {
427 | // deadlock, make Go print error and stack traces
428 | go._callbackShutdown = true;
429 | go._inst.exports.run();
430 | }
431 | });
432 | return go.run(result.instance);
433 | }).catch((err) => {
434 | throw err;
435 | });
436 | }
437 | })();
438 |
--------------------------------------------------------------------------------
/test/jasmine/jasmine-html.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2008-2014 Pivotal Labs
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining
5 | a copy of this software and associated documentation files (the
6 | "Software"), to deal in the Software without restriction, including
7 | without limitation the rights to use, copy, modify, merge, publish,
8 | distribute, sublicense, and/or sell copies of the Software, and to
9 | permit persons to whom the Software is furnished to do so, subject to
10 | the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be
13 | included in all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 | */
23 | jasmineRequire.html = function(j$) {
24 | j$.ResultsNode = jasmineRequire.ResultsNode();
25 | j$.HtmlReporter = jasmineRequire.HtmlReporter(j$);
26 | j$.QueryString = jasmineRequire.QueryString();
27 | j$.HtmlSpecFilter = jasmineRequire.HtmlSpecFilter();
28 | };
29 |
30 | jasmineRequire.HtmlReporter = function(j$) {
31 |
32 | var noopTimer = {
33 | start: function() {},
34 | elapsed: function() { return 0; }
35 | };
36 |
37 | function HtmlReporter(options) {
38 | var env = options.env || {},
39 | getContainer = options.getContainer,
40 | createElement = options.createElement,
41 | createTextNode = options.createTextNode,
42 | onRaiseExceptionsClick = options.onRaiseExceptionsClick || function() {},
43 | timer = options.timer || noopTimer,
44 | results = [],
45 | specsExecuted = 0,
46 | failureCount = 0,
47 | pendingSpecCount = 0,
48 | htmlReporterMain,
49 | symbols;
50 |
51 | this.initialize = function() {
52 | clearPrior();
53 | htmlReporterMain = createDom('div', {className: 'jasmine_html-reporter'},
54 | createDom('div', {className: 'banner'},
55 | createDom('a', {className: 'title', href: 'http://jasmine.github.io/', target: '_blank'}),
56 | createDom('span', {className: 'version'}, j$.version)
57 | ),
58 | createDom('ul', {className: 'symbol-summary'}),
59 | createDom('div', {className: 'alert'}),
60 | createDom('div', {className: 'results'},
61 | createDom('div', {className: 'failures'})
62 | )
63 | );
64 | getContainer().appendChild(htmlReporterMain);
65 |
66 | symbols = find('.symbol-summary');
67 | };
68 |
69 | var totalSpecsDefined;
70 | this.jasmineStarted = function(options) {
71 | totalSpecsDefined = options.totalSpecsDefined || 0;
72 | timer.start();
73 | };
74 |
75 | var summary = createDom('div', {className: 'summary'});
76 |
77 | var topResults = new j$.ResultsNode({}, '', null),
78 | currentParent = topResults;
79 |
80 | this.suiteStarted = function(result) {
81 | currentParent.addChild(result, 'suite');
82 | currentParent = currentParent.last();
83 | };
84 |
85 | this.suiteDone = function(result) {
86 | if (currentParent == topResults) {
87 | return;
88 | }
89 |
90 | currentParent = currentParent.parent;
91 | };
92 |
93 | this.specStarted = function(result) {
94 | currentParent.addChild(result, 'spec');
95 | };
96 |
97 | var failures = [];
98 | this.specDone = function(result) {
99 | if(noExpectations(result) && console && console.error) {
100 | console.error('Spec \'' + result.fullName + '\' has no expectations.');
101 | }
102 |
103 | if (result.status != 'disabled') {
104 | specsExecuted++;
105 | }
106 |
107 | symbols.appendChild(createDom('li', {
108 | className: noExpectations(result) ? 'empty' : result.status,
109 | id: 'spec_' + result.id,
110 | title: result.fullName
111 | }
112 | ));
113 |
114 | if (result.status == 'failed') {
115 | failureCount++;
116 |
117 | var failure =
118 | createDom('div', {className: 'spec-detail failed'},
119 | createDom('div', {className: 'description'},
120 | createDom('a', {title: result.fullName, href: specHref(result)}, result.fullName)
121 | ),
122 | createDom('div', {className: 'messages'})
123 | );
124 | var messages = failure.childNodes[1];
125 |
126 | for (var i = 0; i < result.failedExpectations.length; i++) {
127 | var expectation = result.failedExpectations[i];
128 | messages.appendChild(createDom('div', {className: 'result-message'}, expectation.message));
129 | messages.appendChild(createDom('div', {className: 'stack-trace'}, expectation.stack));
130 | }
131 |
132 | failures.push(failure);
133 | }
134 |
135 | if (result.status == 'pending') {
136 | pendingSpecCount++;
137 | }
138 | };
139 |
140 | this.jasmineDone = function() {
141 | var banner = find('.banner');
142 | banner.appendChild(createDom('span', {className: 'duration'}, 'finished in ' + timer.elapsed() / 1000 + 's'));
143 |
144 | var alert = find('.alert');
145 |
146 | alert.appendChild(createDom('span', { className: 'exceptions' },
147 | createDom('label', { className: 'label', 'for': 'raise-exceptions' }, 'raise exceptions'),
148 | createDom('input', {
149 | className: 'raise',
150 | id: 'raise-exceptions',
151 | type: 'checkbox'
152 | })
153 | ));
154 | var checkbox = find('#raise-exceptions');
155 |
156 | checkbox.checked = !env.catchingExceptions();
157 | checkbox.onclick = onRaiseExceptionsClick;
158 |
159 | if (specsExecuted < totalSpecsDefined) {
160 | var skippedMessage = 'Ran ' + specsExecuted + ' of ' + totalSpecsDefined + ' specs - run all';
161 | alert.appendChild(
162 | createDom('span', {className: 'bar skipped'},
163 | createDom('a', {href: '?', title: 'Run all specs'}, skippedMessage)
164 | )
165 | );
166 | }
167 | var statusBarMessage = '';
168 | var statusBarClassName = 'bar ';
169 |
170 | if (totalSpecsDefined > 0) {
171 | statusBarMessage += pluralize('spec', specsExecuted) + ', ' + pluralize('failure', failureCount);
172 | if (pendingSpecCount) { statusBarMessage += ', ' + pluralize('pending spec', pendingSpecCount); }
173 | statusBarClassName += (failureCount > 0) ? 'failed' : 'passed';
174 | } else {
175 | statusBarClassName += 'skipped';
176 | statusBarMessage += 'No specs found';
177 | }
178 |
179 | alert.appendChild(createDom('span', {className: statusBarClassName}, statusBarMessage));
180 |
181 | var results = find('.results');
182 | results.appendChild(summary);
183 |
184 | summaryList(topResults, summary);
185 |
186 | function summaryList(resultsTree, domParent) {
187 | var specListNode;
188 | for (var i = 0; i < resultsTree.children.length; i++) {
189 | var resultNode = resultsTree.children[i];
190 | if (resultNode.type == 'suite') {
191 | var suiteListNode = createDom('ul', {className: 'suite', id: 'suite-' + resultNode.result.id},
192 | createDom('li', {className: 'suite-detail'},
193 | createDom('a', {href: specHref(resultNode.result)}, resultNode.result.description)
194 | )
195 | );
196 |
197 | summaryList(resultNode, suiteListNode);
198 | domParent.appendChild(suiteListNode);
199 | }
200 | if (resultNode.type == 'spec') {
201 | if (domParent.getAttribute('class') != 'specs') {
202 | specListNode = createDom('ul', {className: 'specs'});
203 | domParent.appendChild(specListNode);
204 | }
205 | var specDescription = resultNode.result.description;
206 | if(noExpectations(resultNode.result)) {
207 | specDescription = 'SPEC HAS NO EXPECTATIONS ' + specDescription;
208 | }
209 | specListNode.appendChild(
210 | createDom('li', {
211 | className: resultNode.result.status,
212 | id: 'spec-' + resultNode.result.id
213 | },
214 | createDom('a', {href: specHref(resultNode.result)}, specDescription)
215 | )
216 | );
217 | }
218 | }
219 | }
220 |
221 | if (failures.length) {
222 | alert.appendChild(
223 | createDom('span', {className: 'menu bar spec-list'},
224 | createDom('span', {}, 'Spec List | '),
225 | createDom('a', {className: 'failures-menu', href: '#'}, 'Failures')));
226 | alert.appendChild(
227 | createDom('span', {className: 'menu bar failure-list'},
228 | createDom('a', {className: 'spec-list-menu', href: '#'}, 'Spec List'),
229 | createDom('span', {}, ' | Failures ')));
230 |
231 | find('.failures-menu').onclick = function() {
232 | setMenuModeTo('failure-list');
233 | };
234 | find('.spec-list-menu').onclick = function() {
235 | setMenuModeTo('spec-list');
236 | };
237 |
238 | setMenuModeTo('failure-list');
239 |
240 | var failureNode = find('.failures');
241 | for (var i = 0; i < failures.length; i++) {
242 | failureNode.appendChild(failures[i]);
243 | }
244 | }
245 | };
246 |
247 | return this;
248 |
249 | function find(selector) {
250 | return getContainer().querySelector('.jasmine_html-reporter ' + selector);
251 | }
252 |
253 | function clearPrior() {
254 | // return the reporter
255 | var oldReporter = find('');
256 |
257 | if(oldReporter) {
258 | getContainer().removeChild(oldReporter);
259 | }
260 | }
261 |
262 | function createDom(type, attrs, childrenVarArgs) {
263 | var el = createElement(type);
264 |
265 | for (var i = 2; i < arguments.length; i++) {
266 | var child = arguments[i];
267 |
268 | if (typeof child === 'string') {
269 | el.appendChild(createTextNode(child));
270 | } else {
271 | if (child) {
272 | el.appendChild(child);
273 | }
274 | }
275 | }
276 |
277 | for (var attr in attrs) {
278 | if (attr == 'className') {
279 | el[attr] = attrs[attr];
280 | } else {
281 | el.setAttribute(attr, attrs[attr]);
282 | }
283 | }
284 |
285 | return el;
286 | }
287 |
288 | function pluralize(singular, count) {
289 | var word = (count == 1 ? singular : singular + 's');
290 |
291 | return '' + count + ' ' + word;
292 | }
293 |
294 | function specHref(result) {
295 | return '?spec=' + encodeURIComponent(result.fullName);
296 | }
297 |
298 | function setMenuModeTo(mode) {
299 | htmlReporterMain.setAttribute('class', 'jasmine_html-reporter ' + mode);
300 | }
301 |
302 | function noExpectations(result) {
303 | return (result.failedExpectations.length + result.passedExpectations.length) === 0 &&
304 | result.status === 'passed';
305 | }
306 | }
307 |
308 | return HtmlReporter;
309 | };
310 |
311 | jasmineRequire.HtmlSpecFilter = function() {
312 | function HtmlSpecFilter(options) {
313 | var filterString = options && options.filterString() && options.filterString().replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
314 | var filterPattern = new RegExp(filterString);
315 |
316 | this.matches = function(specName) {
317 | return filterPattern.test(specName);
318 | };
319 | }
320 |
321 | return HtmlSpecFilter;
322 | };
323 |
324 | jasmineRequire.ResultsNode = function() {
325 | function ResultsNode(result, type, parent) {
326 | this.result = result;
327 | this.type = type;
328 | this.parent = parent;
329 |
330 | this.children = [];
331 |
332 | this.addChild = function(result, type) {
333 | this.children.push(new ResultsNode(result, type, this));
334 | };
335 |
336 | this.last = function() {
337 | return this.children[this.children.length - 1];
338 | };
339 | }
340 |
341 | return ResultsNode;
342 | };
343 |
344 | jasmineRequire.QueryString = function() {
345 | function QueryString(options) {
346 |
347 | this.setParam = function(key, value) {
348 | var paramMap = queryStringToParamMap();
349 | paramMap[key] = value;
350 | options.getWindowLocation().search = toQueryString(paramMap);
351 | };
352 |
353 | this.getParam = function(key) {
354 | return queryStringToParamMap()[key];
355 | };
356 |
357 | return this;
358 |
359 | function toQueryString(paramMap) {
360 | var qStrPairs = [];
361 | for (var prop in paramMap) {
362 | qStrPairs.push(encodeURIComponent(prop) + '=' + encodeURIComponent(paramMap[prop]));
363 | }
364 | return '?' + qStrPairs.join('&');
365 | }
366 |
367 | function queryStringToParamMap() {
368 | var paramStr = options.getWindowLocation().search.substring(1),
369 | params = [],
370 | paramMap = {};
371 |
372 | if (paramStr.length > 0) {
373 | params = paramStr.split('&');
374 | for (var i = 0; i < params.length; i++) {
375 | var p = params[i].split('=');
376 | var value = decodeURIComponent(p[1]);
377 | if (value === 'true' || value === 'false') {
378 | value = JSON.parse(value);
379 | }
380 | paramMap[decodeURIComponent(p[0])] = value;
381 | }
382 | }
383 |
384 | return paramMap;
385 | }
386 |
387 | }
388 |
389 | return QueryString;
390 | };
391 |
--------------------------------------------------------------------------------