Browse Source

Switch to es6

Michaël Lemaire 7 months ago
parent
commit
a60ad14c79

+ 4 - 2
.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"

+ 4 - 2
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

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

+ 31 - 524
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",

+ 5 - 9
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"
   }
-}
+}

+ 25 - 6
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),
 }

+ 9 - 4
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'

+ 6 - 4
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,

+ 125 - 91
src/common/Iterators.spec.ts

@@ -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]);
+        });
+
+        test.case("constructs an iterator from an array", check => {
+            checkit(check, iarray([]), []);
+            checkit(check, iarray([1, 2, 3]), [1, 2, 3]);
+        });
 
-            // second iteration to check for repeatability
-            test.check.equals(imaterialize(iterator), values);
-        }
+        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("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", check => {
-            checkarray(imap(IEMPTY, i => i % 3 == 0), []);
-            checkarray(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");
         });
 

+ 247 - 174
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> = () => [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
      * 
-     * If the callback returns *stopper*, the iteration is stopped.
+     * The iterator will yield the next value each time it is called, then undefined when the array's end is reached.
      */
-    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;
+    export function iarray<T>(array: T[], offset = 0): Iterable<T> {
+        return {
+            [Symbol.iterator]: function () {
+                return array.slice(offset)[Symbol.iterator]();
             }
-            [value, iterator] = iterator();
         }
     }
 
     /**
-     * Get an iterator on an array
+     * Iterable constructor, from a single value
      * 
-     * The iterator will yield the next value each time it is called, then null when the array's end is reached.
+     * 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 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 irepeat<T>(value: T, count = -1): Iterable<T> {
+        return {
+            [Symbol.iterator]: function* () {
+                let n = count;
+                while (n != 0) {
+                    yield value;
+                    n--;
+                }
             }
         }
     }
 
     /**
-     * Get an iterator yielding a single value
+     * Equivalent of Array.forEach for all iterables.
+     * 
+     * If the callback returns *stopper*, the iteration is stopped.
      */
-    export function isingle<T>(value: T): Iterator<T> {
-        return iarray([value]);
+    export function iforeach<T>(iterable: Iterable<T>, callback: (_: T) => any, stopper: any = null): void {
+        for (let value of iterable) {
+            if (callback(value) === stopper) {
+                break;
+            }
+        }
     }
 
     /**
      * 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));
-        }
-    }
-
-    /**
-     * 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];
+            return ichainit(iterables);
         }
     }
 
-    /**
-     * 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>(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 ipartition<T>(it: Iterator<T>, predicate: (item: T) => boolean): [Iterator<T>, Iterator<T>] {
-        return [ifilter(it, predicate), ifilter(it, x => !predicate(x))];
+    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);
 }

+ 1 - 1
src/common/RObject.ts

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

+ 3 - 3
src/common/Testing.ts

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

+ 8 - 2
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 => {

+ 12 - 1
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<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
      */

+ 3 - 3
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<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));
         }
 

+ 1 - 1
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<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));

+ 20 - 14
src/core/ai/TacticalAI.ts

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

+ 3 - 3
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<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);

+ 4 - 1
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;
     }

+ 1 - 1
tsconfig.json

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