|
14 | 14 |
|
15 | 15 | 'use strict'
|
16 | 16 |
|
17 |
| -const kuroshiro = require('kuroshiro') |
| 17 | +const Kuroshiro = require('kuroshiro') |
| 18 | +const KuromojiAnalyzer = require('kuroshiro-analyzer-kuromoji') |
18 | 19 | const wanakana = require('wanakana')
|
19 | 20 |
|
20 | 21 | const smallKanas = 'ぁぃぅぇぉゃゅょ'
|
21 |
| -let kuroshiroLoaded = false |
22 | 22 |
|
23 | 23 | // 辞書のローディング。
|
24 |
| -exports.loaded = new Promise((resolve, reject) => { |
25 |
| - kuroshiro.init(function (err) { |
26 |
| - if (err) { |
27 |
| - reject(err) |
28 |
| - return |
29 |
| - } |
30 |
| - kuroshiroLoaded = true |
31 |
| - resolve() |
32 |
| - }) |
33 |
| -}) |
34 |
| - |
35 |
| -// しりとりのルールのチェック。 |
36 |
| -exports.check = (word, chain) => { |
37 |
| - if ((chain === undefined) || (chain.length === 0)) { |
38 |
| - return true |
39 |
| - } |
40 |
| - |
41 |
| - if (kuroshiroLoaded) { |
42 |
| - word = kuroshiro.toHiragana(word) |
43 |
| - chain = chain.map((e) => kuroshiro.toHiragana(e)) |
44 |
| - } |
| 24 | +const kuroshiro = new Kuroshiro() |
| 25 | +const loaded = kuroshiro.init(new KuromojiAnalyzer()) |
45 | 26 |
|
46 |
| - // 漢字からひらがなにする。 |
47 |
| - const wordHira = wanakana.toHiragana(word) |
48 |
| - const chainHira = chain.map(wanakana.toHiragana) |
| 27 | +class Win extends Error {} |
| 28 | +exports.Win = Win |
| 29 | +class Bad extends Error {} |
| 30 | +exports.Bad = Bad |
49 | 31 |
|
50 |
| - // 使った名詞をチェックする。 |
51 |
| - if (chainHira.indexOf(wordHira, 0) !== -1) { |
52 |
| - return false |
53 |
| - } |
| 32 | +// しりとりのルールのチェック。 |
| 33 | +exports.check = (word, chain) => loaded.then(() => { |
| 34 | + return Promise.all([ |
| 35 | + kuroshiro.convert(word, { to: 'hiragana' }), |
| 36 | + ...chain.map(e => kuroshiro.convert(e, { to: 'hiragana' })) |
| 37 | + ]).then(([word, ...chain]) => { |
| 38 | + // 漢字からひらがなにする。 |
| 39 | + const wordHira = wanakana.toHiragana(word) |
| 40 | + if (wordHira[wordHira.length - 1] === 'ん') { |
| 41 | + throw new Bad('ends with ん') |
| 42 | + } |
54 | 43 |
|
55 |
| - // しりとりの最初の文字をチェックする。 |
56 |
| - const validKanas = exports.kanas(chain[0]) |
57 |
| - for (const k of validKanas) { |
58 |
| - const begin = wordHira.slice(0, k.length) |
59 |
| - if (begin === k) { |
| 44 | + if ((chain === undefined) || (chain.length === 0)) { |
60 | 45 | return true
|
61 | 46 | }
|
62 |
| - } |
63 |
| - return false |
64 |
| -} |
65 | 47 |
|
66 |
| -// 名詞からしりとりのひらがなを選ぶ。 |
67 |
| -exports.kanas = (word) => new Set((() => { |
68 |
| - if (kuroshiroLoaded) { |
69 |
| - word = kuroshiro.toHiragana(word) |
70 |
| - } |
| 48 | + // 使った名詞をチェックする。 |
| 49 | + const chainHira = chain.map(wanakana.toHiragana) |
| 50 | + if (chainHira.indexOf(wordHira, 0) !== -1) { |
| 51 | + throw new Bad('already used') |
| 52 | + } |
71 | 53 |
|
72 |
| - const wordKana = wanakana.toKatakana(word) |
73 |
| - const wordHira = wanakana.toHiragana(wordKana) |
74 |
| - const kk = wordKana[word.length - 1] |
75 |
| - const hk = wordHira[wordHira.length - 1] |
| 54 | + // しりとりの最初の文字をチェックする。 |
| 55 | + return exports.kanas(chain[0]).then(validKanas => { |
| 56 | + for (const k of validKanas) { |
| 57 | + const begin = wordHira.slice(0, k.length) |
| 58 | + if (begin === k) { |
| 59 | + return true |
| 60 | + } |
| 61 | + } |
| 62 | + throw new Bad('文字 does not match') |
| 63 | + }) |
| 64 | + }) |
| 65 | +}) |
76 | 66 |
|
77 |
| - if (hk === 'ん') { |
78 |
| - return [] |
79 |
| - } |
| 67 | +// 名詞からしりとりのひらがなを選ぶ。 |
| 68 | +exports.kanas = word => loaded |
| 69 | + .then(() => kuroshiro.convert(word, { to: 'hiragana' })) |
| 70 | + .then((word) => { |
| 71 | + const wordKana = wanakana.toKatakana(word) |
| 72 | + const wordHira = wanakana.toHiragana(wordKana) |
| 73 | + const kk = wordKana[wordKana.length - 1] |
| 74 | + const hk = wordHira[wordHira.length - 1] |
| 75 | + |
| 76 | + if (hk === 'ん') { |
| 77 | + return [] |
| 78 | + } |
80 | 79 |
|
81 |
| - if ((kk === 'ー') && (wordKana.length > 1)) { |
82 |
| - return [ |
83 |
| - wordHira[wordHira.length - 1], |
84 |
| - wordHira[wordHira.length - 2] |
85 |
| - ] |
86 |
| - } |
| 80 | + if ((kk === 'ー') && (wordKana.length > 1)) { |
| 81 | + return [ |
| 82 | + wordHira[wordHira.length - 1], |
| 83 | + wordHira[wordHira.length - 2] |
| 84 | + ] |
| 85 | + } |
87 | 86 |
|
88 |
| - if (smallKanas.includes(hk)) { |
| 87 | + if (smallKanas.includes(hk)) { |
| 88 | + return [ |
| 89 | + String.fromCharCode(hk.charCodeAt(0) + 1), |
| 90 | + wordHira.slice(-2) |
| 91 | + ] |
| 92 | + } |
89 | 93 | return [
|
90 |
| - String.fromCharCode(hk.charCodeAt(0) + 1), |
91 |
| - wordHira.slice(-2) |
| 94 | + wordHira[wordHira.length - 1] |
92 | 95 | ]
|
93 |
| - } |
94 |
| - |
95 |
| - return [ |
96 |
| - wordHira[wordHira.length - 1] |
97 |
| - ] |
98 |
| -})()) |
| 96 | + }) |
| 97 | + .then(result => new Set(result)) |
99 | 98 |
|
100 | 99 | // しりとりのゲームループ。
|
101 |
| -exports.interact = (dict, word, chain) => new Promise((resolve, reject) => { |
102 |
| - const next = exports.kanas(word) |
103 |
| - if ((next.size === 0) || !exports.check(word, chain)) { |
104 |
| - return reject({loose: true}) |
105 |
| - } |
106 |
| - const key = next.values().next().value |
107 |
| - dict(key).then((words) => { |
108 |
| - const unused = [] |
109 |
| - if (words) { |
110 |
| - for (const k of Object.keys(words)) { |
111 |
| - if (!chain.includes(words[k])) { |
112 |
| - unused.push(k) |
113 |
| - } |
| 100 | +exports.interact = (dict, word, chain) => exports.check(word, chain) |
| 101 | + .then(() => exports.kanas(word)) |
| 102 | + .then(validKanas => dict(validKanas.values().next().value)) |
| 103 | + .then(words => { |
| 104 | + const unused = words ? Object.keys(words).filter(k => !chain.includes(words[k])) : [] |
| 105 | + if (unused.length === 0) { |
| 106 | + throw new Win('no more unused word remaining in dictionary') |
114 | 107 | }
|
115 |
| - } |
116 |
| - if (unused.length === 0) { |
117 |
| - return reject({win: true}) |
118 |
| - } |
119 |
| - const w = unused[Math.floor(Math.random() * unused.length)] |
120 |
| - const wk = words[w] |
121 |
| - if (wk.length === 0) { |
122 |
| - return reject({error: 'empty dictionary entry for key: ' + w}) |
123 |
| - } |
124 |
| - if (wk[wk.length - 1] === 'ん') { |
125 |
| - return reject({win: true, word: w, kana: wk}) |
126 |
| - } |
127 |
| - resolve({word: w, kana: wk}) |
128 |
| - }) |
129 |
| -}) |
| 108 | + const w = unused[Math.floor(Math.random() * unused.length)] |
| 109 | + const wk = words[w] |
| 110 | + if (wk.length === 0) { |
| 111 | + throw new Error(`no dictionary entry for key: ${w}`) |
| 112 | + } |
| 113 | + if (wk[wk.length - 1] === 'ん') { |
| 114 | + throw new Win('dictionary word ends with ん') |
| 115 | + } |
| 116 | + return { word: w, kana: wk } |
| 117 | + }) |
0 commit comments