diff --git a/.travis.yml b/.travis.yml index 48ef0ed..623b1fd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,11 @@ language: node_js sudo: false +addons: + chrome: stable node_js: - - "node" + - "node" after_success: - - npm run codecov + - npx run codecov cache: directories: - "node_modules" diff --git a/TODO.md b/TODO.md index f3f6c9c..56417f9 100644 --- a/TODO.md +++ b/TODO.md @@ -4,7 +4,7 @@ To-Do-list Phaser 3 migration ------------------ -* Fix valuebar requiring to be in root display list +* Fix valuebar requiring to be in root display list (use tile sprite?) * Restore unit tests about boundaries (in UITools) Menu/settings/saves @@ -31,6 +31,7 @@ Map/story Character sheet --------------- +* Fix action tooltips showing battle information ("not enough power"...) * Improve attribute tooltips * Implement sliders for personality traits * Center the portraits when there are less than 5 @@ -38,6 +39,7 @@ Character sheet Battle ------ +* Move animation should face the target (if any) at the end, not the direction * Improve arena ships layering (sometimes information is displayed behind other sprites) * In the ship tooltip, show power cost, toggled and overheat states * Display shield (and its (dis)appearance) @@ -101,6 +103,7 @@ Common UI * If ProgressiveMessage animation performance is bad, show the text directly * Add caret/focus and configurable background to text input * Release keybord grabbing when UITextInput is hidden or loses focus +* UI parents should only be containers, not images * Mobile: think UI layout so that fingers do not block the view (right and left handed) * Mobile: display tooltips larger and on the side of screen where the finger is not * Mobile: targetting in two times, using a draggable target indicator @@ -109,7 +112,6 @@ Technical --------- * Use tk-serializer package (may need to switch to webpack) -* Fix tooltips and input events on mobile * Pause timers when the game is paused (at least animation timers) * Pack sounds * Add toggles for shaders, automatically disable them if too slow, and initially disable them on mobile diff --git a/out/aiworker.js b/out/aiworker.js index ab3d70a..386d119 100644 --- a/out/aiworker.js +++ b/out/aiworker.js @@ -1,9 +1,9 @@ var handler = { get(target, name) { - return new Proxy({}, handler); + return new Proxy(function () { }, handler); } } -var Phaser = new Proxy({}, handler); +var Phaser = new Proxy(function () { }, handler); //var debug = console.log; var debug = function () { }; diff --git a/package-lock.json b/package-lock.json index 9787a2b..328f5b4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -78,22 +78,11 @@ "es6-promisify": "^5.0.0" } }, - "ajv": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", - "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", - "dev": true, - "requires": { - "co": "^4.6.0", - "fast-deep-equal": "^1.0.0", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.3.0" - } - }, "amdefine": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", - "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=" + "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", + "dev": true }, "ansi-colors": { "version": "1.1.0", @@ -206,18 +195,6 @@ "integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==", "dev": true }, - "asn1": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", - "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=", - "dev": true - }, - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true - }, "assign-symbols": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", @@ -253,59 +230,12 @@ "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==" }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "dev": true - }, "atob": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", "dev": true }, - "aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", - "dev": true - }, - "aws4": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.7.0.tgz", - "integrity": "sha512-32NDda82rhwD9/JBCCkB+MRYDp0oSvlo2IL6rQWA10PQi7tDUM3eqMSltXmY+Oyl/7N3P3qNtAlv7X0d9bI28w==", - "dev": true - }, - "babel-polyfill": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.26.0.tgz", - "integrity": "sha1-N5k3q8Z9eJWXCtxiHyhM2WbPIVM=", - "dev": true, - "requires": { - "babel-runtime": "^6.26.0", - "core-js": "^2.5.0", - "regenerator-runtime": "^0.10.5" - }, - "dependencies": { - "regenerator-runtime": { - "version": "0.10.5", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz", - "integrity": "sha1-M2w+/BIgrc7dosn6tntaeVWjNlg=", - "dev": true - } - } - }, - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, - "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" - } - }, "backo2": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", @@ -400,16 +330,6 @@ "integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=", "dev": true }, - "bcrypt-pbkdf": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", - "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", - "dev": true, - "optional": true, - "requires": { - "tweetnacl": "^0.14.3" - } - }, "bcryptjs": { "version": "2.4.3", "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", @@ -445,7 +365,8 @@ "bluebird": { "version": "2.11.0", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-2.11.0.tgz", - "integrity": "sha1-U0uQM8AiyVecVro7Plpcqvu2UOE=" + "integrity": "sha1-U0uQM8AiyVecVro7Plpcqvu2UOE=", + "dev": true }, "body-parser": { "version": "1.19.0", @@ -534,12 +455,6 @@ "integrity": "sha1-+PeLdniYiO858gXNY39o5wISKyw=", "dev": true }, - "buffer-from": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.0.tgz", - "integrity": "sha512-c5mRlguI/Pe2dSZmpER62rSCu0ryKmWddzRYsuXc50U2/g8jMOulc31VZMa4mYx31U5xsmSOpDCgH88Vl9cDGQ==", - "dev": true - }, "builtin-modules": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", @@ -591,12 +506,6 @@ "map-obj": "^1.0.0" } }, - "caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", - "dev": true - }, "catharsis": { "version": "0.8.10", "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.8.10.tgz", @@ -674,12 +583,6 @@ } } }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", - "dev": true - }, "codecov": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/codecov/-/codecov-3.4.0.tgz", @@ -724,15 +627,6 @@ "integrity": "sha512-EDpX3a7wHMWFA7PUHWPHNWqOxIIRSJetuwl0AS5Oi/5FMV8kWm69RTlgm00GKjBO1xFHMtBbL49yRtMMdticBw==", "dev": true }, - "combined-stream": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", - "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", - "dev": true, - "requires": { - "delayed-stream": "~1.0.0" - } - }, "commander": { "version": "2.20.0", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz", @@ -763,18 +657,6 @@ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true }, - "concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - } - }, "connect": { "version": "3.6.6", "resolved": "https://registry.npmjs.org/connect/-/connect-3.6.6.tgz", @@ -842,15 +724,6 @@ "integrity": "sha1-XQKkaFCt8bSjF5RqOSj8y1v9BCU=", "dev": true }, - "dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0" - } - }, "date-format": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/date-format/-/date-format-2.0.0.tgz", @@ -935,12 +808,6 @@ } } }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "dev": true - }, "depd": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", @@ -982,16 +849,6 @@ "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=", "dev": true }, - "ecc-jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", - "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", - "dev": true, - "optional": true, - "requires": { - "jsbn": "~0.1.0" - } - }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -1342,36 +1199,6 @@ } } }, - "extract-zip": { - "version": "1.6.7", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.6.7.tgz", - "integrity": "sha1-qEC0uK9kAyZMjbV/Txp0Mz74H+k=", - "dev": true, - "requires": { - "concat-stream": "1.6.2", - "debug": "2.6.9", - "mkdirp": "0.5.1", - "yauzl": "2.4.1" - } - }, - "extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", - "dev": true - }, - "fast-deep-equal": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", - "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=", - "dev": true - }, - "fast-json-stable-stringify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", - "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", - "dev": true - }, "fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", @@ -1387,15 +1214,6 @@ "websocket-driver": ">=0.5.1" } }, - "fd-slicer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz", - "integrity": "sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU=", - "dev": true, - "requires": { - "pend": "~1.2.0" - } - }, "fill-range": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", @@ -1499,23 +1317,6 @@ "for-in": "^1.0.1" } }, - "forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", - "dev": true - }, - "form-data": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz", - "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", - "dev": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "1.0.6", - "mime-types": "^2.1.12" - } - }, "fragment-cache": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", @@ -1537,15 +1338,13 @@ "integrity": "sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=", "dev": true }, - "fs-extra": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-1.0.0.tgz", - "integrity": "sha1-zTzl9+fLYUWIP8rjGR6Yd/hYeVA=", + "fs-access": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fs-access/-/fs-access-1.0.1.tgz", + "integrity": "sha1-1qh/JiJxzv6+wwxVNAf7mV2od3o=", "dev": true, "requires": { - "graceful-fs": "^4.1.2", - "jsonfile": "^2.1.0", - "klaw": "^1.0.0" + "null-check": "^1.0.0" } }, "fs.realpath": { @@ -2121,15 +1920,6 @@ "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", "dev": true }, - "getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0" - } - }, "glob": { "version": "7.1.4", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", @@ -2204,22 +1994,6 @@ } } }, - "har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", - "dev": true - }, - "har-validator": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", - "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", - "dev": true, - "requires": { - "ajv": "^5.1.0", - "har-schema": "^2.0.0" - } - }, "has-binary2": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.3.tgz", @@ -2281,16 +2055,6 @@ } } }, - "hasha": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/hasha/-/hasha-2.2.0.tgz", - "integrity": "sha1-eNfL/B5tZjA/55g3NlmEUXsvbuE=", - "dev": true, - "requires": { - "is-stream": "^1.0.1", - "pinkie-promise": "^2.0.0" - } - }, "hosted-git-info": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.6.0.tgz", @@ -2339,17 +2103,6 @@ "requires-port": "^1.0.0" } }, - "http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - } - }, "https-proxy-agent": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz", @@ -2601,18 +2354,6 @@ "isobject": "^3.0.1" } }, - "is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", - "dev": true - }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", - "dev": true - }, "is-utf8": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", @@ -2658,12 +2399,6 @@ "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", "dev": true }, - "isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", - "dev": true - }, "istanbul": { "version": "0.4.5", "resolved": "https://registry.npmjs.org/istanbul/-/istanbul-0.4.5.tgz", @@ -2784,13 +2519,6 @@ "xmlcreate": "^2.0.0" } }, - "jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "dev": true, - "optional": true - }, "jsdoc": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-3.6.2.tgz", @@ -2832,24 +2560,6 @@ } } }, - "json-schema": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", - "dev": true - }, - "json-schema-traverse": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", - "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", - "dev": true - }, - "json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", - "dev": true - }, "json5": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", @@ -2865,27 +2575,6 @@ } } }, - "jsonfile": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", - "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.6" - } - }, - "jsprim": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", - "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", - "dev": true, - "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.2.3", - "verror": "1.10.0" - } - }, "just-debounce": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/just-debounce/-/just-debounce-1.0.0.tgz", @@ -2947,6 +2636,16 @@ } } }, + "karma-chrome-launcher": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-2.2.0.tgz", + "integrity": "sha512-uf/ZVpAabDBPvdPdveyk1EPgbnloPvFFGgmRhYLTDH7gEB4nZdSBk8yTU47w1g/drLSx5uMOkjKk7IWKfWg/+w==", + "dev": true, + "requires": { + "fs-access": "^1.0.0", + "which": "^1.2.1" + } + }, "karma-coverage": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/karma-coverage/-/karma-coverage-1.1.2.tgz", @@ -2983,24 +2682,6 @@ "jasmine-core": "^3.3" } }, - "karma-phantomjs-launcher": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/karma-phantomjs-launcher/-/karma-phantomjs-launcher-1.0.4.tgz", - "integrity": "sha1-0jyjSAG9qYY60xjju0vUBisTrNI=", - "dev": true, - "requires": { - "lodash": "^4.0.1", - "phantomjs-prebuilt": "^2.1.7" - }, - "dependencies": { - "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", - "dev": true - } - } - }, "karma-spec-reporter": { "version": "0.0.32", "resolved": "https://registry.npmjs.org/karma-spec-reporter/-/karma-spec-reporter-0.0.32.tgz", @@ -3010,27 +2691,12 @@ "colors": "^1.1.2" } }, - "kew": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/kew/-/kew-0.7.0.tgz", - "integrity": "sha1-edk9LTM2PW/dKXCzNdkUGtWR15s=", - "dev": true - }, "kind-of": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", "dev": true }, - "klaw": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz", - "integrity": "sha1-QIhDO0azsbolnXh4XY6W9zugJDk=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.9" - } - }, "levn": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", @@ -3110,7 +2776,8 @@ "lodash": { "version": "3.10.1", "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", - "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=" + "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=", + "dev": true }, "lodash.padend": { "version": "4.6.1", @@ -3470,18 +3137,18 @@ "remove-trailing-separator": "^1.0.1" } }, + "null-check": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/null-check/-/null-check-1.0.0.tgz", + "integrity": "sha1-l33/1xdgErnsMNKjnbXPcqBDnt0=", + "dev": true + }, "number-is-nan": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", "dev": true }, - "oauth-sign": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", - "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=", - "dev": true - }, "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -3739,35 +3406,6 @@ "through": "~2.3" } }, - "pend": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", - "dev": true - }, - "performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", - "dev": true - }, - "phantomjs-prebuilt": { - "version": "2.1.16", - "resolved": "https://registry.npmjs.org/phantomjs-prebuilt/-/phantomjs-prebuilt-2.1.16.tgz", - "integrity": "sha1-79ISpKOWbTZHaE6ouniFSb4q7+8=", - "dev": true, - "requires": { - "es6-promise": "^4.0.3", - "extract-zip": "^1.6.5", - "fs-extra": "^1.0.0", - "hasha": "^2.2.0", - "kew": "^0.7.0", - "progress": "^1.1.8", - "request": "^2.81.0", - "request-progress": "^2.0.1", - "which": "^1.2.10" - } - }, "phaser": { "version": "3.17.0", "resolved": "https://registry.npmjs.org/phaser/-/phaser-3.17.0.tgz", @@ -3842,6 +3480,7 @@ "version": "0.3.5", "resolved": "https://registry.npmjs.org/process-pool/-/process-pool-0.3.5.tgz", "integrity": "sha1-lrQKexzd2SRhu9Ygx2arCO1L7zc=", + "dev": true, "requires": { "bluebird": "^2.9.14", "lodash": "^3.3.1", @@ -3849,12 +3488,6 @@ "source-map": "^0.4.0" } }, - "progress": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz", - "integrity": "sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74=", - "dev": true - }, "proxy-middleware": { "version": "0.15.0", "resolved": "https://registry.npmjs.org/proxy-middleware/-/proxy-middleware-0.15.0.tgz", @@ -3867,24 +3500,12 @@ "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", "dev": true }, - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", - "dev": true - }, "qjobs": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz", "integrity": "sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==", "dev": true }, - "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", - "dev": true - }, "range-parser": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", @@ -3977,12 +3598,6 @@ "strip-indent": "^1.0.1" } }, - "regenerator-runtime": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", - "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", - "dev": true - }, "regex-not": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", @@ -4041,43 +3656,6 @@ "is-finite": "^1.0.0" } }, - "request": { - "version": "2.87.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.87.0.tgz", - "integrity": "sha512-fcogkm7Az5bsS6Sl0sibkbhcKsnyon/jV1kF3ajGmF0c8HrttdKTPRT9hieOaQHA5HEq6r8OyWOo/o781C1tNw==", - "dev": true, - "requires": { - "aws-sign2": "~0.7.0", - "aws4": "^1.6.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.5", - "extend": "~3.0.1", - "forever-agent": "~0.6.1", - "form-data": "~2.3.1", - "har-validator": "~5.0.3", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.17", - "oauth-sign": "~0.8.2", - "performance-now": "^2.1.0", - "qs": "~6.5.1", - "safe-buffer": "^5.1.1", - "tough-cookie": "~2.3.3", - "tunnel-agent": "^0.6.0", - "uuid": "^3.1.0" - } - }, - "request-progress": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/request-progress/-/request-progress-2.0.1.tgz", - "integrity": "sha1-XTa7V5YcZzqlt4jbyBQf3yO0Tgg=", - "dev": true, - "requires": { - "throttleit": "^1.0.0" - } - }, "requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", @@ -4120,7 +3698,8 @@ "rewire": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/rewire/-/rewire-2.5.2.tgz", - "integrity": "sha1-ZCfee3/u+n02QBUH62SlOFvFjcc=" + "integrity": "sha1-ZCfee3/u+n02QBUH62SlOFvFjcc=", + "dev": true }, "rfdc": { "version": "1.1.2", @@ -4513,6 +4092,7 @@ "version": "0.4.4", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", + "dev": true, "requires": { "amdefine": ">=0.0.4" } @@ -4591,22 +4171,6 @@ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" }, - "sshpk": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.1.tgz", - "integrity": "sha1-Ew9Zde3a2WPx1W+SuaxsUfqfg+s=", - "dev": true, - "requires": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "tweetnacl": "~0.14.0" - } - }, "static-extend": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", @@ -4787,12 +4351,6 @@ } } }, - "throttleit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.0.tgz", - "integrity": "sha1-nnhYNtr0Z0MUWlmEtiaNgoUorGw=", - "dev": true - }, "through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", @@ -4872,37 +4430,12 @@ "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", "dev": true }, - "tough-cookie": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz", - "integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==", - "dev": true, - "requires": { - "punycode": "^1.4.1" - } - }, "trim-newlines": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", "dev": true }, - "tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", - "dev": true, - "requires": { - "safe-buffer": "^5.0.1" - } - }, - "tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", - "dev": true, - "optional": true - }, "type-check": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", @@ -4939,12 +4472,6 @@ } } }, - "typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", - "dev": true - }, "typescript": { "version": "3.4.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.4.5.tgz", @@ -5153,17 +4680,6 @@ "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", "dev": true }, - "verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - } - }, "void-elements": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", @@ -5243,15 +4759,6 @@ "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", "dev": true }, - "yauzl": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.4.1.tgz", - "integrity": "sha1-lSj0QtqxsihOWLQ3m7GU4i4MQAU=", - "dev": true, - "requires": { - "fd-slicer": "~1.0.1" - } - }, "yeast": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", diff --git a/package.json b/package.json index 4627436..04cec98 100644 --- a/package.json +++ b/package.json @@ -6,10 +6,7 @@ "scripts": { "build": "run build", "test": "run ci", - "start": "run continuous", - "codecov": "remap-istanbul -i out/coverage/coverage.json -o out/coverage/mapped.json -t json && codecov -f out/coverage/mapped.json", - "deploy": "run deploy", - "deployx": "run deployx" + "start": "run continuous" }, "repository": { "type": "git", @@ -19,18 +16,18 @@ "license": "MIT", "devDependencies": { "@types/jasmine": "^3.3.12", - "babel-polyfill": "6.26.0", "codecov": "^3.4.0", "gamefroot-texture-packer": "github:Gamefroot/Gamefroot-Texture-Packer#f3687111afc94f80ea8f2877c188fb8e2004e8ff", "glob": "^7.1.4", "glob-watcher": "^5.0.3", "jasmine": "^3.4.0", "karma": "^4.1.0", + "karma-chrome-launcher": "^2.2.0", "karma-coverage": "^1.1.2", "karma-jasmine": "^2.0.1", - "karma-phantomjs-launcher": "1.0.4", "karma-spec-reporter": "^0.0.32", "live-server": "1.2.1", + "process-pool": "^0.3.5", "remap-istanbul": "^0.13.0", "runjs": "^4.4.2", "shelljs": "^0.8.3", @@ -40,7 +37,6 @@ "dependencies": { "jasmine-core": "^3.4.0", "parse": "^2.4.0", - "phaser": "^3.17.0", - "process-pool": "^0.3.5" + "phaser": "^3.17.0" } -} +} \ No newline at end of file diff --git a/runfile.js b/runfile.js index 1c8920f..4417e5a 100644 --- a/runfile.js +++ b/runfile.js @@ -37,15 +37,14 @@ async function exec(command) { */ async function ts(dist = false) { console.log("Building app..."); - await exec(`tsc -p ${dist ? "./tsconfig.dist.json" : "."}`); + await exec(`tsc --project ${dist ? "./tsconfig.dist.json" : "."}`); } /** * Start watching for typescript changes */ async function watch_ts() { - watch(["./src/**/*.ts", "package.json"], () => ts()); - await forever(); + await exec(`tsc --project . --watch --preserveWatchOutput`); } /** @@ -171,7 +170,7 @@ async function build(dist = false) { */ async function optimize() { // TODO do not overwrite dev build - await exec("uglifyjs out/build.dist.js --source-map --output out/build.js"); + await exec("uglifyjs out/build.dist.js --source-map --ecma 6 --mangle --keep-classnames --compress --output out/build.js"); } /** @@ -201,9 +200,18 @@ async function karma() { * Run tests in karma (suppose is already built) */ async function test(task) { + console.log("Running tests..."); await karma(); } +/** + * Run tests in karma when the build changes + */ +async function watch_test(task) { + watch(["out/*.js", "out/*.html"], () => karma()); + await forever(); +} + /** * Run tests in karma, using freshly built app (for continuous integration) */ @@ -241,10 +249,19 @@ async function continuous() { serve(), watch_ts(), watch_data(), - watch_vendors() + watch_vendors(), + watch_test(), ]); } +/** + * Sends code coverage to codecov service + */ +async function codecov() { + await exec("remap-istanbul -i out/coverage/coverage.json -o out/coverage/mapped.json -t json"); + await exec("codecov -f out/coverage/mapped.json"); +} + /** * Wrapper around an async execution function, to make it a runjs command */ @@ -268,9 +285,11 @@ module.exports = { watch_vendors: command(watch_vendors), build: command(build), test: command(test), + watch_test: command(watch_test), deploy: command(deploy), deployx: command(deployx), serve: command(serve), continuous: command(continuous), - ci: command(ci) + ci: command(ci), + codecov: command(codecov), } diff --git a/spec/support/karma.conf.js b/spec/support/karma.conf.js index 7d06096..60f6771 100644 --- a/spec/support/karma.conf.js +++ b/spec/support/karma.conf.js @@ -4,10 +4,15 @@ module.exports = function (config) { basePath: '../..', frameworks: ['jasmine'], singleRun: true, - browsers: ['PhantomJS'], - reporters: ['dots', 'coverage'], + browsers: ['ChromeHeadless'], + colors: true, + reporters: ['spec', 'coverage'], logLevel: config.LOG_WARN, + client: { + captureConsole: false + }, + preprocessors: { 'out/build.js': ['coverage'] }, @@ -20,11 +25,11 @@ module.exports = function (config) { }, specReporter: { - showSpecTiming: true + showSpecTiming: true, + suppressPassed: true }, files: [ - 'node_modules/babel-polyfill/dist/polyfill.js', 'out/vendor/phaser/phaser.js', 'out/vendor/parse/parse.min.js', 'out/build.js' diff --git a/src/MainUI.ts b/src/MainUI.ts index cbf86c8..3d0cbbf 100644 --- a/src/MainUI.ts +++ b/src/MainUI.ts @@ -11,10 +11,10 @@ if (typeof window != "undefined") { // In node, does not extend Phaser classes var handler = { get(target: any, name: any): any { - return new Proxy({}, handler); + return new Proxy(function () { }, handler); } } - global.Phaser = new Proxy({}, handler); + global.Phaser = new Proxy(function () { }, handler); } if (typeof module != "undefined") { @@ -23,7 +23,9 @@ if (typeof window != "undefined") { } module TK.SpaceTac { - // Router between game views + /** + * Main class to bootstrap the whole game + */ export class MainUI extends Phaser.Game { // Current game session session: GameSession @@ -48,7 +50,7 @@ module TK.SpaceTac { super({ width: 1920, height: 1080, - type: Phaser.AUTO, // cannot really use HEADLESS because of bugs + type: testmode ? Phaser.CANVAS : Phaser.WEBGL, // cannot really use HEADLESS because of bugs backgroundColor: '#000000', parent: '-space-tac', disableContextMenu: true, diff --git a/src/common/Iterators.spec.ts b/src/common/Iterators.spec.ts index 20e7a31..f9c1579 100644 --- a/src/common/Iterators.spec.ts +++ b/src/common/Iterators.spec.ts @@ -1,28 +1,44 @@ module TK { testing("Iterators", test => { - function checkit(base_iterator: Iterator, ...values: (T | null)[]) { - let iterator = base_iterator; - values.forEach(value => { - let [head, tail] = iterator(); - test.check.equals(head, value); - iterator = tail; - }); + function checkit(check: TestContext, base_iterator: Iterable, values: T[], infinite = false) { + function checker(check: TestContext) { + let iterator = base_iterator[Symbol.iterator](); + values.forEach((value, idx) => { + let state = iterator.next(); + check.equals(state.done, false, `index ${idx} not done`); + check.equals(state.value, value, `index ${idx} value`); + }); + if (!infinite) { + range(3).forEach(oidx => { + let state = iterator.next(); + check.equals(state.done, true, `index ${values.length + oidx} done`); + }); + } + } - // second iteration to check for repeatability - iterator = base_iterator; - values.forEach(value => { - let [head, tail] = iterator(); - test.check.equals(head, value); - iterator = tail; - }); + check.in("first iteration", checker); + check.in("second iteration", checker); } - function checkarray(iterator: Iterator, values: T[]) { - test.check.equals(imaterialize(iterator), values); + test.case("constructs an iterator from a recurrent formula", check => { + checkit(check, irecur(1, x => x + 2), [1, 3, 5], true); + checkit(check, irecur(4, x => x ? x - 1 : null), [4, 3, 2, 1, 0]); + }); - // second iteration to check for repeatability - test.check.equals(imaterialize(iterator), values); - } + test.case("constructs an iterator from an array", check => { + checkit(check, iarray([]), []); + checkit(check, iarray([1, 2, 3]), [1, 2, 3]); + }); + + test.case("constructs an iterator from a single value", check => { + checkit(check, isingle(1), [1]); + checkit(check, isingle("a"), ["a"]); + }); + + test.case("repeats a value", check => { + checkit(check, irepeat("a"), ["a", "a", "a", "a"], true); + checkit(check, irepeat("a", 3), ["a", "a", "a"]); + }); test.case("calls a function for each yielded value", check => { let iterator = iarray([1, 2, 3]); @@ -49,16 +65,6 @@ module TK { check.equals(result, [1, 2]); }); - test.case("creates an iterator from an array", check => { - checkit(iarray([]), null, null, null); - checkit(iarray([1, 2, 3]), 1, 2, 3, null, null, null); - }); - - test.case("creates an iterator from a single value", check => { - checkarray(isingle(1), [1]); - checkarray(isingle("a"), ["a"]); - }); - test.case("finds the first item passing a predicate", check => { check.equals(ifirst(iarray([]), i => i % 2 == 0), null); check.equals(ifirst(iarray([1, 2, 3]), i => i % 2 == 0), 2); @@ -79,24 +85,24 @@ module TK { }); test.case("creates an iterator in a range of integers", check => { - checkarray(irange(4), [0, 1, 2, 3]); - checkarray(irange(4, 1), [1, 2, 3, 4]); - checkarray(irange(5, 3, 2), [3, 5, 7, 9, 11]); - checkit(irange(), 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12); + checkit(check, irange(4), [0, 1, 2, 3]); + checkit(check, irange(4, 1), [1, 2, 3, 4]); + checkit(check, irange(5, 3, 2), [3, 5, 7, 9, 11]); + checkit(check, irange(), [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], true); }); test.case("uses a step iterator to scan numbers", check => { - checkit(istep(), 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12); - checkit(istep(3), 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14); - checkarray(istep(3, irepeat(1, 4)), [3, 4, 5, 6, 7]); - checkarray(istep(8, IEMPTY), [8]); - checkit(istep(1, irange()), 1, 1, 2, 4, 7, 11, 16); + checkit(check, istep(), [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], true); + checkit(check, istep(3), [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14], true); + checkit(check, istep(3, irepeat(1, 4)), [3, 4, 5, 6, 7]); + checkit(check, istep(8, IEMPTY), [8]); + checkit(check, istep(1, irange()), [1, 1, 2, 4, 7, 11, 16], true); }); test.case("skips a number of values", check => { - checkarray(iskip(irange(7), 3), [3, 4, 5, 6]); - checkarray(iskip(irange(7), 12), []); - checkarray(iskip(IEMPTY, 3), []); + checkit(check, iskip(irange(7), 3), [3, 4, 5, 6]); + checkit(check, iskip(irange(7), 12), []); + checkit(check, iskip(IEMPTY, 3), []); }); test.case("gets a value at an iterator position", check => { @@ -107,86 +113,114 @@ module TK { check.equals(iat(IEMPTY, 0), null); }); - test.case("chains iterators", check => { - checkarray(ichain(), []); - checkarray(ichain(irange(3)), [0, 1, 2]); - checkarray(ichain(iarray([1, 2]), iarray([]), iarray([3, 4, 5])), [1, 2, 3, 4, 5]); - }); - test.case("chains iterator of iterators", check => { - checkarray(ichainit(IEMPTY), []); - checkarray(ichainit(iarray([iarray([1, 2, 3]), iarray([]), iarray([4, 5])])), [1, 2, 3, 4, 5]); + checkit(check, ichainit(IEMPTY), []); + checkit(check, ichainit(iarray([iarray([1, 2, 3]), iarray([]), iarray([4, 5])])), [1, 2, 3, 4, 5]); }); - test.case("repeats a value", check => { - checkit(irepeat("a"), "a", "a", "a", "a"); - checkarray(irepeat("a", 3), ["a", "a", "a"]); + test.case("chains iterators", check => { + checkit(check, ichain(), []); + checkit(check, ichain(irange(3)), [0, 1, 2]); + checkit(check, ichain(iarray([1, 2]), iarray([]), iarray([3, 4, 5])), [1, 2, 3, 4, 5]); }); test.case("loops an iterator", check => { - checkarray(iloop(irange(3), 2), [0, 1, 2, 0, 1, 2]); - checkit(iloop(irange(1)), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); + checkit(check, iloop(irange(3), 2), [0, 1, 2, 0, 1, 2]); + checkit(check, iloop(irange(1)), [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], true); let onloop = check.mockfunc("onloop"); - let iterator = iloop(irange(2), 3, onloop.func); - function next() { - let value; - [value, iterator] = iterator(); - return value; - } - check.equals(next(), 0); - check.called(onloop, 0); - check.equals(next(), 1); - check.called(onloop, 0); - check.equals(next(), 0); - check.called(onloop, 1); - check.equals(next(), 1); - check.called(onloop, 0); - check.equals(next(), 0); - check.called(onloop, 1); - check.equals(next(), 1); - check.called(onloop, 0); - check.equals(next(), null); - check.called(onloop, 0); + let iterator = iloop(irange(2), 3, onloop.func)[Symbol.iterator](); + check.in("idx 0", check => { + check.equals(iterator.next().value, 0); + check.called(onloop, 0); + }); + check.in("idx 1", check => { + check.equals(iterator.next().value, 1); + check.called(onloop, 0); + }); + check.in("idx 2", check => { + check.equals(iterator.next().value, 0); + check.called(onloop, 1); + }); + check.in("idx 3", check => { + check.equals(iterator.next().value, 1); + check.called(onloop, 0); + }); + check.in("idx 4", check => { + check.equals(iterator.next().value, 0); + check.called(onloop, 1); + }); + check.in("idx 5", check => { + check.equals(iterator.next().value, 1); + check.called(onloop, 0); + }); + check.in("idx 6", check => { + check.equals(iterator.next().value, undefined); + check.called(onloop, 0); + }); }); test.case("maps an iterator", check => { - checkarray(imap(IEMPTY, i => i * 2), []); - checkarray(imap(irange(3), i => i * 2), [0, 2, 4]); + checkit(check, imap(IEMPTY, i => i * 2), []); + checkit(check, imap(irange(3), i => i * 2), [0, 2, 4]); }); - test.case("filters an iterator", check => { - checkarray(imap(IEMPTY, i => i % 3 == 0), []); - checkarray(ifilter(irange(12), i => i % 3 == 0), [0, 3, 6, 9]); + test.case("reduces an iterator", check => { + check.equals(ireduce(IEMPTY, (a, b) => a + b, 2), 2); + check.equals(ireduce([9], (a, b) => a + b, 2), 11); + check.equals(ireduce([9, 1], (a, b) => a + b, 2), 12); + }); + + test.case("filters an iterator with a predicate", check => { + checkit(check, imap(IEMPTY, i => i % 3 == 0), []); + checkit(check, ifilter(irange(12), i => i % 3 == 0), [0, 3, 6, 9]); + }); + + test.case("filters an iterator with a type guard", check => { + let result = ifiltertype(<(number | string)[]>[1, "a", 2, "b"], (x): x is number => typeof x == "number"); + checkit(check, result, [1, 2]); + }); + + test.case("filters an iterator with a class type", check => { + let o1 = new RObject(); + let o2 = new RObject(); + let o3 = new RObjectContainer(); + let result = ifilterclass([1, "a", o1, 2, o2, o3, "b"], RObject); + checkit(check, result, [o1, o2]); }); test.case("combines iterators", check => { let iterator = icombine(iarray([1, 2, 3]), iarray(["a", "b"])); - checkarray(iterator, [[1, "a"], [1, "b"], [2, "a"], [2, "b"], [3, "a"], [3, "b"]]); + checkit(check, iterator, [[1, "a"], [1, "b"], [2, "a"], [2, "b"], [3, "a"], [3, "b"]]); }); test.case("zips iterators", check => { - checkarray(izip(IEMPTY, IEMPTY), []); - checkarray(izip(iarray([1, 2, 3]), iarray(["a", "b"])), [[1, "a"], [2, "b"]]); + checkit(check, izip(IEMPTY, IEMPTY), []); + checkit(check, izip(iarray([1, 2, 3]), iarray(["a", "b"])), [[1, "a"], [2, "b"]]); - checkarray(izipg(IEMPTY, IEMPTY), []); - checkarray(izipg(iarray([1, 2, 3]), iarray(["a", "b"])), <[number | null, string | null][]>[[1, "a"], [2, "b"], [3, null]]); + checkit(check, izipg(IEMPTY, IEMPTY), []); + checkit(check, izipg(iarray([1, 2, 3]), iarray(["a", "b"])), <[number | undefined, string | undefined][]>[[1, "a"], [2, "b"], [3, undefined]]); }); test.case("partitions iterators", check => { let [it1, it2] = ipartition(IEMPTY, () => true); - checkarray(it1, []); - checkarray(it2, []); + checkit(check, it1, []); + checkit(check, it2, []); [it1, it2] = ipartition(irange(5), i => i % 2 == 0); - checkarray(it1, [0, 2, 4]); - checkarray(it2, [1, 3]); + checkit(check, it1, [0, 2, 4]); + checkit(check, it2, [1, 3]); + }); + + test.case("alternatively pick from several iterables", check => { + checkit(check, ialternate([]), []); + checkit(check, ialternate([[1, 2, 3, 4], [], iarray([5, 6]), IEMPTY, iarray([7, 8, 9])]), [1, 5, 7, 2, 6, 8, 3, 9, 4]); }); test.case("returns unique items", check => { - checkarray(iunique(IEMPTY), []); - checkarray(iunique(iarray([5, 3, 2, 3, 4, 5])), [5, 3, 2, 4]); - checkarray(iunique(iarray([5, 3, 2, 3, 4, 5]), 4), [5, 3, 2, 4]); + checkit(check, iunique(IEMPTY), []); + checkit(check, iunique(iarray([5, 3, 2, 3, 4, 5])), [5, 3, 2, 4]); + checkit(check, iunique(iarray([5, 3, 2, 3, 4, 5]), 4), [5, 3, 2, 4]); check.throw(() => imaterialize(iunique(iarray([5, 3, 2, 3, 4, 5]), 3)), "Unique count limit on iterator"); }); diff --git a/src/common/Iterators.ts b/src/common/Iterators.ts index fb81ca4..1f78373 100644 --- a/src/common/Iterators.ts +++ b/src/common/Iterators.ts @@ -3,116 +3,134 @@ * * They allow to work on infinite streams of values, with limited memory consumption. * - * Functions in this file that do not return an Iterator are "materializing", meaning that they + * Functions in this file that do not return an Iterable are "materializing", meaning that they * may consume iterators up to the end, and will not work well on infinite iterators. + * + * These iterators are guaranteed to be repeatable, meaning that calling Symbol.iterator on them will start over. */ module TK { /** - * An iterator is a function without side effect, that returns the current value - * and an iterator over the next values. + * Empty iterator */ - export type Iterator = () => [T | null, Iterator]; - - function _getIEND(): [null, Iterator] { - return [null, _getIEND]; + export const IATEND: Iterator = { + next: function () { + return { done: true, value: undefined }; + } } /** - * IEND is a return value for iterators, indicating end of iteration. + * Empty iterable */ - export const IEND: [null, Iterator] = [null, _getIEND]; + export const IEMPTY: Iterable = { + [Symbol.iterator]: () => IATEND + } /** - * Empty iterator, returning IEND + * Iterable constructor, from an initial value, and a step value */ - export const IEMPTY = () => IEND; + export function irecur(start: T, step: (a: T) => T | null): Iterable { + return { + [Symbol.iterator]: function* () { + let val: T | null = start; + do { + yield val; + val = step(val); + } while (val !== null); + } + } + } /** - * Equivalent of Array.forEach for lazy iterators. + * Iterable constructor, from an array + * + * The iterator will yield the next value each time it is called, then undefined when the array's end is reached. + */ + export function iarray(array: T[], offset = 0): Iterable { + return { + [Symbol.iterator]: function () { + return array.slice(offset)[Symbol.iterator](); + } + } + } + + /** + * Iterable constructor, from a single value + * + * The value will be yielded only once, not repeated over. + */ + export function isingle(value: T): Iterable { + return iarray([value]); + } + + /** + * Iterable that repeats the same value. + */ + export function irepeat(value: T, count = -1): Iterable { + return { + [Symbol.iterator]: function* () { + let n = count; + while (n != 0) { + yield value; + n--; + } + } + } + } + + /** + * Equivalent of Array.forEach for all iterables. * * If the callback returns *stopper*, the iteration is stopped. */ - export function iforeach(iterator: Iterator, callback: (_: T) => any, stopper: any = null) { - let value: T | null; - [value, iterator] = iterator(); - while (value !== null) { - let returned = callback(value); - if (returned === stopper) { - return; - } - [value, iterator] = iterator(); - } - } - - /** - * Get an iterator on an array - * - * The iterator will yield the next value each time it is called, then null when the array's end is reached. - */ - export function iarray(array: T[], offset = 0): Iterator { - return () => { - if (offset < array.length) { - return [array[offset], iarray(array, offset + 1)]; - } else { - return IEND; + export function iforeach(iterable: Iterable, callback: (_: T) => any, stopper: any = null): void { + for (let value of iterable) { + if (callback(value) === stopper) { + break; } } } - /** - * Get an iterator yielding a single value - */ - export function isingle(value: T): Iterator { - return iarray([value]); - } - /** * Returns the first item passing a predicate */ - export function ifirst(iterator: Iterator, predicate: (item: T) => boolean): T | null { - let result: T | null = null; - iforeach(iterator, item => { - if (predicate(item)) { - result = item; - return null; - } else { - return undefined; + export function ifirst(iterable: Iterable, predicate: (item: T) => boolean): T | null { + for (let value of iterable) { + if (predicate(value)) { + return value; } - }); - return result; + } + return null; } /** * Returns the first non-null result of a value-yielding predicate, applied to each iterator element */ - export function ifirstmap(iterator: Iterator, predicate: (item: T1) => T2 | null): T2 | null { - let result: T2 | null = null; - iforeach(iterator, item => { - let mapped = predicate(item); - if (mapped) { - result = mapped; - return null; - } else { - return undefined; + export function ifirstmap(iterable: Iterable, predicate: (item: T1) => T2 | null): T2 | null { + for (let value of iterable) { + let res = predicate(value); + if (res !== null) { + return res; } - }); - return result; + } + return null; } /** - * Materialize an array from consuming an iterator + * Materialize an array from consuming an iterable * * To avoid materializing infinite iterators (and bursting memory), the item count is limited to 1 million, and an * exception is thrown when this limit is reached. */ - export function imaterialize(iterator: Iterator, limit = 1000000): T[] { + export function imaterialize(iterable: Iterable, limit = 1000000): T[] { let result: T[] = []; - iforeach(iterator, value => { + + for (let value of iterable) { result.push(value); if (result.length >= limit) { throw new Error("Length limit on iterator materialize"); } - }); + } + return result; } @@ -121,8 +139,18 @@ module TK { * * If *count* is not specified, the iterator is infinite */ - export function irange(count: number = -1, start = 0, step = 1): Iterator { - return () => (count != 0) ? [start, irange(count - 1, start + step, step)] : IEND; + export function irange(count: number = -1, start = 0, step = 1): Iterable { + return { + [Symbol.iterator]: function* () { + let i = start; + let n = count; + while (n != 0) { + yield i; + i += step; + n--; + } + } + } } /** @@ -132,95 +160,81 @@ module TK { * * With no argument, istep() == irange() */ - export function istep(start = 0, step = irepeat(1)): Iterator { - return () => { - let [value, iterator] = step(); - return [start, value === null ? IEMPTY : istep(start + value, iterator)]; + export function istep(start = 0, step_iterable = irepeat(1)): Iterable { + return { + [Symbol.iterator]: function* () { + let i = start; + yield i; + for (let step of step_iterable) { + i += step; + yield i; + } + } } } /** * Skip a given number of values from an iterator, discarding them. */ - export function iskip(iterator: Iterator, count = 1): Iterator { - let value: T | null; - while (count--) { - [value, iterator] = iterator(); + export function iskip(iterable: Iterable, count = 1): Iterable { + return { + [Symbol.iterator]: function () { + let iterator = iterable[Symbol.iterator](); + let n = count; + while (n-- > 0) { + iterator.next(); + } + return iterator; + } } - return iterator; } /** * Return the value at a given position in the iterator */ - export function iat(iterator: Iterator, position: number): T | null { + export function iat(iterable: Iterable, position: number): T | null { if (position < 0) { return null; } else { if (position > 0) { - iterator = iskip(iterator, position); + iterable = iskip(iterable, position); } - return iterator()[0]; + let iterator = iterable[Symbol.iterator](); + let state = iterator.next(); + return state.done ? null : state.value; } } /** - * Chain an iterator of iterators. + * Chain an iterable of iterables. * * This will yield values from the first yielded iterator, then the second one, and so on... */ - export function ichainit(iterators: Iterator>): Iterator { - return function () { - let [iterators_head, iterators_tail] = iterators(); - if (iterators_head == null) { - return IEND; - } else { - let [head, tail] = iterators_head(); - while (head == null) { - [iterators_head, iterators_tail] = iterators_tail(); - if (iterators_head == null) { - break; + export function ichainit(iterables: Iterable>): Iterable { + return { + [Symbol.iterator]: function* () { + for (let iterable of iterables) { + for (let value of iterable) { + yield value; } - [head, tail] = iterators_head(); } - return [head, ichain(tail, ichainit(iterators_tail))]; } } } /** - * Chain iterators. + * Chain iterables. * * This will yield values from the first iterator, then the second one, and so on... */ - export function ichain(...iterators: Iterator[]): Iterator { - if (iterators.length == 0) { + export function ichain(...iterables: Iterable[]): Iterable { + if (iterables.length == 0) { return IEMPTY; } else { - return ichainit(iarray(iterators)); + return ichainit(iterables); } } - /** - * Wrap an iterator, calling *onstart* when the first value of the wrapped iterator is yielded. - */ - function ionstart(iterator: Iterator, onstart: Function): Iterator { - return () => { - let [head, tail] = iterator(); - if (head !== null) { - onstart(); - } - return [head, tail]; - } - } - - /** - * Iterator that repeats the same value. - */ - export function irepeat(value: T, count = -1): Iterator { - return iloop(iarray([value]), count); - } - /** * Loop an iterator for a number of times. * @@ -228,25 +242,36 @@ module TK { * * onloop may be used to know when the iterator resets. */ - export function iloop(base: Iterator, count = -1, onloop?: Function): Iterator { - if (count == 0) { - return IEMPTY; - } else { - let next = onloop ? ionstart(base, onloop) : base; - return ichainit(() => [base, iarray([iloop(next, count - 1)])]); + export function iloop(base: Iterable, count = -1, onloop?: Function): Iterable { + return { + [Symbol.iterator]: function* () { + let n = count; + let start = false; + while (n-- != 0) { + for (let value of base) { + if (start) { + if (onloop) { + onloop(); + } + start = false; + } + yield value; + } + start = true; + } + } } } /** * Iterator version of "map". */ - export function imap(iterator: Iterator, mapfunc: (_: T1) => T2): Iterator { - return () => { - let [head, tail] = iterator(); - if (head === null) { - return IEND; - } else { - return [mapfunc(head), imap(tail, mapfunc)]; + export function imap(iterable: Iterable, mapfunc: (_: T1) => T2): Iterable { + return { + [Symbol.iterator]: function* () { + for (let value of iterable) { + yield mapfunc(value); + } } } } @@ -254,76 +279,123 @@ module TK { /** * Iterator version of "reduce". */ - export function ireduce(iterator: Iterator, reduce: (item1: T, item2: T) => T, init: T): T { + export function ireduce(iterable: Iterable, reduce: (item1: T, item2: T) => T, init: T): T { let result = init; - iforeach(iterator, item => { - result = reduce(result, item); - }); + for (let value of iterable) { + result = reduce(result, value); + } return result; } /** * Iterator version of "filter". */ - export function ifilter(iterator: Iterator, filterfunc: (_: T) => boolean): Iterator { - return () => { - let [value, iter] = iterator(); - while (value !== null && !filterfunc(value)) { - [value, iter] = iter(); + export function ifilter(iterable: Iterable, filterfunc: (_: T) => boolean): Iterable { + return { + [Symbol.iterator]: function* () { + for (let value of iterable) { + if (filterfunc(value)) { + yield value; + } + } } - return [value, ifilter(iter, filterfunc)]; } } /** - * Combine two iterators. + * Type filter, to return a list of instances of a given type + */ + export function ifiltertype(iterable: Iterable, filter: (item: any) => item is T): Iterable { + return ifilter(iterable, filter); + } + + /** + * Class filter, to return a list of instances of a given type + */ + export function ifilterclass(iterable: Iterable, classref: { new(...args: any[]): T }): Iterable { + return ifilter(iterable, (item): item is T => item instanceof classref); + } + + /** + * Combine two iterables. * * This iterates through the second one several times, so if one iterator may be infinite, * it should be the first one. */ - export function icombine(it1: Iterator, it2: Iterator): Iterator<[T1, T2]> { + export function icombine(it1: Iterable, it2: Iterable): Iterable<[T1, T2]> { return ichainit(imap(it1, v1 => imap(it2, (v2): [T1, T2] => [v1, v2]))); } /** - * Advance two iterators at the same time, yielding item pairs + * Advance through two iterables at the same time, yielding item pairs * * Iteration will stop at the first of the two iterators that stops. */ - export function izip(it1: Iterator, it2: Iterator): Iterator<[T1, T2]> { - return () => { - let [val1, nit1] = it1(); - let [val2, nit2] = it2(); - if (val1 !== null && val2 !== null) { - return [[val1, val2], izip(nit1, nit2)]; - } else { - return IEND; + export function izip(it1: Iterable, it2: Iterable): Iterable<[T1, T2]> { + return { + [Symbol.iterator]: function* () { + let iterator1 = it1[Symbol.iterator](); + let iterator2 = it2[Symbol.iterator](); + let state1 = iterator1.next(); + let state2 = iterator2.next(); + while (!state1.done && !state2.done) { + yield [state1.value, state2.value]; + state1 = iterator1.next(); + state2 = iterator2.next(); + } } } } /** - * Advance two iterators at the same time, yielding item pairs (greedy version) + * Advance two iterables at the same time, yielding item pairs (greedy version) * - * Iteration will stop when both iterators are consumed, returning partial couples (null in the peer) if needed. + * Iteration will stop when both iterators are consumed, returning partial couples (undefined in the peer) if needed. */ - export function izipg(it1: Iterator, it2: Iterator): Iterator<[T1 | null, T2 | null]> { - return () => { - let [val1, nit1] = it1(); - let [val2, nit2] = it2(); - if (val1 === null && val2 === null) { - return IEND; - } else { - return [[val1, val2], izipg(nit1, nit2)]; + export function izipg(it1: Iterable, it2: Iterable): Iterable<[T1 | undefined, T2 | undefined]> { + return { + [Symbol.iterator]: function* () { + let iterator1 = it1[Symbol.iterator](); + let iterator2 = it2[Symbol.iterator](); + let state1 = iterator1.next(); + let state2 = iterator2.next(); + while (!state1.done || !state2.done) { + yield [state1.value, state2.value]; + state1 = iterator1.next(); + state2 = iterator2.next(); + } } } } /** - * Partition in two iterators, one with values that pass the predicate, the other with values that don't + * Partition in two iterables, one with values that pass the predicate, the other with values that don't */ - export function ipartition(it: Iterator, predicate: (item: T) => boolean): [Iterator, Iterator] { - return [ifilter(it, predicate), ifilter(it, x => !predicate(x))]; + export function ipartition(iterable: Iterable, predicate: (item: T) => boolean): [Iterable, Iterable] { + return [ifilter(iterable, predicate), ifilter(iterable, x => !predicate(x))]; + } + + /** + * Alternate between several iterables (pick one from the first one, then one from the second...) + */ + export function ialternate(iterables: Iterable[]): Iterable { + return { + [Symbol.iterator]: function* () { + let iterators = iterables.map(iterable => iterable[Symbol.iterator]()); + let done: boolean; + do { + done = false; + // TODO Remove "dried-out" iterators + for (let iterator of iterators) { + let state = iterator.next(); + if (!state.done) { + done = true; + yield state.value; + } + } + } while (done); + } + } } /** @@ -335,29 +407,30 @@ module TK { * * This function is O(n²) */ - export function iunique(it: Iterator, limit = 1000000): Iterator { - function internal(it: Iterator, limit: number, done: T[]): Iterator { - let [value, iterator] = it(); - while (value !== null && contains(done, value)) { - [value, iterator] = iterator(); - } - if (value === null) { - return IEMPTY; - } else if (limit <= 0) { - throw new Error("Unique count limit on iterator"); - } else { - let head = value; - return () => [head, internal(it, limit - 1, done.concat([head]))]; + export function iunique(iterable: Iterable, limit = 1000000): Iterable { + return { + [Symbol.iterator]: function* () { + let done: T[] = []; + let n = limit; + for (let value of iterable) { + if (!contains(done, value)) { + if (n-- > 0) { + done.push(value); + yield value; + } else { + throw new Error("Unique count limit on iterator"); + } + } + } } } - return internal(it, limit, []); } /** * Common reduce shortcuts */ - export const isum = (iterator: Iterator) => ireduce(iterator, (a, b) => a + b, 0); - export const icat = (iterator: Iterator) => ireduce(iterator, (a, b) => a + b, ""); - export const imin = (iterator: Iterator) => ireduce(iterator, Math.min, Infinity); - export const imax = (iterator: Iterator) => ireduce(iterator, Math.max, -Infinity); + export const isum = (iterable: Iterable) => ireduce(iterable, (a, b) => a + b, 0); + export const icat = (iterable: Iterable) => ireduce(iterable, (a, b) => a + b, ""); + export const imin = (iterable: Iterable) => ireduce(iterable, Math.min, Infinity); + export const imax = (iterable: Iterable) => ireduce(iterable, Math.max, -Infinity); } diff --git a/src/common/RObject.ts b/src/common/RObject.ts index 049b2dd..4c39585 100644 --- a/src/common/RObject.ts +++ b/src/common/RObject.ts @@ -93,7 +93,7 @@ module TK { /** * Get all contained objects (iterator) */ - iterator(): Iterator { + iterator(): Iterable { return iarray(this.list()); } } diff --git a/src/common/Testing.ts b/src/common/Testing.ts index bce9d07..6a123f5 100644 --- a/src/common/Testing.ts +++ b/src/common/Testing.ts @@ -41,10 +41,10 @@ module TK { /** * Add an asynchronous setup step for each case of the suite */ - asetup(body: () => Promise, cleanup?: Function): void { - beforeEach((done) => body().then(done)); + asetup(body: () => Promise, cleanup?: () => Promise): void { + beforeEach(async () => await body()); if (cleanup) { - afterEach(() => cleanup()); + afterEach(async () => await cleanup()); } } diff --git a/src/common/Tools.spec.ts b/src/common/Tools.spec.ts index 6e6fcb2..9196de2 100644 --- a/src/common/Tools.spec.ts +++ b/src/common/Tools.spec.ts @@ -48,12 +48,14 @@ module TK.Specs { }); test.case("checks for null value", check => { - let value: number | null = 5; + let value: number | null | undefined = 5; check.equals(nn(value), 5); value = 0; check.equals(nn(value), 0); value = null; check.throw(() => nn(value), "Null value"); + value = undefined; + check.throw(() => nn(value), "Undefined value"); }); test.case("removes null values from arrays", check => { @@ -271,7 +273,7 @@ module TK.Specs { check.equals(dict(items(obj)), obj); }); - test.case("iterates an enum", check => { + test.case("gets an enum values", check => { enum Test { ZERO, ONE, @@ -281,6 +283,10 @@ module TK.Specs { var result: any[] = []; iterenum(Test, item => result.push(item)); check.equals(result, [0, 1, 2]); + check.equals(result, [Test.ZERO, Test.ONE, Test.TWO]); + + check.equals(enumvalues(Test), [0, 1, 2]); + check.equals(enumvalues(Test), [Test.ZERO, Test.ONE, Test.TWO]); }); test.case("create a dict from an array of couples", check => { diff --git a/src/common/Tools.ts b/src/common/Tools.ts index a92be91..a631d7b 100644 --- a/src/common/Tools.ts +++ b/src/common/Tools.ts @@ -74,9 +74,11 @@ module TK { /** * Check if a value if null, throwing an exception if its the case */ - export function nn(value: T | null): T { + export function nn(value: T | null | undefined): T { if (value === null) { throw new Error("Null value"); + } else if (typeof value == "undefined") { + throw new Error("Undefined value"); } else { return value; } @@ -351,6 +353,15 @@ module TK { } } + /** + * Collect enum values. + */ + export function enumvalues(obj: T): number[] { + let result: number[] = []; + iterenum(obj, val => result.push(val)); + return result; + } + /** * Create a dictionary from a list of couples */ diff --git a/src/core/Battle.ts b/src/core/Battle.ts index 67c88c8..51118bf 100644 --- a/src/core/Battle.ts +++ b/src/core/Battle.ts @@ -113,7 +113,7 @@ module TK.SpaceTac { /** * Return an iterator over all ships engaged in the battle */ - iships(alive_only = false): Iterator { + iships(alive_only = false): Iterable { let result = ichainit(imap(iarray(this.fleets), fleet => iarray(fleet.ships))); return alive_only ? ifilter(result, ship => ship.alive) : result; } @@ -121,14 +121,14 @@ module TK.SpaceTac { /** * Return an iterator over ships allies of (or owned by) a player */ - iallies(ship: Ship, alive_only = false): Iterator { + iallies(ship: Ship, alive_only = false): Iterable { return ifilter(this.iships(alive_only), iship => iship.fleet.player.is(ship.fleet.player)); } /** * Return an iterator over ships enemy of a player */ - ienemies(ship: Ship, alive_only = false): Iterator { + ienemies(ship: Ship, alive_only = false): Iterable { return ifilter(this.iships(alive_only), iship => !iship.fleet.player.is(ship.fleet.player)); } diff --git a/src/core/MoveFireSimulator.ts b/src/core/MoveFireSimulator.ts index 4c7d3bc..81a8ec4 100644 --- a/src/core/MoveFireSimulator.ts +++ b/src/core/MoveFireSimulator.ts @@ -73,7 +73,7 @@ module TK.SpaceTac { /** * Get an iterator for scanning a circle */ - scanCircle(x: number, y: number, radius: number, nr = 6, na = 30): Iterator { + scanCircle(x: number, y: number, radius: number, nr = 6, na = 30): Iterable { let rcount = nr ? 1 / (nr - 1) : 0; return ichainit(imap(istep(0, irepeat(rcount, nr - 1)), r => { let angles = Math.max(1, Math.ceil(na * r)); diff --git a/src/core/ai/TacticalAI.ts b/src/core/ai/TacticalAI.ts index d58aa30..4eca9c3 100644 --- a/src/core/ai/TacticalAI.ts +++ b/src/core/ai/TacticalAI.ts @@ -2,7 +2,7 @@ /// module TK.SpaceTac { - export type TacticalProducer = Iterator; + export type TacticalProducer = Iterable; export type TacticalEvaluator = (maneuver: Maneuver) => number; /** @@ -14,17 +14,23 @@ module TK.SpaceTac { */ export class TacticalAI extends AbstractAI { private producers: TacticalProducer[] = [] + private work: Iterator = IATEND private evaluators: TacticalEvaluator[] = [] private best: Maneuver | null = null private best_score = 0 + private produced = 0 + private evaluated = 0 protected initWork(): void { this.best = null; this.best_score = -Infinity; this.producers = this.getDefaultProducers(); + this.work = ialternate(this.producers)[Symbol.iterator](); this.evaluators = this.getDefaultEvaluators(); + this.produced = 0; + this.evaluated = 0; if (this.debug) { console.log("AI started", this.name, this.ship.name); @@ -32,22 +38,18 @@ module TK.SpaceTac { } protected doWorkUnit(): boolean { - if (this.producers.length > 0 && this.getDuration() < 8000) { - // Produce a maneuver - let maneuver: Maneuver | null = null; - let producer = this.producers.shift(); - if (producer) { - [maneuver, producer] = producer(); - } - - if (maneuver && maneuver.isPossible()) { - if (producer) { - this.producers.push(producer); - } + let state = this.work.next(); + if (!state.done && this.getDuration() < 8000) { + let maneuver = state.value; + this.produced++; + if (maneuver.isPossible()) { // Evaluate the maneuver let score = this.evaluate(maneuver); - //console.debug("AI evaluation", maneuver, score); + this.evaluated++; + if (this.debug) { + console.debug("AI evaluation", maneuver, score); + } if ((Math.abs(score - this.best_score) < 0.0001 && this.random.bool()) || score > this.best_score) { this.best = maneuver; this.best_score = score; @@ -56,6 +58,10 @@ module TK.SpaceTac { return true; } else if (this.best) { + if (!state.done) { + console.warn(`AI did not analyze every possible maneuver (${this.produced} produced, ${this.evaluated} evaluated)`); + } + // Choose the best maneuver so far let best_maneuver = this.best; if (this.debug) { diff --git a/src/core/ai/TacticalAIHelpers.ts b/src/core/ai/TacticalAIHelpers.ts index cb436df..ac56688 100644 --- a/src/core/ai/TacticalAIHelpers.ts +++ b/src/core/ai/TacticalAIHelpers.ts @@ -2,7 +2,7 @@ module TK.SpaceTac { /** * Get a list of all playable actions (like the actionbar for player) for a ship */ - function getPlayableActions(ship: Ship): Iterator { + function getPlayableActions(ship: Ship): Iterable { let actions = ship.actions.listAll(); return ifilter(iarray(actions), action => !action.checkCannotBeApplied(ship)); } @@ -47,7 +47,7 @@ module TK.SpaceTac { /** * Iterator of a list of "random" arena coordinates, based on a grid */ - static scanArena(battle: Battle, cells = 10, random = RandomGenerator.global): Iterator { + static scanArena(battle: Battle, cells = 10, random = RandomGenerator.global): Iterable { return imap(irange(cells * cells), cellpos => { let y = Math.floor(cellpos / cells); let x = cellpos - y * cells; @@ -87,7 +87,7 @@ module TK.SpaceTac { */ static produceInterestingBlastShots(ship: Ship, battle: Battle): TacticalProducer { // TODO Work with groups of 3, 4 ... - let weapons = >ifilter(getPlayableActions(ship), action => action instanceof TriggerAction && action.blast > 0); + let weapons = ifilter(ifilterclass(getPlayableActions(ship), TriggerAction), action => action.blast > 0); let enemies = battle.ienemies(ship, true); // TODO This produces duplicates (x, y) and (y, x) let couples = ifilter(icombine(enemies, enemies), ([e1, e2]) => e1 != e2); diff --git a/src/ui/TestGame.ts b/src/ui/TestGame.ts index 0f1b8a5..7b8ef2b 100644 --- a/src/ui/TestGame.ts +++ b/src/ui/TestGame.ts @@ -59,7 +59,10 @@ module TK.SpaceTac.UI.Specs { testgame.ui.scene.add("test", scene, true, scenedata); testgame.view = scene; - }), () => testgame.ui.destroy(true)); + }), () => new Promise((resolve) => { + testgame.ui.events.on("destroy", () => resolve()); + testgame.ui.destroy(true); + })); return testgame; } diff --git a/tsconfig.json b/tsconfig.json index 56efc77..7f23acc 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -14,7 +14,7 @@ "es6", "scripthost" ], - "target": "es5" + "target": "es6" }, "include": [ "src/**/*.ts"