wikipediaのAPIを使ってゲーム作ってみた#4

<h1>はじめに</h1>
こんにちは！前回は、文中にあるワードをクリックしたら、そのワードの記事を生成する機能を追加しました。

前の記事からかなり時間が経過してしまいましたね・・・。自分でコードを見返したら「これなんで動いてんの？」状態になっちゃってました笑

Qiitaに自分である程度解説書いておいてよかった～

というわけで、今回は、「スタートとなるワード」と「ゴールとなるワード」を取得し、ゴールに辿りついたら「ゴール！」みたいなのを表示させるようにしましょう。

前回の記事はこちらです👇

https://qiita.com/ITTON2001/items/5e37fa4e8ddff76577b4

<h1>作業開始</h1>
<h3>1.スタートとなるワードを取得する</h3>

wikipediaに収納されている記事のタイトルをランダムに取得し、それをスタートにしましょう。
では、その関数を作ります。
```python:views.py
def select_word():
    import requests
    #wikipediaからランダムに記事を受け取るAPI
    S = requests.Session()
    URL = "https://ja.wikipedia.org/w/api.php"
    PARAMS = {
        "action": "query",
        "format": "json",
        "list": "random",
        "rnlimit": "1",
        "rnnamespace": "0",
    }

    #要求したデータを受け取る
    R = S.get(url=URL, params=PARAMS)
    DATA = R.json()

    #データをランダムに受け取る
    RANDOMS = DATA["query"]["random"]
    selected_word = RANDOMS[0]["title"]
           
    return selected_word

```
この関数を使えばwikipediaからランダムな記事のタイトルを取得できます。
WikipediaのAPIについては#1👇で詳しく解説しているので割愛します。

https://qiita.com/ITTON2001/items/cbc317f39874efa2c514

select_first_word()で生成したselected_wordを使って、スタートのページを表示させることができます。

<h3>2.ゴールとなるワードを取得する</h3>
こちらも、スタートとなるワードを取得した時と全く同じようにすればいいのですが、ゴールをwikipediaにあるすべての記事を対象にしてしまうと、めちゃくちゃマイナーな用語の記事が出てきて、ゲームが難しすぎるので、タイトル画面でモード選択できるようにします。

```python:views.py
#タイトル画面
@csrf_exempt
def title(request):
    #モード選択用
    if 'checkbox' in request.POST:
        #チェックボックスのデータを受け取る
        checkedbox = request.POST.get('checkbox')

        #モードに応じて目標の文字を受け取る
        if checkedbox == "1":           
            #別モード
            #今回は実装しない
        elif checkedbox == "2":
            #wikiモード
            goal_word = select_word()
        else:
            print("エラーです")

        # セッションにgoal_wordを保存
        request.session['goal_word'] = goal_word

    return render(request, 'main/title.html')


```
```javascript:titlse.js
// スタートボタン
function submitForm(event) {
    event.preventDefault(); // デフォルトのフォーム送信をキャンセル

    var form = document.getElementById("start");
    var formData = new FormData(form);

    // チェックボックスの値を取得
    var checkbox = document.querySelector('input[name="checkbox"]:checked');
    var checkboxValue = checkbox ? checkbox.value : null

    //チェックボックスの値が取得されなかったら
    if (checkboxValue === null) {
        console.error('チェックボックスが選択されていません');
        alert('ゲームモードを選択してください');
        return;
    }

    //formDataにチェックボックスの値を追加
    formData.append('checkbox', checkboxValue);

    // フォームデータを送信
    fetch(form.action, {
        method: form.method,
        body: formData,
    }).then(function(response) {
        // ページ遷移
        window.location.href = "/maingame/";
        console.log("ページ遷移・・・完了")
    }).catch(function(error) {
        console.error('エラー:', error);
    });
}

// 排他的モード選択
function toggleCheckboxes(clickedCheckbox) {
    var checkboxes = document.getElementsByName("checkbox");
    checkboxes.forEach(function(checkbox) {
        if (checkbox !== clickedCheckbox) {
            checkbox.checked = false;
        }
    });
}

```
```html:titlse.html
{% extends "main/base.html" %}
{% load static %}

{% block header %}
    <link rel="stylesheet" type="text/css" href="{% static 'css/title.css' %}">
    <script src="{% static 'js/title.js' %}"></script>
{% endblock %}

{% block content %}
    <div class="title_panel">
        <form id="start" action="" method="POST">
            <div  class="start_button btn-shine">
                <a href="#" onclick="submitForm(event)">ゲーム開始</a>
            </div>
            
            <label>
                <input type="checkbox" id="countrymode" name="checkbox"  value="1" onclick="toggleCheckboxes(this)">別モード
            </label>
            <label>
                <input type="checkbox" id="wikimode" name="checkbox" value="2" onclick="toggleCheckboxes(this)">wikiモード
            </label>
        </form>
    </div>
{% endblock %}

```

まず、javascriptとhtmlの方で、モード選択の情報を受け取るチェックボックスを作成します。

```javascript:title.js
    //htmlから「start」のIDのformを受け取る
    var form = document.getElementById("start");
    //formのデータをformDataとして宣言する
    var formData = new FormData(form);

    // チェックされたチェックボックスの値を追加する。
    // 今回はhtmlでそれぞれのvalueに1や2を設定しているので、それを登録する。
    var checkbox = document.querySelector('input[name="checkbox"]:checked');
    var checkboxValue = checkbox ? checkbox.value : null

    //チェックボックスの値が取得されなかったらエラーメッセージを表示する
    if (checkboxValue === null) {
        console.error('チェックボックスが選択されていません');
        alert('ゲームモードを選択してください');
        return;
    }

    //formDataに値を追加
    formData.append('checkbox', checkboxValue);
```
この時、チェックボックスが複数選択できてしまうと、モード選択ができないので、1つ選択したら、それ以外の選択を無効化する関数を作ります。
```javascript:title.js
// 排他的モード選択
function toggleCheckboxes(clickedCheckbox) {
    //htmlでname属性がcheckboxと定義されている要素を取得する。
    var checkboxes = document.getElementsByName("checkbox");
    //各チェックボックスを調査し、clickedCheckbox（クリックしたチェックボックス）でなければ、そのチェックボックスをfalseにする。
    checkboxes.forEach(function(checkbox) {
        if (checkbox !== clickedCheckbox) {
            checkbox.checked = false;
        }
    });
}
```
javascriptはこんな感じです。
次はpython側の処理です。
受け取ったモードデータに基づいてゴールのワードを設定します。

チェックされたチェックボックスの値に応じて、ゴールに設定するワードを決めます。
今回は、wikipedia内にあるランダムなワードを取得します。
```python:views.py
    #モード選択用
    if 'checkbox' in request.POST:
        #チェックボックスのデータを受け取る
        checkedbox = request.POST.get('checkbox')

        #モードに応じて目標の文字を受け取る
        if checkedbox == "1":           
            #別モード
            #今回は実装しない
        elif checkedbox == "2":
            #wikiモード
            goal_word = select_word()
        else:
            print("エラーです")
```

さて、ここで決定したゴールのワードをメインのhtmlで表示したいので、メイン画面を定義しているmain()に送ります。
ただ、main()でtitle()を呼び出すわけにはいかないので、間接的にデータを送らなければなりません。
これを実現するには、`request.session`というDjangoフレームワーク内でセッション管理を行うためのオブジェクトを利用して、無理やり送ります。
もっとスマートなやり方を知っている方は是非教えてください・・・。
```python:views.py
# セッションにgoal_wordを保存
request.session['goal_word'] = goal_word
```

これでmain()でtitle()で定義したゴールのワードを受け取ることができます。
#3で作成したmain()にゴールのワードを受け取って、htmlに渡せるようにしましょう。

```python:views.py
#メインの画面
@csrf_exempt
def main(request):         
    #ワードクリック用
    if 'word' in request.POST:
        #ここは変化なし
    #スタートゲーム用
    else:
        # セッションからgoal_wordを取得
        goal_word = request.session.get('goal_word')#←追加

        #省略

    #main.htmlに情報を返す
    return render(request, 'main/main.html', {
        'page_text': page_text, 
        'page_title': page_title,
        'res': res,
        'goal_word':goal_word,#←追加
        })
```
```html:main.html
{% extends "main/base.html" %}
{% load static %}

{% block header %}
    <title>Result</title>
    <script src="{% static 'js/main.js' %}"></script>
{% endblock %}

{% block content %}
    <!-- ここにゴールのワードを表示させる -->
    <div id="goal-word" data-goal-word="{{ goal_word }}">
        {{ goal_word }}
    </div>

    <div class="main-text" id="main-text">
        <h1>{{ page_title }}</h1>
        <p>{{ res|safe }}</p>
    </div>

{% endblock %}
```
これで無事、メインの画面にゴールのワードを表示させられるようになりました。

<h3>3.ゴールの処理</h3>

最後に、ゴールのワードにたどり着いたら、何らかのアクションが発生するようにしておきましょう。
今回は、表示されている記事のタイトルとゴールのワードが一致した時に「ゴール！」と画面上に表示させましょう。

まず、一致の判断をjavascriptでさせるためにjavascriptでゴールのワードを受け取ります。

```{{ goal_word }}```の中にある、正しいゴールのワードを取得するには、htmlで```data-goal-word```に```{{ goal_word }}```の中のデータを入れることで、受け取ることができるようになります。

```javascript:main.js
// IDがgoal-wordで、data-goal-wordのデータを受け取る
let goalWord = document.getElementById('goal-word').getAttribute('data-goal-word');
```
ここでデータを入れた```goalWord```と、ページを作るときにviews.pyから受け取る```page_title```を比較して、一致したらhtmlに「ゴール！」と書き込みます。

```javascript:main.js
    //省略
        
    .then(data => {
            let goalWord = document.getElementById('goal-word').getAttribute('data-goal-word');
            // 受け取ったデータを使用してHTMLを再構築する
            let html = '';
            html += `
                    <h1>${data.page_title}</h1>
                    <p>${data.res}</p>
                `;
                
            //下のif文を追加
            //ゴールにたどり着いたとき
            if (goalWord === data.page_title) {
                html += "<p>正しい</p>";
            }

            
            document.getElementById('main-text').innerHTML = html;
        })
```

すごくシンプルですが、一応ゴールした時に何らかのアクションを起こすことができました。

<h1>おわりに</h1>

以上、「スタートとなるワード」と「ゴールとなるワード」を取得し、ゴールに辿りついたら「ゴール！」と表示させる所まで作成しました。

一応、ゲームとして遊べるようにはなりましたね！

ちょっと時間を空けてしまったので、説明が難しかったな・・・。
もし分かりにくいところや、改善点があれば、是非教えてください！！

次回は、まだモードが一個のままなので、新しいモードを追加していきたいと思います。

Github：https://github.com/ITTON2001/WikiWord