1
0
Форк 0
This commit is contained in:
Michaël Lemaire 2019-05-13 23:17:58 +02:00
джерело 8d83c8371a
коміт a60ad14c79
20 змінених файлів з 524 додано та 858 видалено

@ -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"

@ -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

@ -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 () { };

555
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",

@ -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"
}
}
}

@ -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),
}

@ -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'

@ -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,

@ -1,28 +1,44 @@
module TK {
testing("Iterators", test => {
function checkit<T>(base_iterator: Iterator<T>, ...values: (T | null)[]) {
let iterator = base_iterator;
values.forEach(value => {
let [head, tail] = iterator();
test.check.equals(head, value);
iterator = tail;
});
function checkit<T>(check: TestContext, base_iterator: Iterable<T>, 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<T>(iterator: Iterator<T>, 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(<number[]>[]), 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");
});

@ -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> = () => [T | null, Iterator<T>];
function _getIEND(): [null, Iterator<any>] {
return [null, _getIEND];
export const IATEND: Iterator<any> = {
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<any>] = [null, _getIEND];
export const IEMPTY: Iterable<any> = {
[Symbol.iterator]: () => IATEND
}
/**
* Empty iterator, returning IEND
* Iterable constructor, from an initial value, and a step value
*/
export const IEMPTY = () => IEND;
export function irecur<T, S>(start: T, step: (a: T) => T | null): Iterable<T> {
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<T>(array: T[], offset = 0): Iterable<T> {
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<T>(value: T): Iterable<T> {
return iarray([value]);
}
/**
* Iterable that repeats the same value.
*/
export function irepeat<T>(value: T, count = -1): Iterable<T> {
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<T>(iterator: Iterator<T>, 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<T>(array: T[], offset = 0): Iterator<T> {
return () => {
if (offset < array.length) {
return [array[offset], iarray(array, offset + 1)];
} else {
return IEND;
export function iforeach<T>(iterable: Iterable<T>, 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<T>(value: T): Iterator<T> {
return iarray([value]);
}
/**
* Returns the first item passing a predicate
*/
export function ifirst<T>(iterator: Iterator<T>, 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<T>(iterable: Iterable<T>, 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<T1, T2>(iterator: Iterator<T1>, 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<T1, T2>(iterable: Iterable<T1>, 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<T>(iterator: Iterator<T>, limit = 1000000): T[] {
export function imaterialize<T>(iterable: Iterable<T>, 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<number> {
return () => (count != 0) ? [start, irange(count - 1, start + step, step)] : IEND;
export function irange(count: number = -1, start = 0, step = 1): Iterable<number> {
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<number> {
return () => {
let [value, iterator] = step();
return [start, value === null ? IEMPTY : istep(start + value, iterator)];
export function istep(start = 0, step_iterable = irepeat(1)): Iterable<number> {
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<T>(iterator: Iterator<T>, count = 1): Iterator<T> {
let value: T | null;
while (count--) {
[value, iterator] = iterator();
export function iskip<T>(iterable: Iterable<T>, count = 1): Iterable<T> {
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<T>(iterator: Iterator<T>, position: number): T | null {
export function iat<T>(iterable: Iterable<T>, 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<T>(iterators: Iterator<Iterator<T>>): Iterator<T> {
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<T>(iterables: Iterable<Iterable<T>>): Iterable<T> {
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<T>(...iterators: Iterator<T>[]): Iterator<T> {
if (iterators.length == 0) {
export function ichain<T>(...iterables: Iterable<T>[]): Iterable<T> {
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<T>(iterator: Iterator<T>, onstart: Function): Iterator<T> {
return () => {
let [head, tail] = iterator();
if (head !== null) {
onstart();
}
return [head, tail];
}
}
/**
* Iterator that repeats the same value.
*/
export function irepeat<T>(value: T, count = -1): Iterator<T> {
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<T>(base: Iterator<T>, count = -1, onloop?: Function): Iterator<T> {
if (count == 0) {
return IEMPTY;
} else {
let next = onloop ? ionstart(base, onloop) : base;
return ichainit(() => [base, iarray([iloop(next, count - 1)])]);
export function iloop<T>(base: Iterable<T>, count = -1, onloop?: Function): Iterable<T> {
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<T1, T2>(iterator: Iterator<T1>, mapfunc: (_: T1) => T2): Iterator<T2> {
return () => {
let [head, tail] = iterator();
if (head === null) {
return IEND;
} else {
return [mapfunc(head), imap(tail, mapfunc)];
export function imap<T1, T2>(iterable: Iterable<T1>, mapfunc: (_: T1) => T2): Iterable<T2> {
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<T>(iterator: Iterator<T>, reduce: (item1: T, item2: T) => T, init: T): T {
export function ireduce<T>(iterable: Iterable<T>, 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<T>(iterator: Iterator<T>, filterfunc: (_: T) => boolean): Iterator<T> {
return () => {
let [value, iter] = iterator();
while (value !== null && !filterfunc(value)) {
[value, iter] = iter();
export function ifilter<T>(iterable: Iterable<T>, filterfunc: (_: T) => boolean): Iterable<T> {
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<T>(iterable: Iterable<any>, filter: (item: any) => item is T): Iterable<T> {
return ifilter(iterable, filter);
}
/**
* Class filter, to return a list of instances of a given type
*/
export function ifilterclass<T>(iterable: Iterable<any>, classref: { new(...args: any[]): T }): Iterable<T> {
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<T1, T2>(it1: Iterator<T1>, it2: Iterator<T2>): Iterator<[T1, T2]> {
export function icombine<T1, T2>(it1: Iterable<T1>, it2: Iterable<T2>): 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<T1, T2>(it1: Iterator<T1>, it2: Iterator<T2>): 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<T1, T2>(it1: Iterable<T1>, it2: Iterable<T2>): 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<T1, T2>(it1: Iterator<T1>, it2: Iterator<T2>): 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<T1, T2>(it1: Iterable<T1>, it2: Iterable<T2>): 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<T>(it: Iterator<T>, predicate: (item: T) => boolean): [Iterator<T>, Iterator<T>] {
return [ifilter(it, predicate), ifilter(it, x => !predicate(x))];
export function ipartition<T>(iterable: Iterable<T>, predicate: (item: T) => boolean): [Iterable<T>, Iterable<T>] {
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<T>(iterables: Iterable<T>[]): Iterable<T> {
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<T>(it: Iterator<T>, limit = 1000000): Iterator<T> {
function internal(it: Iterator<T>, limit: number, done: T[]): Iterator<T> {
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<T>(iterable: Iterable<T>, limit = 1000000): Iterable<T> {
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<number>) => ireduce(iterator, (a, b) => a + b, 0);
export const icat = (iterator: Iterator<string>) => ireduce(iterator, (a, b) => a + b, "");
export const imin = (iterator: Iterator<number>) => ireduce(iterator, Math.min, Infinity);
export const imax = (iterator: Iterator<number>) => ireduce(iterator, Math.max, -Infinity);
export const isum = (iterable: Iterable<number>) => ireduce(iterable, (a, b) => a + b, 0);
export const icat = (iterable: Iterable<string>) => ireduce(iterable, (a, b) => a + b, "");
export const imin = (iterable: Iterable<number>) => ireduce(iterable, Math.min, Infinity);
export const imax = (iterable: Iterable<number>) => ireduce(iterable, Math.max, -Infinity);
}

@ -93,7 +93,7 @@ module TK {
/**
* Get all contained objects (iterator)
*/
iterator(): Iterator<T> {
iterator(): Iterable<T> {
return iarray(this.list());
}
}

@ -41,10 +41,10 @@ module TK {
/**
* Add an asynchronous setup step for each case of the suite
*/
asetup(body: () => Promise<void>, cleanup?: Function): void {
beforeEach((done) => body().then(done));
asetup(body: () => Promise<void>, cleanup?: () => Promise<void>): void {
beforeEach(async () => await body());
if (cleanup) {
afterEach(() => cleanup());
afterEach(async () => await cleanup());
}
}

@ -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 => {

@ -74,9 +74,11 @@ module TK {
/**
* Check if a value if null, throwing an exception if its the case
*/
export function nn<T>(value: T | null): T {
export function nn<T>(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<T>(obj: T): number[] {
let result: number[] = [];
iterenum(obj, val => result.push(val));
return result;
}
/**
* Create a dictionary from a list of couples
*/

@ -113,7 +113,7 @@ module TK.SpaceTac {
/**
* Return an iterator over all ships engaged in the battle
*/
iships(alive_only = false): Iterator<Ship> {
iships(alive_only = false): Iterable<Ship> {
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<Ship> {
iallies(ship: Ship, alive_only = false): Iterable<Ship> {
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<Ship> {
ienemies(ship: Ship, alive_only = false): Iterable<Ship> {
return ifilter(this.iships(alive_only), iship => !iship.fleet.player.is(ship.fleet.player));
}

@ -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<Target> {
scanCircle(x: number, y: number, radius: number, nr = 6, na = 30): Iterable<Target> {
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));

@ -2,7 +2,7 @@
/// <reference path="Maneuver.ts"/>
module TK.SpaceTac {
export type TacticalProducer = Iterator<Maneuver>;
export type TacticalProducer = Iterable<Maneuver>;
export type TacticalEvaluator = (maneuver: Maneuver) => number;
/**
@ -14,17 +14,23 @@ module TK.SpaceTac {
*/
export class TacticalAI extends AbstractAI {
private producers: TacticalProducer[] = []
private work: Iterator<Maneuver> = 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) {

@ -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<BaseAction> {
function getPlayableActions(ship: Ship): Iterable<BaseAction> {
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<Target> {
static scanArena(battle: Battle, cells = 10, random = RandomGenerator.global): Iterable<Target> {
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 = <Iterator<TriggerAction>>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);

@ -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;
}

@ -14,7 +14,7 @@
"es6",
"scripthost"
],
"target": "es5"
"target": "es6"
},
"include": [
"src/**/*.ts"