Implemented AI (“Rando”), refactored card rendering.

This commit is contained in:
Gareth Latty
2019-07-28 18:42:45 +01:00
parent 3836ef8b90
commit 110acdb70f
51 changed files with 1534 additions and 875 deletions
+3 -1
View File
@@ -32,6 +32,8 @@
"allowTypedFunctionExpressions": true,
"allowHigherOrderFunctions": true
}
]
],
"@typescript-eslint/no-empty-interface": "off"
}
}
+203 -155
View File
@@ -5,18 +5,18 @@
"requires": true,
"dependencies": {
"@babel/code-frame": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz",
"integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==",
"version": "7.5.5",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz",
"integrity": "sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw==",
"dev": true,
"requires": {
"@babel/highlight": "^7.0.0"
}
},
"@babel/highlight": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz",
"integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==",
"version": "7.5.0",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.5.0.tgz",
"integrity": "sha512-7dV4eu9gBxoM0dAnj/BCFDW9LFU0zvTrkq0ugM7pnHEgguOEeOz1so2ZghEdzviYzQEED0r4EAgpsBChKy1TRQ==",
"dev": true,
"requires": {
"chalk": "^2.0.0",
@@ -154,6 +154,12 @@
"@types/express": "*"
}
},
"@types/json-schema": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.3.tgz",
"integrity": "sha512-Il2DtDVRGDcqjDtE+rF8iqg1CArehSK84HZJCT7AMITlyXRBpuPhqGLDQMowraqqu1coEaimg4ZOqggt6L6L+A==",
"dev": true
},
"@types/json-stable-stringify": {
"version": "1.0.32",
"resolved": "https://registry.npmjs.org/@types/json-stable-stringify/-/json-stable-stringify-1.0.32.tgz",
@@ -237,9 +243,9 @@
"dev": true
},
"@types/uuid": {
"version": "3.4.4",
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-3.4.4.tgz",
"integrity": "sha512-tPIgT0GUmdJQNSHxp0X2jnpQfBSTfGxUMc/2CXBU2mnyTFVYVa2ojpoQ74w0U2yn2vw3jnC640+77lkFFpdVDw==",
"version": "3.4.5",
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-3.4.5.tgz",
"integrity": "sha512-MNL15wC3EKyw1VLF+RoVO4hJJdk9t/Hlv3rt1OL65Qvuadm4BYo6g9ZJQqoq7X8NBFSsQXgAujWciovh2lpVjA==",
"dev": true,
"requires": {
"@types/node": "*"
@@ -262,12 +268,12 @@
"dev": true
},
"@typescript-eslint/eslint-plugin": {
"version": "1.11.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-1.11.0.tgz",
"integrity": "sha512-mXv9ccCou89C8/4avKHuPB2WkSZyY/XcTQUXd5LFZAcLw1I3mWYVjUu6eS9Ja0QkP/ClolbcW9tb3Ov/pMdcqw==",
"version": "1.13.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-1.13.0.tgz",
"integrity": "sha512-WQHCozMnuNADiqMtsNzp96FNox5sOVpU8Xt4meaT4em8lOG1SrOv92/mUbEHQVh90sldKSfcOc/I0FOb/14G1g==",
"dev": true,
"requires": {
"@typescript-eslint/experimental-utils": "1.11.0",
"@typescript-eslint/experimental-utils": "1.13.0",
"eslint-utils": "^1.3.1",
"functional-red-black-tree": "^1.0.1",
"regexpp": "^2.0.1",
@@ -275,31 +281,32 @@
}
},
"@typescript-eslint/experimental-utils": {
"version": "1.11.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-1.11.0.tgz",
"integrity": "sha512-7LbfaqF6B8oa8cp/315zxKk8FFzosRzzhF8Kn/ZRsRsnpm7Qcu25cR/9RnAQo5utZ2KIWVgaALr+ZmcbG47ruw==",
"version": "1.13.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-1.13.0.tgz",
"integrity": "sha512-zmpS6SyqG4ZF64ffaJ6uah6tWWWgZ8m+c54XXgwFtUv0jNz8aJAVx8chMCvnk7yl6xwn8d+d96+tWp7fXzTuDg==",
"dev": true,
"requires": {
"@typescript-eslint/typescript-estree": "1.11.0",
"@types/json-schema": "^7.0.3",
"@typescript-eslint/typescript-estree": "1.13.0",
"eslint-scope": "^4.0.0"
}
},
"@typescript-eslint/parser": {
"version": "1.11.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-1.11.0.tgz",
"integrity": "sha512-5xBExyXaxVyczrZvbRKEXvaTUFFq7gIM9BynXukXZE0zF3IQP/FxF4mPmmh3gJ9egafZFqByCpPTFm3dk4SY7Q==",
"version": "1.13.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-1.13.0.tgz",
"integrity": "sha512-ITMBs52PCPgLb2nGPoeT4iU3HdQZHcPaZVw+7CsFagRJHUhyeTgorEwHXhFf3e7Evzi8oujKNpHc8TONth8AdQ==",
"dev": true,
"requires": {
"@types/eslint-visitor-keys": "^1.0.0",
"@typescript-eslint/experimental-utils": "1.11.0",
"@typescript-eslint/typescript-estree": "1.11.0",
"@typescript-eslint/experimental-utils": "1.13.0",
"@typescript-eslint/typescript-estree": "1.13.0",
"eslint-visitor-keys": "^1.0.0"
}
},
"@typescript-eslint/typescript-estree": {
"version": "1.11.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-1.11.0.tgz",
"integrity": "sha512-fquUHF5tAx1sM2OeRCC7wVxFd1iMELWMGCzOSmJ3pLzArj9+kRixdlC4d5MncuzXpjEqc6045p3KwM0o/3FuUA==",
"version": "1.13.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-1.13.0.tgz",
"integrity": "sha512-b5rCmd2e6DCC6tCTN9GSUAuxdYwCM/k/2wdjHGrIRGPSJotWMCe/dGpi66u42bhuh8q3QBzqM4TMA1GUUCJvdw==",
"dev": true,
"requires": {
"lodash.unescape": "4.0.1",
@@ -330,9 +337,9 @@
}
},
"acorn": {
"version": "6.1.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-6.1.1.tgz",
"integrity": "sha512-jPTiwtOxaHNaAPg/dmrJ/beuzLRnXtB0kQPQ8JpotKJgTB6rX6c8mlf315941pyjBSaPg8NHXS9fhP4u17DpGA==",
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-6.2.1.tgz",
"integrity": "sha512-JD0xT5FCRDNyjDda3Lrg/IxFscp9q4tiYtxE1/nOzlKCk7hIRuYjhq1kCNkbPjMRMZuFq20HNQn1I9k8Oj0E+Q==",
"dev": true
},
"acorn-jsx": {
@@ -342,9 +349,9 @@
"dev": true
},
"ajv": {
"version": "6.10.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz",
"integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==",
"version": "6.10.2",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz",
"integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==",
"requires": {
"fast-deep-equal": "^2.0.1",
"fast-json-stable-stringify": "^2.0.0",
@@ -871,12 +878,6 @@
"text-hex": "1.0.x"
}
},
"commander": {
"version": "2.20.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz",
"integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==",
"dev": true
},
"component-emitter": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz",
@@ -912,9 +913,9 @@
}
},
"content-security-policy-builder": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/content-security-policy-builder/-/content-security-policy-builder-2.0.0.tgz",
"integrity": "sha512-j+Nhmj1yfZAikJLImCvPJFE29x/UuBi+/MWqggGGc515JKaZrjuei2RhULJmy0MsstW3E3htl002bwmBNMKr7w=="
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/content-security-policy-builder/-/content-security-policy-builder-2.1.0.tgz",
"integrity": "sha512-/MtLWhJVvJNkA9dVLAp6fg9LxD2gfI6R2Fi1hPmfjYXSahJJzcfvoeDOxSyp4NvxMuwWv3WMssE9o31DoULHrQ=="
},
"content-type": {
"version": "1.0.4",
@@ -1067,9 +1068,9 @@
}
},
"dns-prefetch-control": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/dns-prefetch-control/-/dns-prefetch-control-0.1.0.tgz",
"integrity": "sha1-YN20V3dOF48flBXwyrsOhbCzALI="
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/dns-prefetch-control/-/dns-prefetch-control-0.2.0.tgz",
"integrity": "sha512-hvSnros73+qyZXhHFjx2CMLwoj3Fe7eR9EJsFsqmcI1bB2OBWL/+0YzaEaKssCHnj/6crawNnUyw74Gm2EKe+Q=="
},
"doctrine": {
"version": "3.0.0",
@@ -1081,9 +1082,9 @@
}
},
"dont-sniff-mimetype": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/dont-sniff-mimetype/-/dont-sniff-mimetype-1.0.0.tgz",
"integrity": "sha1-WTKJDcn04vGeXrAqIAJuXl78j1g="
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/dont-sniff-mimetype/-/dont-sniff-mimetype-1.1.0.tgz",
"integrity": "sha512-ZjI4zqTaxveH2/tTlzS1wFp+7ncxNZaIEWYg3lzZRHkKf5zPT/MnEG6WL0BhHMJUabkh8GeU5NL5j+rEUCb7Ug=="
},
"dot-prop": {
"version": "4.2.0",
@@ -1157,9 +1158,9 @@
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
},
"eslint": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-6.0.1.tgz",
"integrity": "sha512-DyQRaMmORQ+JsWShYsSg4OPTjY56u1nCjAmICrE8vLWqyLKxhFXOthwMj1SA8xwfrv0CofLNVnqbfyhwCkaO0w==",
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-6.1.0.tgz",
"integrity": "sha512-QhrbdRD7ofuV09IuE2ySWBz0FyXCq0rriLTZXZqaWSI79CVtHVRdkFuFTViiqzZhkCgfOh9USpriuGN2gIpZDQ==",
"dev": true,
"requires": {
"@babel/code-frame": "^7.0.0",
@@ -1168,7 +1169,7 @@
"cross-spawn": "^6.0.5",
"debug": "^4.0.1",
"doctrine": "^3.0.0",
"eslint-scope": "^4.0.3",
"eslint-scope": "^5.0.0",
"eslint-utils": "^1.3.1",
"eslint-visitor-keys": "^1.0.0",
"espree": "^6.0.0",
@@ -1176,30 +1177,37 @@
"esutils": "^2.0.2",
"file-entry-cache": "^5.0.1",
"functional-red-black-tree": "^1.0.1",
"glob-parent": "^3.1.0",
"glob-parent": "^5.0.0",
"globals": "^11.7.0",
"ignore": "^4.0.6",
"import-fresh": "^3.0.0",
"imurmurhash": "^0.1.4",
"inquirer": "^6.2.2",
"inquirer": "^6.4.1",
"is-glob": "^4.0.0",
"js-yaml": "^3.13.1",
"json-stable-stringify-without-jsonify": "^1.0.1",
"levn": "^0.3.0",
"lodash": "^4.17.11",
"lodash": "^4.17.14",
"minimatch": "^3.0.4",
"mkdirp": "^0.5.1",
"natural-compare": "^1.4.0",
"optionator": "^0.8.2",
"progress": "^2.0.0",
"regexpp": "^2.0.1",
"semver": "^5.5.1",
"strip-ansi": "^4.0.0",
"strip-json-comments": "^2.0.1",
"semver": "^6.1.2",
"strip-ansi": "^5.2.0",
"strip-json-comments": "^3.0.1",
"table": "^5.2.3",
"text-table": "^0.2.0"
"text-table": "^0.2.0",
"v8-compile-cache": "^2.0.3"
},
"dependencies": {
"ansi-regex": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
"integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
"dev": true
},
"cross-spawn": {
"version": "6.0.5",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
@@ -1211,6 +1219,14 @@
"semver": "^5.5.0",
"shebang-command": "^1.2.0",
"which": "^1.2.9"
},
"dependencies": {
"semver": {
"version": "5.7.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz",
"integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==",
"dev": true
}
}
},
"debug": {
@@ -1222,11 +1238,57 @@
"ms": "^2.1.1"
}
},
"eslint-scope": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.0.0.tgz",
"integrity": "sha512-oYrhJW7S0bxAFDvWqzvMPRm6pcgcnWc4QnofCAqRTRfQC0JcwenzGglTtsLyIuuWFfkqDG9vz67cnttSd53djw==",
"dev": true,
"requires": {
"esrecurse": "^4.1.0",
"estraverse": "^4.1.1"
}
},
"glob-parent": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.0.0.tgz",
"integrity": "sha512-Z2RwiujPRGluePM6j699ktJYxmPpJKCfpGA13jz2hmFZC7gKetzrWvg5KN3+OsIFmydGyZ1AVwERCq1w/ZZwRg==",
"dev": true,
"requires": {
"is-glob": "^4.0.1"
}
},
"lodash": {
"version": "4.17.15",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
"dev": true
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true
},
"semver": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
"dev": true
},
"strip-ansi": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
"integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
"dev": true,
"requires": {
"ansi-regex": "^4.1.0"
}
},
"strip-json-comments": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.0.1.tgz",
"integrity": "sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==",
"dev": true
}
}
},
@@ -1259,10 +1321,13 @@
}
},
"eslint-utils": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.3.1.tgz",
"integrity": "sha512-Z7YjnIldX+2XMcjr7ZkgEsOj/bREONV60qYeB/bjMAqqqZ4zxKyWX+BOUkdmRmA9riiIPVvo5x86m5elviOk0Q==",
"dev": true
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.0.tgz",
"integrity": "sha512-7ehnzPaP5IIEh1r1tkjuIrxqhNkzUJa9z3R92tLJdZIVdWaczEhr3EbhGtsMrVxi1KeR8qA7Off6SWc5WNQqyQ==",
"dev": true,
"requires": {
"eslint-visitor-keys": "^1.0.0"
}
},
"eslint-visitor-keys": {
"version": "1.0.0",
@@ -1420,9 +1485,9 @@
"integrity": "sha512-h6aK1da4tpqWSbyCa3FxB/V6Ehd4EEB15zyQq9qe75OZBp0krinNKuH4rAY+S/U/2I36vdLAUFSjQJ+TFmODng=="
},
"express-winston": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/express-winston/-/express-winston-3.2.0.tgz",
"integrity": "sha512-AcVSV016U7aPs1Uknoil962SU124UgNkdakP564X6w+lH0oS1FhV3uctUj2/dOog7np4uvsAHhBmSoICjZMDmA==",
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/express-winston/-/express-winston-3.2.1.tgz",
"integrity": "sha512-yh49mpFcoLAFs4yuplIutTAqs+jgRQYB2bLoDTpMvQDARzQKHvABfj2PYMPfmk087+OsEBJmm96i6//q9RJnnw==",
"requires": {
"chalk": "^2.4.1",
"lodash": "^4.17.10"
@@ -1458,9 +1523,9 @@
}
},
"external-editor": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.0.3.tgz",
"integrity": "sha512-bn71H9+qWoOQKyZDo25mOMVpSmXROAsTJVVVYzrrtol3d4y+AsKjf4Iwl2Q+IuT0kFSQ1qo166UuIwqYq7mGnA==",
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz",
"integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==",
"dev": true,
"requires": {
"chardet": "^0.7.0",
@@ -2406,25 +2471,25 @@
"integrity": "sha512-dEHCG2LraR6PNvSGxosZHIRgxF5sNLOIBFEHbj8lfP9WWmu/PWPMzsip1drdVSOFi51N2pU7gZavrgn7sbGFuw=="
},
"helmet": {
"version": "3.18.0",
"resolved": "https://registry.npmjs.org/helmet/-/helmet-3.18.0.tgz",
"integrity": "sha512-TsKlGE5UVkV0NiQ4PllV9EVfZklPjyzcMEMjWlyI/8S6epqgRT+4s4GHVgc25x0TixsKvp3L7c91HQQt5l0+QA==",
"version": "3.20.0",
"resolved": "https://registry.npmjs.org/helmet/-/helmet-3.20.0.tgz",
"integrity": "sha512-Ob+TqmQFZ5f7WgP8kBbAzNPsbf6p1lOj5r+327/ymw/IILWih3wcx9u/u/S8Mwv5wbBkO7Li6x5s23t3COhUKw==",
"requires": {
"depd": "2.0.0",
"dns-prefetch-control": "0.1.0",
"dont-sniff-mimetype": "1.0.0",
"dns-prefetch-control": "0.2.0",
"dont-sniff-mimetype": "1.1.0",
"expect-ct": "0.2.0",
"feature-policy": "0.3.0",
"frameguard": "3.1.0",
"helmet-crossdomain": "0.3.0",
"helmet-csp": "2.7.1",
"hide-powered-by": "1.0.0",
"helmet-crossdomain": "0.4.0",
"helmet-csp": "2.8.0",
"hide-powered-by": "1.1.0",
"hpkp": "2.0.0",
"hsts": "2.2.0",
"ienoopen": "1.1.0",
"nocache": "2.1.0",
"referrer-policy": "1.2.0",
"x-xss-protection": "1.1.0"
"x-xss-protection": "1.2.0"
},
"dependencies": {
"depd": {
@@ -2435,25 +2500,25 @@
}
},
"helmet-crossdomain": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/helmet-crossdomain/-/helmet-crossdomain-0.3.0.tgz",
"integrity": "sha512-YiXhj0E35nC4Na5EPE4mTfoXMf9JTGpN4OtB4aLqShKuH9d2HNaJX5MQoglO6STVka0uMsHyG5lCut5Kzsy7Lg=="
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/helmet-crossdomain/-/helmet-crossdomain-0.4.0.tgz",
"integrity": "sha512-AB4DTykRw3HCOxovD1nPR16hllrVImeFp5VBV9/twj66lJ2nU75DP8FPL0/Jp4jj79JhTfG+pFI2MD02kWJ+fA=="
},
"helmet-csp": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/helmet-csp/-/helmet-csp-2.7.1.tgz",
"integrity": "sha512-sCHwywg4daQ2mY0YYwXSZRsgcCeerUwxMwNixGA7aMLkVmPTYBl7gJoZDHOZyXkqPrtuDT3s2B1A+RLI7WxSdQ==",
"version": "2.8.0",
"resolved": "https://registry.npmjs.org/helmet-csp/-/helmet-csp-2.8.0.tgz",
"integrity": "sha512-MlCPeM0Sm3pS9RACRihx70VeTHmkQwa7sum9EK1tfw1VZyvFU0dBWym9nHh3CRkTRNlyNm/WFCMvuh9zXkOjNw==",
"requires": {
"camelize": "1.0.0",
"content-security-policy-builder": "2.0.0",
"content-security-policy-builder": "2.1.0",
"dasherize": "2.0.0",
"platform": "1.3.5"
}
},
"hide-powered-by": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/hide-powered-by/-/hide-powered-by-1.0.0.tgz",
"integrity": "sha1-SoWtZYgfYoV/xwr3F0oRhNzM4ys="
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/hide-powered-by/-/hide-powered-by-1.1.0.tgz",
"integrity": "sha512-Io1zA2yOA1YJslkr+AJlWSf2yWFkKjvkcL9Ni1XSUqnGLr/qRQe2UI3Cn/J9MsJht7yEVCe0SscY1HgVMujbgg=="
},
"hpkp": {
"version": "2.0.0",
@@ -2518,9 +2583,9 @@
"dev": true
},
"import-fresh": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.0.0.tgz",
"integrity": "sha512-pOnA9tfM3Uwics+SaBLCNyZZZbK+4PTu0OPZtLlMIrv17EdBoC15S9Kn8ckJ9TZTyKb3ywNE5y1yeDxxGA7nTQ==",
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.1.0.tgz",
"integrity": "sha512-PpuksHKGt8rXfWEr9m9EHIpgyyaltBy8+eF6GJM0QCAxMgxCfucMF3mjecK2QsJr0amJW7gTqh5/wht0z2UhEQ==",
"dev": true,
"requires": {
"parent-module": "^1.0.0",
@@ -2561,9 +2626,9 @@
"dev": true
},
"inquirer": {
"version": "6.4.1",
"resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.4.1.tgz",
"integrity": "sha512-/Jw+qPZx4EDYsaT6uz7F4GJRNFMRdKNeUZw3ZnKV8lyuUgz/YWRCSUAJMZSVhSq4Ec0R2oYnyi6b3d4JXcL5Nw==",
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.5.0.tgz",
"integrity": "sha512-scfHejeG/lVZSpvCXpsB4j/wQNPM5JC8kiElOI0OUTwmc1RTpXr4H32/HOlQHcZiYl2z2VElwuCVDRG8vFmbnA==",
"dev": true,
"requires": {
"ansi-escapes": "^3.2.0",
@@ -2572,7 +2637,7 @@
"cli-width": "^2.0.0",
"external-editor": "^3.0.3",
"figures": "^2.0.0",
"lodash": "^4.17.11",
"lodash": "^4.17.12",
"mute-stream": "0.0.7",
"run-async": "^2.2.0",
"rxjs": "^6.4.0",
@@ -2587,6 +2652,12 @@
"integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
"dev": true
},
"lodash": {
"version": "4.17.15",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
"dev": true
},
"strip-ansi": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
@@ -2976,9 +3047,9 @@
}
},
"lodash": {
"version": "4.17.11",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
"integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg=="
"version": "4.17.15",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A=="
},
"lodash.includes": {
"version": "4.3.0",
@@ -3175,9 +3246,9 @@
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
},
"mixin-deep": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz",
"integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==",
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz",
"integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==",
"dev": true,
"requires": {
"for-in": "^1.0.2",
@@ -3954,9 +4025,9 @@
"dev": true
},
"set-value": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz",
"integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==",
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz",
"integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==",
"dev": true,
"requires": {
"extend-shallow": "^2.0.1",
@@ -4269,13 +4340,13 @@
}
},
"table": {
"version": "5.4.1",
"resolved": "https://registry.npmjs.org/table/-/table-5.4.1.tgz",
"integrity": "sha512-E6CK1/pZe2N75rGZQotFOdmzWQ1AILtgYbMAbAjvms0S1l5IDB47zG3nCnFGB/w+7nB3vKofbLXCH7HPBo864w==",
"version": "5.4.4",
"resolved": "https://registry.npmjs.org/table/-/table-5.4.4.tgz",
"integrity": "sha512-IIfEAUx5QlODLblLrGTTLJA7Tk0iLSGBvgY8essPRVNGHAzThujww1YqHLs6h3HfTg55h++RzLHH5Xw/rfv+mg==",
"dev": true,
"requires": {
"ajv": "^6.9.1",
"lodash": "^4.17.11",
"ajv": "^6.10.2",
"lodash": "^4.17.14",
"slice-ansi": "^2.1.0",
"string-width": "^3.0.0"
},
@@ -4286,6 +4357,12 @@
"integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
"dev": true
},
"lodash": {
"version": "4.17.15",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
"dev": true
},
"string-width": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
@@ -4410,18 +4487,6 @@
"resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.3.0.tgz",
"integrity": "sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw=="
},
"ts-json-schema-generator": {
"version": "0.50.2",
"resolved": "https://registry.npmjs.org/ts-json-schema-generator/-/ts-json-schema-generator-0.50.2.tgz",
"integrity": "sha512-S6n8kHpU2y7IJvr07rA2Xn9wbKRWTs31jvgpFQnkWn4n7jWXOj/yGEzp1UN/qQOBB0r3qSZjsOMr2KzuaSWTGA==",
"dev": true,
"requires": {
"commander": "~2.20.0",
"glob": "~7.1.4",
"json-stable-stringify": "^1.0.1",
"typescript": "~3.5.2"
}
},
"ts-postgres": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/ts-postgres/-/ts-postgres-1.1.0.tgz",
@@ -4454,9 +4519,9 @@
"dev": true
},
"tsutils": {
"version": "3.14.0",
"resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.14.0.tgz",
"integrity": "sha512-SmzGbB0l+8I0QwsPgjooFRaRvHLBLNYM8SeQ0k6rtNDru5sCGeLJcZdwilNndN+GysuFjF5EIYgN8GfFG6UeUw==",
"version": "3.14.1",
"resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.14.1.tgz",
"integrity": "sha512-kiuZzD1uUA5DxGj/uxbde+ymp6VVdAxdzOIlAFbYKrPyla8/uiJ9JLBm1QsPhOm4Muj0/+cWEDP99yoCUcSl6Q==",
"dev": true,
"requires": {
"tslib": "^1.8.1"
@@ -4481,9 +4546,9 @@
}
},
"typescript": {
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.5.2.tgz",
"integrity": "sha512-7KxJovlYhTX5RaRbUdkAXN1KUZ8PwWlTzQdHV6xNqvuFOs7+WBo10TQUqT19Q/Jz2hk5v9TQDIhyLhhJY4p5AA==",
"version": "3.5.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.5.3.tgz",
"integrity": "sha512-ACzBtm/PhXBDId6a6sDJfroT2pOWt/oOnk4/dElG5G33ZL776N3Y6/6bKZJBFpd+b05F3Ct9qDjMeJmRWtE2/g==",
"dev": true
},
"typescript-json-schema": {
@@ -4543,38 +4608,15 @@
}
},
"union-value": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz",
"integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=",
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz",
"integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==",
"dev": true,
"requires": {
"arr-union": "^3.1.0",
"get-value": "^2.0.6",
"is-extendable": "^0.1.1",
"set-value": "^0.4.3"
},
"dependencies": {
"extend-shallow": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
"integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
"dev": true,
"requires": {
"is-extendable": "^0.1.0"
}
},
"set-value": {
"version": "0.4.3",
"resolved": "https://registry.npmjs.org/set-value/-/set-value-0.4.3.tgz",
"integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=",
"dev": true,
"requires": {
"extend-shallow": "^2.0.1",
"is-extendable": "^0.1.1",
"is-plain-object": "^2.0.1",
"to-object-path": "^0.3.0"
}
}
"set-value": "^2.0.1"
}
},
"unique-string": {
@@ -4705,6 +4747,12 @@
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz",
"integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA=="
},
"v8-compile-cache": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.0.3.tgz",
"integrity": "sha512-CNmdbwQMBjwr9Gsmohvm0pbL954tJrNzf6gWL3K+QMQf00PF7ERGrEiLgjuU3mKreLC2MeGhUsNV9ybTbLgd3w==",
"dev": true
},
"vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
@@ -4856,9 +4904,9 @@
"integrity": "sha1-fnLj+6I+D/Zp3dU34chX09SxYU8="
},
"x-xss-protection": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/x-xss-protection/-/x-xss-protection-1.1.0.tgz",
"integrity": "sha512-rx3GzJlgEeZ08MIcDsU2vY2B1QEriUKJTSiNHHUIem6eg9pzVOr2TL3Y4Pd6TMAM5D5azGjcxqI62piITBDHVg=="
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/x-xss-protection/-/x-xss-protection-1.2.0.tgz",
"integrity": "sha512-xN0kV+8XfOQM2OPPBdEbGtbvJNNP1pvZR7sE6d44cjJFQG4OiGDdienPg5iOUGswBTiGbBvtYDURd30BMJwwqg=="
},
"xdg-basedir": {
"version": "3.0.0",
+8 -8
View File
@@ -25,16 +25,16 @@
"config.json5"
],
"dependencies": {
"ajv": "^6.10.0",
"ajv": "^6.10.2",
"axios": "^0.19.0",
"body-parser": "^1.19.0",
"express": "^4.17.1",
"express-async-errors": "^3.1.1",
"express-winston": "^3.2.0",
"express-winston": "^3.2.1",
"express-ws": "^4.0.0",
"generic-pool": "^3.7.1",
"hashids": "^1.2.2",
"helmet": "^3.18.0",
"helmet": "^3.20.0",
"http-status-codes": "^1.3.2",
"json5": "^2.1.0",
"jsonwebtoken": "^8.5.1",
@@ -55,16 +55,16 @@
"@types/json5": "0.0.30",
"@types/jsonwebtoken": "^8.3.2",
"@types/source-map-support": "^0.5.0",
"@types/uuid": "^3.4.4",
"@types/uuid": "^3.4.5",
"@types/wu": "^2.1.39",
"@typescript-eslint/eslint-plugin": "^1.11.0",
"@typescript-eslint/parser": "^1.11.0",
"eslint": "^6.0.1",
"@typescript-eslint/eslint-plugin": "^1.13.0",
"@typescript-eslint/parser": "^1.13.0",
"eslint": "^6.1.0",
"eslint-config-prettier": "^6.0.0",
"eslint-plugin-prettier": "^3.1.0",
"nodemon": "^1.19.1",
"prettier": "1.18.2",
"typescript": "^3.5.2",
"typescript": "^3.5.3",
"typescript-json-validator": "^2.2.2"
}
}
@@ -4,6 +4,7 @@ import * as rules from "../../../games/rules";
import { Handler } from "../../handler";
import * as configure from "../configure";
import * as houseRuleChanged from "../../../events/lobby-event/configured/house-rule-changed";
import * as rando from "../../../games/rules/rando";
/**
* Set the hand size for the lobby.
@@ -24,24 +25,40 @@ export const is = (action: Action): action is ChangeHouseRule =>
action.action === name;
export const handle: Handler<ChangeHouseRule> = (auth, lobby, action) => {
const houseRules = lobby.config.rules.houseRules;
const hr = lobby.config.rules.houseRules;
const events = [];
let changed = false;
switch (action.change.houseRule) {
case "PackingHeat":
houseRules.packingHeat = action.change.settings;
if (hr.packingHeat !== action.change.settings) {
hr.packingHeat = action.change.settings;
changed = true;
}
break;
case "Rando":
houseRules.rando = action.change.settings;
const userEvents = rando.change(lobby, hr.rando, action.change.settings);
if (userEvents !== null) {
events.push(...userEvents);
changed = true;
}
break;
case "Reboot":
houseRules.reboot = action.change.settings;
if (hr.reboot !== action.change.settings) {
hr.reboot = action.change.settings;
changed = true;
}
break;
}
lobby.config.version += 1;
return {
lobby,
events: [
if (changed) {
lobby.config.version += 1;
events.push(
event.targetAll(houseRuleChanged.of(action.change, lobby.config.version))
]
};
);
return {
lobby,
events
};
} else {
return {};
}
};
@@ -1,4 +1,5 @@
import * as card from "../../games/cards/card";
import * as round from "../../games/game/round";
import * as publicRound from "../../games/game/round/public";
/**
@@ -11,10 +12,10 @@ export interface GameStarted {
}
export const of = (
round: publicRound.Playing,
startedRound: round.Playing,
hand?: card.Response[]
): GameStarted => ({
event: "GameStarted",
round,
round: round.censor(startedRound),
...(hand !== undefined ? { hand } : {})
});
@@ -1,6 +1,6 @@
import * as card from "../../games/cards/card";
import { Round } from "../../games/game/round";
import * as user from "../../user";
import * as round from "../../games/game/round";
/**
* Indicates a new round has started.
@@ -14,17 +14,11 @@ export interface RoundStarted {
drawn?: card.Response[];
}
export const of = (
id: round.Id,
czar: user.Id,
players: user.Id[],
call: card.Call,
drawn?: card.Response[]
): RoundStarted => ({
export const of = (round: Round, drawn?: card.Response[]): RoundStarted => ({
event: "RoundStarted",
id: id.toString(),
czar,
players,
call,
id: round.id.toString(),
czar: round.czar,
players: Array.from(round.players.keys()),
call: round.call,
...(drawn === undefined ? {} : { drawn })
});
@@ -1,3 +1,4 @@
import { User } from "../../user";
import * as user from "../../user";
/**
@@ -15,12 +16,16 @@ interface Base {
export interface Joined extends Base {
event: "Joined";
name: user.Name;
privilege?: user.Privilege;
control?: user.Control;
}
export const joined = (user: user.Id, name: user.Name): Joined => ({
export const joined = (id: user.Id, user: User): Joined => ({
event: "Joined",
user,
name
user: id,
name: user.name,
...(user.privilege !== "Unprivileged" ? { privilege: user.privilege } : {}),
...(user.control !== "Human" ? { control: user.control } : {})
});
/**
+103 -5
View File
@@ -1,7 +1,16 @@
import * as event from "../event";
import * as gameStarted from "../events/game-event/game-started";
import * as playSubmitted from "../events/game-event/play-submitted";
import * as roundStarted from "../events/game-event/round-started";
import { ServerState } from "../server-state";
import * as finishedPlaying from "../timeout/finished-playing";
import { User } from "../user";
import * as user from "../user";
import * as util from "../util";
import * as card from "./cards/card";
import { Decks } from "./cards/decks";
import * as decks from "./cards/decks";
import * as play from "./cards/play";
import * as round from "./game/round";
import { Round } from "./game/round";
import * as publicRound from "./game/round/public";
@@ -10,6 +19,8 @@ import * as player from "./player";
import { Player } from "./player";
import * as rules from "./rules";
import { Rules } from "./rules";
import * as lobby from "../lobby";
import * as timeout from "../timeout";
import wu from "wu";
/**
@@ -45,7 +56,6 @@ export const censor: (game: Game) => Public = game => ({
const newPlayerForUser = (decks: Decks, rules: Rules): Player => ({
hand: decks.responses.draw(rules.handSize),
control: "Human",
score: 0
});
@@ -79,8 +89,96 @@ export const start = (
};
};
export const nextCzar = (game: Game): user.Id => {
const current = game.round.czar;
const index = game.playerOrder.findIndex(id => id === current) + 1;
return game.playerOrder[index >= game.playerOrder.length ? 0 : index];
export const atStartOfRound = (
server: ServerState,
first: boolean,
game: Game & { round: round.Playing }
): {
game: Game & { round: round.Playing };
events?: Iterable<event.Distributor>;
timeouts?: Iterable<timeout.TimeoutAfter>;
} => {
const slotCount = card.slotCount(game.round.call);
const events = [];
if (
slotCount > 2 ||
(slotCount === 2 && game.rules.houseRules.packingHeat !== undefined)
) {
const responseDeck = game.decks.responses;
const drawnByPlayer = new Map();
for (const [id, playerState] of game.players) {
if (player.role(game, id) === "Player") {
const drawn = responseDeck.draw(slotCount - 1);
drawnByPlayer.set(id, { drawn });
playerState.hand.push(...drawn);
}
}
if (!first) {
events.push(
event.additionally(roundStarted.of(game.round), drawnByPlayer)
);
}
} else {
if (!first) {
events.push(event.targetAll(roundStarted.of(game.round)));
}
}
if (first) {
events.push(
event.playerSpecificAddition(
gameStarted.of(game.round),
(id, user, player) => ({
hand: player.hand
})
)
);
}
const ais = game.rules.houseRules.rando.current;
for (const ai of ais) {
const player = game.players.get(ai) as Player;
const plays = game.round.plays;
const playId = play.id();
plays.push({
id: playId,
play: player.hand.slice(0, slotCount),
playedBy: ai,
revealed: false
});
events.push(event.targetAll(playSubmitted.of(ai)));
}
const timeouts = [];
const timeout = finishedPlaying.ifNeeded(game.round);
if (timeout !== undefined) {
timeouts.push({
timeout: timeout,
after: server.config.timeouts.finishedPlayingDelay
});
}
return { game, events, timeouts };
};
const canBeCzar = (user: User): boolean =>
user.control !== "Computer" &&
user.presence === "Joined" &&
user.role === "Player";
const czarAfter = (lobby: lobby.WithActiveGame, nextIndex: number): user.Id => {
const game = lobby.game;
const potentialCzar =
game.playerOrder[nextIndex >= game.playerOrder.length ? 0 : nextIndex];
return canBeCzar(lobby.users.get(potentialCzar) as User)
? potentialCzar
: czarAfter(lobby, nextIndex + 1);
};
export const nextCzar = (lobby: lobby.WithActiveGame): user.Id => {
const game = lobby.game;
const current = game.round.czar;
const nextIndex = game.playerOrder.findIndex(id => id === current) + 1;
return czarAfter(lobby, nextIndex);
};
-8
View File
@@ -7,7 +7,6 @@ import { Game } from "./game";
*/
export interface Player {
hand: Hand;
control: Control;
score: Score;
}
@@ -15,7 +14,6 @@ export interface Player {
* A player containing only state all users can see.
*/
export interface Public {
control: Control;
score: Score;
}
@@ -24,11 +22,6 @@ export interface Public {
*/
export type Role = "Czar" | "Player";
/**
* Who controls the player.
*/
export type Control = "Human" | "Computer";
/**
* How many points the player has scored.
* @TJS-type integer
@@ -40,7 +33,6 @@ export type Score = number;
* Produce a public version of the given player.
*/
export const censor = (player: Player): Public => ({
control: player.control,
score: player.score
});
+11 -22
View File
@@ -1,4 +1,9 @@
/** The rules for a standard game.*/
import { HouseRules } from "./rules/houseRules";
import * as houseRules from "./rules/houseRules";
import * as rando from "./rules/rando";
/** The rules for a standard game.
*/
export interface Rules {
/**
* The number of cards in each player's hand.
@@ -21,13 +26,7 @@ export interface Rules {
export interface Public {
handSize: number;
scoreLimit?: number;
houseRules: HouseRules;
}
export interface HouseRules {
packingHeat?: PackingHeat;
reboot?: Reboot;
rando?: Rando;
houseRules: houseRules.Public;
}
/**
@@ -36,13 +35,12 @@ export interface HouseRules {
export const create = (): Rules => ({
handSize: 10,
scoreLimit: 25,
houseRules: {}
houseRules: houseRules.create()
});
/**
* Configuration for the "Packing Heat" house rule.
*/
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface PackingHeat {}
/**
@@ -60,18 +58,9 @@ export interface Reboot {
cost: number;
}
export interface Rando {
/**
* The number of AI players to add to the game.
* @TJS-type integer
* @minimum 1
* @maximum 10
*/
number: number;
}
export const censor = (rules: Rules): Public => ({
...rules
...rules,
houseRules: houseRules.censor(rules.houseRules)
});
export interface ChangeBase<Name extends string, HouseRule> {
@@ -80,7 +69,7 @@ export interface ChangeBase<Name extends string, HouseRule> {
}
export type ChangePackingHeat = ChangeBase<"PackingHeat", PackingHeat>;
export type ChangeRando = ChangeBase<"Rando", Rando>;
export type ChangeRando = ChangeBase<"Rando", rando.Public>;
export type ChangeReboot = ChangeBase<"Reboot", Reboot>;
export type Change = ChangePackingHeat | ChangeRando | ChangeReboot;
+30
View File
@@ -0,0 +1,30 @@
import { PackingHeat, Reboot, Rules } from "../rules";
import { Rando } from "./rando";
import * as rando from "./rando";
/**
* Non-standard rules that can be applied to a game.
*/
export interface HouseRules {
packingHeat?: PackingHeat;
reboot?: Reboot;
rando: Rando;
}
/**
* The public view of the internal model.
*/
export interface Public {
packingHeat?: PackingHeat;
reboot?: Reboot;
rando?: rando.Public;
}
export const create = (): HouseRules => ({
rando: rando.create()
});
export const censor = (houseRules: HouseRules): Public => ({
...houseRules,
rando: rando.censor(houseRules.rando)
});
+149
View File
@@ -0,0 +1,149 @@
import { RegisterUser } from "../../action/initial/register-user";
import * as event from "../../event";
import * as presenceChanged from "../../events/lobby-event/presence-changed";
import { Lobby } from "../../lobby";
import * as lobby from "../../lobby";
import { User } from "../../user";
import * as util from "../../util";
import * as user from "../../user";
/**
* The maximum number of AI players allowed in a single game.
*/
const max = 10;
/**
* The default name for an AI.
*/
export const aiName = "Rando Cardrissian";
/**
* Apart from the one inherited from Cards against Humanity (a reference to
* Star Wars), we use some references to famous AIs and computers from films
* and games.
*/
export const aiNames = new Set([
"HAL 9000", // 2001: A Space Odyssey
"GLaDOS", // Portal
"Wheatley", // Portal
"TEC-XX", // Paper Mario
"EDI", // Mass Effect
"343 Guilty Spark", // Halo
"Cortana", // Halo
"J.A.R.V.I.S.", // MCU
"Deep Thought", // HHGTTG
"Gibson", // Hackers
"Skynet", // Terminator
"Project 2501", // GITS
"SHODAN", // System Shock
"Mr. House" // Fallout: New Vegas
]);
/**
* The internal model of the Rando house rule.
*/
export interface Rando {
/**
* The ids of active AI players in the game.
*/
current: user.Id[];
/**
* The names or ids of potential AI players that are not currently active in
* the game.
*/
unused: (user.Id | RegisterUser)[];
}
/**
* Create the initial model.
*/
export const create = (): Rando => ({
current: [],
unused: [aiName]
.concat(util.shuffled(aiNames).slice(0, max - 1))
.map(name => ({ name }))
});
/**
* The public view of the Rando house rule.
*/
export interface Public {
/**
* The number of AI players to add to the game.
* @TJS-type integer
* @minimum 1
* @maximum 10
*/
number: number;
}
/**
* Get the public view of the given internal model.
*/
export const censor = (rando: Rando): Public | undefined =>
rando.current.length > 0 ? { number: rando.current.length } : undefined;
const isId = (ai: user.Id | RegisterUser): ai is user.Id =>
typeof ai === "string";
export const createIfNeeded = (
inLobby: Lobby,
ai: user.Id | RegisterUser
): { user: user.Id; events: Iterable<event.Distributor> } => {
if (isId(ai)) {
return {
user: ai,
events: [
event.targetAll(
presenceChanged.joined(ai, inLobby.users.get(ai) as User)
)
]
};
} else {
return lobby.addUser(inLobby, ai, user => ({
...user,
control: "Computer"
}));
}
};
/**
* Change the internal model to match the given public representation.
*/
export function* change(
inLobby: Lobby,
config: Rando,
changeTo?: Public
): Iterable<event.Distributor> | null {
const want = changeTo !== undefined ? changeTo.number : 0;
const have = config.current.length;
const eventsCollection = [];
if (want === have) {
return null;
}
if (want > have) {
const toAdd = want - have;
const added = config.unused
.splice(0, toAdd)
.map(ai => createIfNeeded(inLobby, ai));
for (const { user, events } of added) {
const userData = inLobby.users.get(user) as User;
userData.presence = "Joined";
config.current.push(user);
eventsCollection.push(...events);
}
} else if (have > want) {
const toRemove = have - want;
const removed = config.current.splice(
config.current.length - toRemove,
toRemove
);
for (const ai of removed) {
const user = inLobby.users.get(ai) as User;
user.presence = "Left";
eventsCollection.push(event.targetAll(presenceChanged.left(ai)));
}
config.unused.splice(0, 0, ...removed);
}
yield* eventsCollection;
}
+1 -1
View File
@@ -126,7 +126,7 @@ async function main(): Promise<void> {
return {
change: {
lobby,
events: [event.targetAll(presenceChanged.joined(id, newUser.name))],
events: [event.targetAll(presenceChanged.joined(id, newUser))],
timeouts: [
{
timeout: userDisconnect.of(id),
+22 -1
View File
@@ -1,4 +1,7 @@
import { CreateLobby } from "./action/initial/create-lobby";
import { RegisterUser } from "./action/initial/register-user";
import * as event from "./event";
import * as presenceChanged from "./events/lobby-event/presence-changed";
import * as game from "./games/game";
import { Game } from "./games/game";
import * as rules from "./games/rules";
@@ -8,7 +11,6 @@ import { GameCode } from "./lobby/game-code";
import * as user from "./user";
import { User } from "./user";
import * as util from "./util";
import * as token from "./user/token";
/**
* A game lobby.
@@ -132,3 +134,22 @@ export const censor = (lobby: Lobby): Public => ({
config: config.censor(lobby.config),
...(lobby.game === undefined ? {} : { game: game.censor(lobby.game) })
});
export const addUser = (
lobby: Lobby,
registration: RegisterUser,
change?: (user: User) => User
): {
user: user.Id;
events: Iterable<event.Distributor>;
} => {
const newUser = user.create(registration);
const changedUser = change === undefined ? newUser : change(newUser);
const id = lobby.nextUserId.toString();
lobby.nextUserId += 1;
lobby.users.set(id, changedUser);
return {
user: id,
events: [event.targetAll(presenceChanged.joined(id, changedUser))]
};
};
+6 -2
View File
@@ -15,12 +15,16 @@ export abstract class TaskBase<T> implements Task {
}
protected abstract async begin(server: ServerState): Promise<T>;
protected abstract resolve(lobby: Lobby, work: T): Change;
protected abstract resolve(
lobby: Lobby,
work: T,
server: ServerState
): Change;
public async handle(server: ServerState): Promise<void> {
const work = await this.begin(server);
await change.apply(server, this.gameCode, lobby =>
this.resolve(lobby, work)
this.resolve(lobby, work, server)
);
}
}
+13 -13
View File
@@ -1,11 +1,8 @@
import wu from "wu";
import * as event from "../event";
import * as gameStarted from "../events/game-event/game-started";
import * as decks from "../games/cards/decks";
import * as source from "../games/cards/source";
import * as sources from "../games/cards/sources";
import * as game from "../games/game";
import * as round from "../games/game/round";
import { Lobby } from "../lobby";
import { Change } from "../lobby/change";
import { GameCode } from "../lobby/game-code";
@@ -28,20 +25,23 @@ export class StartGame extends task.TaskBase<decks.Templates[]> {
);
}
protected resolve(lobby: Lobby, work: decks.Templates[]): Change {
protected resolve(
lobby: Lobby,
work: decks.Templates[],
server: ServerState
): Change {
if (lobby.game !== undefined) {
return {};
}
const lobbyGame = game.start(work, lobby.users.keys(), lobby.config.rules);
const gameRound = round.censor(lobbyGame.round);
const baseEvent = gameStarted.of(gameRound);
const events = [
event.playerSpecificAddition(baseEvent, (id, user, player) => ({
hand: player.hand
}))
];
lobby.game = lobbyGame;
return { lobby, events };
const atStartOfRound = game.atStartOfRound(server, true, lobbyGame);
lobby.game = atStartOfRound.game;
return {
lobby,
events: atStartOfRound.events,
timeouts: atStartOfRound.timeouts
};
}
// This is super unlikely timing-wise, and if it happens, the user just has
+24 -39
View File
@@ -1,10 +1,9 @@
import wu from "wu";
import * as event from "../event";
import * as roundStarted from "../events/game-event/round-started";
import { Game } from "../games/game";
import * as game from "../games/game";
import * as lobby from "../lobby";
import { Playing } from "../games/game/round";
import * as timeout from "../timeout";
import * as card from "../games/cards/card";
import * as player from "../games/player";
/**
* Indicates that the round should start if it is still appropriate to do so.
@@ -21,15 +20,14 @@ export const handle: timeout.Handler<RoundStart> = (
server,
timeout,
gameCode,
lobby
inLobby
) => {
const lobbyGame = lobby.game;
if (lobbyGame !== undefined) {
if (lobby.hasActiveGame(inLobby)) {
const lobbyGame = inLobby.game;
const gameRound = lobbyGame.round;
if (gameRound.stage === "Complete") {
const czar = game.nextCzar(lobbyGame);
const czar = game.nextCzar(inLobby);
const [call] = lobbyGame.decks.calls.replace(gameRound.call);
const slotCount = card.slotCount(call);
const roundId = gameRound.id + 1;
const playersInRound = new Set(
wu(lobbyGame.playerOrder).filter(id => id !== czar)
@@ -37,37 +35,24 @@ export const handle: timeout.Handler<RoundStart> = (
lobbyGame.decks.responses.discard(
gameRound.plays.flatMap(play => play.play)
);
lobbyGame.round = {
stage: "Playing",
id: roundId,
czar: czar,
players: playersInRound,
call: call,
plays: []
};
const playersArray = Array.from(playersInRound);
const baseEvent = roundStarted.of(roundId, czar, playersArray, call);
let events;
if (
slotCount > 2 ||
(slotCount === 2 &&
lobbyGame.rules.houseRules.packingHeat !== undefined)
) {
const responseDeck = lobbyGame.decks.responses;
const drawnByPlayer = new Map();
for (const [id, playerState] of lobbyGame.players) {
if (player.role(lobbyGame, id) === "Player") {
const drawn = responseDeck.draw(slotCount - 1);
drawnByPlayer.set(id, { drawn });
playerState.hand.push(...drawn);
}
const updatedGame: Game & { round: Playing } = {
...lobbyGame,
round: {
stage: "Playing",
id: roundId,
czar: czar,
players: playersInRound,
call: call,
plays: []
}
events = [event.additionally(baseEvent, drawnByPlayer)];
} else {
events = [event.targetAll(baseEvent)];
}
return { lobby, events };
};
const atStartOfRound = game.atStartOfRound(server, false, updatedGame);
inLobby.game = atStartOfRound.game;
return {
inLobby,
events: atStartOfRound.events,
timeouts: atStartOfRound.timeouts
};
}
}
return {};
+17 -15
View File
@@ -6,6 +6,7 @@ export interface User {
presence: Presence;
connection: Connection;
privilege: Privilege;
control: Control;
role: Role;
}
@@ -17,6 +18,7 @@ export interface Public {
presence: Presence;
connection: Connection;
privilege: Privilege;
control: Control;
role: Role;
}
@@ -52,6 +54,11 @@ export type Connection = "Connected" | "Disconnected";
*/
export type Role = "Spectator" | "Player";
/**
* Who/what is controlling the player—a human or the computer?
*/
export type Control = "Human" | "Computer";
/**
* If the user is playing.
*/
@@ -69,26 +76,21 @@ export const isSpectating: (user: User) => boolean = user =>
* @param registration The details of the user to create.
* @param privilege The level of privilege the user has.
*/
export function create(
export const create = (
registration: RegisterUser,
privilege: Privilege = "Unprivileged"
): User {
return {
name: registration.name,
presence: "Joined",
connection: "Connected",
privilege: privilege,
role: "Player"
};
}
): User => ({
name: registration.name,
presence: "Joined",
connection: "Connected",
privilege: privilege,
control: "Human",
role: "Player"
});
/**
* Gives a version of the user with only publicly visible properties.
*/
export const censor: (user: User) => Public = user => ({
name: user.name,
presence: user.presence,
connection: user.connection,
privilege: user.privilege,
role: user.role
...user
});