Help us understand the problem. What is going on with this article?

最強のしりとりAIを作ってみた

しりとりができるAIを作ってみた。

前回はGASを使ってLINE BOTを作りましたが、
今回はWebでJavascriptを使って作ってみます。

使ったAPI

gooラボ ひらがな化API
ウィキペディア MediaWikiAPI

あとjQueryとFont Awesomeのアイコンフォントを使います。
使用したアイコンはこれこれです

コード

index.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>しりとり AI</title>
    <link rel="stylesheet" href="chat_UI.css">
    <script src="https://kit.fontawesome.com/xxxxx.js" crossorigin="anonymous"></script>
    <link rel="icon" href="./icons/favicon.ico">
</head>
<body>
    <script src="./jquery-3.4.1.min.js"></script>
    <div id="chat-box">
        <div class="kaiwa">
            <!--左からの吹き出し-->
                <figure class="kaiwa-img-left">
                    <img src="./icons/Wikipedia-logo-v2-ja.png" alt="no-img2">
                    <figcaption class="kaiwa-img-description">
                        しりとり AI
                    </figcaption>
                </figure>
                <div class="kaiwa-text-right">
                    <p class="kaiwa-text">
                        こんにちは。僕はしりとりAIだよ。<br>
                        Wikipediaから集めたデータを使ってしりとりができるよ。<br>
                        じゃあ、始めるよ~
                    </p>
                </div>
        </div>
        <!--左からの吹き出し 終了-->

            <div class="kaiwa">
                <figure class="kaiwa-img-left">
                    <img src="./icons/Wikipedia-logo-v2-ja.png" alt="no-img2">
                    <figcaption class="kaiwa-img-description">
                        しりとり AI
                    </figcaption>
                </figure>
                <div class="kaiwa-text-right">
                    <p class="kaiwa-text">
                        「しりとり」
                    </p>
                </div>
            </div>
    </div>
    <div id="form">
        <textarea id="text" cols="30" rows="3" placeholder="「り」から始まる言葉"></textarea>
        <div id="buttons">
            <button id="submit"><i class="far fa-paper-plane"></i> <span id="submit_text">送信</span></button>
            <button id="btn"><i class="fas fa-microphone"></i> <span id="btn_text">マイク</span></button>

        </div>
    </div>

    <script src="./siritori.js"></script>
</body>
</html>

次に見た目を作るCSSです。

chat_UI.css
/*——————–
 吹き出しを作る
——————–*/

/* 全体のスタイル */

.kaiwa {
  margin-bottom: 35px;
}

/* 左画像 */

.kaiwa-img-left {
  margin: 0;
  float: left;
  width: 60px;
  height: 60px;
  margin-right: -70px;
}

/* 右画像 */

.kaiwa-img-right {
  margin: 0;
  margin-right: 10px;
  float: right;
  width: 60px;
  height: 60px;
  margin-left: -70px;
}

.kaiwa figure img {
  width: 100%;
  height: 100%;
  border: 1px solid #aaa;
  border-radius: 50%;
  margin: 0;
}

/* 画像の下のテキスト */

.kaiwa-img-description {
  padding: 20px 0 0;
  font-size: 12px;
  text-align: center;
  position: relative;
  bottom: 15px;
}

/* 左からの吹き出しテキスト */

.kaiwa-text-right {
  position: relative;
  margin-left: 80px;
  padding: 10px;
  border-radius: 10px;
  background: #eee;
  margin-right: 20%;
  float: left;
}

/* 右からの吹き出しテキスト */

.kaiwa-text-left {
  position: relative;
  margin-right: 80px;
  padding: 10px;
  border-radius: 10px;
  background-color: #9cd6e7;
  margin-left: 20%;
  float: right;
}

p.kaiwa-text {
  margin: 0 0 20px;
}

p.kaiwa-text:last-child {
  margin-bottom: 0;
}

/* 左の三角形を作る */

.kaiwa-text-right:before {
  position: absolute;
  content: '';
  border: 10px solid transparent;
  top: 15px;
  left: -20px;
}

.kaiwa-text-right:after {
  position: absolute;
  content: '';
  border: 10px solid transparent;
  border-right: 10px solid #eee;
  top: 15px;
  left: -19px;
}

/* 右の三角形を作る */

.kaiwa-text-left:before {
  position: absolute;
  content: '';
  border: 10px solid transparent;
  top: 15px;
  right: -20px;
}

.kaiwa-text-left:after {
  position: absolute;
  content: '';
  border: 10px solid transparent;
  border-left: 10px solid #9cd6e7;
  top: 15px;
  right: -19px;
}

/* 回り込み解除 */

.kaiwa:after, .kaiwa:before {
  clear: both;
  content: "";
  display: block;
}

#text {
  resize: none;
  float: left;
  margin-right: 10px;
}

#chat-box {
  height: 500px;
  overflow-y: scroll;
}

#btn, #submit {
  margin-left: 10px;
  margin-bottom: 50px;
}

#btn, #submit {
  border-radius: 25px;
  position: relative;
  display: inline-block;
  font-weight: bold;
  padding: 0.25em 0.5em;
  text-decoration: none;
  color: #FFF;
  background: #00bcd4;
  transition: .4s;
  border-bottom: solid 4px #627295;
}

#submit:hover {
  background: #00ff00;
}

#btn:hover {
  background: #ff0000;
}

#btn:active {
  /*ボタンを押したとき*/
  -webkit-transform: translateY(4px);
  transform: translateY(4px);
  /*下に動く*/
  border-bottom: none;
  /*線を消す*/
}

#submit:active {
  /*ボタンを押したとき*/
  -webkit-transform: translateY(4px);
  transform: translateY(4px);
  /*下に動く*/
  border-bottom: none;
  /*線を消す*/
}

::placeholder {
  color: #ff0000;
  opacity: 0.5;
  font-size: 18px;
}

index.htmlの8行目の

<script src="https://kit.fontawesome.com/xxxxx.js" crossorigin="anonymous"</script>

の「xxxxx.js」の部分は自分で変更してください。

ここまで書くと、このような見た目になると思います。プレビュー

次はしりとりの処理のJavascriptのコードです。

siritori.js
var msg = new SpeechSynthesisUtterance();
//msg.lang = 'ja-JP'; //言語
var words = [];
var Word_history = [];
var cpu_word = "";
var next_word = "";
var Iswork = false;
//音声認識の準備
const obj = document.getElementById("chat-box");
SpeechRecognition = webkitSpeechRecognition || SpeechRecognition;
const speech = new SpeechRecognition();
if ('SpeechRecognition' in window) {
    // ユーザのブラウザは音声合成に対応しています。
} else {
    $("#btn").hide();
}
speech.lang = "ja-JP";
speech.continuous = true;
//使用する変数を用意
$("#submit").click(function () {
    $("#submit").css('background-color', '#999999');
    $("#btn").css('background-color', '#999999');
    var text = $("#text").val();
    if (text == "") {
        $("#btn").prop("disabled", false);
        $("#submit").prop("disabled", false);
        $("#btn_text").text("マイク");
        $("#submit_text").text("送信");
        $("#btn").css('background-color', '#00bcd4');
        $("#submit").css('background-color', '#00bcd4');
        return; //何もないなら関数を終了させる
    }
    $("#btn").prop("disabled", true);
    $("#btn_text").text("処理中");
    $("#submit").prop("disabled", true);
    $("#submit_text").text("処理中");
    console.log("リザルト")
    console.log(text);//textが結果
    //ここから返答処理
    $("#chat-box").html($("#chat-box").html() + "<div class=\"kaiwa\"><!–右からの吹き出し–><figure class=\"kaiwa-img-right\"><img src=\"./icons/human_icon.png\" alt=\"no-img2\"><figcaption class=\"kaiwa-img-description\">あなた</figcaption></figure><div class=\"kaiwa-text-left\"><p class=\"kaiwa-text\">「" + text + "」</p></div></div><!–右からの吹き出し 終了–>");
    obj.scrollTop = obj.scrollHeight;
    //処理が終わったら考え中の文字を削除し、結果を入れる
    if (next_word != str_chenge(text, 1)[0]) {
        say("" + next_word + "」から言葉を始めてね!", $("#chat-box"));
        obj.scrollTop = obj.scrollHeight;
        $("#text").val("");
        $("#btn").prop("disabled", false);
        $("#submit").prop("disabled", false);
        $("#btn_text").text("マイク");
        $("#submit_text").text("送信");
        $("#btn").css('background-color', '#00bcd4');
        $("#submit").css('background-color', '#00bcd4');
        return;
    } else if (Word_history.indexOf(text) != -1) {
        say("" + text + "」は、もう使われた言葉だよ!", $("#chat-box"));
        obj.scrollTop = obj.scrollHeight;
        $("#text").val("");
        $("#btn").prop("disabled", false);
        $("#submit").prop("disabled", false);
        $("#btn_text").text("マイク");
        $("#submit_text").text("送信");
        $("#btn").css('background-color', '#00bcd4');
        $("#submit").css('background-color', '#00bcd4');
        return;
    } else {
        Word_history.push(text);
        siritori(text).then(function (value) {
            // 非同期処理成功
            console.log(value);
            $("#text").attr("placeholder", "" + str_chenge(value, -1)[0] + "」から始まる言葉");
            next_word = str_chenge(value, -1)[0]
            say("" + value + "", $("#chat-box"))
            Word_history.push(value);
            obj.scrollTop = obj.scrollHeight;
            msg.text = value;
            speechSynthesis.speak(msg);
            console.log("処理終了");
            $("#text").val("");
            $("#submit").prop("disabled", false);
            $("#btn").prop("disabled", false);
            $("#btn_text").text("マイク");
            $("#submit_text").text("送信");
            $("#btn").css('background-color', '#00bcd4');
            $("#submit").css('background-color', '#00bcd4');
        }).catch(function (error) {
            // 非同期処理失敗。呼ばれない
            console.log(error);
            say("エラーが起きました", $("#chat-box"))
            $("#text").val("");
            $("#btn").prop("disabled", false);
            $("#submit").prop("disabled", false);
            $("#btn_text").text("マイク");
            $("#submit_text").text("送信");
            $("#btn").css('background-color', '#00bcd4');
            $("#submit").css('background-color', '#00bcd4');
        });
    }
})
$("#btn").click(function () {
    // 音声認識をスタート
    if (!Iswork) {
        Iswork = true;
        $("#btn").prop("disabled", true);
        $("#btn_text").text("マイクで録音中");
        $("#btn").css('background-color', '#ff0000');
        $("#submit").prop("disabled", true);
        speech.start();
    } else { return; }
});
speech.onnomatch = function () {
    console.log("認識できませんでした");
    say("認識できませんでした", $("#chat-box"))
    $("#btn").prop("disabled", false);
    $("#btn_text").text("マイク");
    $("#submit").prop("disabled", false);
    $("#submit_text").text("送信");
    $("#btn").css('background-color', '#00bcd4');
    $("#submit").css('background-color', '#00bcd4');
    Iswork = false;
};
speech.onerror = function () {
    console.log("認識できませんでした");
    say("認識できませんでした", $("#chat-box"))
    $("#btn").prop("disabled", false);
    $("#btn_text").text("マイク");
    $("#submit").prop("disabled", false);
    $("#submit_text").text("送信");
    $("#btn").css('background-color', '#00bcd4');
    $("#submit").css('background-color', '#00bcd4');
    Iswork = false;
};
//音声自動文字起こし機能
speech.onresult = function (e) {
    $("#btn_text").text("処理中");
    $("#submit").prop("disabled", true);
    $("#submit_text").text("処理中");
    $("#submit").css('background-color', '#999999');
    $("#btn").css('background-color', '#999999');
    console.log("リザルト")
    speech.stop();
    if (e.results[0].isFinal) {
        console.log("聞き取り成功!")
        var autotext = e.results[0][0].transcript
        console.log(e);
        console.log(autotext);//autotextが結果
        //ここから返答処理
        $("#chat-box").html($("#chat-box").html() + "<div class=\"kaiwa\"><!–右からの吹き出し–><figure class=\"kaiwa-img-right\"><img src=\"./icons/human_icon.png\" alt=\"no-img2\"><figcaption class=\"kaiwa-img-description\">あなた</figcaption></figure><div class=\"kaiwa-text-left\"><p class=\"kaiwa-text\">「" + autotext + "」</p></div></div><!–右からの吹き出し 終了–>");
        obj.scrollTop = obj.scrollHeight;
        //処理が終わったら考え中の文字を削除し、結果を入れる
        if (next_word != str_chenge(autotext, 1)[0]) {
            say("" + next_word + "」から言葉を始めてね!", $("#chat-box"));
            obj.scrollTop = obj.scrollHeight;
            $("#text").val("");
            $("#btn").prop("disabled", false);
            $("#submit").prop("disabled", false);
            $("#btn_text").text("マイク");
            $("#submit_text").text("送信");
            $("#btn").css('background-color', '#00bcd4');
            $("#submit").css('background-color', '#00bcd4');
            return;
        } else if (Word_history.indexOf(autotext) != -1) {
            say("" + autotext + "」は、もう使われた言葉だよ!", $("#chat-box"));
            obj.scrollTop = obj.scrollHeight;
            $("#text").val("");
            $("#btn").prop("disabled", false);
            $("#submit").prop("disabled", false);
            $("#btn_text").text("マイク");
            $("#submit_text").text("送信");
            $("#btn").css('background-color', '#00bcd4');
            $("#submit").css('background-color', '#00bcd4');
            return;
        } else {
            Word_history.push(autotext);
            siritori(autotext).then(function (value) {
                // 非同期処理成功
                console.log(value);
                $("#text").attr("placeholder", "" + str_chenge(value, -1)[0] + "」から始まる言葉");
                next_word = str_chenge(value, -1)[0]
                say("" + value + "", $("#chat-box"));
                Word_history.push(value);
                obj.scrollTop = obj.scrollHeight;
                msg.text = value; speechSynthesis.speak(msg);
                console.log("処理終了")
                $("#btn").prop("disabled", false);
                $("#btn").css('background-color', '#00bcd4');
                $("#submit").css('background-color', '#00bcd4');
                $("#btn_text").text("マイク");
                $("#submit").prop("disabled", false);
                $("#submi_text").text("送信");
                Iswork = false;
            }).catch(function (error) {
                // 非同期処理失敗。呼ばれない
                console.log(error);
                $("#btn").prop("disabled", false);
                $("#btn").css('background-color', '#00bcd4');
                $("#submit").css('background-color', '#00bcd4');
                $("#btn_text").text("マイク");
                $("#submit").prop("disabled", false);
                $("#submit_text").text("送信");
                Iswork = false;
            });
        }
    }
}
function siritori(user_msg) {
    return new Promise(function (resolve, reject) {
        words = [];
        var chenges = str_chenge(user_msg, -1)
        var taskA = new Promise(function (resolve, reject) {
            WikipediaAPI(chenges[0], resolve);
        });
        var taskB = new Promise(function (resolve, reject) {
            WikipediaAPI(chenges[1], resolve);
        });
        Promise.all([taskA, taskB]).then(function () {
            console.log(words);
            cpu_word = words[Math.floor(Math.random() * words.length)]
            resolve(cpu_word);
        })
    });
}
function WikipediaAPI(query, end) {
    var NG_word = ["", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", ""
        , "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", ""
        , "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", ""
        , "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "綿", "", "", ""
        , "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", ""];
    //API呼び出し
    console.log(query)
    $.ajax({
        type: "GET",
        timeout: 10000,
        dataType: "jsonp",
        url: "https://ja.wikipedia.org/w/api.php?format=json&action=query&list=prefixsearch&pssearch=" + query + "&pslimit=200&psnamespace=0",
        async: false,
        success: function (json) {
            console.log(json)
            json.query.prefixsearch.forEach(function (value) {
                if (value.title != query) {
                    var word = value.title;
                    word = word.replace(/ *\([^)]*\) */g, "");
                    if (NG_word.indexOf(word.slice(-1)) == -1 && Word_history.indexOf(word) == -1) {
                        words.push(word);
                    }
                }
            });
            end();
        }
    });

}
function str_chenge(str, ran) {
    var range = ran
    if (range == 1) {
        range = [0, 1]
    } else {
        range = [-1, undefined]
    }
    const hiragana = ["", "", "", "", "",
        "", "", "", "", "",
        "", "", "", "", "",
        "", "", "", "", "",
        "", "", "", "", "",
        "", "", "", "", "",
        "", "", "", "", "",
        "", "", "",
        "", "", "",
        "", "", "", "", "",
        "",
        "", "", "",
        "", "", "", "", "",
        "", "", "", "", "",
        "", "", "", "", "",
        "", "", "", "", "",
    ]
    const katakana = ["", "", "", "", "",
        "", "", "", "", "",
        "", "", "", "", "",
        "", "", "", "", "",
        "", "", "", "", "",
        "", "", "", "", "",
        "", "", "", "", "",
        "", "", "",
        "", "", "",
        "", "", "", "", "",
        "",
        "", "", "",
        "", "", "", "", "",
        "", "", "", "", "",
        "", "", "", "", "",
        "", "", "", "", "",]
    var r = [];
    var func_str = str;
    if (func_str.slice(range[0], range[1]) == "" || func_str.slice(range[0], range[1]) == "-" || func_str.slice(range[0], range[1]) == "!" || func_str.slice(range[0], range[1]) == "?" || func_str.slice(range[0], range[1]) == "" || func_str.slice(range[0], range[1]) == "") {
        func_str = func_str.slice(-2);
        func_str = func_str.slice(0, 1);
    }
    if (hiragana.indexOf(func_str.slice(range[0], range[1])) != -1) {//ひらがな
        r.push(func_str.slice(range[0], range[1]));
        r.push(katakana[hiragana.indexOf(func_str.slice(range[0], range[1]))]);
        console.log(r)
    } else if (katakana.indexOf(str.slice(range[0], range[1])) != -1) {//カタカナ
        r.push(hiragana[katakana.indexOf(func_str.slice(range[0], range[1]))]);
        r.push(func_str.slice(range[0], range[1]));
        console.log(r)
    } else {//漢字
        $.ajax({
            type: 'POST',
            timeout: 10000,
            url: "https://labs.goo.ne.jp/api/hiragana",
            async: false,
            'headers': {
                'Content-Type': "application/json",
            },
            data: JSON.stringify({
                'app_id': '自分のapp id',
                'sentence': func_str,
                'output_type': 'hiragana'
            }),
        }).done(function (data) {
            func_str = data.converted;
            if (func_str.slice(range[0], range[1]) == "") {
                func_str = func_str.slice(-2);
                func_str = func_str.slice(0, 1);
            } else {
                func_str = func_str.slice(range[0], range[1]);
            }
            r.push(func_str.slice(range[0], range[1]));
            r.push(katakana[hiragana.indexOf(func_str.slice(range[0], range[1]))]);
            console.log(r)
        });
    }
    switch (r[0]) {//小文字変換 ひらがな
        case "":
            r[0] = "";
            break;
        case "":
            r[0] = "";
            break;
        case "":
            r[0] = "";
            break;
        case "":
            r[0] = "";
            break;
        case "":
            r[0] = "";
            break;
        case "":
            r[0] = "";
            break;
        case "":
            r[0] = "";
            break;
        case "":
            r[0] = "";
            break;
        case "":
            r[0] = "";
            break;
        case "":
            r[0] = "";
            break;
        default:
            break;
    }
    switch (r[1]) {//小文字変換 カタカナ
        case "":
            r[1] = "";
            break;
        case "":
            r[1] = "";
            break;
        case "":
            r[1] = "";
            break;
        case "":
            r[1] = "";
            break;
        case "":
            r[1] = "";
            break;
        case "":
            r[1] = "";
            break;
        case "":
            r[1] = "";
            break;
        case "":
            r[1] = "";
            break;
        case "":
            r[1] = "";
            break;
        case "":
            r[1] = "";
            break;
        case "":
            r[1] = "";
            break;
        case "":
            r[1] = "";
            break;
        default:
            break;
    }
    console.log(r);
    return r;
}
function say(text, element) {
    element.html(element.html() + "<div class=\"kaiwa\"><!-左からの吹き出し-><figure class=\"kaiwa-img-left\"><img src=\"./icons/Wikipedia-logo-v2-ja.png\" alt=\"no-img2\"><figcaption class=\"kaiwa-img-description\">しりとり AI</figcaption></figure><div class=\"kaiwa-text-right\"><p class=\"kaiwa-text\">" + text + "</p></div></div><!-左からの吹き出し 終了->")
}

とても長くなりました!

解説

このしりとりAIはなんと音声認識ができます!
しかし、SpeechRecognitionはまだブラウザが完全に対応してないのでバグが多いです。
(Windows版Chrome 80.0.3987.149 では結構しっかり動いた)
なのでtextareaから入力することをお勧めします。

siritori.jsではtextareaまたは音声認識で入力が来たら、まず前の言葉の最後の文字と頭文字が同じかチェックし、そのあとその言葉が今までに出たかをチェックします。

if (next_word != str_chenge(text, 1)[0]) {
//      ・・・略・・・
} else if (Word_history.indexOf(text) != -1) {
//      ・・・略・・・
}

その二つの条件をクリアしたら
関数siritori()を呼び、その中でWikipediaのAPIからしりとりの条件にある言葉を取得して返答しています。
関数WikipediaAPI()の中の配列NG_wordには「ん」で読み仮名が終わる漢字など返答の候補に入れる際、除外してほしい文字が入っています。
関数str_chengeは言葉をひらがなや、カタカナに変換し、最後や最初の一文字を返します。
「ー」や「!」などが最後の一文字の場合はその一つ前の文字を、
「ぁ」や「ゃ」などの小さい文字は「あ」や「や」に変換してくれます。

ひらがな化APIの部分にはgooラボAPI利用登録からgithubで登録してappidを取得して、str_chenge()のapp_idを書き換えてください。

function str_chenge(str, ran) {
    //  省略
     else {//漢字
        $.ajax({
            type: 'POST',
            timeout: 10000,
            url: "https://labs.goo.ne.jp/api/hiragana",
            async: false,
            'headers': {
                'Content-Type': "application/json",
            },
            data: JSON.stringify({
                'app_id': 'xxxxxxxxxxxxxx',// <=ここを書き換える
                'sentence': func_str,
                'output_type': 'hiragana'
            }),
        })
//   省略

こんなコードを書くとWikipediaの頭脳を持ったしりとりAIが完成します。

なんと!このAIは最後の文字を「ん」にしても言葉を返してきます!

遊んでみてください!
しりとり AI
GitHub

709
プログラミングやってます 神ゲー作ってます ダウンロードは下のサイトからどーぞ
http://razupai2468.starfree.jp
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした