From 7c4ac882ef8fbf83191cb112d74eeafe3f5222de Mon Sep 17 00:00:00 2001 From: syuilo Date: Fri, 10 May 2019 11:55:07 +0900 Subject: [PATCH] maze --- package-lock.json | 173 +++++++++++ package.json | 6 + src/ai.ts | 22 ++ src/index.ts | 2 + src/module.ts | 23 ++ src/modules/fortune/vocabulary.ts | 2 +- src/modules/maze/index.ts | 472 ++++++++++++++++++++++++++++++ src/serifs.ts | 4 + 8 files changed, 703 insertions(+), 1 deletion(-) create mode 100644 src/modules/maze/index.ts diff --git a/package-lock.json b/package-lock.json index 4dabe2c..02b1beb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3,6 +3,11 @@ "requires": true, "lockfileVersion": 1, "dependencies": { + "@types/caseless": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.2.tgz", + "integrity": "sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w==" + }, "@types/chalk": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/@types/chalk/-/chalk-2.2.0.tgz", @@ -16,6 +21,14 @@ "resolved": "https://registry.npmjs.org/@types/events/-/events-1.2.0.tgz", "integrity": "sha512-KEIlhXnIutzKwRbQkGWb/I4HFqBuUykAdHgDED6xqwXJfONCjF5VoE0cXEiurh3XauygxzeDzgtXUqvLkxFzzA==" }, + "@types/form-data": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@types/form-data/-/form-data-2.2.1.tgz", + "integrity": "sha512-JAMFhOaHIciYVh8fb5/83nmuO/AHwmto+Hq7a9y8FzLDcC1KCU344XDOMEmahnrTFlHjgh4L0WJFczNIX2GxnQ==", + "requires": { + "@types/node": "*" + } + }, "@types/lokijs": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/@types/lokijs/-/lokijs-1.5.2.tgz", @@ -34,6 +47,30 @@ "@types/retry": "*" } }, + "@types/random-seed": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@types/random-seed/-/random-seed-0.3.3.tgz", + "integrity": "sha512-kHsCbIRHNXJo6EN5W8EA5b4i1hdT6jaZke5crBPLUcLqaLdZ0QBq8QVMbafHzhjFF83Cl9qlee2dChD18d/kPg==" + }, + "@types/request": { + "version": "2.48.1", + "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.1.tgz", + "integrity": "sha512-ZgEZ1TiD+KGA9LiAAPPJL68Id2UWfeSO62ijSXZjFJArVV+2pKcsVHmrcu+1oiE3q6eDGiFiSolRc4JHoerBBg==", + "requires": { + "@types/caseless": "*", + "@types/form-data": "*", + "@types/node": "*", + "@types/tough-cookie": "*" + } + }, + "@types/request-promise-native": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/@types/request-promise-native/-/request-promise-native-1.0.16.tgz", + "integrity": "sha512-gbLf6cg1XGBU8BObOgs5VkCQo5JFz2GstgZjyE4FRbig/jiCEdiynu2fCzJlw3qYPuoj59spKnvuRLN4PsMvhA==", + "requires": { + "@types/request": "*" + } + }, "@types/retry": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", @@ -44,6 +81,16 @@ "resolved": "https://registry.npmjs.org/@types/seedrandom/-/seedrandom-2.4.28.tgz", "integrity": "sha512-SMA+fUwULwK7sd/ZJicUztiPs8F1yCPwF3O23Z9uQ32ME5Ha0NmDK9+QTsYE4O2tHXChzXomSWWeIhCnoN1LqA==" }, + "@types/tmp": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@types/tmp/-/tmp-0.1.0.tgz", + "integrity": "sha512-6IwZ9HzWbCq6XoQWhxLpDjuADodH/MKXRUIDFudvgjcVdjFknvmR+DNsoUeer4XPrEnrZs04Jj+kfV9pFsrhmA==" + }, + "@types/tough-cookie": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-2.3.5.tgz", + "integrity": "sha512-SCcK7mvGi3+ZNz833RRjFIxrn4gI1PPR3NtuIS+6vMkvmsGjosqTJwRt5bAEFLRz+wtJMWv8+uOnZf2hi2QXTg==" + }, "@types/uuid": { "version": "3.4.4", "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-3.4.4.tgz", @@ -138,6 +185,11 @@ "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + }, "bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", @@ -146,6 +198,15 @@ "tweetnacl": "^0.14.3" } }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "buffer-from": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", @@ -187,6 +248,11 @@ "delayed-stream": "~1.0.0" } }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", @@ -264,6 +330,11 @@ "mime-types": "^2.1.12" } }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, "getpass": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", @@ -272,6 +343,19 @@ "assert-plus": "^1.0.0" } }, + "glob": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, "har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", @@ -301,6 +385,20 @@ "sshpk": "^1.7.0" } }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", @@ -311,6 +409,11 @@ "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" }, + "jpeg-js": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.3.5.tgz", + "integrity": "sha512-hvaExqwmQDS8O9qnZAVDXGWU43Tbu1V0wMZmjROjT11jloSgGICZpscG+P6Nyi1BVAvyu2ARRx8qmEW30sxgdQ==" + }, "jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", @@ -383,6 +486,14 @@ "mime-db": "~1.38.0" } }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, "misskey-reversi": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/misskey-reversi/-/misskey-reversi-0.0.5.tgz", @@ -393,11 +504,34 @@ "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "opentype.js": { + "version": "0.4.11", + "resolved": "https://registry.npmjs.org/opentype.js/-/opentype.js-0.4.11.tgz", + "integrity": "sha1-KBojkGOcwVkxyVXY1jwUp8d3K0E=" + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + }, "performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" }, + "pngjs": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.4.0.tgz", + "integrity": "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==" + }, "promise-retry": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-1.1.1.tgz", @@ -417,11 +551,29 @@ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" }, + "pureimage": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/pureimage/-/pureimage-0.1.6.tgz", + "integrity": "sha512-t74leLaXyD3VGmMbcFZNWZoHqPQNX805gHYNBCrNEVRjUkDfdlopLJnDJI9QYdhWXwtNiD02coq+NB7+CryAwg==", + "requires": { + "jpeg-js": "^0.3.3", + "opentype.js": "^0.4.3", + "pngjs": "^3.3.1" + } + }, "qs": { "version": "6.5.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" }, + "random-seed": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/random-seed/-/random-seed-0.3.0.tgz", + "integrity": "sha1-2UXy4fOPSejViRNDG4v2u5N1Vs0=", + "requires": { + "json-stringify-safe": "^5.0.1" + } + }, "reconnecting-websocket": { "version": "4.1.10", "resolved": "https://registry.npmjs.org/reconnecting-websocket/-/reconnecting-websocket-4.1.10.tgz", @@ -477,6 +629,14 @@ "resolved": "https://registry.npmjs.org/retry/-/retry-0.10.1.tgz", "integrity": "sha1-52OI0heZLCUnUCQdPTlW/tmNj/Q=" }, + "rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "requires": { + "glob": "^7.1.3" + } + }, "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", @@ -551,6 +711,14 @@ "resolved": "https://registry.npmjs.org/timeout-as-promise/-/timeout-as-promise-1.0.0.tgz", "integrity": "sha1-c2foEfyZKs/Nzaq/LlDfr4shV28=" }, + "tmp": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.1.0.tgz", + "integrity": "sha512-J7Z2K08jbGcdA1kkQpJSqLF6T0tdQqpR2pnSUXsIchbPdTI9v3e85cLW0d6WDhwuAleOV71j2xWs8qMPfK7nKw==", + "requires": { + "rimraf": "^2.6.3" + } + }, "tough-cookie": { "version": "2.4.3", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", @@ -620,6 +788,11 @@ "extsprintf": "^1.2.0" } }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, "ws": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz", diff --git a/package.json b/package.json index 4459081..fe81e2f 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,10 @@ "@types/lokijs": "1.5.2", "@types/node": "10.0.5", "@types/promise-retry": "1.1.3", + "@types/random-seed": "0.3.3", + "@types/request-promise-native": "1.0.16", "@types/seedrandom": "2.4.28", + "@types/tmp": "0.1.0", "@types/uuid": "3.4.4", "@types/ws": "6.0.1", "autobind-decorator": "2.4.0", @@ -19,11 +22,14 @@ "mecab-async": "0.1.2", "misskey-reversi": "0.0.5", "promise-retry": "1.1.1", + "pureimage": "0.1.6", + "random-seed": "0.3.0", "reconnecting-websocket": "4.1.10", "request": "2.88.0", "request-promise-native": "1.0.7", "seedrandom": "2.4.3", "timeout-as-promise": "1.0.0", + "tmp": "0.1.0", "ts-node": "8.0.3", "typescript": "2.8.3", "uuid": "3.3.2", diff --git a/src/ai.ts b/src/ai.ts index 5fccd4a..fcfbb38 100644 --- a/src/ai.ts +++ b/src/ai.ts @@ -1,5 +1,6 @@ // AI CORE +import * as fs from 'fs'; import autobind from 'autobind-decorator'; import * as loki from 'lokijs'; import * as request from 'request-promise-native'; @@ -59,6 +60,7 @@ export default class 藍 { }>; public friends: loki.Collection; + public moduleData: loki.Collection; /** * 藍インスタンスを生成します @@ -105,6 +107,10 @@ export default class 藍 { this.friends = this.getCollection('friends', { indices: ['userId'] }); + + this.moduleData = this.getCollection('moduleData', { + indices: ['module'] + }); //#endregion // Init stream @@ -281,6 +287,22 @@ export default class 藍 { return friend; } + /** + * ファイルをドライブにアップロードします + */ + @autobind + public async upload(readStream: fs.ReadStream) { + const res = await request.post({ + url: `${config.apiUrl}/drive/files/create`, + formData: { + i: config.i, + file: readStream + }, + json: true + }); + return res; + } + /** * 投稿します */ diff --git a/src/index.ts b/src/index.ts index 0847dcb..78d75b6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -19,6 +19,7 @@ import DiceModule from './modules/dice'; import ServerModule from './modules/server'; import FollowModule from './modules/follow'; import ValentineModule from './modules/valentine'; +import MazeModule from './modules/maze'; import chalk from 'chalk'; import * as request from 'request-promise-native'; @@ -69,6 +70,7 @@ promiseRetry(retry => { new BirthdayModule(), new ValentineModule(), new KeywordModule(), + new MazeModule(), ]); }).catch(e => { log(chalk.red('Failed to fetch the account')); diff --git a/src/module.ts b/src/module.ts index 5907e6f..c5e3ba7 100644 --- a/src/module.ts +++ b/src/module.ts @@ -5,9 +5,21 @@ export default abstract class Module { public abstract readonly name: string; protected ai: 藍; + private doc: any; public init(ai: 藍) { this.ai = ai; + + this.doc = this.ai.moduleData.findOne({ + module: this.name + }); + + if (this.doc == null) { + this.doc = this.ai.moduleData.insertOne({ + module: this.name, + data: {} + }); + } } public abstract install(): InstallerResult; @@ -48,4 +60,15 @@ export default abstract class Module { public setTimeoutWithPersistence(delay: number, data?: any) { this.ai.setTimeoutWithPersistence(this, delay, data); } + + @autobind + protected getData() { + return this.doc.data; + } + + @autobind + protected setData(data: any) { + this.doc.data = data; + this.ai.moduleData.update(this.doc); + } } diff --git a/src/modules/fortune/vocabulary.ts b/src/modules/fortune/vocabulary.ts index 96f6135..a02dc48 100644 --- a/src/modules/fortune/vocabulary.ts +++ b/src/modules/fortune/vocabulary.ts @@ -35,7 +35,7 @@ export const itemPrefixes = [ 'Microsoft製', 'Apple製', '人類の技術を結集して作った', - '2018年製', + '2018年製', // TODO ランダム '500kgくらいある', '高級', '腐った', diff --git a/src/modules/maze/index.ts b/src/modules/maze/index.ts new file mode 100644 index 0000000..8a39e35 --- /dev/null +++ b/src/modules/maze/index.ts @@ -0,0 +1,472 @@ +/** + * Random avatar generator + */ + +const p = require('pureimage'); +import * as gen from 'random-seed'; +import * as fs from 'fs'; +import autobind from 'autobind-decorator'; +import Module from '../../module'; +import serifs from '../../serifs'; +import * as tmp from 'tmp'; + +type CellType = 'empty' | 'left' | 'right' | 'top' | 'bottom' | 'leftTop' | 'leftBottom' | 'rightTop' | 'rightBottom' | 'leftRightTop' | 'leftRightBottom' | 'leftTopBottom' | 'rightTopBottom' | 'leftRight' | 'topBottom' | 'cross'; + +type Dir = 'left' | 'right' | 'top' | 'bottom'; + +const cellVariants = { + empty: { + digg: { left: 'left', right: 'right', top: 'top', bottom: 'bottom' }, + cross: { left: false, right: false, top: false, bottom: false }, + }, + left: { + digg: { left: null, right: 'leftRight', top: 'leftTop', bottom: 'leftBottom' }, + cross: { left: false, right: false, top: false, bottom: false }, + }, + right: { + digg: { left: 'leftRight', right: null, top: 'rightTop', bottom: 'rightBottom' }, + cross: { left: false, right: false, top: false, bottom: false }, + }, + top: { + digg: { left: 'leftTop', right: 'rightTop', top: null, bottom: 'topBottom' }, + cross: { left: false, right: false, top: false, bottom: false }, + }, + bottom: { + digg: { left: 'leftBottom', right: 'rightBottom', top: 'topBottom', bottom: null }, + cross: { left: false, right: false, top: false, bottom: false }, + }, + leftTop: { + digg: { left: null, right: 'leftRightTop', top: null, bottom: 'leftTopBottom' }, + cross: { left: false, right: false, top: false, bottom: false }, + }, + leftBottom: { + digg: { left: null, right: 'leftRightBottom', top: 'leftTopBottom', bottom: null }, + cross: { left: false, right: false, top: false, bottom: false }, + }, + rightTop: { + digg: { left: 'leftRightTop', right: null, top: null, bottom: 'rightTopBottom' }, + cross: { left: false, right: false, top: false, bottom: false }, + }, + rightBottom: { + digg: { left: 'leftRightBottom', right: null, top: 'rightTopBottom', bottom: null }, + cross: { left: false, right: false, top: false, bottom: false }, + }, + leftRightTop: { + digg: { left: null, right: null, top: null, bottom: null }, + cross: { left: false, right: false, top: false, bottom: false }, + }, + leftRightBottom: { + digg: { left: null, right: null, top: null, bottom: null }, + cross: { left: false, right: false, top: false, bottom: false }, + }, + leftTopBottom: { + digg: { left: null, right: null, top: null, bottom: null }, + cross: { left: false, right: false, top: false, bottom: false }, + }, + rightTopBottom: { + digg: { left: null, right: null, top: null, bottom: null }, + cross: { left: false, right: false, top: false, bottom: false }, + }, + leftRight: { + digg: { left: null, right: null, top: 'leftRightTop', bottom: 'leftRightBottom' }, + cross: { left: false, right: false, top: true, bottom: true }, + }, + topBottom: { + digg: { left: 'leftTopBottom', right: 'rightTopBottom', top: null, bottom: null }, + cross: { left: true, right: true, top: false, bottom: false }, + }, + cross: { + digg: { left: 'cross', right: 'cross', top: 'cross', bottom: 'cross' }, + cross: { left: false, right: false, top: false, bottom: false }, + }, +} as { [k in CellType]: { + digg: { left: CellType | null; right: CellType | null; top: CellType | null; bottom: CellType | null; }; + cross: { left: boolean; right: boolean; top: boolean; bottom: boolean; }; +} }; + +const imageSize = 2048; // px +const margin = 256; +const mazeAreaSize = imageSize - (margin * 2); + +const themes = [{ + bg1: '#C1D9CE', + bg2: '#F2EDD5', + wall: '#0F8AA6', + road: '#C1D9CE', + marker: '#84BFBF', +}, { + bg1: '#17275B', + bg2: '#1F2E67', + wall: '#17275B', + road: '#6A77A4', + marker: '#E6E5E3', +}, { + bg1: '#194035', + bg2: '#417334', + wall: '#594020', + road: '#A67926', + marker: '#2E4D59', +}]; + +export default class extends Module { + public readonly name = 'maze'; + + @autobind + public install() { + this.post(); + setInterval(this.post, 1000 * 60 * 3); + + return {}; + } + + @autobind + private async post() { + const now = new Date(); + if (now.getHours() !== 11) return; + const date = `${now.getFullYear()}-${now.getMonth()}-${now.getDate()}`; + const data = this.getData(); + if (data.lastPosted == date) return; + data.lastPosted = date; + this.setData(data); + + const seed = date; + + this.log('Maze generating...'); + const maze = this.genMize(seed); + + this.log('Maze rendering...'); + const [temp] = await this.createTemp(); + await this.renderMaze(seed, maze, fs.createWriteStream(temp)); + + this.log('Image uploading...'); + const file = await this.ai.upload(fs.createReadStream(temp)); + + this.log('Posting...'); + this.ai.post({ + text: serifs.maze.post, + fileIds: [file.id] + }); + } + + @autobind + private createTemp(): Promise<[string, any]> { + return new Promise<[string, any]>((res, rej) => { + tmp.file((e, path, fd, cleanup) => { + if (e) return rej(e); + res([path, cleanup]); + }); + }); + } + + @autobind + private genMize(seed) { + const rand = gen.create(seed); + + const mazeSize = 10 + rand(21); + + // maze (filled by 'empty') + const maze: CellType[][] = new Array(mazeSize); + for (let i = 0; i < mazeSize; i++) { + maze[i] = new Array(mazeSize).fill('empty'); + } + + const origin = { + x: rand(mazeSize), + y: rand(mazeSize), + }; + + function checkDiggable(x: number, y: number, dir: Dir) { + if (cellVariants[maze[x][y]].digg[dir] === null) return false; + + const newPos = + dir === 'top' ? { x: x, y: y - 1 } : + dir === 'bottom' ? { x: x, y: y + 1 } : + dir === 'left' ? { x: x - 1, y: y } : + dir === 'right' ? { x: x + 1, y: y } : + { x, y }; + + if (newPos.x < 0 || newPos.y < 0 || newPos.x >= mazeSize || newPos.y >= mazeSize) return false; + + const cell = maze[newPos.x][newPos.y]; + if (cell === 'empty') return true; + if (cellVariants[cell].cross[dir] && checkDiggable(newPos.x, newPos.y, dir)) return true; + + return false; + } + + function diggFrom(x: number, y: number) { + const isUpDiggable = checkDiggable(x, y, 'top'); + const isRightDiggable = checkDiggable(x, y, 'right'); + const isDownDiggable = checkDiggable(x, y, 'bottom'); + const isLeftDiggable = checkDiggable(x, y, 'left'); + + if (!isUpDiggable && !isRightDiggable && !isDownDiggable && !isLeftDiggable) return; + + const dirs: Dir[] = []; + if (isUpDiggable) dirs.push('top'); + if (isRightDiggable) dirs.push('right'); + if (isDownDiggable) dirs.push('bottom'); + if (isLeftDiggable) dirs.push('left'); + + const dir = dirs[rand(dirs.length)]; + + maze[x][y] = cellVariants[maze[x][y]].digg[dir]; + + if (dir === 'top') { + maze[x][y - 1] = maze[x][y - 1] === 'empty' ? 'bottom' : 'cross'; + diggFrom(x, y - 1); + return; + } + if (dir === 'right') { + maze[x + 1][y] = maze[x + 1][y] === 'empty' ? 'left' : 'cross'; + diggFrom(x + 1, y); + return; + } + if (dir === 'bottom') { + maze[x][y + 1] = maze[x][y + 1] === 'empty' ? 'top' : 'cross'; + diggFrom(x, y + 1); + return; + } + if (dir === 'left') { + maze[x - 1][y] = maze[x - 1][y] === 'empty' ? 'right' : 'cross'; + diggFrom(x - 1, y); + return; + } + } + + diggFrom(origin.x, origin.y); + + let hasEmptyCell = true; + while (hasEmptyCell) { + const nonEmptyCells = []; + + for (let y = 0; y < mazeSize; y++) { + for (let x = 0; x < mazeSize; x++) { + const cell = maze[x][y]; + if (cell !== 'empty' && cell !== 'cross') nonEmptyCells.push([x, y]); + } + } + + const pos = nonEmptyCells[rand(nonEmptyCells.length)]; + + diggFrom(pos[0], pos[1]); + + hasEmptyCell = false; + for (let y = 0; y < mazeSize; y++) { + for (let x = 0; x < mazeSize; x++) { + if (maze[x][y] === 'empty') hasEmptyCell = true; + } + } + } + + return maze; + } + + @autobind + private renderMaze(seed, maze: CellType[][], stream: fs.WriteStream): Promise { + const rand = gen.create(seed); + const mazeSize = maze.length; + + const colors = themes[rand(themes.length)]; + + const canvas = p.make(imageSize, imageSize); + const ctx = canvas.getContext('2d'); + + ctx.fillStyle = colors.bg1; + ctx.beginPath(); + ctx.fillRect(0, 0, imageSize, imageSize); + + ctx.fillStyle = colors.bg2; + ctx.beginPath(); + ctx.fillRect(margin / 2, margin / 2, imageSize - ((margin / 2) * 2), imageSize - ((margin / 2) * 2)); + + // Draw + function drawCell(ctx, x, y, size, left, right, top, bottom, mark) { + const wallThickness = size / 8; + const wallMargin = size / 4; + const markerMargin = size / 3; + + ctx.fillStyle = colors.road; + if (left) { + ctx.beginPath(); + ctx.fillRect(x, y + wallMargin, size - wallMargin, size - (wallMargin * 2)); + } + if (right) { + ctx.beginPath(); + ctx.fillRect(x + wallMargin, y + wallMargin, size - wallMargin, size - (wallMargin * 2)); + } + if (top) { + ctx.beginPath(); + ctx.fillRect(x + wallMargin, y, size - (wallMargin * 2), size - wallMargin); + } + if (bottom) { + ctx.beginPath(); + ctx.fillRect(x + wallMargin, y + wallMargin, size - (wallMargin * 2), size - wallMargin); + } + + if (mark) { + ctx.fillStyle = colors.marker; + ctx.beginPath(); + ctx.fillRect(x + markerMargin, y + markerMargin, size - (markerMargin * 2), size - (markerMargin * 2)); + } + + const wallLeftTopX = x + wallMargin - (wallThickness / 2); + const wallLeftTopY = y + wallMargin - (wallThickness / 2); + const wallRightTopX = x + size - wallMargin - (wallThickness / 2); + const wallRightTopY = y + wallMargin - (wallThickness / 2); + const wallLeftBottomX = x + wallMargin - (wallThickness / 2); + const wallLeftBottomY = y + size - wallMargin - (wallThickness / 2); + const wallRightBottomX = x + size - wallMargin - (wallThickness / 2); + const wallRightBottomY = y + size - wallMargin - (wallThickness / 2); + + ctx.fillStyle = colors.wall; + if (left && right && top && bottom) { + ctx.beginPath(); + if (rand(2) === 0) { + ctx.fillRect(x + wallMargin - (wallThickness / 2), y, wallThickness, size); + ctx.fillRect(x + size - wallMargin - (wallThickness / 2), y, wallThickness, size); + ctx.fillRect(x, y + wallMargin - (wallThickness / 2), wallMargin, wallThickness); + ctx.fillRect(x + size - wallMargin, y + wallMargin - (wallThickness / 2), wallMargin, wallThickness); + ctx.fillRect(x, y + size - wallMargin - (wallThickness / 2), wallMargin, wallThickness); + ctx.fillRect(x + size - wallMargin, y + size - wallMargin - (wallThickness / 2), wallMargin, wallThickness); + } else { + ctx.fillRect(x, y + wallMargin - (wallThickness / 2), size, wallThickness); + ctx.fillRect(x, y + size - wallMargin - (wallThickness / 2), size, wallThickness); + ctx.fillRect(wallLeftTopX, y, wallThickness, wallMargin - (wallThickness / 2)); + ctx.fillRect(wallRightTopX, y, wallThickness, wallMargin - (wallThickness / 2)); + ctx.fillRect(wallLeftTopX, wallLeftBottomY, wallThickness, wallMargin + (wallThickness / 2)); + ctx.fillRect(wallRightTopX, wallRightBottomY, wallThickness, wallMargin + (wallThickness / 2)); + } + return; + } + + if (!left && right && !top && bottom) { + ctx.beginPath(); + ctx.fillRect(wallLeftTopX, wallLeftTopY, size - wallMargin + (wallThickness / 2), wallThickness); + ctx.fillRect(wallLeftTopX, wallLeftTopY, wallThickness, size - wallMargin + (wallThickness / 2)); + } + + if (right && bottom) { + ctx.fillRect(wallRightBottomX, wallRightBottomY, wallMargin + (wallThickness / 2), wallThickness); + ctx.fillRect(wallRightBottomX, wallRightBottomY, wallThickness, wallMargin + (wallThickness / 2)); + } + + if (left && !right && !top && bottom) { + ctx.beginPath(); + ctx.fillRect(x, y + wallMargin - (wallThickness / 2), size - wallMargin + (wallThickness / 2), wallThickness); + ctx.fillRect(wallRightTopX, y + wallMargin - (wallThickness / 2), wallThickness, size - wallMargin + (wallThickness / 2)); + } + + if (left && bottom) { + ctx.fillRect(x, wallLeftBottomY, wallMargin - (wallThickness / 2), wallThickness); + ctx.fillRect(wallLeftBottomX, wallLeftBottomY, wallThickness, wallMargin + (wallThickness / 2)); + } + + if (!left && right && top && !bottom) { + ctx.beginPath(); + ctx.fillRect(wallLeftBottomX, y, wallThickness, size - wallMargin + (wallThickness / 2)); + ctx.fillRect(wallLeftBottomX, wallLeftBottomY, size - wallMargin + (wallThickness / 2), wallThickness); + } + + if (right && top) { + ctx.fillRect(wallRightTopX, y, wallThickness, wallMargin - (wallThickness / 2)); + ctx.fillRect(wallRightTopX, wallRightTopY, size - wallMargin - (wallThickness * 2), wallThickness); + } + + if (left && !right && top && !bottom) { + ctx.beginPath(); + ctx.fillRect(x, wallLeftBottomY, size - wallMargin - (wallThickness / 2), wallThickness); + ctx.fillRect(wallRightTopX, y, wallThickness, size - wallMargin + (wallThickness / 2)); + } + + if (left && top) { + ctx.fillRect(wallLeftTopX, y, wallThickness, wallMargin + (wallThickness / 2)); + ctx.fillRect(x, wallLeftTopY, wallMargin - (wallThickness / 2), wallThickness); + } + + if (!left && right && !top && !bottom) { + ctx.beginPath(); + ctx.fillRect(wallLeftTopX, wallLeftTopY, size - wallMargin + (wallThickness / 2), wallThickness); + ctx.fillRect(wallLeftBottomX, wallLeftBottomY, size - wallMargin + (wallThickness / 2), wallThickness); + ctx.fillRect(wallLeftTopX, wallLeftTopY, wallThickness, size - wallMargin - (wallThickness * 2)); + return; + } + + if (left && !right && !top && !bottom) { + ctx.beginPath(); + ctx.fillRect(x, wallLeftTopY, size - wallMargin + (wallThickness / 2), wallThickness); + ctx.fillRect(x, wallLeftBottomY, size - wallMargin + (wallThickness / 2), wallThickness); + ctx.fillRect(wallRightTopX, wallRightTopY, wallThickness, size - wallMargin - (wallThickness * 2)); + return; + } + + if (!left && !right && !top && bottom) { + ctx.beginPath(); + ctx.fillRect(wallLeftTopX, wallLeftTopY, wallThickness, size - wallMargin + (wallThickness / 2)); + ctx.fillRect(wallRightTopX, wallRightTopY, wallThickness, size - wallMargin + (wallThickness / 2)); + ctx.fillRect(wallLeftTopX, wallLeftTopY, size - wallMargin - (wallThickness * 2), wallThickness); + return; + } + + if (!left && !right && top && !bottom) { + ctx.beginPath(); + ctx.fillRect(wallLeftTopX, y, wallThickness, size - wallMargin + (wallThickness / 2)); + ctx.fillRect(wallRightTopX, y, wallThickness, size - wallMargin + (wallThickness / 2)); + ctx.fillRect(wallLeftBottomX, wallLeftBottomY, size - wallMargin - (wallThickness * 2), wallThickness); + return; + } + + if (top && bottom) { + if (!left) { + ctx.beginPath(); + ctx.fillRect(x + wallMargin - (wallThickness / 2), y, wallThickness, size); + } + if (!right) { + ctx.beginPath(); + ctx.fillRect(x + size - wallMargin - (wallThickness / 2), y, wallThickness, size); + } + } + if (left && right) { + if (!top) { + ctx.beginPath(); + ctx.fillRect(x, y + wallMargin - (wallThickness / 2), size, wallThickness); + } + if (!bottom) { + ctx.beginPath(); + ctx.fillRect(x, y + size - wallMargin - (wallThickness / 2), size, wallThickness); + } + } + } + + const cellSize = mazeAreaSize / mazeSize; + + for (let x = 0; x < mazeSize; x++) { + for (let y = 0; y < mazeSize; y++) { + const actualX = margin + (cellSize * x); + const actualY = margin + (cellSize * y); + + const cell = maze[x][y]; + + const mark = (x === 0 && y === 0) || (x === mazeSize - 1 && y === mazeSize - 1); + + if (cell === 'left') drawCell(ctx, actualX, actualY, cellSize, true, false, false, false, mark); + if (cell === 'right') drawCell(ctx, actualX, actualY, cellSize, false, true, false, false, mark); + if (cell === 'top') drawCell(ctx, actualX, actualY, cellSize, false, false, true, false, mark); + if (cell === 'bottom') drawCell(ctx, actualX, actualY, cellSize, false, false, false, true, mark); + if (cell === 'leftTop') drawCell(ctx, actualX, actualY, cellSize, true, false, true, false, mark); + if (cell === 'leftBottom') drawCell(ctx, actualX, actualY, cellSize, true, false, false, true, mark); + if (cell === 'rightTop') drawCell(ctx, actualX, actualY, cellSize, false, true, true, false, mark); + if (cell === 'rightBottom') drawCell(ctx, actualX, actualY, cellSize, false, true, false, true, mark); + if (cell === 'leftRightTop') drawCell(ctx, actualX, actualY, cellSize, true, true, true, false, mark); + if (cell === 'leftRightBottom') drawCell(ctx, actualX, actualY, cellSize, true, true, false, true, mark); + if (cell === 'leftTopBottom') drawCell(ctx, actualX, actualY, cellSize, true, false, true, true, mark); + if (cell === 'rightTopBottom') drawCell(ctx, actualX, actualY, cellSize, false, true, true, true, mark); + if (cell === 'leftRight') drawCell(ctx, actualX, actualY, cellSize, true, true, false, false, mark); + if (cell === 'topBottom') drawCell(ctx, actualX, actualY, cellSize, false, false, true, true, mark); + if (cell === 'cross') drawCell(ctx, actualX, actualY, cellSize, true, true, true, true, mark); + } + } + + return p.encodePNGToStream(canvas, stream); + } +} diff --git a/src/serifs.ts b/src/serifs.ts index 8f418a0..fdb0120 100644 --- a/src/serifs.ts +++ b/src/serifs.ts @@ -311,6 +311,10 @@ export default { server: { cpu: 'サーバーの負荷が高そうです。大丈夫でしょうか...?' }, + + maze: { + post: 'この回路、解読してみてください! #AiMaze' + }, }; export function getSerif(variant: string | string[]): string {