Skip to content

shiritori: update dependencies #17

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Apr 17, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 26 additions & 33 deletions functions/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,14 @@ const { actionssdk, SimpleResponse } = require('actions-on-google');
const functions = require('firebase-functions');
const shiritori = require('./shiritori');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
admin.initializeApp();

const corpus = 'noun';

const dict = k => {
return admin.database()
const dict = k => admin.database()
.ref(corpus).child(k)
.once('value')
.then(snap => snap.val());
};

const app = actionssdk({
// リクエストとレスポンスをロギングする。
Expand All @@ -45,35 +43,30 @@ const app = actionssdk({
})
});

app.intent('actions.intent.MAIN', (conv) => {
conv.ask('どうぞ、始めて下さい');
});
app.intent('actions.intent.MAIN',
conv => conv.ask('どうぞ、始めて下さい'));

app.intent('actions.intent.TEXT', (conv, input) => {
return shiritori.loaded.then(() => {
return shiritori.interact(dict, input, conv.data.used)
.then(result => {
conv.data.used.unshift(input);
conv.data.used.unshift(result.word);
conv.ask(new SimpleResponse({
speech: result.word,
text: `${result.word} [${result.kana}]`
}));
})
.catch(result => {
if (result.win) {
if (result.word) {
conv.close(`${result.word} [${result.kana}]`);
} else {
conv.close('すごい!あなたの勝ちです。');
}
} else if (result.loose) {
conv.close('ざんねん。あなたの負けです。');
} else {
throw result;
}
});
});
});
app.intent('actions.intent.TEXT',
(conv, input) => shiritori.interact(dict, input, conv.data.used)
.then(result => {
conv.data.used.unshift(input);
conv.data.used.unshift(result.word);
conv.ask(new SimpleResponse({
speech: result.word,
text: `${result.word} [${result.kana}]`
}));
})
.catch(reason => {
switch (reason.constructor) {
case shiritori.Win:
conv.close('すごい!あなたの勝ちです。');
break;
case shiritori.Bad:
conv.close('ざんねん。あなたの負けです。');
break;
default:
throw reason;
}
}));

exports.shiritoriV3 = functions.https.onRequest(app);
24 changes: 14 additions & 10 deletions functions/package.json
Original file line number Diff line number Diff line change
@@ -1,26 +1,30 @@
{
"name": "shiritori-agent",
"version": "0.0.2",
"description": "shiritori agent fullfillment",
"version": "0.0.1",
"private": true,
"license": "Apache Version 2.0",
"author": "Google Inc.",
"license": "Apache License 2.0",
"repository": {
"type": "git",
"url": "https://github.com/actions-on-google/actionssdk-shiritori-ja-nodejs"
},
"private": true,
"engines": {
"node": "~6.0"
"node": "8"
},
"scripts": {
"lint": "eslint --fix \"**/*.js\"",
"start": "firebase serve --only functions",
"deploy": "firebase deploy --only functions:shiritoriV3"
},
"dependencies": {
"actions-on-google": "^2.1.x",
"firebase-admin": "^5.12.1",
"firebase-functions": "^1.0.3",
"kuroshiro": "^0.2.1",
"object.entries": "^1.0.4",
"actions-on-google": "^2.6.0",
"firebase-admin": "^7.2.0",
"firebase-functions": "^2.2.1",
"kuroshiro": "^1.1.2",
"object.entries": "^1.1.0",
"shiritori": "file:shiritori",
"wanakana": "^3.1.0"
"wanakana": "^4.0.2"
},
"devDependencies": {
"eslint": "^3.19.0",
Expand Down
174 changes: 81 additions & 93 deletions functions/shiritori/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,116 +14,104 @@

'use strict'

const kuroshiro = require('kuroshiro')
const Kuroshiro = require('kuroshiro')
const KuromojiAnalyzer = require('kuroshiro-analyzer-kuromoji')
const wanakana = require('wanakana')

const smallKanas = 'ぁぃぅぇぉゃゅょ'
let kuroshiroLoaded = false

// 辞書のローディング。
exports.loaded = new Promise((resolve, reject) => {
kuroshiro.init(function (err) {
if (err) {
reject(err)
return
}
kuroshiroLoaded = true
resolve()
})
})

// しりとりのルールのチェック。
exports.check = (word, chain) => {
if ((chain === undefined) || (chain.length === 0)) {
return true
}

if (kuroshiroLoaded) {
word = kuroshiro.toHiragana(word)
chain = chain.map((e) => kuroshiro.toHiragana(e))
}
const kuroshiro = new Kuroshiro()
const loaded = kuroshiro.init(new KuromojiAnalyzer())

// 漢字からひらがなにする。
const wordHira = wanakana.toHiragana(word)
const chainHira = chain.map(wanakana.toHiragana)
class Win extends Error {}
exports.Win = Win
class Bad extends Error {}
exports.Bad = Bad

// 使った名詞をチェックする。
if (chainHira.indexOf(wordHira, 0) !== -1) {
return false
}
// しりとりのルールのチェック。
exports.check = (word, chain) => loaded.then(() => {
return Promise.all([
kuroshiro.convert(word, { to: 'hiragana' }),
...chain.map(e => kuroshiro.convert(e, { to: 'hiragana' }))
]).then(([word, ...chain]) => {
// 漢字からひらがなにする。
const wordHira = wanakana.toHiragana(word)
if (wordHira[wordHira.length - 1] === 'ん') {
throw new Bad('ends with ん')
}

// しりとりの最初の文字をチェックする。
const validKanas = exports.kanas(chain[0])
for (const k of validKanas) {
const begin = wordHira.slice(0, k.length)
if (begin === k) {
if ((chain === undefined) || (chain.length === 0)) {
return true
}
}
return false
}

// 名詞からしりとりのひらがなを選ぶ
exports.kanas = (word) => new Set((() => {
if (kuroshiroLoaded) {
word = kuroshiro.toHiragana(word)
}
// 使った名詞をチェックする
const chainHira = chain.map(wanakana.toHiragana)
if (chainHira.indexOf(wordHira, 0) !== -1) {
throw new Bad('already used')
}

const wordKana = wanakana.toKatakana(word)
const wordHira = wanakana.toHiragana(wordKana)
const kk = wordKana[word.length - 1]
const hk = wordHira[wordHira.length - 1]
// しりとりの最初の文字をチェックする。
return exports.kanas(chain[0]).then(validKanas => {
for (const k of validKanas) {
const begin = wordHira.slice(0, k.length)
if (begin === k) {
return true
}
}
throw new Bad('文字 does not match')
})
})
})

if (hk === 'ん') {
return []
}
// 名詞からしりとりのひらがなを選ぶ。
exports.kanas = word => loaded
.then(() => kuroshiro.convert(word, { to: 'hiragana' }))
.then((word) => {
const wordKana = wanakana.toKatakana(word)
const wordHira = wanakana.toHiragana(wordKana)
const kk = wordKana[wordKana.length - 1]
const hk = wordHira[wordHira.length - 1]

if (hk === 'ん') {
return []
}

if ((kk === 'ー') && (wordKana.length > 1)) {
return [
wordHira[wordHira.length - 1],
wordHira[wordHira.length - 2]
]
}
if ((kk === 'ー') && (wordKana.length > 1)) {
return [
wordHira[wordHira.length - 1],
wordHira[wordHira.length - 2]
]
}

if (smallKanas.includes(hk)) {
if (smallKanas.includes(hk)) {
return [
String.fromCharCode(hk.charCodeAt(0) + 1),
wordHira.slice(-2)
]
}
return [
String.fromCharCode(hk.charCodeAt(0) + 1),
wordHira.slice(-2)
wordHira[wordHira.length - 1]
]
}

return [
wordHira[wordHira.length - 1]
]
})())
})
.then(result => new Set(result))

// しりとりのゲームループ。
exports.interact = (dict, word, chain) => new Promise((resolve, reject) => {
const next = exports.kanas(word)
if ((next.size === 0) || !exports.check(word, chain)) {
return reject({loose: true})
}
const key = next.values().next().value
dict(key).then((words) => {
const unused = []
if (words) {
for (const k of Object.keys(words)) {
if (!chain.includes(words[k])) {
unused.push(k)
}
exports.interact = (dict, word, chain) => exports.check(word, chain)
.then(() => exports.kanas(word))
.then(validKanas => dict(validKanas.values().next().value))
.then(words => {
const unused = words ? Object.keys(words).filter(k => !chain.includes(words[k])) : []
if (unused.length === 0) {
throw new Win('no more unused word remaining in dictionary')
}
}
if (unused.length === 0) {
return reject({win: true})
}
const w = unused[Math.floor(Math.random() * unused.length)]
const wk = words[w]
if (wk.length === 0) {
return reject({error: 'empty dictionary entry for key: ' + w})
}
if (wk[wk.length - 1] === 'ん') {
return reject({win: true, word: w, kana: wk})
}
resolve({word: w, kana: wk})
})
})
const w = unused[Math.floor(Math.random() * unused.length)]
const wk = words[w]
if (wk.length === 0) {
throw new Error(`no dictionary entry for key: ${w}`)
}
if (wk[wk.length - 1] === 'ん') {
throw new Win('dictionary word ends with ん')
}
return { word: w, kana: wk }
})
48 changes: 23 additions & 25 deletions functions/shiritori/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,30 +30,28 @@ const rl = readline.createInterface({
const chain = ['しりとり']
console.log(chain[0])
rl.prompt()
shiritori.loaded.then(() => {
rl.on('line', input => {
shiritori
.interact(
kana => Promise.resolve(corpus[kana]),
input,
chain
)
.then(result => {
console.log(`${result.word} [${result.kana}]`)
chain.unshift(input)
chain.unshift(result.kana)
rl.prompt()
})
.catch(reason => {
if (reason.win) {
console.log('すごい!')
process.exit(0)
} else if (reason.loose) {
console.log('ざんねん。')
process.exit(-1)
} else {
throw reason
}
})
rl.on('line', input => {
shiritori.interact(
kana => Promise.resolve(corpus[kana]),
input,
chain
).then(result => {
console.log(`${result.word} [${result.kana}]`)
chain.unshift(input)
chain.unshift(result.kana)
rl.prompt()
}).catch(reason => {
switch (reason.constructor) {
case shiritori.Win:
console.log('すごい!')
process.exit(0)
break
case shiritori.Bad:
console.log('ざんねん。')
process.exit(-1)
break
default:
throw reason
}
})
})
Loading