mirror of
https://github.com/syuilo/ai.git
synced 2024-11-25 06:01:07 +00:00
✌️
This commit is contained in:
parent
d08788a9af
commit
1a2ede3764
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
config.json
|
||||||
|
built
|
||||||
|
node_modules
|
542
package-lock.json
generated
Normal file
542
package-lock.json
generated
Normal file
|
@ -0,0 +1,542 @@
|
||||||
|
{
|
||||||
|
"name": "ai",
|
||||||
|
"requires": true,
|
||||||
|
"lockfileVersion": 1,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/events": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/events/-/events-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-KEIlhXnIutzKwRbQkGWb/I4HFqBuUykAdHgDED6xqwXJfONCjF5VoE0cXEiurh3XauygxzeDzgtXUqvLkxFzzA=="
|
||||||
|
},
|
||||||
|
"@types/node": {
|
||||||
|
"version": "10.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-10.0.5.tgz",
|
||||||
|
"integrity": "sha512-he3QlF+xnGlmsnL1H8/CiM6r25kk0STky6U5yIqNh4Nnp9KlJBSdMMIiCzDYtAFLw2rWnJ4XKc1xB2/u/anYow=="
|
||||||
|
},
|
||||||
|
"@types/ws": {
|
||||||
|
"version": "5.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-5.1.2.tgz",
|
||||||
|
"integrity": "sha512-NkTXUKTYdXdnPE2aUUbGOXE1XfMK527SCvU/9bj86kyFF6kZ9ZnOQ3mK5jADn98Y2vEUD/7wKDgZa7Qst2wYOg==",
|
||||||
|
"requires": {
|
||||||
|
"@types/events": "*",
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ajv": {
|
||||||
|
"version": "5.5.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz",
|
||||||
|
"integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=",
|
||||||
|
"requires": {
|
||||||
|
"co": "^4.6.0",
|
||||||
|
"fast-deep-equal": "^1.0.0",
|
||||||
|
"fast-json-stable-stringify": "^2.0.0",
|
||||||
|
"json-schema-traverse": "^0.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ansi-styles": {
|
||||||
|
"version": "3.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
|
||||||
|
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
|
||||||
|
"requires": {
|
||||||
|
"color-convert": "^1.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"arrify": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz",
|
||||||
|
"integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0="
|
||||||
|
},
|
||||||
|
"asn1": {
|
||||||
|
"version": "0.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz",
|
||||||
|
"integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==",
|
||||||
|
"requires": {
|
||||||
|
"safer-buffer": "~2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"assert-plus": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
|
||||||
|
"integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU="
|
||||||
|
},
|
||||||
|
"async-limiter": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"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="
|
||||||
|
},
|
||||||
|
"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="
|
||||||
|
},
|
||||||
|
"aws4": {
|
||||||
|
"version": "1.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.7.0.tgz",
|
||||||
|
"integrity": "sha512-32NDda82rhwD9/JBCCkB+MRYDp0oSvlo2IL6rQWA10PQi7tDUM3eqMSltXmY+Oyl/7N3P3qNtAlv7X0d9bI28w=="
|
||||||
|
},
|
||||||
|
"bcrypt-pbkdf": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
|
||||||
|
"integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=",
|
||||||
|
"optional": true,
|
||||||
|
"requires": {
|
||||||
|
"tweetnacl": "^0.14.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"buffer-from": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A=="
|
||||||
|
},
|
||||||
|
"caseless": {
|
||||||
|
"version": "0.12.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
|
||||||
|
"integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw="
|
||||||
|
},
|
||||||
|
"chalk": {
|
||||||
|
"version": "2.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz",
|
||||||
|
"integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==",
|
||||||
|
"requires": {
|
||||||
|
"ansi-styles": "^3.2.1",
|
||||||
|
"escape-string-regexp": "^1.0.5",
|
||||||
|
"supports-color": "^5.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"co": {
|
||||||
|
"version": "4.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
|
||||||
|
"integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ="
|
||||||
|
},
|
||||||
|
"color-convert": {
|
||||||
|
"version": "1.9.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.2.tgz",
|
||||||
|
"integrity": "sha512-3NUJZdhMhcdPn8vJ9v2UQJoH0qqoGUkYTgFEPZaPjEtwmmKUfNV46zZmgB2M5M4DCEQHMaCfWHCxiBflLm04Tg==",
|
||||||
|
"requires": {
|
||||||
|
"color-name": "1.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"color-name": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.1.tgz",
|
||||||
|
"integrity": "sha1-SxQVMEz1ACjqgWQ2Q72C6gWANok="
|
||||||
|
},
|
||||||
|
"combined-stream": {
|
||||||
|
"version": "1.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz",
|
||||||
|
"integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=",
|
||||||
|
"requires": {
|
||||||
|
"delayed-stream": "~1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"core-util-is": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
|
||||||
|
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
|
||||||
|
},
|
||||||
|
"dashdash": {
|
||||||
|
"version": "1.14.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
|
||||||
|
"integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=",
|
||||||
|
"requires": {
|
||||||
|
"assert-plus": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"delayed-stream": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||||
|
"integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
|
||||||
|
},
|
||||||
|
"diff": {
|
||||||
|
"version": "3.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz",
|
||||||
|
"integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA=="
|
||||||
|
},
|
||||||
|
"ecc-jsbn": {
|
||||||
|
"version": "0.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
|
||||||
|
"integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=",
|
||||||
|
"optional": true,
|
||||||
|
"requires": {
|
||||||
|
"jsbn": "~0.1.0",
|
||||||
|
"safer-buffer": "^2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"escape-string-regexp": {
|
||||||
|
"version": "1.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
||||||
|
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
|
||||||
|
},
|
||||||
|
"extend": {
|
||||||
|
"version": "3.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
|
||||||
|
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
|
||||||
|
},
|
||||||
|
"extsprintf": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
|
||||||
|
"integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU="
|
||||||
|
},
|
||||||
|
"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="
|
||||||
|
},
|
||||||
|
"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="
|
||||||
|
},
|
||||||
|
"forever-agent": {
|
||||||
|
"version": "0.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
|
||||||
|
"integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE="
|
||||||
|
},
|
||||||
|
"form-data": {
|
||||||
|
"version": "2.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz",
|
||||||
|
"integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=",
|
||||||
|
"requires": {
|
||||||
|
"asynckit": "^0.4.0",
|
||||||
|
"combined-stream": "1.0.6",
|
||||||
|
"mime-types": "^2.1.12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"getpass": {
|
||||||
|
"version": "0.1.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
|
||||||
|
"integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=",
|
||||||
|
"requires": {
|
||||||
|
"assert-plus": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"har-schema": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
|
||||||
|
"integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI="
|
||||||
|
},
|
||||||
|
"har-validator": {
|
||||||
|
"version": "5.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz",
|
||||||
|
"integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=",
|
||||||
|
"requires": {
|
||||||
|
"ajv": "^5.1.0",
|
||||||
|
"har-schema": "^2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"has-flag": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
|
||||||
|
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0="
|
||||||
|
},
|
||||||
|
"http-signature": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
|
||||||
|
"integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=",
|
||||||
|
"requires": {
|
||||||
|
"assert-plus": "^1.0.0",
|
||||||
|
"jsprim": "^1.2.2",
|
||||||
|
"sshpk": "^1.7.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"is-typedarray": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
|
||||||
|
"integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo="
|
||||||
|
},
|
||||||
|
"isstream": {
|
||||||
|
"version": "0.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
|
||||||
|
"integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo="
|
||||||
|
},
|
||||||
|
"jsbn": {
|
||||||
|
"version": "0.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
|
||||||
|
"integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=",
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"json-schema": {
|
||||||
|
"version": "0.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz",
|
||||||
|
"integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM="
|
||||||
|
},
|
||||||
|
"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="
|
||||||
|
},
|
||||||
|
"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="
|
||||||
|
},
|
||||||
|
"jsprim": {
|
||||||
|
"version": "1.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz",
|
||||||
|
"integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=",
|
||||||
|
"requires": {
|
||||||
|
"assert-plus": "1.0.0",
|
||||||
|
"extsprintf": "1.3.0",
|
||||||
|
"json-schema": "0.2.3",
|
||||||
|
"verror": "1.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lodash": {
|
||||||
|
"version": "4.17.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz",
|
||||||
|
"integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg=="
|
||||||
|
},
|
||||||
|
"make-error": {
|
||||||
|
"version": "1.3.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.4.tgz",
|
||||||
|
"integrity": "sha512-0Dab5btKVPhibSalc9QGXb559ED7G7iLjFXBaj9Wq8O3vorueR5K5jaE3hkG6ZQINyhA/JgG6Qk4qdFQjsYV6g=="
|
||||||
|
},
|
||||||
|
"mime-db": {
|
||||||
|
"version": "1.35.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.35.0.tgz",
|
||||||
|
"integrity": "sha512-JWT/IcCTsB0Io3AhWUMjRqucrHSPsSf2xKLaRldJVULioggvkJvggZ3VXNNSRkCddE6D+BUI4HEIZIA2OjwIvg=="
|
||||||
|
},
|
||||||
|
"mime-types": {
|
||||||
|
"version": "2.1.19",
|
||||||
|
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.19.tgz",
|
||||||
|
"integrity": "sha512-P1tKYHVSZ6uFo26mtnve4HQFE3koh1UWVkp8YUC+ESBHe945xWSoXuHHiGarDqcEZ+whpCDnlNw5LON0kLo+sw==",
|
||||||
|
"requires": {
|
||||||
|
"mime-db": "~1.35.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"minimist": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
|
||||||
|
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
|
||||||
|
},
|
||||||
|
"misskey-reversi": {
|
||||||
|
"version": "0.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/misskey-reversi/-/misskey-reversi-0.0.5.tgz",
|
||||||
|
"integrity": "sha512-lALC8WxwQYU7wBUjbmcGc7b9AfNyzBqdoa8Q6sZfS8cfC+zEbi/Ln8m/fZKuM0qhnylBqTQiPQHHhBsSJxz/OQ=="
|
||||||
|
},
|
||||||
|
"mkdirp": {
|
||||||
|
"version": "0.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
|
||||||
|
"integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
|
||||||
|
"requires": {
|
||||||
|
"minimist": "0.0.8"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"minimist": {
|
||||||
|
"version": "0.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
|
||||||
|
"integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"oauth-sign": {
|
||||||
|
"version": "0.8.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz",
|
||||||
|
"integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM="
|
||||||
|
},
|
||||||
|
"performance-now": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
|
||||||
|
"integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns="
|
||||||
|
},
|
||||||
|
"psl": {
|
||||||
|
"version": "1.1.29",
|
||||||
|
"resolved": "https://registry.npmjs.org/psl/-/psl-1.1.29.tgz",
|
||||||
|
"integrity": "sha512-AeUmQ0oLN02flVHXWh9sSJF7mcdFq0ppid/JkErufc3hGIV/AMa8Fo9VgDo/cT2jFdOWoFvHp90qqBH54W+gjQ=="
|
||||||
|
},
|
||||||
|
"punycode": {
|
||||||
|
"version": "1.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
|
||||||
|
"integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4="
|
||||||
|
},
|
||||||
|
"qs": {
|
||||||
|
"version": "6.5.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
|
||||||
|
"integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA=="
|
||||||
|
},
|
||||||
|
"request": {
|
||||||
|
"version": "2.87.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/request/-/request-2.87.0.tgz",
|
||||||
|
"integrity": "sha512-fcogkm7Az5bsS6Sl0sibkbhcKsnyon/jV1kF3ajGmF0c8HrttdKTPRT9hieOaQHA5HEq6r8OyWOo/o781C1tNw==",
|
||||||
|
"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"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"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==",
|
||||||
|
"requires": {
|
||||||
|
"punycode": "^1.4.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"request-promise-core": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.1.tgz",
|
||||||
|
"integrity": "sha1-Pu4AssWqgyOc+wTFcA2jb4HNCLY=",
|
||||||
|
"requires": {
|
||||||
|
"lodash": "^4.13.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"request-promise-native": {
|
||||||
|
"version": "1.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.5.tgz",
|
||||||
|
"integrity": "sha1-UoF3D2jgyXGeUWP9P6tIIhX0/aU=",
|
||||||
|
"requires": {
|
||||||
|
"request-promise-core": "1.1.1",
|
||||||
|
"stealthy-require": "^1.1.0",
|
||||||
|
"tough-cookie": ">=2.3.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"safe-buffer": {
|
||||||
|
"version": "5.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||||
|
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
|
||||||
|
},
|
||||||
|
"safer-buffer": {
|
||||||
|
"version": "2.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||||
|
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
||||||
|
},
|
||||||
|
"source-map": {
|
||||||
|
"version": "0.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||||
|
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
|
||||||
|
},
|
||||||
|
"source-map-support": {
|
||||||
|
"version": "0.5.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.6.tgz",
|
||||||
|
"integrity": "sha512-N4KXEz7jcKqPf2b2vZF11lQIz9W5ZMuUcIOGj243lduidkf2fjkVKJS9vNxVWn3u/uxX38AcE8U9nnH9FPcq+g==",
|
||||||
|
"requires": {
|
||||||
|
"buffer-from": "^1.0.0",
|
||||||
|
"source-map": "^0.6.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"sshpk": {
|
||||||
|
"version": "1.14.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.2.tgz",
|
||||||
|
"integrity": "sha1-xvxhZIo9nE52T9P8306hBeSSupg=",
|
||||||
|
"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",
|
||||||
|
"safer-buffer": "^2.0.2",
|
||||||
|
"tweetnacl": "~0.14.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"stealthy-require": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz",
|
||||||
|
"integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks="
|
||||||
|
},
|
||||||
|
"supports-color": {
|
||||||
|
"version": "5.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz",
|
||||||
|
"integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==",
|
||||||
|
"requires": {
|
||||||
|
"has-flag": "^3.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tough-cookie": {
|
||||||
|
"version": "2.4.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz",
|
||||||
|
"integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==",
|
||||||
|
"requires": {
|
||||||
|
"psl": "^1.1.24",
|
||||||
|
"punycode": "^1.4.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ts-node": {
|
||||||
|
"version": "6.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-6.0.3.tgz",
|
||||||
|
"integrity": "sha512-ARaOMNFEPKg2ZuC1qJddFvHxHNFVckR0g9xLxMIoMqSSIkDc8iS4/LoV53EdDWWNq2FGwqcEf0bVVGJIWpsznw==",
|
||||||
|
"requires": {
|
||||||
|
"arrify": "^1.0.0",
|
||||||
|
"chalk": "^2.3.0",
|
||||||
|
"diff": "^3.1.0",
|
||||||
|
"make-error": "^1.1.1",
|
||||||
|
"minimist": "^1.2.0",
|
||||||
|
"mkdirp": "^0.5.1",
|
||||||
|
"source-map-support": "^0.5.3",
|
||||||
|
"yn": "^2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tunnel-agent": {
|
||||||
|
"version": "0.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
|
||||||
|
"integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=",
|
||||||
|
"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=",
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"typescript": {
|
||||||
|
"version": "2.8.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-2.8.3.tgz",
|
||||||
|
"integrity": "sha512-K7g15Bb6Ra4lKf7Iq2l/I5/En+hLIHmxWZGq3D4DIRNFxMNV6j2SHSvDOqs2tGd4UvD/fJvrwopzQXjLrT7Itw=="
|
||||||
|
},
|
||||||
|
"uuid": {
|
||||||
|
"version": "3.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz",
|
||||||
|
"integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA=="
|
||||||
|
},
|
||||||
|
"verror": {
|
||||||
|
"version": "1.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
|
||||||
|
"integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=",
|
||||||
|
"requires": {
|
||||||
|
"assert-plus": "^1.0.0",
|
||||||
|
"core-util-is": "1.0.2",
|
||||||
|
"extsprintf": "^1.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ws": {
|
||||||
|
"version": "6.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ws/-/ws-6.0.0.tgz",
|
||||||
|
"integrity": "sha512-c2UlYcAZp1VS8AORtpq6y4RJIkJ9dQz18W32SpR/qXGfLDZ2jU4y4wKvvZwqbi7U6gxFQTeE+urMbXU/tsDy4w==",
|
||||||
|
"requires": {
|
||||||
|
"async-limiter": "~1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"yn": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/yn/-/yn-2.0.0.tgz",
|
||||||
|
"integrity": "sha1-5a2ryKz0CPY4X8dklWhMiOavaJo="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
17
package.json
Normal file
17
package.json
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"name": "ai",
|
||||||
|
"main": "./built/front.js",
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "10.0.5",
|
||||||
|
"@types/ws": "5.1.2",
|
||||||
|
"misskey-reversi": "0.0.5",
|
||||||
|
"request": "2.87.0",
|
||||||
|
"request-promise-native": "1.0.5",
|
||||||
|
"ts-node": "6.0.3",
|
||||||
|
"typescript": "2.8.3",
|
||||||
|
"ws": "6.0.0"
|
||||||
|
}
|
||||||
|
}
|
372
src/back.ts
Normal file
372
src/back.ts
Normal file
|
@ -0,0 +1,372 @@
|
||||||
|
/**
|
||||||
|
* -AI-
|
||||||
|
* Botのバックエンド(思考を担当)
|
||||||
|
*
|
||||||
|
* 対話と思考を同じプロセスで行うと、思考時間が長引いたときにストリームから
|
||||||
|
* 切断されてしまうので、別々のプロセスで行うようにします
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as request from 'request-promise-native';
|
||||||
|
import Reversi, { Color } from 'misskey-reversi';
|
||||||
|
|
||||||
|
let game;
|
||||||
|
let form;
|
||||||
|
|
||||||
|
const config = require('../config.json');
|
||||||
|
|
||||||
|
let note;
|
||||||
|
|
||||||
|
function getUserName(user) {
|
||||||
|
return user.name || user.username;
|
||||||
|
}
|
||||||
|
|
||||||
|
process.on('message', async msg => {
|
||||||
|
// 親プロセスからデータをもらう
|
||||||
|
if (msg.type == '_init_') {
|
||||||
|
game = msg.game;
|
||||||
|
form = msg.form;
|
||||||
|
}
|
||||||
|
|
||||||
|
// フォームが更新されたとき
|
||||||
|
if (msg.type == 'update-form') {
|
||||||
|
form.find(i => i.id == msg.body.id).value = msg.body.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ゲームが始まったとき
|
||||||
|
if (msg.type == 'started') {
|
||||||
|
onGameStarted(msg.body);
|
||||||
|
|
||||||
|
//#region TLに投稿する
|
||||||
|
const game = msg.body;
|
||||||
|
const url = `${config.host}/reversi/${game.id}`;
|
||||||
|
const user = game.user1Id == config.id ? game.user2 : game.user1;
|
||||||
|
const isSettai = form[0].value === 0;
|
||||||
|
const text = isSettai
|
||||||
|
? `?[${getUserName(user)}](${config.host}/@${user.username})さんの接待を始めました!`
|
||||||
|
: `対局を?[${getUserName(user)}](${config.host}/@${user.username})さんと始めました! (強さ${form[0].value})`;
|
||||||
|
|
||||||
|
const res = await request.post(`${config.host}/api/notes/create`, {
|
||||||
|
json: {
|
||||||
|
i: config.i,
|
||||||
|
text: `${text}\n→[観戦する](${url})`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
note = res.createdNote;
|
||||||
|
//#endregion
|
||||||
|
}
|
||||||
|
|
||||||
|
// ゲームが終了したとき
|
||||||
|
if (msg.type == 'ended') {
|
||||||
|
// ストリームから切断
|
||||||
|
process.send({
|
||||||
|
type: 'close'
|
||||||
|
});
|
||||||
|
|
||||||
|
//#region TLに投稿する
|
||||||
|
const user = game.user1Id == config.id ? game.user2 : game.user1;
|
||||||
|
const isSettai = form[0].value === 0;
|
||||||
|
const text = isSettai
|
||||||
|
? msg.body.winnerId === null
|
||||||
|
? `?[${getUserName(user)}](${config.host}/@${user.username})さんに接待で引き分けました...`
|
||||||
|
: msg.body.winnerId == config.id
|
||||||
|
? `?[${getUserName(user)}](${config.host}/@${user.username})さんに接待で勝ってしまいました...`
|
||||||
|
: `?[${getUserName(user)}](${config.host}/@${user.username})さんに接待で負けてあげました♪`
|
||||||
|
: msg.body.winnerId === null
|
||||||
|
? `?[${getUserName(user)}](${config.host}/@${user.username})さんと引き分けました~`
|
||||||
|
: msg.body.winnerId == config.id
|
||||||
|
? `?[${getUserName(user)}](${config.host}/@${user.username})さんに勝ちました♪`
|
||||||
|
: `?[${getUserName(user)}](${config.host}/@${user.username})さんに負けました...`;
|
||||||
|
|
||||||
|
await request.post(`${config.host}/api/notes/create`, {
|
||||||
|
json: {
|
||||||
|
i: config.i,
|
||||||
|
renoteId: note.id,
|
||||||
|
text: text
|
||||||
|
}
|
||||||
|
});
|
||||||
|
//#endregion
|
||||||
|
process.exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 打たれたとき
|
||||||
|
if (msg.type == 'set') {
|
||||||
|
onSet(msg.body);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let o: Reversi;
|
||||||
|
let botColor: Color;
|
||||||
|
|
||||||
|
// 各マスの強さ
|
||||||
|
let cellWeights;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ゲーム開始時
|
||||||
|
* @param g ゲーム情報
|
||||||
|
*/
|
||||||
|
function onGameStarted(g) {
|
||||||
|
game = g;
|
||||||
|
|
||||||
|
// リバーシエンジン初期化
|
||||||
|
o = new Reversi(game.settings.map, {
|
||||||
|
isLlotheo: game.settings.isLlotheo,
|
||||||
|
canPutEverywhere: game.settings.canPutEverywhere,
|
||||||
|
loopedBoard: game.settings.loopedBoard
|
||||||
|
});
|
||||||
|
|
||||||
|
// 各マスの価値を計算しておく
|
||||||
|
cellWeights = o.map.map((pix, i) => {
|
||||||
|
if (pix == 'null') return 0;
|
||||||
|
const [x, y] = o.transformPosToXy(i);
|
||||||
|
let count = 0;
|
||||||
|
const get = (x, y) => {
|
||||||
|
if (x < 0 || y < 0 || x >= o.mapWidth || y >= o.mapHeight) return 'null';
|
||||||
|
return o.mapDataGet(o.transformXyToPos(x, y));
|
||||||
|
};
|
||||||
|
|
||||||
|
if (get(x , y - 1) == 'null') count++;
|
||||||
|
if (get(x + 1, y - 1) == 'null') count++;
|
||||||
|
if (get(x + 1, y ) == 'null') count++;
|
||||||
|
if (get(x + 1, y + 1) == 'null') count++;
|
||||||
|
if (get(x , y + 1) == 'null') count++;
|
||||||
|
if (get(x - 1, y + 1) == 'null') count++;
|
||||||
|
if (get(x - 1, y ) == 'null') count++;
|
||||||
|
if (get(x - 1, y - 1) == 'null') count++;
|
||||||
|
//return Math.pow(count, 3);
|
||||||
|
return count >= 4 ? 1 : 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
botColor = game.user1Id == config.id && game.black == 1 || game.user2Id == config.id && game.black == 2;
|
||||||
|
|
||||||
|
if (botColor) {
|
||||||
|
think();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onSet(x) {
|
||||||
|
o.put(x.color, x.pos);
|
||||||
|
|
||||||
|
if (x.next === botColor) {
|
||||||
|
think();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const db = {};
|
||||||
|
|
||||||
|
function think() {
|
||||||
|
console.log('Thinking...');
|
||||||
|
console.time('think');
|
||||||
|
|
||||||
|
const isSettai = form[0].value === 0;
|
||||||
|
|
||||||
|
// 接待モードのときは、全力(5手先読みくらい)で負けるようにする
|
||||||
|
const maxDepth = isSettai ? 5 : form[0].value;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Botにとってある局面がどれだけ有利か取得する
|
||||||
|
*/
|
||||||
|
function staticEval() {
|
||||||
|
let score = o.canPutSomewhere(botColor).length;
|
||||||
|
|
||||||
|
cellWeights.forEach((weight, i) => {
|
||||||
|
// 係数
|
||||||
|
const coefficient = 30;
|
||||||
|
weight = weight * coefficient;
|
||||||
|
|
||||||
|
const stone = o.board[i];
|
||||||
|
if (stone === botColor) {
|
||||||
|
// TODO: 価値のあるマスに設置されている自分の石に縦か横に接するマスは価値があると判断する
|
||||||
|
score += weight;
|
||||||
|
} else if (stone !== null) {
|
||||||
|
score -= weight;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// ロセオならスコアを反転
|
||||||
|
if (game.settings.isLlotheo) score = -score;
|
||||||
|
|
||||||
|
// 接待ならスコアを反転
|
||||||
|
if (isSettai) score = -score;
|
||||||
|
|
||||||
|
return score;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* αβ法での探索
|
||||||
|
*/
|
||||||
|
const dive = (pos: number, alpha = -Infinity, beta = Infinity, depth = 0): number => {
|
||||||
|
// 試し打ち
|
||||||
|
o.put(o.turn, pos);
|
||||||
|
|
||||||
|
const key = o.board.toString();
|
||||||
|
let cache = db[key];
|
||||||
|
if (cache) {
|
||||||
|
if (alpha >= cache.upper) {
|
||||||
|
o.undo();
|
||||||
|
return cache.upper;
|
||||||
|
}
|
||||||
|
if (beta <= cache.lower) {
|
||||||
|
o.undo();
|
||||||
|
return cache.lower;
|
||||||
|
}
|
||||||
|
alpha = Math.max(alpha, cache.lower);
|
||||||
|
beta = Math.min(beta, cache.upper);
|
||||||
|
} else {
|
||||||
|
cache = {
|
||||||
|
upper: Infinity,
|
||||||
|
lower: -Infinity
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const isBotTurn = o.turn === botColor;
|
||||||
|
|
||||||
|
// 勝った
|
||||||
|
if (o.turn === null) {
|
||||||
|
const winner = o.winner;
|
||||||
|
|
||||||
|
// 勝つことによる基本スコア
|
||||||
|
const base = 10000;
|
||||||
|
|
||||||
|
let score;
|
||||||
|
|
||||||
|
if (game.settings.isLlotheo) {
|
||||||
|
// 勝ちは勝ちでも、より自分の石を少なくした方が美しい勝ちだと判定する
|
||||||
|
score = o.winner ? base - (o.blackCount * 100) : base - (o.whiteCount * 100);
|
||||||
|
} else {
|
||||||
|
// 勝ちは勝ちでも、より相手の石を少なくした方が美しい勝ちだと判定する
|
||||||
|
score = o.winner ? base + (o.blackCount * 100) : base + (o.whiteCount * 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 巻き戻し
|
||||||
|
o.undo();
|
||||||
|
|
||||||
|
// 接待なら自分が負けた方が高スコア
|
||||||
|
return isSettai
|
||||||
|
? winner !== botColor ? score : -score
|
||||||
|
: winner === botColor ? score : -score;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (depth === maxDepth) {
|
||||||
|
// 静的に評価
|
||||||
|
const score = staticEval();
|
||||||
|
|
||||||
|
// 巻き戻し
|
||||||
|
o.undo();
|
||||||
|
|
||||||
|
return score;
|
||||||
|
} else {
|
||||||
|
const cans = o.canPutSomewhere(o.turn);
|
||||||
|
|
||||||
|
let value = isBotTurn ? -Infinity : Infinity;
|
||||||
|
let a = alpha;
|
||||||
|
let b = beta;
|
||||||
|
|
||||||
|
// 次のターンのプレイヤーにとって最も良い手を取得
|
||||||
|
for (const p of cans) {
|
||||||
|
if (isBotTurn) {
|
||||||
|
const score = dive(p, a, beta, depth + 1);
|
||||||
|
value = Math.max(value, score);
|
||||||
|
a = Math.max(a, value);
|
||||||
|
if (value >= beta) break;
|
||||||
|
} else {
|
||||||
|
const score = dive(p, alpha, b, depth + 1);
|
||||||
|
value = Math.min(value, score);
|
||||||
|
b = Math.min(b, value);
|
||||||
|
if (value <= alpha) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 巻き戻し
|
||||||
|
o.undo();
|
||||||
|
|
||||||
|
if (value <= alpha) {
|
||||||
|
cache.upper = value;
|
||||||
|
} else if (value >= beta) {
|
||||||
|
cache.lower = value;
|
||||||
|
} else {
|
||||||
|
cache.upper = value;
|
||||||
|
cache.lower = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
db[key] = cache;
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* αβ法での探索(キャッシュ無し)(デバッグ用)
|
||||||
|
*/
|
||||||
|
const dive2 = (pos: number, alpha = -Infinity, beta = Infinity, depth = 0): number => {
|
||||||
|
// 試し打ち
|
||||||
|
o.put(o.turn, pos);
|
||||||
|
|
||||||
|
const isBotTurn = o.turn === botColor;
|
||||||
|
|
||||||
|
// 勝った
|
||||||
|
if (o.turn === null) {
|
||||||
|
const winner = o.winner;
|
||||||
|
|
||||||
|
// 勝つことによる基本スコア
|
||||||
|
const base = 10000;
|
||||||
|
|
||||||
|
let score;
|
||||||
|
|
||||||
|
if (game.settings.isLlotheo) {
|
||||||
|
// 勝ちは勝ちでも、より自分の石を少なくした方が美しい勝ちだと判定する
|
||||||
|
score = o.winner ? base - (o.blackCount * 100) : base - (o.whiteCount * 100);
|
||||||
|
} else {
|
||||||
|
// 勝ちは勝ちでも、より相手の石を少なくした方が美しい勝ちだと判定する
|
||||||
|
score = o.winner ? base + (o.blackCount * 100) : base + (o.whiteCount * 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 巻き戻し
|
||||||
|
o.undo();
|
||||||
|
|
||||||
|
// 接待なら自分が負けた方が高スコア
|
||||||
|
return isSettai
|
||||||
|
? winner !== botColor ? score : -score
|
||||||
|
: winner === botColor ? score : -score;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (depth === maxDepth) {
|
||||||
|
// 静的に評価
|
||||||
|
const score = staticEval();
|
||||||
|
|
||||||
|
// 巻き戻し
|
||||||
|
o.undo();
|
||||||
|
|
||||||
|
return score;
|
||||||
|
} else {
|
||||||
|
const cans = o.canPutSomewhere(o.turn);
|
||||||
|
|
||||||
|
// 次のターンのプレイヤーにとって最も良い手を取得
|
||||||
|
for (const p of cans) {
|
||||||
|
if (isBotTurn) {
|
||||||
|
alpha = Math.max(alpha, dive2(p, alpha, beta, depth + 1));
|
||||||
|
} else {
|
||||||
|
beta = Math.min(beta, dive2(p, alpha, beta, depth + 1));
|
||||||
|
}
|
||||||
|
if (alpha >= beta) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 巻き戻し
|
||||||
|
o.undo();
|
||||||
|
|
||||||
|
return isBotTurn ? alpha : beta;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const cans = o.canPutSomewhere(botColor);
|
||||||
|
const scores = cans.map(p => dive(p));
|
||||||
|
const pos = cans[scores.indexOf(Math.max(...scores))];
|
||||||
|
|
||||||
|
console.log('Thinked:', pos);
|
||||||
|
console.timeEnd('think');
|
||||||
|
|
||||||
|
process.send({
|
||||||
|
type: 'put',
|
||||||
|
pos
|
||||||
|
});
|
||||||
|
}
|
220
src/front.ts
Normal file
220
src/front.ts
Normal file
|
@ -0,0 +1,220 @@
|
||||||
|
/**
|
||||||
|
* -AI-
|
||||||
|
* Botのフロントエンド(ストリームとの対話を担当)
|
||||||
|
*
|
||||||
|
* 対話と思考を同じプロセスで行うと、思考時間が長引いたときにストリームから
|
||||||
|
* 切断されてしまうので、別々のプロセスで行うようにします
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as childProcess from 'child_process';
|
||||||
|
import * as WebSocket from 'ws';
|
||||||
|
import * as request from 'request-promise-native';
|
||||||
|
|
||||||
|
const config = require('../config.json');
|
||||||
|
|
||||||
|
const wsUrl = config.host.replace('http', 'ws');
|
||||||
|
const apiUrl = config.host + '/api';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ホームストリーム
|
||||||
|
*/
|
||||||
|
const homeStream = new WebSocket(`${wsUrl}/?i=${config.i}`);
|
||||||
|
|
||||||
|
homeStream.addEventListener('open', () => {
|
||||||
|
console.log('home stream opened');
|
||||||
|
});
|
||||||
|
|
||||||
|
homeStream.addEventListener('close', () => {
|
||||||
|
console.log('home stream closed');
|
||||||
|
});
|
||||||
|
|
||||||
|
homeStream.addEventListener('message', message => {
|
||||||
|
const msg = JSON.parse(message.data);
|
||||||
|
|
||||||
|
// タイムライン上でなんか言われたまたは返信されたとき
|
||||||
|
if (msg.type == 'mention' || msg.type == 'reply') {
|
||||||
|
const note = msg.body;
|
||||||
|
|
||||||
|
if (note.userId == config.id) return;
|
||||||
|
|
||||||
|
// リアクションする
|
||||||
|
request.post(`${apiUrl}/notes/reactions/create`, {
|
||||||
|
json: {
|
||||||
|
i: config.i,
|
||||||
|
noteId: note.id,
|
||||||
|
reaction: 'love'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (note.text) {
|
||||||
|
if (note.text.indexOf('リバーシ') > -1) {
|
||||||
|
request.post(`${apiUrl}/notes/create`, {
|
||||||
|
json: {
|
||||||
|
i: config.i,
|
||||||
|
replyId: note.id,
|
||||||
|
text: '良いですよ~'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
invite(note.userId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// メッセージでなんか言われたとき
|
||||||
|
if (msg.type == 'messaging_message') {
|
||||||
|
const message = msg.body;
|
||||||
|
if (message.text) {
|
||||||
|
if (message.text.indexOf('リバーシ') > -1) {
|
||||||
|
request.post(`${apiUrl}/messaging/messages/create`, {
|
||||||
|
json: {
|
||||||
|
i: config.i,
|
||||||
|
userId: message.userId,
|
||||||
|
text: '良いですよ~'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
invite(message.userId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// ユーザーを対局に誘う
|
||||||
|
function invite(userId) {
|
||||||
|
request.post(`${apiUrl}/games/reversi/match`, {
|
||||||
|
json: {
|
||||||
|
i: config.i,
|
||||||
|
userId: userId
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* リバーシストリーム
|
||||||
|
*/
|
||||||
|
const reversiStream = new WebSocket(`${wsUrl}/games/reversi?i=${config.i}`);
|
||||||
|
|
||||||
|
reversiStream.addEventListener('open', () => {
|
||||||
|
console.log('reversi stream opened');
|
||||||
|
});
|
||||||
|
|
||||||
|
reversiStream.addEventListener('close', () => {
|
||||||
|
console.log('reversi stream closed');
|
||||||
|
});
|
||||||
|
|
||||||
|
reversiStream.addEventListener('message', message => {
|
||||||
|
const msg = JSON.parse(message.data);
|
||||||
|
|
||||||
|
// 招待されたとき
|
||||||
|
if (msg.type == 'invited') {
|
||||||
|
onInviteMe(msg.body.parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
// マッチしたとき
|
||||||
|
if (msg.type == 'matched') {
|
||||||
|
gameStart(msg.body);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ゲーム開始
|
||||||
|
* @param game ゲーム情報
|
||||||
|
*/
|
||||||
|
function gameStart(game) {
|
||||||
|
// ゲームストリームに接続
|
||||||
|
const gw = new WebSocket(`${wsUrl}/games/reversi-game?i=${config.i}&game=${game.id}`);
|
||||||
|
|
||||||
|
gw.addEventListener('open', () => {
|
||||||
|
console.log('reversi game stream opened');
|
||||||
|
|
||||||
|
// フォーム
|
||||||
|
const form = [{
|
||||||
|
id: 'strength',
|
||||||
|
type: 'radio',
|
||||||
|
label: '強さ',
|
||||||
|
value: 2,
|
||||||
|
items: [{
|
||||||
|
label: '接待',
|
||||||
|
value: 0
|
||||||
|
}, {
|
||||||
|
label: '弱',
|
||||||
|
value: 1
|
||||||
|
}, {
|
||||||
|
label: '中',
|
||||||
|
value: 2
|
||||||
|
}, {
|
||||||
|
label: '強',
|
||||||
|
value: 3
|
||||||
|
}, {
|
||||||
|
label: '最強',
|
||||||
|
value: 5
|
||||||
|
}]
|
||||||
|
}];
|
||||||
|
|
||||||
|
//#region バックエンドプロセス開始
|
||||||
|
const ai = childProcess.fork(__dirname + '/back.js');
|
||||||
|
|
||||||
|
// バックエンドプロセスに情報を渡す
|
||||||
|
ai.send({
|
||||||
|
type: '_init_',
|
||||||
|
game,
|
||||||
|
form
|
||||||
|
});
|
||||||
|
|
||||||
|
ai.on('message', msg => {
|
||||||
|
if (msg.type == 'put') {
|
||||||
|
gw.send(JSON.stringify({
|
||||||
|
type: 'set',
|
||||||
|
pos: msg.pos
|
||||||
|
}));
|
||||||
|
} else if (msg.type == 'close') {
|
||||||
|
gw.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// ゲームストリームから情報が流れてきたらそのままバックエンドプロセスに伝える
|
||||||
|
gw.addEventListener('message', message => {
|
||||||
|
const msg = JSON.parse(message.data);
|
||||||
|
ai.send(msg);
|
||||||
|
});
|
||||||
|
//#endregion
|
||||||
|
|
||||||
|
// フォーム初期化
|
||||||
|
setTimeout(() => {
|
||||||
|
gw.send(JSON.stringify({
|
||||||
|
type: 'init-form',
|
||||||
|
body: form
|
||||||
|
}));
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
|
// どんな設定内容の対局でも受け入れる
|
||||||
|
setTimeout(() => {
|
||||||
|
gw.send(JSON.stringify({
|
||||||
|
type: 'accept'
|
||||||
|
}));
|
||||||
|
}, 2000);
|
||||||
|
});
|
||||||
|
|
||||||
|
gw.addEventListener('close', () => {
|
||||||
|
console.log('reversi game stream closed');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* リバーシの対局に招待されたとき
|
||||||
|
* @param inviter 誘ってきたユーザー
|
||||||
|
*/
|
||||||
|
async function onInviteMe(inviter) {
|
||||||
|
console.log(`Someone invited me: @${inviter.username}`);
|
||||||
|
|
||||||
|
// 承認
|
||||||
|
const game = await request.post(`${apiUrl}/games/reversi/match`, {
|
||||||
|
json: {
|
||||||
|
i: config.i,
|
||||||
|
userId: inviter.id
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
gameStart(game);
|
||||||
|
}
|
20
tsconfig.json
Normal file
20
tsconfig.json
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"noEmitOnError": true,
|
||||||
|
"noImplicitAny": false,
|
||||||
|
"noImplicitReturns": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"sourceMap": false,
|
||||||
|
"target": "es2017",
|
||||||
|
"module": "commonjs",
|
||||||
|
"removeComments": false,
|
||||||
|
"noLib": false,
|
||||||
|
"outDir": "built",
|
||||||
|
"rootDir": "src"
|
||||||
|
},
|
||||||
|
"compileOnSave": false,
|
||||||
|
"include": [
|
||||||
|
"./src/**/*.ts"
|
||||||
|
]
|
||||||
|
}
|
Loading…
Reference in a new issue