├── js.jar ├── tests ├── xmltranslatortestoverride.js ├── utiltest.js ├── uisetup.js ├── suite.js ├── autotesttest.js ├── achievetest.js └── xmltranslatortest.js ├── web ├── mygame │ ├── mygame.js │ ├── scenes │ │ ├── choicescript_stats.txt │ │ ├── gosub.txt │ │ ├── death.txt │ │ ├── ending.txt │ │ ├── animal.txt │ │ ├── variables.txt │ │ └── startup.txt │ ├── credits.html │ └── index.html ├── .htaccess ├── OpenDyslexic-Bold.woff2 ├── OpenDyslexic-Italic.woff2 ├── OpenDyslexic-Regular.woff2 ├── OpenDyslexic-Bold-Italic.woff2 ├── navigator.js ├── index.html ├── alertify.css ├── alertify.min.js └── style.css ├── editor ├── line-numbers.png ├── blankgame.html ├── contenteditable.html └── index.html ├── unittest.sh ├── quicktest.js ├── serve.command ├── compile.command ├── quicktest.command ├── .gitignore ├── randomtest.command ├── run-compile.bat ├── run-quicktest.bat ├── run-server.bat ├── run-randomtest.bat ├── generator.js ├── startupgenerator.js ├── LICENSE.txt ├── generatorNode.js ├── serve.js ├── compile.html ├── index.html ├── headless.js ├── mygamegenerator.js ├── doc ├── more.html └── index.html ├── randomtest.html ├── seedrandom.js ├── compile.js ├── autotest.js └── quicktest.html /js.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfabulich/choicescript/HEAD/js.jar -------------------------------------------------------------------------------- /tests/xmltranslatortestoverride.js: -------------------------------------------------------------------------------- 1 | function xmlTranslatorTestOverride() {} 2 | -------------------------------------------------------------------------------- /web/mygame/mygame.js: -------------------------------------------------------------------------------- 1 | nav = new SceneNavigator(["startup"]); 2 | stats = {}; 3 | 4 | -------------------------------------------------------------------------------- /web/.htaccess: -------------------------------------------------------------------------------- 1 | RedirectMatch permanent ^/$ /mygame/ 2 | RedirectMatch permanent ^/index.html$ /mygame/ -------------------------------------------------------------------------------- /editor/line-numbers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfabulich/choicescript/HEAD/editor/line-numbers.png -------------------------------------------------------------------------------- /unittest.sh: -------------------------------------------------------------------------------- 1 | node $1 tests/suite.js tests/qunit.js web/scene.js web/util.js headless.js tests/scenetest.js -------------------------------------------------------------------------------- /web/OpenDyslexic-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfabulich/choicescript/HEAD/web/OpenDyslexic-Bold.woff2 -------------------------------------------------------------------------------- /web/OpenDyslexic-Italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfabulich/choicescript/HEAD/web/OpenDyslexic-Italic.woff2 -------------------------------------------------------------------------------- /web/OpenDyslexic-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfabulich/choicescript/HEAD/web/OpenDyslexic-Regular.woff2 -------------------------------------------------------------------------------- /web/OpenDyslexic-Bold-Italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfabulich/choicescript/HEAD/web/OpenDyslexic-Bold-Italic.woff2 -------------------------------------------------------------------------------- /quicktest.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | global.require = require; 4 | var file = 'autotest.js'; 5 | require('vm').runInThisContext(require('fs').readFileSync(file), file); -------------------------------------------------------------------------------- /web/mygame/scenes/choicescript_stats.txt: -------------------------------------------------------------------------------- 1 | This is a stats screen! 2 | 3 | *stat_chart 4 | percent Leadership 5 | opposed_pair Strength 6 | Weakness 7 | text Leadership 8 | text Strength 9 | -------------------------------------------------------------------------------- /serve.command: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | cd -- "$(dirname "$0")" 4 | printf "\033c" # clear screen 5 | command -v node >/dev/null 2>&1 || { echo >&2 "ERROR: serve.command requires Node.js, but it's not installed."; exit 1; } 6 | node serve -------------------------------------------------------------------------------- /compile.command: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | cd -- "$(dirname "$0")" 4 | printf "\033c" # clear screen 5 | command -v node >/dev/null 2>&1 || { echo >&2 "ERROR: compile.command requires Node.js, but it's not installed."; exit 1; } 6 | node compile -------------------------------------------------------------------------------- /quicktest.command: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | cd -- "$(dirname "$0")" 4 | printf "\033c" # clear screen 5 | command -v node >/dev/null 2>&1 || { echo >&2 "ERROR: quicktest.command requires Node.js, but it's not installed."; exit 1; } 6 | node quicktest -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | choicescript.zip 2 | revision.txt 3 | web/version.js 4 | android/assets 5 | android/bin 6 | android/gen 7 | iphone/build 8 | iphone/www 9 | *.pbxuser 10 | *.mode1v3 11 | */.DS_Store 12 | .DS_Store 13 | *.svn 14 | randomtest-output.txt 15 | xmloutput 16 | deploy 17 | output.html 18 | -------------------------------------------------------------------------------- /randomtest.command: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | cd -- "$(dirname "$0")" 4 | printf "\033c" # clear screen 5 | command -v node >/dev/null 2>&1 || { echo >&2 "ERROR: randomtest.command requires Node.js, but it's not installed."; exit 1; } 6 | echo Executing randomtest, writing to randomtest-output.txt 7 | node randomtest | tee randomtest-output.txt 8 | -------------------------------------------------------------------------------- /tests/utiltest.js: -------------------------------------------------------------------------------- 1 | 2 | test("emptyString", function() { 3 | var map = parseQueryString(""); 4 | equal(map, null); 5 | }) 6 | test("singleOption", function() { 7 | var map = parseQueryString("?foo=bar"); 8 | deepEqual(map, {foo:"bar"}, "?foo=bar"); 9 | }) 10 | test("multiOption", function() { 11 | var str = "?foo=bar&baz=quz"; 12 | var map = parseQueryString(str); 13 | deepEqual(map, {foo:"bar",baz:"quz"}, str); 14 | }) 15 | -------------------------------------------------------------------------------- /tests/uisetup.js: -------------------------------------------------------------------------------- 1 | window = this; 2 | event = {srcElement: {nodeName: ""}}, 3 | document = { 4 | getElementById: function(){ return this.createElement(); }, 5 | createElement: function() { 6 | var x = { 7 | innerHTML:"", 8 | appendChild: function() {}, 9 | removeChild: function() {}, 10 | style: {display: ""}, 11 | childNodes: [] 12 | } 13 | x.nextSibling = x; 14 | return x; 15 | } 16 | }; 17 | location = {href:"file:///tmp/x"}; 18 | navigator = {userAgent: "test"}; -------------------------------------------------------------------------------- /run-compile.bat: -------------------------------------------------------------------------------- 1 | 2 | ::Copyright 2010 by Dan Fabulich. 3 | :: 4 | ::Dan Fabulich licenses this file to you under the 5 | ::ChoiceScript License, Version 1.0 (the "License"); you may 6 | ::not use this file except in compliance with the License. 7 | ::You may obtain a copy of the License at 8 | :: 9 | :: http://www.choiceofgames.com/LICENSE-1.0.txt 10 | :: 11 | ::See the License for the specific language governing 12 | ::permissions and limitations under the License. 13 | :: 14 | ::Unless required by applicable law or agreed to in writing, 15 | ::software distributed under the License is distributed on an 16 | ::"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 17 | ::either express or implied. 18 | @node compile 19 | @pause 20 | -------------------------------------------------------------------------------- /run-quicktest.bat: -------------------------------------------------------------------------------- 1 | 2 | ::Copyright 2010 by Dan Fabulich. 3 | :: 4 | ::Dan Fabulich licenses this file to you under the 5 | ::ChoiceScript License, Version 1.0 (the "License"); you may 6 | ::not use this file except in compliance with the License. 7 | ::You may obtain a copy of the License at 8 | :: 9 | :: http://www.choiceofgames.com/LICENSE-1.0.txt 10 | :: 11 | ::See the License for the specific language governing 12 | ::permissions and limitations under the License. 13 | :: 14 | ::Unless required by applicable law or agreed to in writing, 15 | ::software distributed under the License is distributed on an 16 | ::"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 17 | ::either express or implied. 18 | @node quicktest 19 | @pause 20 | -------------------------------------------------------------------------------- /run-server.bat: -------------------------------------------------------------------------------- 1 | 2 | ::Copyright 2010 by Dan Fabulich. 3 | :: 4 | ::Dan Fabulich licenses this file to you under the 5 | ::ChoiceScript License, Version 1.0 (the "License"); you may 6 | ::not use this file except in compliance with the License. 7 | ::You may obtain a copy of the License at 8 | :: 9 | :: http://www.choiceofgames.com/LICENSE-1.0.txt 10 | :: 11 | ::See the License for the specific language governing 12 | ::permissions and limitations under the License. 13 | :: 14 | ::Unless required by applicable law or agreed to in writing, 15 | ::software distributed under the License is distributed on an 16 | ::"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 17 | ::either express or implied. 18 | @echo off 19 | node serve 20 | pause 21 | -------------------------------------------------------------------------------- /run-randomtest.bat: -------------------------------------------------------------------------------- 1 | 2 | ::Copyright 2010 by Dan Fabulich. 3 | :: 4 | ::Dan Fabulich licenses this file to you under the 5 | ::ChoiceScript License, Version 1.0 (the "License"); you may 6 | ::not use this file except in compliance with the License. 7 | ::You may obtain a copy of the License at 8 | :: 9 | :: http://www.choiceofgames.com/LICENSE-1.0.txt 10 | :: 11 | ::See the License for the specific language governing 12 | ::permissions and limitations under the License. 13 | :: 14 | ::Unless required by applicable law or agreed to in writing, 15 | ::software distributed under the License is distributed on an 16 | ::"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 17 | ::either express or implied. 18 | @echo off 19 | node randomtest 20 | pause 21 | -------------------------------------------------------------------------------- /web/mygame/scenes/gosub.txt: -------------------------------------------------------------------------------- 1 | Under the hood, this chapter uses the gosub command to execute subroutines. 2 | 3 | The school bully is being mean to you. 4 | 5 | *choice 6 | #Stand up to him. 7 | He's a lot tougher than you are; you hit him, but he barely even notices. 8 | 9 | *gosub feelings 10 | 11 | He beats you up and takes your lunch money. 12 | *finish 13 | #Give him my lunch money. 14 | He takes your money and laughs at you. 15 | 16 | *gosub feelings 17 | 18 | He leaves you alone for the rest of the week. 19 | *finish 20 | 21 | *label feelings 22 | 23 | How do you feel about this? 24 | *choice 25 | #Angry. 26 | Maybe you can get even some day. 27 | *return 28 | #Sad. 29 | You do your best, but you think he can probably see your shame. 30 | *return 31 | -------------------------------------------------------------------------------- /web/mygame/scenes/death.txt: -------------------------------------------------------------------------------- 1 | *comment Copyright 2010 by Dan Fabulich. 2 | *comment 3 | *comment Dan Fabulich licenses this file to you under the 4 | *comment ChoiceScript License, Version 1.0 (the "License"); you may 5 | *comment not use this file except in compliance with the License. 6 | *comment You may obtain a copy of the License at 7 | *comment 8 | *comment http://www.choiceofgames.com/LICENSE-1.0.txt 9 | *comment 10 | *comment See the License for the specific language governing 11 | *comment permissions and limitations under the License. 12 | *comment 13 | *comment Unless required by applicable law or agreed to in writing, 14 | *comment software distributed under the License is distributed on an 15 | *comment "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 16 | *comment either express or implied. 17 | 18 | You have died. 19 | 20 | *goto_scene ending -------------------------------------------------------------------------------- /web/mygame/scenes/ending.txt: -------------------------------------------------------------------------------- 1 | *comment Copyright 2010 by Dan Fabulich. 2 | *comment 3 | *comment Dan Fabulich licenses this file to you under the 4 | *comment ChoiceScript License, Version 1.0 (the "License"); you may 5 | *comment not use this file except in compliance with the License. 6 | *comment You may obtain a copy of the License at 7 | *comment 8 | *comment http://www.choiceofgames.com/LICENSE-1.0.txt 9 | *comment 10 | *comment See the License for the specific language governing 11 | *comment permissions and limitations under the License. 12 | *comment 13 | *comment Unless required by applicable law or agreed to in writing, 14 | *comment software distributed under the License is distributed on an 15 | *comment "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 16 | *comment either express or implied. 17 | 18 | This is the last scene! The game is over! 19 | 20 | *ending -------------------------------------------------------------------------------- /generator.js: -------------------------------------------------------------------------------- 1 | var inputDir = arguments[0] || "web/mygame/scenes"; 2 | var outputDir = arguments[1] || "web/mygame/scenes"; 3 | load("web/scene.js"); 4 | load("web/util.js"); 5 | load("headless.js"); 6 | 7 | var list = new java.io.File(inputDir).listFiles(); 8 | 9 | var i = list.length; 10 | while (i--) { 11 | if (!/\.txt$/.test(list[i].getName())) continue; 12 | var inputMod = list[i].lastModified(); 13 | var outputMod = new java.io.File(list[i].getAbsolutePath()+".json").lastModified(); 14 | if (inputMod <= outputMod) { 15 | print(list[i] + " up to date"); 16 | continue; 17 | } 18 | print(list[i]); 19 | var str = slurpFile(list[i]); 20 | var scene = new Scene(); 21 | scene.loadLines(str); 22 | 23 | var writer = new java.io.BufferedWriter(new java.io.OutputStreamWriter(new java.io.FileOutputStream(outputDir+"/"+list[i].getName().replaceAll(" ", "_") +".json"), "UTF-8")); 24 | writer.write("{\"crc\":" + scene.temps.choice_crc + ", \"lines\":" + toJson(scene.lines)+ ", \"labels\":" + toJson(scene.labels) + "}"); 25 | 26 | writer.close(); 27 | } 28 | 29 | -------------------------------------------------------------------------------- /web/mygame/scenes/animal.txt: -------------------------------------------------------------------------------- 1 | *comment Copyright 2010 by Dan Fabulich. 2 | *comment 3 | *comment Dan Fabulich licenses this file to you under the 4 | *comment ChoiceScript License, Version 1.0 (the "License"); you may 5 | *comment not use this file except in compliance with the License. 6 | *comment You may obtain a copy of the License at 7 | *comment 8 | *comment http://www.choiceofgames.com/LICENSE-1.0.txt 9 | *comment 10 | *comment See the License for the specific language governing 11 | *comment permissions and limitations under the License. 12 | *comment 13 | *comment Unless required by applicable law or agreed to in writing, 14 | *comment software distributed under the License is distributed on an 15 | *comment "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 16 | *comment either express or implied. 17 | 18 | What kind of animal will you be? 19 | *choice 20 | #Lion 21 | *goto claws 22 | #Tiger 23 | *label claws 24 | In that case, you'll have powerful claws and a mighty roar! 25 | *finish 26 | #Elephant 27 | Well, elephants are interesting animals, too. 28 | *finish -------------------------------------------------------------------------------- /startupgenerator.js: -------------------------------------------------------------------------------- 1 | if (typeof load == "undefined") { 2 | fs = require('fs'); 3 | vm = require('vm'); 4 | 5 | load = function(file) { 6 | vm.runInThisContext(fs.readFileSync(file), file); 7 | }; 8 | } 9 | 10 | var mygame = "web/mygame/mygame.js"; 11 | load("web/navigator.js"); 12 | load("web/mygame/mygame.js"); 13 | 14 | console.log("*scene_list"); 15 | var firstScene; 16 | for (var i = 0; i < nav._sceneList.length; i++) { 17 | if (!nav._sceneList[i]) continue; 18 | if (!firstScene) { 19 | firstScene = nav._sceneList[i]; 20 | if (firstScene != "startup") console.log(" startup"); 21 | } 22 | console.log(" " + nav._sceneList[i]); 23 | } 24 | console.log(""); 25 | var mygame = ""+fs.readFileSync("web/mygame/mygame.js"); 26 | var statBlock = /stats\s*=\s*{((\r|\n|.)*?)}/.exec(mygame)[1]; 27 | var lines = statBlock.split(/\r?\n/); 28 | for (var i = 0; i < lines.length; i++) { 29 | var line = lines[i].trim(); 30 | if (!line) { 31 | console.log("\n"); 32 | continue; 33 | } 34 | if (/^,/.test(line)) line = line.substr(1); 35 | var parts = /"?(.*?)"?\s*:\s*(.*)/.exec(line); 36 | var stat = parts[1]; 37 | var value = parts[2]; 38 | var commentParts = /(.*?)\/\/(.*)$/.exec(value); 39 | if (commentParts) { 40 | value = commentParts[1]; 41 | console.log("\n*comment " + commentParts[2]); 42 | } 43 | console.log("*create " + stat + " " + value); 44 | } 45 | 46 | console.log("*goto_scene " + firstScene); -------------------------------------------------------------------------------- /web/mygame/scenes/variables.txt: -------------------------------------------------------------------------------- 1 | *comment Copyright 2010 by Dan Fabulich. 2 | *comment 3 | *comment Dan Fabulich licenses this file to you under the 4 | *comment ChoiceScript License, Version 1.0 (the "License"); you may 5 | *comment not use this file except in compliance with the License. 6 | *comment You may obtain a copy of the License at 7 | *comment 8 | *comment http://www.choiceofgames.com/LICENSE-1.0.txt 9 | *comment 10 | *comment See the License for the specific language governing 11 | *comment permissions and limitations under the License. 12 | *comment 13 | *comment Unless required by applicable law or agreed to in writing, 14 | *comment software distributed under the License is distributed on an 15 | *comment "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 16 | *comment either express or implied. 17 | 18 | *set leadership 10 19 | *set strength 10 20 | 21 | What do you prefer? 22 | *choice 23 | #Leadership 24 | *set leadership +10 25 | *goto action 26 | #Strength 27 | *set strength +10 28 | 29 | *label action 30 | What will you do today? 31 | *choice 32 | #Run for class president 33 | *if leadership > 15 34 | You win the election. 35 | *finish 36 | You lose the election. 37 | *finish 38 | #Lift weights 39 | *if strength > 15 40 | You lift the weights. 41 | *finish 42 | You drop the weights and hurt yourself badly. You never recover. 43 | 44 | *goto_scene death 45 | -------------------------------------------------------------------------------- /tests/suite.js: -------------------------------------------------------------------------------- 1 | var isRhino; 2 | if (typeof java == "undefined") { 3 | isRhino = false; 4 | fs = require('fs'); 5 | vm = require('vm'); 6 | path = require('path'); 7 | var args = process.argv.slice(0); 8 | args.shift(); 9 | args.shift(); 10 | load = function load(file) { 11 | vm.runInThisContext(fs.readFileSync(file), file); 12 | }; 13 | load(args[0]); 14 | print = console.log; 15 | } else { 16 | isRhino = true; 17 | args = arguments; 18 | load(args[0]); 19 | } 20 | 21 | QUnit.init(); 22 | QUnit.config.blocking = false; 23 | QUnit.config.autorun = true; 24 | QUnit.config.updateRate = 0; 25 | 26 | QUnit.testStart = function(x) { 27 | print(" ", x.name); 28 | } 29 | 30 | QUnit.moduleStart = function(x) { 31 | print(x.name); 32 | } 33 | 34 | QUnit.log = function(entry) { 35 | if (entry.result) return; 36 | var message = entry.message; 37 | if (typeof message === "undefined") message = ""; 38 | print(" ", entry.result ? 'PASS' : 'FAIL', message); 39 | if (!entry.result && (typeof entry.expected != "undefined")) { 40 | if (entry.actual) { 41 | print(" expected <"+entry.expected+ "> was <"+entry.actual+">"); 42 | } else { 43 | print(" expected <"+entry.expected+ ">"); 44 | } 45 | } 46 | }; 47 | 48 | var finalResults; 49 | QUnit.done = function(results) { 50 | finalResults = results; 51 | } 52 | 53 | for (var i = 1; i < args.length; i++) { 54 | load(args[i]); 55 | } 56 | 57 | if (finalResults.failed) { 58 | print(finalResults.failed, "FAILED out of", finalResults.total, "total"); 59 | isRhino ? java.lang.System.exit(1) : process.exit(1); 60 | } else { 61 | print(finalResults.total, "PASSED"); 62 | } 63 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | CHOICESCRIPT LICENSE (CSL) v1.0 2 | 3 | Non-commercial usage and modification of the works is permitted 4 | provided that (1) the ChoiceScript license is retained with the works, 5 | so that any entity that uses the works is notified of this license, 6 | and (2) the user grants at no charge an unrestricted, transferrable, 7 | non-exclusive license to Dan Fabulich and Adam Morse for any 8 | modifications, if any, made to the ChoiceScript interpreter. The user 9 | may not use the ChoiceScript interpreter or code written for use with 10 | the ChoiceScript interpreter for any commercial purposes, including 11 | sales of complete applications, use of the code to generate 12 | advertising revenue, or any other commercial purpose. If you are 13 | interested in a commercial license, please contact 14 | support@choiceofgames.com. 15 | 16 | DISCLAIMER 17 | 18 | Unless required by applicable law or agreed to in writing, the 19 | licensor provides the work on an "AS IS" BASIS, WITHOUT WARRANTIES OR 20 | CONDITIONS OF ANY KIND, either express or implied, including, without 21 | limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, 22 | MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely 23 | responsible for determining the appropriateness of using or 24 | redistributing the works and assume any risks associated with your 25 | exercise of permissions under this license. 26 | 27 | In no event and under no legal theory, whether in tort (including 28 | negligence), contract, or otherwise, unless required by applicable law 29 | (such as deliberate and grossly negligent acts) or agreed to in 30 | writing, shall the licensor be liable to you for damages, including 31 | any direct, indirect, special, incidental, or consequential damages of 32 | any character arising as a result of this license or out of the use or 33 | inability to use the works (including but not limited to damages for 34 | loss of goodwill, work stoppage, computer failure or malfunction, or 35 | any and all other commercial damages or losses), even if the licensor 36 | has been advised of the possibility of such damages. -------------------------------------------------------------------------------- /generatorNode.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var path = require('path'); 3 | var inputDir = process.argv[2] || "web/mygame/scenes"; 4 | var outputDir = process.argv[3] || "web/mygame/scenes"; 5 | var singleFile = process.argv[4] || false; 6 | eval(fs.readFileSync("web/scene.js", "utf-8")); 7 | eval(fs.readFileSync("web/util.js", "utf-8")); 8 | eval(fs.readFileSync("headless.js", "utf-8")); 9 | 10 | var list = fs.readdirSync(inputDir); 11 | 12 | var i = list.length; 13 | var writer; 14 | var first = true; 15 | 16 | if (singleFile) { 17 | writer = fs.createWriteStream(outputDir + "/allScenes.js"); 18 | writer.write("oldStats = stats;\n", "utf-8"); 19 | writer.write("allScenes = {", "utf-8"); 20 | } 21 | 22 | while (i--) { 23 | if (!/\.txt$/.test(list[i])) continue; 24 | var filePath = inputDir + '/' + list[i]; 25 | var inputMod = fs.statSync(filePath).mtime.getTime(); 26 | if (singleFile) { 27 | if (first) { 28 | first = false; 29 | } else { 30 | writer.write(",", "utf-8"); 31 | } 32 | writer.write('"'); 33 | writer.write(list[i].replace(/\.txt/, ""), "utf-8"); 34 | writer.write('":'); 35 | } else { 36 | var outputMod = 0; 37 | if (fs.existsSync(filePath + ".json")) { 38 | outputMod = fs.statSync(filePath + ".json").mtime.getTime();; 39 | } 40 | if (inputMod <= outputMod) { 41 | console.log(list[i] + " up to date"); 42 | continue; 43 | } 44 | writer = fs.createWriteStream(outputDir + '/' + list[i].replace(/ /g, "_") + ".json"); 45 | } 46 | console.log(list[i]); 47 | var str = slurpFile(filePath); 48 | var scene = new Scene(); 49 | scene.name = list[i].replace('.txt', ''); 50 | scene.loadLines(str); 51 | 52 | writer.write("{\"crc\":" + scene.crc + ", \"lines\":" + toJson(scene.lines)+ ", \"labels\":" + toJson(scene.labels) + "}", "utf-8"); 53 | 54 | if (!singleFile) writer.end(); 55 | } 56 | 57 | if (singleFile) { 58 | writer.write("}\n", "utf-8"); 59 | var mygame = fs.readFileSync("web/mygame/mygame.js", "utf-8"); 60 | writer.write(mygame.substring(1), "utf-8"); 61 | writer.write("\nstats = oldStats;"); 62 | writer.write(";\nreinjectNavigator();"); 63 | writer.end(); 64 | } 65 | 66 | -------------------------------------------------------------------------------- /serve.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const http = require('http'); 4 | const path = require('path'); 5 | const {URL} = require('url'); 6 | const fs = require('fs'); 7 | const child_process = require('child_process'); 8 | 9 | const dir = __dirname; 10 | 11 | let base; 12 | 13 | const mimeTypes = { 14 | '.html': 'text/html', 15 | '.htm': 'text/html', 16 | '.js': 'text/javascript', 17 | '.css': 'text/css', 18 | '.png': 'image/png', 19 | '.jpg': 'image/jpeg', 20 | '.txt': 'text/plain', 21 | } 22 | 23 | const requestHandler = (request, response) => { 24 | //console.log(request.url); 25 | const requestUrl = new URL(request.url, base); 26 | const requestPath = requestUrl.pathname; 27 | let requestFile = path.normalize(`${dir}/${requestPath}`); 28 | if (!requestFile.startsWith(dir)) { 29 | response.statusCode = 400; 30 | response.end(); 31 | return; 32 | } else if (requestFile.endsWith(path.sep)) { 33 | requestFile += 'index.html'; 34 | } 35 | const stream = fs.createReadStream(requestFile); 36 | const streamError = e => { 37 | if (e.code === 'ENOENT') { 38 | response.statusCode = 404; 39 | response.end('File not found'); 40 | } else if (e.code === 'EISDIR') { 41 | response.statusCode = 301; 42 | response.setHeader('Location', requestUrl.pathname + '/'); 43 | response.end(); 44 | } else { 45 | response.statusCode = 500; 46 | response.end('Error loading file'); 47 | console.log(e); 48 | } 49 | }; 50 | stream.on('error', streamError); 51 | const mimeType = mimeTypes[path.extname(requestFile).toLowerCase()]; 52 | if (mimeType) { 53 | response.setHeader('Content-Type', mimeType); 54 | } 55 | response.setHeader('Cache-Control', 'max-age=0'); 56 | stream.pipe(response); 57 | } 58 | 59 | const server = http.createServer(requestHandler); 60 | 61 | function openUrl(url) { 62 | switch(process.platform) { 63 | case "win32": { 64 | child_process.execFile('cmd', ['/c', 'start', '""', url.replace(/&/g, "^&")]); 65 | break; 66 | } 67 | case "darwin": { 68 | child_process.execFile('open', [url]); 69 | break; 70 | } 71 | } 72 | } 73 | 74 | server.listen({port: 0, host:'127.0.0.1'}, (err) => { 75 | base = `http://localhost:${server.address().port}`; 76 | console.log(`server is ready: ${base}`); 77 | console.log(`Press Ctrl-C or close this window to stop the server`); 78 | openUrl(base); 79 | }) -------------------------------------------------------------------------------- /web/mygame/credits.html: -------------------------------------------------------------------------------- 1 | Credits<!-- | INSERTINSERTINSERT game name here --> 2 | 3 | 4 | 15 | 26 | 27 | 28 |

Credits

29 | 30 | 44 | 45 |

About Choice of Games LLC

46 |

Choice of Games LLC is a California Limited Liability Company dedicated to producing high-quality, text-based, multiple-choice games. We produce games in house, including Choice of the Dragon and Choice of Broadsides. We have also developed a simple scripting language for writing text-based games, ChoiceScript, which we make available to others for use in their projects, and we host games produced by other designers using ChoiceScript on our website. All of our games are available on the web. We also produce mobile versions of our games that can be played on iPhones, Android phones, and other smartphones.

47 | 48 |

We announce new games on our blog, Twitter, Facebook, and Google Plus. 49 | 50 |

More about Choice of Games LLC

51 | 52 | 53 | 54 |

Back to the game 55 | 56 | 57 | -------------------------------------------------------------------------------- /web/mygame/scenes/startup.txt: -------------------------------------------------------------------------------- 1 | *comment Copyright 2010 by Dan Fabulich. 2 | *comment 3 | *comment Dan Fabulich licenses this file to you under the 4 | *comment ChoiceScript License, Version 1.0 (the "License"); you may 5 | *comment not use this file except in compliance with the License. 6 | *comment You may obtain a copy of the License at 7 | *comment 8 | *comment http://www.choiceofgames.com/LICENSE-1.0.txt 9 | *comment 10 | *comment See the License for the specific language governing 11 | *comment permissions and limitations under the License. 12 | *comment 13 | *comment Unless required by applicable law or agreed to in writing, 14 | *comment software distributed under the License is distributed on an 15 | *comment "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 16 | *comment either express or implied. 17 | 18 | *title My First ChoiceScript Game 19 | *author Anonymous 20 | *scene_list 21 | startup 22 | animal 23 | variables 24 | gosub 25 | ending 26 | death 27 | 28 | *create leadership 50 29 | *create strength 50 30 | 31 | Welcome to your very first ChoiceScript game! 32 | 33 | Copyright 2010 by Dan Fabulich. 34 | 35 | Dan Fabulich licenses this file to you under the 36 | ChoiceScript License, Version 1.0 (the "License"); you may 37 | not use this file except in compliance with the License. 38 | You may obtain a copy of the License at 39 | 40 | *link http://www.choiceofgames.com/LICENSE-1.0.txt 41 | 42 | See the License for the specific language governing 43 | permissions and limitations under the License. 44 | 45 | Unless required by applicable law or agreed to in writing, 46 | software distributed under the License is distributed on an 47 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 48 | either express or implied. 49 | 50 | *page_break 51 | 52 | Your majesty, your people are starving in the streets, and threaten revolution. 53 | Our enemies to the west are weak, but they threaten soon to invade. What will you do? 54 | 55 | *choice 56 | #Make pre-emptive war on the western lands. 57 | If you can seize their territory, your kingdom will flourish. But your army's 58 | morale is low and the kingdom's armory is empty. How will you win the war? 59 | *choice 60 | #Drive the peasants like slaves; if we work hard enough, we'll win. 61 | Unfortunately, morale doesn't work like that. Your army soon turns against you 62 | and the kingdom falls to the western barbarians. 63 | *finish 64 | #Appoint charismatic knights and give them land, peasants, and resources. 65 | Your majesty's people are eminently resourceful. Your knights win the day, 66 | but take care: they may soon demand a convention of parliament. 67 | *finish 68 | #Steal food and weapons from the enemy in the dead of night. 69 | A cunning plan. Soon your army is a match for the westerners; they choose 70 | not to invade for now, but how long can your majesty postpone the inevitable? 71 | *finish 72 | #Beat swords to plowshares and trade food to the westerners for protection. 73 | The westerners have you at the point of a sword. They demand unfair terms 74 | from you. 75 | *choice 76 | #Accept the terms for now. 77 | Eventually, the barbarian westerners conquer you anyway, destroying their 78 | bread basket, and the entire region starves. 79 | *finish 80 | #Threaten to salt our fields if they don't offer better terms. 81 | They blink. Your majesty gets a fair price for wheat. 82 | *finish 83 | #Abdicate the throne. I have clearly mismanaged this kingdom! 84 | The kingdom descends into chaos, but you manage to escape with your own hide. 85 | Perhaps in time you can return to restore order to this fair land. 86 | *finish 87 | -------------------------------------------------------------------------------- /compile.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 101 | 102 | 103 |

Loading...
104 | 105 | 117 | 118 | -------------------------------------------------------------------------------- /web/navigator.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 by Dan Fabulich. 3 | * 4 | * Dan Fabulich licenses this file to you under the 5 | * ChoiceScript License, Version 1.0 (the "License"); you may 6 | * not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.choiceofgames.com/LICENSE-1.0.txt 10 | * 11 | * See the License for the specific language governing 12 | * permissions and limitations under the License. 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 17 | * either express or implied. 18 | */ 19 | function SceneNavigator(sceneList) { 20 | this.setSceneList(sceneList); 21 | this.startingStats = {}; 22 | } 23 | 24 | SceneNavigator.prototype.setSceneList = function setSceneList(sceneList) { 25 | this._sceneList = sceneList; 26 | this._sceneMap = {}; 27 | for (var i = 0; i < sceneList.length-1; i++) { 28 | var scene1 = sceneList[i]; 29 | var scene2 = sceneList[i+1]; 30 | this._sceneMap[scene1] = scene2; 31 | } 32 | this._startupScene = sceneList[0]; 33 | }; 34 | 35 | SceneNavigator.prototype.nextSceneName = function nextSceneName(currentSceneName) { 36 | var nextScene = this._sceneMap[currentSceneName]; 37 | //if (!nextScene) throw new Error("No scene follows " + currentSceneName); 38 | return nextScene; 39 | }; 40 | 41 | SceneNavigator.prototype.getStartupScene = function getStartupScene() { 42 | return this._startupScene; 43 | }; 44 | 45 | SceneNavigator.prototype.setStartingStatsClone = function setStartingStatsClone(stats) { 46 | this.startingStats = {}; 47 | for (var i in stats) { 48 | this.startingStats[i] = stats[i]; 49 | } 50 | }; 51 | 52 | SceneNavigator.prototype.resetStats = function resetStats(stats) { 53 | for (var i in stats) { 54 | delete stats[i]; 55 | } 56 | for (i in this.startingStats) { 57 | stats[i] = this.startingStats[i]; 58 | } 59 | this.bugLog = []; 60 | }; 61 | 62 | SceneNavigator.prototype.repairStats = function repairStats(stats) { 63 | for (var i in this.startingStats) { 64 | var startingStat = this.startingStats[i]; 65 | if (startingStat === null || startingStat === undefined) continue; 66 | if (typeof(stats[i]) === "undefined" || stats[i] === null) { 67 | stats[i] = this.startingStats[i]; 68 | } 69 | } 70 | }; 71 | 72 | SceneNavigator.prototype.bugLog = []; 73 | SceneNavigator.prototype.achievements = {}; 74 | SceneNavigator.prototype.achievementList = []; 75 | SceneNavigator.prototype.achieved = {}; 76 | SceneNavigator.prototype.products = {}; 77 | 78 | SceneNavigator.prototype.loadAchievements = function(achievementArray) { 79 | if (!achievementArray) return; 80 | this.achievements = {}; 81 | this.achievementList = []; 82 | for (var i = 0; i < achievementArray.length; i++) { 83 | var achievement = achievementArray[i]; 84 | var achievementName = achievement[0]; 85 | var visible = achievement[1]; 86 | var points = achievement[2]; 87 | var title = achievement[3]; 88 | var earnedDescription = achievement[4]; 89 | var preEarnedDescription = achievement[5]; 90 | this.achievements[achievementName] = { 91 | visible: visible, 92 | points: points, 93 | title: title, 94 | earnedDescription: earnedDescription, 95 | preEarnedDescription: preEarnedDescription 96 | }; 97 | this.achievementList.push(achievementName); 98 | } 99 | }; 100 | SceneNavigator.prototype.loadProducts = function(productArray, purchaseMap) { 101 | if (!productArray && !purchaseMap) return; 102 | this.products = {}; 103 | for (var i = 0; i < productArray; i++) { 104 | this.products[productArray[i]] = {}; 105 | } 106 | for (var scene in purchaseMap) { 107 | var product = purchaseMap[scene]; 108 | this.products[product] = {}; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /editor/blankgame.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 22 | 23 | 24 | 25 | 26 | Multiple Choice Example Game | My First ChoiceScript Game 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 74 | 75 |
76 | 78 | 95 |
96 |
97 |
98 | 101 |
102 | 104 | 107 | 108 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 20 | 21 | 22 | 29 | 30 | 31 |

Loading...

32 | 33 | -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 20 | 21 | 22 | 29 | 30 | 31 |

Loading...

32 | 33 | -------------------------------------------------------------------------------- /web/mygame/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 48 | 49 |
50 | 52 | 70 |
71 |
72 |
73 | 76 |
77 | 79 | 82 |

Make your own games with ChoiceScript

83 |
84 | 98 | 99 |
Keyboard Shortcuts
100 |
101 |
Scroll Down
102 |
Space
103 |
Scroll Up
104 |
Shift + Space
105 |
Next Option
106 |
J
107 |
Previous Option
108 |
K
109 |
Next (Press and Hold)
110 |
Enter
111 |
Jump to Option
112 |
1 - 9
113 |
Show Stats
114 |
Q
115 |
Menu
116 |
W
117 |
Show Shortcuts
118 |
?
119 |
120 |
121 | 122 | 123 | -------------------------------------------------------------------------------- /headless.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 by Dan Fabulich. 3 | * 4 | * Dan Fabulich licenses this file to you under the 5 | * ChoiceScript License, Version 1.0 (the "License"); you may 6 | * not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.choiceofgames.com/LICENSE-1.0.txt 10 | * 11 | * See the License for the specific language governing 12 | * permissions and limitations under the License. 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 17 | * either express or implied. 18 | */ 19 | printed = []; 20 | headless = true; 21 | _global = this; 22 | debughelp = function debughelp() { 23 | debugger; 24 | }; 25 | printx = function printx(msg, parent) { 26 | printed.push(msg); 27 | }; 28 | function println(msg, parent) { 29 | printed.push(msg); 30 | printed.push("
"); 31 | } 32 | function printParagraph(msg, parent) { 33 | if (msg === "") return; 34 | printed.push("

"); 35 | printed.push(msg); 36 | printed.push("

"); 37 | } 38 | _global = this; 39 | 40 | isRhino = typeof(java) != "undefined"; 41 | 42 | clearScreen = function clearScreen(code) {code.call();}; 43 | saveCookie = function(callback) { if (callback) callback.call(); }; 44 | loadTempStats = function (stats, callback) { if (callback) callback.call(null, stats); }; 45 | clearTemp = function() {}; 46 | doneLoading = function() {}; 47 | printFooter = function() {}; 48 | printShareLinks = function() {}; 49 | printLink = function() {}; 50 | kindleButton = function() {}; 51 | printImage = function() {}; 52 | showPassword = function() {}; 53 | printDiscount = function() {}; 54 | achieve = function() {}; 55 | 56 | isRegistered = function() {return false;}; 57 | isRegisterAllowed = function() {return false;}; 58 | isFullScreenAdvertisingSupported = function() {return false;}; 59 | isRestorePurchasesSupported = function() {return false;}; 60 | areSaveSlotsSupported = function() {return false;}; 61 | isAdvertisingSupported = function() {return false;}; 62 | isPrerelease = function() {return false;}; 63 | 64 | showFullScreenAdvertisementButton = function(message, callback) { callback(); } 65 | 66 | function fileExists(filePath) { 67 | if (isRhino) { 68 | return new java.io.File(filePath).exists(); 69 | } else { 70 | return fs.existsSync(filePath); 71 | } 72 | } 73 | 74 | function fileLastMod(filePath) { 75 | if (isRhino) { 76 | return new java.io.File(filePath).lastModified(); 77 | } else { 78 | if (fs.existsSync(filePath)) return fs.statSync(filePath).mtime.getTime(); 79 | return 0; 80 | } 81 | } 82 | 83 | function mkdirs(filePath) { 84 | if (isRhino) { 85 | new java.io.File(filePath).mkdirs(); 86 | } else { 87 | if (!fs.existsSync(filePath)) { 88 | var parentDir = path.dirname(filePath); 89 | if (!fs.existsSync(parentDir)) { 90 | mkdirs(parentDir); 91 | } 92 | fs.mkdirSync(filePath); 93 | } 94 | 95 | } 96 | } 97 | 98 | function slurpFile(name, throwOnError) { 99 | return slurpFileLines(name, throwOnError).join('\n'); 100 | } 101 | 102 | function slurpFileLines(name, throwOnError) { 103 | var lines, line, i, invalidCharacter; 104 | if (isRhino) { 105 | lines = []; 106 | var reader = new java.io.BufferedReader(new java.io.InputStreamReader(new java.io.FileInputStream(name), "UTF-8")); 107 | for (i = 0; !!(line = reader.readLine()); i++) { 108 | if (i === 0 && line.charCodeAt(0) == 65279) line = line.substring(1); 109 | if (throwOnError) { 110 | invalidCharacter = line.match(/^(.*)\ufffd/); 111 | if (invalidCharacter) throw new Error(name+" line " + (i+1) + ": invalid character. (ChoiceScript text should be saved in the UTF-8 encoding.)\n" + invalidCharacter[0]); 112 | } 113 | lines.push(line); 114 | } 115 | return lines; 116 | } else { 117 | var blob = fs.readFileSync(name, "utf-8"); 118 | lines = blob.split(/\r?\n/); 119 | var firstLine = lines[0]; 120 | // strip byte order mark 121 | if (firstLine.charCodeAt(0) == 65279) lines[0] = firstLine.substring(1); 122 | if (throwOnError) { 123 | for (i = 0; i < lines.length; i++) { 124 | line = lines[i]; 125 | invalidCharacter = line.match(/^(.*)\ufffd/); 126 | if (invalidCharacter) throw new Error(name+" line " + (i+1) + ": invalid character. (ChoiceScript text should be saved in the UTF-8 encoding.)\n" + invalidCharacter[0]); 127 | } 128 | } 129 | return lines; 130 | } 131 | } 132 | 133 | function slurpImage(name) { 134 | var blob = fs.readFileSync(name); 135 | var dataType; 136 | if (/\.jpe?g$/i.test(name)) { 137 | dataType = 'image/jpeg'; 138 | } else if (/\.png/i.test(name)) { 139 | dataType = 'image/png'; 140 | } 141 | return `data:${dataType};base64,${Buffer.from(blob).toString('base64')}`; 142 | } 143 | 144 | function initStore() { return false; } 145 | -------------------------------------------------------------------------------- /web/alertify.css: -------------------------------------------------------------------------------- 1 | .alertify-show, 2 | .alertify-log { 3 | -webkit-transition: all 500ms cubic-bezier(0.175, 0.885, 0.320, 1); /* older webkit */ 4 | -webkit-transition: all 500ms cubic-bezier(0.175, 0.885, 0.320, 1.275); 5 | -moz-transition: all 500ms cubic-bezier(0.175, 0.885, 0.320, 1.275); 6 | -ms-transition: all 500ms cubic-bezier(0.175, 0.885, 0.320, 1.275); 7 | -o-transition: all 500ms cubic-bezier(0.175, 0.885, 0.320, 1.275); 8 | transition: all 500ms cubic-bezier(0.175, 0.885, 0.320, 1.275); /* easeOutBack */ 9 | } 10 | .alertify-hide { 11 | -webkit-transition: all 250ms cubic-bezier(0.600, 0, 0.735, 0.045); /* older webkit */ 12 | -webkit-transition: all 250ms cubic-bezier(0.600, -0.280, 0.735, 0.045); 13 | -moz-transition: all 250ms cubic-bezier(0.600, -0.280, 0.735, 0.045); 14 | -ms-transition: all 250ms cubic-bezier(0.600, -0.280, 0.735, 0.045); 15 | -o-transition: all 250ms cubic-bezier(0.600, -0.280, 0.735, 0.045); 16 | transition: all 250ms cubic-bezier(0.600, -0.280, 0.735, 0.045); /* easeInBack */ 17 | } 18 | .alertify-cover { 19 | position: fixed; z-index: 99999; 20 | top: 0; right: 0; bottom: 0; left: 0; 21 | } 22 | .alertify { 23 | position: fixed; z-index: 99999; 24 | top: 50px; left: 50%; 25 | width: 550px; 26 | margin-left: -275px; 27 | } 28 | .alertify-hidden { 29 | top: -50px; 30 | visibility: hidden; 31 | } 32 | .alertify-logs { 33 | position: fixed; 34 | z-index: 5000; 35 | bottom: 10px; 36 | right: 10px; 37 | width: 300px; 38 | } 39 | .alertify-log { 40 | display: block; 41 | margin-top: 10px; 42 | position: relative; 43 | right: -300px; 44 | } 45 | .alertify-log-show { 46 | right: 0; 47 | } 48 | .alertify-dialog { 49 | padding: 25px; 50 | } 51 | .alertify-resetFocus { 52 | border: 0; 53 | clip: rect(0 0 0 0); 54 | height: 1px; 55 | margin: -1px; 56 | overflow: hidden; 57 | padding: 0; 58 | position: absolute; 59 | width: 1px; 60 | } 61 | .alertify-inner { 62 | text-align: center; 63 | } 64 | .alertify-text { 65 | margin-bottom: 15px; 66 | width: 100%; 67 | -webkit-box-sizing: border-box; 68 | -moz-box-sizing: border-box; 69 | box-sizing: border-box; 70 | font-size: 100%; 71 | } 72 | .alertify-buttons { 73 | } 74 | .alertify-button { 75 | /* line-height and font-size for input button */ 76 | line-height: 1.5; 77 | font-size: 100%; 78 | display: inline-block; 79 | cursor: pointer; 80 | margin-left: 5px; 81 | } 82 | 83 | @media only screen and (max-width: 680px) { 84 | .alertify, 85 | .alertify-logs { 86 | width: 90%; 87 | -webkit-box-sizing: border-box; 88 | -moz-box-sizing: border-box; 89 | box-sizing: border-box; 90 | } 91 | .alertify { 92 | left: 5%; 93 | margin: 0; 94 | } 95 | } 96 | /** 97 | * Default Look and Feel 98 | */ 99 | .alertify, 100 | .alertify-log { 101 | font-family: sans-serif; 102 | } 103 | .alertify { 104 | background: #FFF; 105 | border: 10px solid #333; /* browsers that don't support rgba */ 106 | border: 10px solid rgba(0,0,0,.7); 107 | border-radius: 8px; 108 | box-shadow: 0 3px 3px rgba(0,0,0,.3); 109 | -webkit-background-clip: padding; /* Safari 4? Chrome 6? */ 110 | -moz-background-clip: padding; /* Firefox 3.6 */ 111 | background-clip: padding-box; /* Firefox 4, Safari 5, Opera 10, IE 9 */ 112 | } 113 | .alertify-text { 114 | border: 1px solid #CCC; 115 | padding: 10px; 116 | border-radius: 4px; 117 | } 118 | .alertify-button { 119 | border-radius: 4px; 120 | color: #FFF; 121 | font-weight: bold; 122 | padding: 6px 15px; 123 | text-decoration: none; 124 | text-shadow: 1px 1px 0 rgba(0,0,0,.5); 125 | box-shadow: inset 0 1px 0 0 rgba(255,255,255,.5); 126 | background-image: -webkit-linear-gradient(top, rgba(255,255,255,.3), rgba(255,255,255,0)); 127 | background-image: -moz-linear-gradient(top, rgba(255,255,255,.3), rgba(255,255,255,0)); 128 | background-image: -ms-linear-gradient(top, rgba(255,255,255,.3), rgba(255,255,255,0)); 129 | background-image: -o-linear-gradient(top, rgba(255,255,255,.3), rgba(255,255,255,0)); 130 | background-image: linear-gradient(top, rgba(255,255,255,.3), rgba(255,255,255,0)); 131 | } 132 | .alertify-button:hover, 133 | .alertify-button:focus { 134 | outline: none; 135 | box-shadow: 0 0 15px #2B72D5; 136 | background-image: -webkit-linear-gradient(top, rgba(0,0,0,.1), rgba(0,0,0,0)); 137 | background-image: -moz-linear-gradient(top, rgba(0,0,0,.1), rgba(0,0,0,0)); 138 | background-image: -ms-linear-gradient(top, rgba(0,0,0,.1), rgba(0,0,0,0)); 139 | background-image: -o-linear-gradient(top, rgba(0,0,0,.1), rgba(0,0,0,0)); 140 | background-image: linear-gradient(top, rgba(0,0,0,.1), rgba(0,0,0,0)); 141 | } 142 | .alertify-button:active { 143 | position: relative; 144 | top: 1px; 145 | } 146 | .alertify-button-cancel { 147 | background-color: #FE1A00; 148 | border: 1px solid #D83526; 149 | } 150 | .alertify-button-ok { 151 | background-color: #5CB811; 152 | border: 1px solid #3B7808; 153 | } 154 | 155 | .alertify-log { 156 | background: #1F1F1F; 157 | background: rgba(0,0,0,.9); 158 | padding: 15px; 159 | border-radius: 4px; 160 | color: #FFF; 161 | text-shadow: -1px -1px 0 rgba(0,0,0,.5); 162 | } 163 | .alertify-log-error { 164 | background: #FE1A00; 165 | background: rgba(254,26,0,.9); 166 | } 167 | .alertify-log-success { 168 | background: #5CB811; 169 | background: rgba(92,184,17,.9); 170 | } -------------------------------------------------------------------------------- /web/alertify.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * alertify 3 | * An unobtrusive customizable JavaScript notification system 4 | * 5 | * @author Fabien Doiron 6 | * @copyright Fabien Doiron 2012 7 | * @license MIT 8 | * @link http://fabien-d.github.com/alertify.js/ 9 | * @module alertify 10 | * @version 0.3.0 11 | */ 12 | (function(e,t){"use strict";var n=e.document,r;r=function(){var r={},i={},s=!1,o={ENTER:13,ESC:27,SPACE:32},u=[],a,f,l,c,h;return i={buttons:{holder:'',submit:'',ok:'{{ok}}',cancel:'{{cancel}}'},input:'
',message:'

{{message}}

',log:'
{{message}}
'},a=function(e){return n.getElementById(e)},r={labels:{ok:"OK",cancel:"Cancel"},delay:5e3,addListeners:function(r){var i=a("alertify-resetFocus"),s=a("alertify-ok")||t,u=a("alertify-cancel")||t,f=a("alertify-text")||t,l=a("alertify-form")||t,c=typeof s!="undefined",h=typeof u!="undefined",p=typeof f!="undefined",d="",v=this,m,g,y,b,w;m=function(e){typeof e.preventDefault!="undefined"&&e.preventDefault(),y(e),typeof f!="undefined"&&(d=f.value),typeof r=="function"&&r(!0,d)},g=function(e){typeof e.preventDefault!="undefined"&&e.preventDefault(),y(e),typeof r=="function"&&r(!1)},y=function(e){v.hide(),v.unbind(n.body,"keyup",b),v.unbind(i,"focus",w),p&&v.unbind(l,"submit",m),c&&v.unbind(s,"click",m),h&&v.unbind(u,"click",g)},b=function(e){var t=e.keyCode;t===o.SPACE&&!p&&m(e),t===o.ESC&&h&&g(e)},w=function(e){p?f.focus():h?u.focus():s.focus()},this.bind(i,"focus",w),c&&this.bind(s,"click",m),h&&this.bind(u,"click",g),this.bind(n.body,"keyup",b),p&&this.bind(l,"submit",m),e.setTimeout(function(){f?(f.focus(),f.select()):s.focus()},50)},bind:function(e,t,n){typeof e.addEventListener=="function"?e.addEventListener(t,n,!1):e.attachEvent&&e.attachEvent("on"+t,n)},build:function(e){var t="",n=e.type,r=e.message,s=e.cssClass||"";t+='
',n==="prompt"&&(t+='
'),t+='
',t+=i.message.replace("{{message}}",r),n==="prompt"&&(t+=i.input),t+=i.buttons.holder,t+="
",n==="prompt"&&(t+="
"),t+='Reset Focus',t+="
";switch(n){case"confirm":t=t.replace("{{buttons}}",i.buttons.cancel+i.buttons.ok),t=t.replace("{{ok}}",this.labels.ok).replace("{{cancel}}",this.labels.cancel);break;case"prompt":t=t.replace("{{buttons}}",i.buttons.cancel+i.buttons.submit),t=t.replace("{{ok}}",this.labels.ok).replace("{{cancel}}",this.labels.cancel);break;case"alert":t=t.replace("{{buttons}}",i.buttons.ok),t=t.replace("{{ok}}",this.labels.ok);break;default:}return c.className="alertify alertify-show alertify-"+n+" "+s,l.className="alertify-cover",t},close:function(e,t){var n=t&&!isNaN(t)?+t:this.delay;this.bind(e,"click",function(){h.removeChild(e)}),setTimeout(function(){typeof e!="undefined"&&e.parentNode===h&&h.removeChild(e)},n)},dialog:function(e,t,r,i,o){f=n.activeElement;var a=function(){if(c&&c.scrollTop!==null)return;a()};if(typeof e!="string")throw new Error("message must be a string");if(typeof t!="string")throw new Error("type must be a string");if(typeof r!="undefined"&&typeof r!="function")throw new Error("fn must be a function");return typeof this.init=="function"&&(this.init(),a()),u.push({type:t,message:e,callback:r,placeholder:i,cssClass:o}),s||this.setup(),this},extend:function(e){if(typeof e!="string")throw new Error("extend method must have exactly one paramter");return function(t,n){return this.log(t,e,n),this}},hide:function(){u.splice(0,1),u.length>0?this.setup():(s=!1,c.className="alertify alertify-hide alertify-hidden",l.className="alertify-cover alertify-hidden",f?f.focus():null)},init:function(){n.createElement("nav"),n.createElement("article"),n.createElement("section"),l=n.createElement("div"),l.setAttribute("id","alertify-cover"),l.className="alertify-cover alertify-hidden",n.body.appendChild(l),c=n.createElement("section"),c.setAttribute("id","alertify"),c.className="alertify alertify-hidden",n.body.appendChild(c),h=n.createElement("section"),h.setAttribute("id","alertify-logs"),h.className="alertify-logs",n.body.appendChild(h),n.body.setAttribute("tabindex","0"),delete this.init},log:function(e,t,n){var r=function(){if(h&&h.scrollTop!==null)return;r()};return typeof this.init=="function"&&(this.init(),r()),this.notify(e,t,n),this},notify:function(e,t,r){var i=n.createElement("article");i.className="alertify-log"+(typeof t=="string"&&t!==""?" alertify-log-"+t:""),i.innerHTML=e,h.insertBefore(i,h.firstChild),setTimeout(function(){i.className=i.className+" alertify-log-show"},50),this.close(i,r)},set:function(e){var t;if(typeof e!="object"&&e instanceof Array)throw new Error("args must be an object");for(t in e)e.hasOwnProperty(t)&&(this[t]=e[t])},setup:function(){var e=u[0];s=!0,c.innerHTML=this.build(e),typeof e.placeholder=="string"&&e.placeholder!==""&&(a("alertify-text").value=e.placeholder),this.addListeners(e.callback)},unbind:function(e,t,n){typeof e.removeEventListener=="function"?e.removeEventListener(t,n,!1):e.detachEvent&&e.detachEvent("on"+t,n)}},{alert:function(e,t,n){return r.dialog(e,"alert",t,"",n),this},confirm:function(e,t,n){return r.dialog(e,"confirm",t,"",n),this},extend:r.extend,init:r.init,log:function(e,t,n){return r.log(e,t,n),this},prompt:function(e,t,n,i){return r.dialog(e,"prompt",t,n,i),this},success:function(e,t){return r.log(e,"success",t),this},error:function(e,t){return r.log(e,"error",t),this},set:function(e){r.set(e)},labels:r.labels}},typeof define=="function"?define([],function(){return new r}):typeof e.alertify=="undefined"&&(e.alertify=new r)})(this); -------------------------------------------------------------------------------- /editor/contenteditable.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 33 | 138 | 139 | 140 | 141 |
1
Your majesty, your people are starving in the streets, and threaten revolution. 142 | Our enemies to the west are weak, but they threaten soon to invade. What will you do? 143 | 144 | *choice 145 | #Make pre-emptive war on the western lands. 146 | If you can seize their territory, your kingdom will flourish. But your army's 147 | morale is low and the kingdom's armory is empty. How will you win the war? 148 | *choice 149 | #Drive the peasants like slaves; if we work hard enough, we'll win. 150 | Unfortunately, morale doesn't work like that. Your army soon turns against you 151 | and the kingdom falls to the western barbarians. 152 | *finish 153 | #Appoint charismatic knights and give them land, peasants, and resources. 154 | Your majesty's people are eminently resourceful. Your knights win the day, 155 | but take care: they may soon demand a convention of parliament. 156 | *finish 157 | #Steal food and weapons from the enemy in the dead of night. 158 | A cunning plan. Soon your army is a match for the westerners; they choose 159 | not to invade for now, but how long can your majesty postpone the inevitable? 160 | *finish 161 | #Beat swords to plowshares and trade food to the westerners for protection. 162 | The westerners have you at the point of a sword. They demand unfair terms 163 | from you. 164 | *choice 165 | #Accept the terms for now. 166 | Eventually, the barbarian westerners conquer you anyway, destroying their 167 | bread basket, and the entire region starves. 168 | *finish 169 | #Threaten to salt our fields if they don't offer better terms. 170 | They blink. Your majesty gets a fair price for wheat. 171 | *finish 172 | #Abdicate the throne. I have clearly mismanaged this kingdom! 173 | The kingdom descends into chaos, but you manage to escape with your own hide. 174 | Perhaps in time you can return to restore order to this fair land. 175 | *finish
176 | 177 | -------------------------------------------------------------------------------- /tests/autotesttest.js: -------------------------------------------------------------------------------- 1 | nav = { 2 | nextSceneName: function() { return "";} 3 | } 4 | 5 | function autotestScene(text, expectedCoverage, expectedUncovered) { 6 | stats = {}; 7 | var result = autotester(text, undefined, 'startup'); 8 | deepEqual(result[0], expectedCoverage, "coverage"); 9 | var uncovered = result[1]; 10 | deepEqual(uncovered, expectedUncovered, "uncovered"); 11 | } 12 | 13 | test("textOnly", function() { 14 | var scene = "foo\nbar\nbaz"; 15 | autotestScene(scene, [1,1,1,0]); 16 | }) 17 | test("unreachable", function() { 18 | var scene = "foo\n*goto baz\nbar\n*label baz\nbaz"; 19 | autotestScene(scene, [1,1,0,1,1,0], [3]); 20 | }) 21 | test("basicIf", function() { 22 | var scene = "" 23 | +"\n*temp blah" 24 | +"\n*set blah 2" 25 | +"\n*if blah = 2" 26 | +"\n *finish" 27 | +"\n*elseif blah = 3" 28 | +"\n *finish" 29 | +"\n*elseif blah = 4" 30 | +"\n *finish" 31 | ; 32 | autotestScene(scene, [1,1,1,1,1,1,1,1,1,1,0]); 33 | }) 34 | test("basicChoice", function() { 35 | var scene = "" 36 | +"\nFoo" 37 | +"\n*choice" 38 | +"\n #foo" 39 | +"\n *finish" 40 | +"\n #bar" 41 | +"\n *finish" 42 | +"\n #baz" 43 | +"\n *finish" 44 | +"\n #quz" 45 | +"\n *finish" 46 | ; 47 | autotestScene(scene, [1,1,1,1,1,1,1,1,1,1,1,0]); 48 | }) 49 | 50 | test("gotoChoice", function() { 51 | var scene = "" 52 | +"\nFoo" 53 | +"\n*choice" 54 | +"\n #foo" 55 | +"\n *goto end" 56 | +"\n #bar" 57 | +"\n *goto end" 58 | +"\n #baz" 59 | +"\n *goto end" 60 | +"\n #quz" 61 | +"\n *goto end" 62 | +"\n*label end" 63 | +"\nthe end" 64 | ; 65 | autotestScene(scene, [1,1,1,1,1,1,1,1,1,1,1,1,1,0]); 66 | }) 67 | test("conditionalChoices", function() { 68 | var scene = "" 69 | +"\nFoo" 70 | +"\n*choice" 71 | +"\n *if true" 72 | +"\n #foo" 73 | +"\n *finish" 74 | +"\n *if false" 75 | +"\n #bar" 76 | +"\n *finish" 77 | +"\n #baz" 78 | +"\n *finish" 79 | +"\n #quz" 80 | +"\n *finish" 81 | ; 82 | autotestScene(scene, [1,1,1,1,1,1,1,1,1,1,1,1,1,0]); 83 | }) 84 | test("conditionalChoicesElse", function() { 85 | var scene = "" 86 | +"\nFoo" 87 | +"\n*choice" 88 | +"\n *if true" 89 | +"\n #foo" 90 | +"\n *finish" 91 | +"\n *else" 92 | +"\n #bar" 93 | +"\n *finish" 94 | +"\n #baz" 95 | +"\n *finish" 96 | +"\n #quz" 97 | +"\n *finish" 98 | ; 99 | autotestScene(scene, [1,1,1,1,1,1,1,1,1,1,1,1,1,0]); 100 | }) 101 | test("conditionalChoicesElseIf", function() { 102 | var scene = "" 103 | +"\nFoo" 104 | +"\n*choice" 105 | +"\n *if true" 106 | +"\n #foo" 107 | +"\n *finish" 108 | +"\n *elseif false" 109 | +"\n #bar" 110 | +"\n *finish" 111 | +"\n #baz" 112 | +"\n *finish" 113 | +"\n #quz" 114 | +"\n *finish" 115 | ; 116 | autotestScene(scene, [1,1,1,1,1,1,1,1,1,1,1,1,1,0]); 117 | }) 118 | test("oneLineConditionalChoices", function() { 119 | var scene = "" 120 | +"\nFoo" 121 | +"\n*choice" 122 | +"\n *if (true) #foo" 123 | +"\n *finish" 124 | +"\n *if (false) #bar" 125 | +"\n *finish" 126 | +"\n #baz" 127 | +"\n *finish" 128 | +"\n #quz" 129 | +"\n *finish" 130 | ; 131 | autotestScene(scene, [1,1,1,1,1,1,1,1,1,1,1,0]); 132 | }) 133 | test("choiceInIfList", function() { 134 | var scene = "" 135 | +"\n*temp foo" 136 | +"\n*set foo 1" 137 | +"\n" 138 | +"\n*if foo = 1" 139 | +"\n 1" 140 | +"\n 1" 141 | +"\n*if foo = 2" 142 | +"\n *choice" 143 | +"\n #This" 144 | +"\n *finish" 145 | +"\n #That" 146 | +"\n *finish" 147 | +"\n*if foo = 3" 148 | +"\n 3" 149 | +"\n 3" 150 | +"\n*if foo = 4" 151 | +"\n 4" 152 | +"\n 4" 153 | ; 154 | autotestScene(scene, [1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,2,1,1,1,0]); 155 | }) 156 | test("nestedChoice", function() { 157 | var scene = "" 158 | +"\n*temp foo" 159 | +"\n*set foo 1" 160 | +"\n" 161 | +"\n*if foo = 1" 162 | +"\n 1" 163 | +"\n *if foo = 2" 164 | +"\n 2" 165 | +"\n *if foo = 3" 166 | +"\n 3" 167 | +"\n" 168 | +"\n*page_break" 169 | +"\ndone" 170 | ; 171 | autotestScene(scene, [1,1,1,1,1,1,1,1,2,1,3,3,1,0]); 172 | }) 173 | test("badElseIf", function() { 174 | stats = {}; 175 | var scene = "" 176 | +"\n*temp blah" 177 | +"\n*set blah 2" 178 | +"\n*if blah = 2" 179 | +"\n two" 180 | +"\n*elseif blah = 3" 181 | "+\n three"; 182 | raises(function() {autotester(scene)}, null, "Fall out of if statement"); 183 | }) 184 | test("repeatedGosub", function() { 185 | var scene = "" 186 | +"\nHi" 187 | +"\n*gosub foo" 188 | +"\n*gosub Foo" 189 | +"\nBye" 190 | +"\n*finish" 191 | +"\n*label foo" 192 | +"\nFoo" 193 | +"\n*return" 194 | ; 195 | autotestScene(scene, [1,1,2,1,1,1,1,1,1,0]); 196 | }) 197 | test("purchase", function() { 198 | var scene = "" 199 | +"\n*product foo" 200 | +"\n*check_purchase foo" 201 | +"\n*if choice_purchased_foo" 202 | +"\n *goto bought" 203 | +"\nBuy it!" 204 | +"\n*purchase foo $0.99 bought" 205 | +"\n*delay_break 5" 206 | +"\n*label bought" 207 | +"\nDone" 208 | ; 209 | autotestScene(scene, [1,1,1,1,1,1,1,1,2,1,0]); 210 | }) 211 | test("repeatedStatChart", function() { 212 | var scene = "" 213 | +"\n*temp x" 214 | +"\n*set x \"foo\"" 215 | +"\nHi" 216 | +"\n*stat_chart" 217 | +"\n text x" 218 | +"\n" 219 | +"\nHello again" 220 | +"\n" 221 | +"\n*stat_chart" 222 | +"\n text x" 223 | +"\n" 224 | +"\nGood bye for now!" 225 | +"\n" 226 | +"\nAll done" 227 | ; 228 | autotestScene(scene, [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0]); 229 | }) 230 | test("afterFakeChoice", function() { 231 | var scene = "" 232 | +"\n*fake_choice" 233 | +"\n #Foo" 234 | +"\n #Bar" 235 | +"\n Baz" 236 | +"\nQuz" 237 | ; 238 | autotestScene(scene, [1,1,1,1,1,2,0]); 239 | }) 240 | test("ifInChoiceMistakenForRealIf", function() { 241 | var scene = "" 242 | +"\n*choice" 243 | +"\n #One" 244 | +"\n One" 245 | +"\n *choice" 246 | +"\n #A" 247 | +"\n A" 248 | +"\n *if false" 249 | +"\n impossibleA" 250 | +"\n *finish" 251 | +"\n *if false" 252 | +"\n #B" 253 | +"\n impossibleB" 254 | +"\n *finish" 255 | +"\n #Two" 256 | +"\n Two" 257 | +"\n *finish" 258 | ; 259 | raises(function() {autotester(scene)}, null, "Fall out of *choice statement"); 260 | }) 261 | -------------------------------------------------------------------------------- /editor/index.html: -------------------------------------------------------------------------------- 1 | 2 | 14 | Basic ChoiceScript Editor 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 52 | 87 | 173 | 174 | 175 |
 
176 | 177 |
217 | 218 | -------------------------------------------------------------------------------- /mygamegenerator.js: -------------------------------------------------------------------------------- 1 | if (typeof process !== "undefined" && typeof require !== "undefined") { 2 | fs = require("fs"); 3 | vm = require("vm"); 4 | vm.runInThisContext(fs.readFileSync("headless.js"), "headless.js"); 5 | args = process.argv; 6 | args.shift(); 7 | args.shift(); 8 | 9 | gameDir = args[0] || "mygame"; 10 | beta = args[1]; 11 | rootDir = null; 12 | try { 13 | console.log(generateMygame(gameDir, beta, fs)); 14 | } catch (e) { 15 | console.error(e.message); 16 | process.exit(1); 17 | } 18 | } 19 | 20 | function generateMygame(gameDir = "mygame", beta, fs) { 21 | var output = ""; 22 | function parseSceneList(lines, lineNum) { 23 | var nextIndent = null; 24 | var scenes = []; 25 | var purchases = {}; 26 | var line; 27 | while (typeof (line = lines[++lineNum]) != "undefined") { 28 | if (!line.trim()) continue; 29 | 30 | var indent = /^(\s*)/.exec(line)[1].length; 31 | if (nextIndent === null || nextIndent === undefined) { 32 | // initialize nextIndent with whatever indentation the line turns out to be 33 | // ...unless it's not indented at all 34 | if (indent === 0) throw new Error("invalid scene_list indent, expected at least one row"); 35 | this.indent = nextIndent = indent; 36 | } 37 | if (indent === 0) break; 38 | if (indent != this.indent) { 39 | // all scenes are supposed to be at the same indentation level 40 | throw new Error("invalid scene_list indent, expected " + this.indent + ", was " + indent); 41 | } 42 | 43 | line = line.trim(); 44 | var purchaseMatch = /^\$(\w*)\s+(.*)/.exec(line); 45 | if (purchaseMatch) { 46 | line = purchaseMatch[2]; 47 | var product = purchaseMatch[1].trim() || "adfree"; 48 | purchases[line] = product; 49 | } 50 | if (!scenes.length && "startup" != line) scenes.push("startup"); 51 | scenes.push(line); 52 | } 53 | return { scenes: scenes, purchases: purchases, lineNum: lineNum - 1 }; 54 | } 55 | 56 | function parseAchievement(data, lines, lineNum) { 57 | var nextIndent = null; 58 | var parsed = /(\S+)\s+(\S+)\s+(\S+)\s+(.*)/.exec(data); 59 | var achievementName = parsed[1] = parsed[1].toLowerCase(); 60 | var visibility = parsed[2]; 61 | var visible = (visibility != "hidden"); 62 | parsed[2] = visible; 63 | var pointString = parsed[3]; 64 | parsed[3] = pointString * 1; 65 | var title = parsed[4]; 66 | var line = lines[++lineNum]; 67 | var preEarnedDescription = line.trim(); 68 | parsed[6] = preEarnedDescription; 69 | 70 | var postEarnedDescription = null; 71 | while (typeof (line = lines[++lineNum]) != "undefined") { 72 | if (line.trim()) break; 73 | } 74 | if (/^\s/.test(line)) { 75 | postEarnedDescription = line.trim(); 76 | } else { 77 | // No indent means the next line is not a post-earned description 78 | lineNum--; 79 | } 80 | if (postEarnedDescription === null) postEarnedDescription = preEarnedDescription; 81 | parsed[5] = postEarnedDescription; 82 | parsed.shift(); 83 | achievements.push(parsed); 84 | return lineNum; 85 | } 86 | 87 | function parseCheckPurchase(data) { 88 | var products = data.split(" "); 89 | for (var i = 0; i < products.length; i++) { 90 | var product = products[i]; 91 | if (!productMap[product]) { 92 | purchases["fake:" + product] = product; 93 | } 94 | } 95 | } 96 | 97 | function parseCreateValue(value) { 98 | if (/^true$/i.test(value)) value = "true"; 99 | if (/^false$/i.test(value)) value = "false"; 100 | if (/^".*"$/.test(value)) value = value.slice(1, -1).replace(/\\(.)/g, "$1"); 101 | return value; 102 | } 103 | 104 | function parseCreateArray(line) { 105 | var result = /^(\w+)\s+(.*)/.exec(line); 106 | var variable = result[1].toLowerCase(); 107 | var values = result[2].split(/\s+/); 108 | var length = Number(values.shift()); 109 | if (values.length === 1) { 110 | var value = parseCreateValue(values[0]); 111 | for (var i = 0; i < length; i++) { 112 | stats[variable + "_" + (i + 1)] = value; 113 | } 114 | } else { 115 | for (var i = 0; i < length; i++) { 116 | var value = parseCreateValue(values[i]); 117 | stats[variable + "_" + (i + 1)] = value; 118 | } 119 | } 120 | } 121 | 122 | var lines = slurpFileLines((rootDir ?? "web/") + gameDir + "/scenes/startup.txt"); 123 | var stats = {}, purchases = {}, productMap = {}; 124 | var scenes = ["startup"]; 125 | var create = /^\*create +(\w+) +(.*)/; 126 | var result, variable, value; 127 | var achievements = []; 128 | 129 | var ignoredInitialCommands = { "comment": 1, "author": 1, "ifid": 1 }; 130 | 131 | for (var i = 0; i < lines.length; i++) { 132 | var line = ("" + lines[i]).trim(); 133 | if (!line) { continue; } 134 | var result = /^\s*\*(\w+)(.*)/.exec(line); 135 | if (!result) break; 136 | var command = result[1].toLowerCase(); 137 | var data = result[2].trim(); 138 | if (ignoredInitialCommands[command]) { continue; } 139 | else if (command == "create") { 140 | var result = /^(\w*)(.*)/.exec(data); 141 | variable = result[1]; 142 | value = parseCreateValue(result[2].trim()); 143 | stats[variable.toLowerCase()] = value; 144 | } else if (command == "create_array") { 145 | parseCreateArray(data); 146 | } else if (command == "scene_list") { 147 | result = parseSceneList(lines, i); 148 | scenes = result.scenes; 149 | purchases = result.purchases; 150 | i = result.lineNum; 151 | } else if (command == "title") { 152 | stats.choice_title = data; 153 | } else if (command == "achievement") { 154 | i = parseAchievement(data, lines, i); 155 | } else if (command == "product") { 156 | // ignore products for now; we compute them from check_purchases 157 | // *product this only has an effect on quicktest 158 | } else if (command === "bug") { 159 | if (beta === '"beta"' && data === "choice_beta") { 160 | continue; 161 | } 162 | console.error("startup.txt contains *bug"); 163 | process.exit(1); 164 | } else { 165 | break; 166 | } 167 | } 168 | 169 | for (var scene in purchases) { 170 | productMap[purchases[scene]] = scene; 171 | } 172 | 173 | if (fs) { 174 | var sceneDir = fs.readdirSync("web/" + gameDir + "/scenes"); 175 | for (i = 0; i < sceneDir.length; i++) { 176 | var lines = slurpFileLines("web/" + gameDir + "/scenes/" + sceneDir[i]); 177 | for (var j = 0; j < lines.length; j++) { 178 | var line = ("" + lines[j]).trim(); 179 | if (!line) { continue; } 180 | var result = /^\s*\*(\w+)(.*)/.exec(line); 181 | if (!result) continue; 182 | var command = result[1].toLowerCase(); 183 | var data = result[2].trim(); 184 | if (command == "check_purchase") { 185 | parseCheckPurchase(data); 186 | } else if (command == "delay_ending") { 187 | purchases["fake:skiponce"] = "skiponce"; 188 | } 189 | } 190 | } 191 | } 192 | 193 | output += ("\ufeffnav = new SceneNavigator("); 194 | 195 | function logJson(x) { 196 | var json = JSON.stringify(x, null, " "); 197 | json = json.replace(/[\u007f-\uffff]/g, function (c) { 198 | return '\\u' + ('0000' + c.charCodeAt(0).toString(16)).slice(-4); 199 | }); 200 | output += (json); 201 | } 202 | 203 | logJson(scenes); 204 | 205 | output += (");\nstats = "); 206 | 207 | logJson(stats); 208 | 209 | output += (";\npurchases = "); 210 | 211 | logJson(purchases); 212 | 213 | output += (";\nachievements = "); 214 | logJson(achievements); 215 | output += (";\n"); 216 | 217 | if (beta === '"beta"' || beta === '"beta-iap"') { 218 | output += ("beta = " + beta + ";\n"); 219 | const betaPassword = slurpFile("beta-password.txt").trim(); 220 | output += (`betaPassword = "${btoa(`beta:${betaPassword}`)}";`) 221 | } 222 | 223 | output += ("nav.setStartingStatsClone(stats);"); 224 | output += ("if (achievements.length) {\n nav.loadAchievements(achievements);\n}"); 225 | output += ("\nif (nav.loadProducts) nav.loadProducts([], purchases);\n"); 226 | return output; 227 | } 228 | -------------------------------------------------------------------------------- /tests/achievetest.js: -------------------------------------------------------------------------------- 1 | 2 | test("spell", function() { 3 | equal("zero", spell(0)); 4 | equal("ten", spell(10)); 5 | equal("thirty", spell(30)); 6 | equal("twenty-five", spell(25)); 7 | equal(100, spell(100)); 8 | }); 9 | 10 | nav = { 11 | achievements: {}, 12 | achieved: {}, 13 | achievementList: [], 14 | loadAchievements: function(achievementArray) { 15 | if (!achievementArray) return; 16 | for (var i = 0; i < achievementArray.length; i++) { 17 | var achievement = achievementArray[i]; 18 | var achievementName = achievement[0]; 19 | var visible = achievement[1]; 20 | var points = achievement[2]; 21 | var title = achievement[3]; 22 | var earnedDescription = achievement[4]; 23 | var preEarnedDescription = achievement[5]; 24 | this.achievements[achievementName] = { 25 | visible: visible, 26 | points: points, 27 | title: title, 28 | earnedDescription: earnedDescription, 29 | preEarnedDescription: preEarnedDescription, 30 | }; 31 | this.achievementList.push(achievementName); 32 | } 33 | }, 34 | resetAchievements: function() { 35 | this.achievements = {}; 36 | this.achieved = {}; 37 | this.achievementList = []; 38 | } 39 | }; 40 | 41 | test("three visible", function() { 42 | nav.resetAchievements(); 43 | nav.loadAchievements([ 44 | ["foo", true, 50, "Foo", "Earned Foo", "Pre Earned Foo"], 45 | ["bar", true, 50, "Bar", "Earned Bar", "Pre Earned Bar"], 46 | ["baz", true, 50, "Baz", "Earned Baz", "Pre Earned Baz"], 47 | ]); 48 | var output = {}; 49 | printAchievements(output); 50 | equal(output.innerHTML, "You haven't unlocked any achievements yet. There are three possible achievements, worth a total of 150 points.

"+ 51 | "Foo: Pre Earned Foo (50 points)
Bar: Pre Earned Bar (50 points)
Baz: Pre Earned Baz (50 points)

"); 52 | 53 | nav.achieved = {foo:1}; 54 | printAchievements(output); 55 | equal(output.innerHTML, "You have unlocked one out of three possible achievements, earning you a score of 50 out of a possible 150 points.

"+ 56 | "Foo: Earned Foo (50 points)

There are still two achievements remaining.

Bar: Pre Earned Bar (50 points)
Baz: Pre Earned Baz (50 points)

"); 57 | 58 | nav.achieved = {foo:1, bar: 1}; 59 | printAchievements(output); 60 | equal(output.innerHTML, "You have unlocked two out of three possible achievements, earning you a score of 100 out of a possible 150 points.

"+ 61 | "Foo: Earned Foo (50 points)
Bar: Earned Bar (50 points)

There is still one achievement remaining.

Baz: Pre Earned Baz (50 points)

"); 62 | 63 | nav.achieved = {foo:1, bar: 1, baz: 1}; 64 | printAchievements(output); 65 | equal(output.innerHTML, "Congratulations! You have unlocked all three achievements, earning a total of 150 points, a perfect score.

"+ 66 | "Foo: Earned Foo (50 points)
Bar: Earned Bar (50 points)
Baz: Earned Baz (50 points)

"); 67 | }); 68 | 69 | test("two visible", function() { 70 | nav.resetAchievements(); 71 | nav.loadAchievements([ 72 | ["foo", true, 50, "Foo", "Earned Foo", "Pre Earned Foo"], 73 | ["bar", true, 50, "Bar", "Earned Bar", "Pre Earned Bar"], 74 | ]); 75 | var output = {}; 76 | printAchievements(output); 77 | equal(output.innerHTML, "You haven't unlocked any achievements yet. There are two possible achievements, worth a total of 100 points.

"+ 78 | "Foo: Pre Earned Foo (50 points)
Bar: Pre Earned Bar (50 points)

"); 79 | 80 | nav.achieved = {foo:1}; 81 | printAchievements(output); 82 | equal(output.innerHTML, "You have unlocked one out of two possible achievements, earning you a score of 50 out of a possible 100 points.

"+ 83 | "Foo: Earned Foo (50 points)

There is still one achievement remaining.

Bar: Pre Earned Bar (50 points)

"); 84 | 85 | nav.achieved = {foo:1, bar: 1}; 86 | printAchievements(output); 87 | equal(output.innerHTML, "Congratulations! You have unlocked both achievements, earning a total of 100 points, a perfect score.

"+ 88 | "Foo: Earned Foo (50 points)
Bar: Earned Bar (50 points)

"); 89 | }); 90 | 91 | test("three hidden", function() { 92 | nav.resetAchievements(); 93 | nav.loadAchievements([ 94 | ["foo", false, 50, "Foo", "Earned Foo", null], 95 | ["bar", false, 50, "Bar", "Earned Bar", null], 96 | ["baz", false, 50, "Baz", "Earned Baz", null], 97 | ]); 98 | var output = {}; 99 | printAchievements(output); 100 | equal(output.innerHTML, "You haven't unlocked any achievements yet. There are three hidden achievements, worth a total of 150 points.

"); 101 | 102 | nav.achieved = {foo:1}; 103 | printAchievements(output); 104 | equal(output.innerHTML, "You have unlocked one out of three possible achievements, earning you a score of 50 out of a possible 150 points.

"+ 105 | "Foo: Earned Foo (50 points)

There are still two hidden achievements remaining.

"); 106 | 107 | nav.achieved = {foo:1, bar: 1}; 108 | printAchievements(output); 109 | equal(output.innerHTML, "You have unlocked two out of three possible achievements, earning you a score of 100 out of a possible 150 points.

"+ 110 | "Foo: Earned Foo (50 points)
Bar: Earned Bar (50 points)

There is still one hidden achievement remaining.

"); 111 | 112 | nav.achieved = {foo:1, bar: 1, baz: 1}; 113 | printAchievements(output); 114 | equal(output.innerHTML, "Congratulations! You have unlocked all three achievements, earning a total of 150 points, a perfect score.

"+ 115 | "Foo: Earned Foo (50 points)
Bar: Earned Bar (50 points)
Baz: Earned Baz (50 points)

"); 116 | }); 117 | 118 | test("two visible, two hidden", function() { 119 | nav.resetAchievements(); 120 | nav.loadAchievements([ 121 | ["foo", true, 50, "Foo", "Earned Foo", "Pre Earned Foo"], 122 | ["bar", true, 50, "Bar", "Earned Bar", "Pre Earned Bar"], 123 | ["baz", false, 50, "Baz", "Earned Baz", null], 124 | ["quz", false, 50, "Quz", "Earned Quz", null], 125 | ]); 126 | var output = {}; 127 | printAchievements(output); 128 | equal(output.innerHTML, "You haven't unlocked any achievements yet. There are four possible achievements (including two hidden achievements), worth a total of 200 points.

"+ 129 | "Foo: Pre Earned Foo (50 points)
Bar: Pre Earned Bar (50 points)

"); 130 | 131 | nav.achieved = {foo:1}; 132 | printAchievements(output); 133 | equal(output.innerHTML, "You have unlocked one out of four possible achievements, earning you a score of 50 out of a possible 200 points.

"+ 134 | "Foo: Earned Foo (50 points)

There are still three achievements remaining, including two hidden achievements.

Bar: Pre Earned Bar (50 points)

"); 135 | 136 | nav.achieved = {foo:1, baz:1}; 137 | printAchievements(output); 138 | equal(output.innerHTML, "You have unlocked two out of four possible achievements, earning you a score of 100 out of a possible 200 points.

"+ 139 | "Foo: Earned Foo (50 points)
Baz: Earned Baz (50 points)

There are still two achievements remaining, including one hidden achievement.

Bar: Pre Earned Bar (50 points)

"); 140 | 141 | nav.achieved = {foo:1, bar:1}; 142 | printAchievements(output); 143 | equal(output.innerHTML, "You have unlocked two out of four possible achievements, earning you a score of 100 out of a possible 200 points.

"+ 144 | "Foo: Earned Foo (50 points)
Bar: Earned Bar (50 points)

There are still two hidden achievements remaining.

"); 145 | 146 | nav.achieved = {baz:1, quz:1}; 147 | printAchievements(output); 148 | equal(output.innerHTML, "You have unlocked two out of four possible achievements, earning you a score of 100 out of a possible 200 points.

"+ 149 | "Baz: Earned Baz (50 points)
Quz: Earned Quz (50 points)

There are still two achievements remaining.

Foo: Pre Earned Foo (50 points)
Bar: Pre Earned Bar (50 points)

"); 150 | }); 151 | 152 | test("two visible, one hidden", function() { 153 | nav.resetAchievements(); 154 | nav.loadAchievements([ 155 | ["foo", true, 50, "Foo", "Earned Foo", "Pre Earned Foo"], 156 | ["bar", true, 50, "Bar", "Earned Bar", "Pre Earned Bar"], 157 | ["baz", false, 50, "Baz", "Earned Baz", null], 158 | ]); 159 | var output = {}; 160 | printAchievements(output); 161 | equal(output.innerHTML, "You haven't unlocked any achievements yet. There are three possible achievements (including one hidden achievement), worth a total of 150 points.

"+ 162 | "Foo: Pre Earned Foo (50 points)
Bar: Pre Earned Bar (50 points)

"); 163 | 164 | nav.achieved = {foo:1}; 165 | printAchievements(output); 166 | equal(output.innerHTML, "You have unlocked one out of three possible achievements, earning you a score of 50 out of a possible 150 points.

"+ 167 | "Foo: Earned Foo (50 points)

There are still two achievements remaining, including one hidden achievement.

Bar: Pre Earned Bar (50 points)

"); 168 | }); 169 | -------------------------------------------------------------------------------- /doc/more.html: -------------------------------------------------------------------------------- 1 | Advanced ChoiceScript 2 | 3 |

Advanced ChoiceScript

4 |

A guide to more advanced features in the ChoiceScript programming language. Please post on the ChoiceScript google group if you have questions about this document.

5 | 6 |

Don't Start Here!

7 | 8 |

Be sure to read our basic ChoiceScript Introduction page before reading this advanced documentation

9 | 10 |

More Commands

11 | 12 | 73 |

Advanced Techniques

74 | 167 | 168 |

Questions?

169 | 170 |

Please post on the ChoiceScript google group if you have questions about this document.

171 | 172 | -------------------------------------------------------------------------------- /randomtest.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Randomtest 5 | 6 | 7 |

How many times would you like to run randomtest?

8 |

Starting seed number:

9 |

10 |

11 |

12 |

13 |

14 |

15 |

Record data for auto() balancing

16 | 17 | 18 | 202 | 203 | -------------------------------------------------------------------------------- /seedrandom.js: -------------------------------------------------------------------------------- 1 | // seedrandom.js 2 | // Author: David Bau 3/11/2010 3 | // 4 | // Defines a method Math.seedrandom() that, when called, substitutes 5 | // an explicitly seeded RC4-based algorithm for Math.random(). Also 6 | // supports automatic seeding from local or network sources of entropy. 7 | // 8 | // Usage: 9 | // 10 | // 11 | // 12 | // Math.seedrandom('yipee'); Sets Math.random to a function that is 13 | // initialized using the given explicit seed. 14 | // 15 | // Math.seedrandom(); Sets Math.random to a function that is 16 | // seeded using the current time, dom state, 17 | // and other accumulated local entropy. 18 | // The generated seed string is returned. 19 | // 20 | // Math.seedrandom('yowza', true); 21 | // Seeds using the given explicit seed mixed 22 | // together with accumulated entropy. 23 | // 24 | // 25 | // Seeds using physical random bits downloaded 26 | // from random.org. 27 | // 28 | // Examples: 29 | // 30 | // Math.seedrandom("hello"); // Use "hello" as the seed. 31 | // document.write(Math.random()); // Always 0.5463663768140734 32 | // document.write(Math.random()); // Always 0.43973793770592234 33 | // var rng1 = Math.random; // Remember the current prng. 34 | // 35 | // var autoseed = Math.seedrandom(); // New prng with an automatic seed. 36 | // document.write(Math.random()); // Pretty much unpredictable. 37 | // 38 | // Math.random = rng1; // Continue "hello" prng sequence. 39 | // document.write(Math.random()); // Always 0.554769432473455 40 | // 41 | // Math.seedrandom(autoseed); // Restart at the previous seed. 42 | // document.write(Math.random()); // Repeat the 'unpredictable' value. 43 | // 44 | // Notes: 45 | // 46 | // Each time seedrandom('arg') is called, entropy from the passed seed 47 | // is accumulated in a pool to help generate future seeds for the 48 | // zero-argument form of Math.seedrandom, so entropy can be injected over 49 | // time by calling seedrandom with explicit data repeatedly. 50 | // 51 | // On speed - This javascript implementation of Math.random() is about 52 | // 3-10x slower than the built-in Math.random() because it is not native 53 | // code, but this is typically fast enough anyway. Seeding is more expensive, 54 | // especially if you use auto-seeding. Some details (timings on Chrome 4): 55 | // 56 | // Our Math.random() - avg less than 0.002 milliseconds per call 57 | // seedrandom('explicit') - avg less than 0.5 milliseconds per call 58 | // seedrandom('explicit', true) - avg less than 2 milliseconds per call 59 | // seedrandom() - avg about 38 milliseconds per call 60 | // 61 | // LICENSE (BSD): 62 | // 63 | // Copyright 2010 David Bau, all rights reserved. 64 | // 65 | // Redistribution and use in source and binary forms, with or without 66 | // modification, are permitted provided that the following conditions are met: 67 | // 68 | // 1. Redistributions of source code must retain the above copyright 69 | // notice, this list of conditions and the following disclaimer. 70 | // 71 | // 2. Redistributions in binary form must reproduce the above copyright 72 | // notice, this list of conditions and the following disclaimer in the 73 | // documentation and/or other materials provided with the distribution. 74 | // 75 | // 3. Neither the name of this module nor the names of its contributors may 76 | // be used to endorse or promote products derived from this software 77 | // without specific prior written permission. 78 | // 79 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 80 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 81 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 82 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 83 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 84 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 85 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 86 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 87 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 88 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 89 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 90 | // 91 | /** 92 | * All code is in an anonymous closure to keep the global namespace clean. 93 | * 94 | * @param {number=} overflow 95 | * @param {number=} startdenom 96 | */ 97 | (function (pool, math, width, chunks, significance, overflow, startdenom) { 98 | 99 | 100 | // 101 | // seedrandom() 102 | // This is the seedrandom function described above. 103 | // 104 | math['seedrandom'] = function seedrandom(seed, use_entropy) { 105 | var key = []; 106 | var arc4; 107 | 108 | // Flatten the seed string or build one from local entropy if needed. 109 | seed = mixkey(flatten( 110 | use_entropy ? [seed, pool] : 111 | arguments.length ? seed : 112 | [new Date().getTime(), pool, window], 3), key); 113 | 114 | // Use the seed to initialize an ARC4 generator. 115 | arc4 = new ARC4(key); 116 | 117 | // Mix the randomness into accumulated entropy. 118 | mixkey(arc4.S, pool); 119 | 120 | // Override Math.random 121 | 122 | // This function returns a random double in [0, 1) that contains 123 | // randomness in every bit of the mantissa of the IEEE 754 value. 124 | 125 | math['random'] = function random() { // Closure to return a random double: 126 | var n = arc4.g(chunks); // Start with a numerator n < 2 ^ 48 127 | var d = startdenom; // and denominator d = 2 ^ 48. 128 | var x = 0; // and no 'extra last byte'. 129 | while (n < significance) { // Fill up all significant digits by 130 | n = (n + x) * width; // shifting numerator and 131 | d *= width; // denominator and generating a 132 | x = arc4.g(1); // new least-significant-byte. 133 | } 134 | while (n >= overflow) { // To avoid rounding up, before adding 135 | n /= 2; // last byte, shift everything 136 | d /= 2; // right using integer math until 137 | x >>>= 1; // we have exactly the desired bits. 138 | } 139 | return (n + x) / d; // Form the number within [0, 1). 140 | }; 141 | 142 | // Return the seed that was used 143 | return seed; 144 | }; 145 | 146 | // 147 | // ARC4 148 | // 149 | // An ARC4 implementation. The constructor takes a key in the form of 150 | // an array of at most (width) integers that should be 0 <= x < (width). 151 | // 152 | // The g(count) method returns a pseudorandom integer that concatenates 153 | // the next (count) outputs from ARC4. Its return value is a number x 154 | // that is in the range 0 <= x < (width ^ count). 155 | // 156 | /** @constructor */ 157 | function ARC4(key) { 158 | var t, u, me = this, keylen = key.length; 159 | var i = 0, j = me.i = me.j = me.m = 0; 160 | me.S = []; 161 | me.c = []; 162 | 163 | // The empty key [] is treated as [0]. 164 | if (!keylen) { key = [keylen++]; } 165 | 166 | // Set up S using the standard key scheduling algorithm. 167 | while (i < width) { me.S[i] = i++; } 168 | for (i = 0; i < width; i++) { 169 | t = me.S[i]; 170 | j = lowbits(j + t + key[i % keylen]); 171 | u = me.S[j]; 172 | me.S[i] = u; 173 | me.S[j] = t; 174 | } 175 | 176 | // The "g" method returns the next (count) outputs as one number. 177 | me.g = function getnext(count) { 178 | var s = me.S; 179 | var i = lowbits(me.i + 1); var t = s[i]; 180 | var j = lowbits(me.j + t); var u = s[j]; 181 | s[i] = u; 182 | s[j] = t; 183 | var r = s[lowbits(t + u)]; 184 | while (--count) { 185 | i = lowbits(i + 1); t = s[i]; 186 | j = lowbits(j + t); u = s[j]; 187 | s[i] = u; 188 | s[j] = t; 189 | r = r * width + s[lowbits(t + u)]; 190 | } 191 | me.i = i; 192 | me.j = j; 193 | return r; 194 | }; 195 | // For robust unpredictability discard an initial batch of values. 196 | // See http://www.rsa.com/rsalabs/node.asp?id=2009 197 | me.g(width); 198 | } 199 | 200 | // 201 | // flatten() 202 | // Converts an object tree to nested arrays of strings. 203 | // 204 | /** @param {Object=} result 205 | * @param {string=} prop */ 206 | function flatten(obj, depth, result, prop) { 207 | result = []; 208 | if (depth && typeof(obj) == 'object') { 209 | for (prop in obj) { 210 | if (prop.indexOf('S') < 5) { // Avoid FF3 bug (local/sessionStorage) 211 | try { result.push(flatten(obj[prop], depth - 1)); } catch (e) {} 212 | } 213 | } 214 | } 215 | return result.length ? result : '' + obj; 216 | } 217 | 218 | // 219 | // mixkey() 220 | // Mixes a string seed into a key that is an array of integers, and 221 | // returns a shortened string seed that is equivalent to the result key. 222 | // 223 | /** @param {number=} smear 224 | * @param {number=} j */ 225 | function mixkey(seed, key, smear, j) { 226 | seed += ''; // Ensure the seed is a string 227 | smear = 0; 228 | for (j = 0; j < seed.length; j++) { 229 | key[lowbits(j)] = 230 | lowbits((smear ^= key[lowbits(j)] * 19) + seed.charCodeAt(j)); 231 | } 232 | seed = ''; 233 | for (j in key) { seed += String.fromCharCode(key[j]); } 234 | return seed; 235 | } 236 | 237 | // 238 | // lowbits() 239 | // A quick "n mod width" for width a power of 2. 240 | // 241 | function lowbits(n) { return n & (width - 1); } 242 | 243 | // 244 | // The following constants are related to IEEE 754 limits. 245 | // 246 | startdenom = math.pow(width, chunks); 247 | significance = math.pow(2, significance); 248 | overflow = significance * 2; 249 | 250 | // 251 | // When seedrandom.js is loaded, we immediately mix a few bits 252 | // from the built-in RNG into the entropy pool. Because we do 253 | // not want to intefere with determinstic PRNG state later, 254 | // seedrandom will not call math.random on its own again after 255 | // initialization. 256 | // 257 | mixkey(math.random(), pool); 258 | 259 | // End anonymous scope, and pass initial values. 260 | })( 261 | [], // pool: entropy pool starts empty 262 | Math, // math: package containing random, pow, and seedrandom 263 | 256, // width: each RC4 output is 0 <= x < 256 264 | 6, // chunks: at least six RC4 outputs for each double 265 | 52 // significance: there are 52 significant digits in a double 266 | ); 267 | -------------------------------------------------------------------------------- /compile.js: -------------------------------------------------------------------------------- 1 | knownScenes = []; 2 | var scene_object = ""; 3 | var success = true; 4 | var skip = false; 5 | var loadFailed = false; 6 | 7 | var rootDir; 8 | 9 | if (typeof process != "undefined") { 10 | var outputFile = process.argv[2] || "output.html"; 11 | rootDir = process.argv[3]; 12 | if (rootDir) { 13 | rootDir += "/"; 14 | } else { 15 | rootDir = "web/"; 16 | } 17 | fs = require('fs'); 18 | path = require('path'); 19 | vm = require('vm'); 20 | load = function(file) { 21 | vm.runInThisContext(fs.readFileSync(file), file); 22 | }; 23 | load(rootDir+ "scene.js"); 24 | load(rootDir+"navigator.js"); 25 | load(rootDir+"util.js"); 26 | load("headless.js"); 27 | load(rootDir+"mygame/mygame.js"); 28 | load("mygamegenerator.js"); 29 | var {content} = compile(); 30 | fs.writeFileSync(outputFile, content, "utf8"); 31 | console.log('Generated', path.resolve(outputFile)); 32 | } 33 | 34 | if (!rootDir) rootDir = "web/"; 35 | 36 | function compile(){ 37 | if (typeof window !== 'undefined' && "file:" === window.location.protocol && !window.slurpedFiles) { 38 | window.loading.innerHTML = "

Please \"upload\" the choicescript folder (including compile.html).

"; 39 | var input = document.createElement("input"); 40 | input.type = "file"; 41 | input.webkitdirectory = true; 42 | loading.appendChild(input); 43 | input.addEventListener('change', function() { 44 | var candidates = []; 45 | var numFiles = input.files.length; 46 | for (var i = 0; i < numFiles; i++) { 47 | var file = input.files[i]; 48 | if (input.files[i].name == "scene.js") { 49 | candidates.push(file); 50 | } 51 | } 52 | if (!candidates.length) { 53 | alert("We couldn't find scene.js in the folder you chose. Please try again. (Note that compile.html requires access to the entire choicescript directory, not just the mygame folder"); 54 | } else if (candidates.length > 1) { 55 | if (candidates.length > 1) { 56 | alert("There were multiple files called scene.js in the folder you chose. Please try again.\n" + 57 | candidates.map(function(file) {return "\u2022 " + file.webkitRelativePath}).join("\n")); 58 | } 59 | } 60 | rootDir = candidates[0].webkitRelativePath.replace(/\/scene.js$/, "/"); 61 | var rootDirTest = new RegExp("^" + rootDir + ".*\.(js|css|html|txt)"); 62 | loading.innerHTML = ""; 63 | var webFiles = [].filter.call(input.files, function(file) { 64 | return rootDirTest.test(file.webkitRelativePath); 65 | }); 66 | Promise.all(webFiles.map(function(file) {return new Response(file).text()})).then(function(results) { 67 | window.slurpedFiles = {}; 68 | for (var i = 0; i < webFiles.length; i++) { 69 | slurpedFiles[webFiles[i].webkitRelativePath] = results[i]; 70 | } 71 | slurpFile = function(url, throwOnError) { 72 | var parts = url.split('/'); 73 | var newParts = []; 74 | for (var i = 0; i < parts.length; i++) { 75 | if (".." === parts[i]) { 76 | newParts.pop(); 77 | } else { 78 | newParts.push(parts[i]); 79 | } 80 | } 81 | url = newParts.join('/'); 82 | if (throwOnError && ! slurpedFiles[url]) { 83 | throw new Error("Error: Could not open " + url); 84 | } 85 | return slurpedFiles[url]; 86 | } 87 | var compiledResult = compile(); 88 | if (compiledResult) finish(compiledResult); 89 | }) 90 | }); 91 | return; 92 | } 93 | 94 | function safeSlurpFile(file) { 95 | try { 96 | return slurpFile(file, false); 97 | } catch (e) { 98 | return null; 99 | } 100 | } 101 | 102 | //1. Grab the game's html file 103 | var url = rootDir+"mygame/index.html"; 104 | var game_html = slurpFile(url, true); 105 | 106 | //2. Find and extract all .js file data 107 | var next_file = ""; 108 | var patt = /]*><\/script>/gim; 109 | var doesMatch; 110 | var jsStore = ""; 111 | console.log("\nExtracting js data from:"); 112 | while (doesMatch = patt.exec(game_html)) { 113 | console.log(doesMatch[1]); 114 | if (doesMatch[1] === 'mygame.js') { 115 | next_file = generateMygame(); 116 | } else { 117 | next_file = safeSlurpFile(rootDir + 'mygame/' + doesMatch[1]); 118 | } 119 | if (next_file != "undefined" && next_file !== null) { 120 | jsStore = jsStore + "\n;\n" + next_file; 121 | } 122 | } 123 | 124 | console.log(""); 125 | 126 | //3. Find and extract all .css file data 127 | patt = /^/gim; 141 | game_html=game_html.replace(patt,""); 142 | 143 | //5. Remove js links 144 | patt = /^ 6 | 7 | 8 | 9 | 464 | 465 | 466 |
Loading...
467 | 470 | 471 | 472 | --------------------------------------------------------------------------------