医療情報技師の過去問サイトを爆速で作った話【Python／PHP】

# 背景
作成した医療情報技師の過去問サイトは以下です。

【2025/05/09】
サイト再開のご要望のため、以下のサイトに更新しました。

https://ryomeblog.github.io/hit/

~~http://homebit2019.php.xdomain.jp/~~

医療情報技師の資格試験が8月にあり、過去問の勉強しようと過去問サイトを探していたが、なかなか良いWebサイトがない。。。

以下のWebサイトを見つけたが、いくつか問題点があって、自作でWebサイトを作ることにしました。

https://iryoujyouhou.wiki.fc2.com/wiki/トップページ

# 問題点について

[背景](#背景)で述べた問題点についてですが、以下となります。

### 1. 問題の正解不正解を記録してくれない

自分が今何問正解しているのかを毎回紙にメモするのが面倒でした。
合格基準を満たしているか確認するために正答率も出したかったので、システム側で記録して欲しかった。。。

### 2. スマホ画面だと解答が丸見え

<img width="300" alt="01.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/449867/02b3d7ef-261a-128f-80d3-d0a3f67794c6.jpeg">

上記の画像のようにスマホ画面だと解答が丸見えでした。
見なければ良い話ですが、どうしても目に入ってしまって、問題解くのやめてしまうんですよね。。。

### 3. ボタンを押して正解か不正解か判別してほしい

![16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/449867/7430e052-deca-6c73-4e6f-457eb1d34f78.png)

![15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/449867/5dec9ba0-d260-419d-8ba4-5b0dad519409.png)

完全に個人的な意見ですが、ボタンを押して正解か不正解かメッセージで表示して欲しかったです。
チェックボックスをクリックして解答を確認するのではなく、正解不正解もシステム側で判定してほしい。。。


上記のような理由から自作でWebサイトを作ることになりました。
最初は暗記カードアプリ等の別な手段も考えましたが、暗記カード作るのに時間かかりそうで諦めました。

# 下準備

医療情報技師の過去問サイトを作るにあたって、以下の課題がありました。

### 課題1：問題と選択肢、解答をどうやって持ってくるか

これに関しては、先ほど紹介した以下のサイトから取得してきました。

https://iryoujyouhou.wiki.fc2.com/wiki/トップページ

取得方法に関しては、Pythonでリクエストを送って、レスポンスのhtmlから欲しい部分だけ抜いて、JSONファイルにしました。
以下、コードです。（コード長いので折りたたみました。）

<details><summary>Pythonコード</summary>

```python:main.py
import requests
import re
import json
import os


def create_json(url, year, num, file_name):
    question = str(num).zfill(2)

    try:
        response = requests.get(url)
        print(str(year) + '-' + str(question) + '-' + str(file_name))
        try:
            text = response.text[0:re.search('解説】.*</span></div>', response.text).start()]
        except:
            try:
                text = response.text[0:re.search('<div>.*解説】.*</div>', response.text).start()]
            except:
                text = response.text

        if not response.text.find('削除問題') < 0:
            print(str(year) + '-' + str(question) + '-' + str(file_name) + 'skip')
            return

        if not response.text.find('が見つかりませんでした。') < 0:
            print(str(year) + '-' + str(question) + '-' + str(file_name) + 'skip')
            return

        # タイトル
        title = re.findall('<title>.*</title>', text)[0].replace('<title>', '').replace('</title>', '')

        # 問題
        start = text.find('<div><span style="font-weight: bold;">問', 0)
        try:
            end = text.find('<div>5)', 0) + len(re.findall('<div>5\).*</div>\n<br />', text)[0])
        except:
            try:
                end = text.find('5)', 0) + len(re.findall('5\).*</pre>', text)[0])
            except:
                end = text.find('<div>5)', 0) + len(re.findall('<div>5\).*</div>', text)[0])
        if end < start or start < 0:
            start = text.find('<div class="user_body">', 0) + \
                    len(re.findall('<div class="user_body">', text)[0])
        q = text[start:end].replace('<div>', '').replace('</div>', '').replace('<br />', '') \
            .replace('<span style="font-weight: bold;">', '').replace('</span>', '').replace('	', '') \
            .replace('<pre>', '').replace('</pre>', '')
        splitq = q.split('\n')
        for num in range(len(splitq)):
            if not splitq[len(splitq) - num - 1].find('1)') < 0:
                select1 = splitq[len(splitq) - num - 1].replace('1) ', '').replace('\n', '')
                break
        for num in range(len(splitq)):
            if not splitq[len(splitq) - num - 1].find('2)') < 0:
                select2 = splitq[len(splitq) - num - 1].replace('2) ', '').replace('\n', '')
                break
        for num in range(len(splitq)):
            if not splitq[len(splitq) - num - 1].find('3)') < 0:
                select3 = splitq[len(splitq) - num - 1].replace('3) ', '').replace('\n', '')
                break
        for num in range(len(splitq)):
            if not splitq[len(splitq) - num - 1].find('4)') < 0:
                select4 = splitq[len(splitq) - num - 1].replace('4) ', '').replace('\n', '')
                break
        for num in range(len(splitq)):
            if not splitq[len(splitq) - num - 1].find('5)') < 0:
                select5 = splitq[len(splitq) - num - 1].replace('5) ', '').replace('\n', '')
                break

        # 解答
        try:
            ans = re.findall('【解答】.*</span></div>', text)[0].replace('</span></div>', '').replace('【解答】', '') \
                .replace('"', '').replace('\'', '')
        except:
            try:
                ans = re.findall('解答】.*</span></div>', text)[0].replace('</span></div>', '').replace('解答】', '') \
                    .replace('"', '').replace('\'', '')
            except:
                ans = re.findall('解答】.*</div>', text)[0].replace('</span></div>', '').replace('解答】', '') \
                    .replace('"', '').replace('\'', '')

        dist = {
            'title': title,
            'question': q,
            'select1': select1,
            'select2': select2,
            'select3': select3,
            'select4': select4,
            'select5': select5,
            'answer': ans,
            'url': url
        }

        with open('./json/' + str(year) + '/' + str(file_name) + '-' + str(year) + '-' + str(question) + '.json', "w",
                  encoding="utf-8") as outputFile:
            json.dump(dist, outputFile, indent=2, ensure_ascii=False)

    except Exception as e:
        print('Error：' + str(year) + '-' + str(question) + '-' + str(file_name))


def create(year, question_ipt, question_mms, question_mis):
    try:
        os.mkdir('./json/' + str(year))
    except FileExistsError:
        pass

    # IPT
    for num in range(question_ipt):
        question = str(num+1).zfill(2)
        url = 'https://iryoujyouhou.wiki.fc2.com/wiki/' + str(year) + '>情報処理技術系>問題と解説>' + question
        create_json(url, year, str(num+1), 'ipt')


    # MMS
    for num in range(question_mms):
        question = str(num+1).zfill(2)
        url = 'https://iryoujyouhou.wiki.fc2.com/wiki/' + str(year) + '>医学医療系>問題と解説>' + question
        create_json(url, year, str(num+1), 'mms')

    # MIS
    for num in range(question_mis):
        question = str(num+1).zfill(2)
        url = 'https://iryoujyouhou.wiki.fc2.com/wiki/' + str(year) + '>医療情報システム系>問題と解説>' + question
        create_json(url, year, str(num+1), 'mis')

if __name__ == "__main__":
    create('2021', 50, 50, 60)
    create('2019', 50, 50, 60)
    create('2018', 50, 50, 60)
    create('2017', 50, 50, 60)
    create('2016', 50, 50, 60)
    create('2015', 50, 50, 60)
    create('2014', 50, 50, 60)
    create('2013', 50, 50, 60)
    create('2012', 50, 50, 60)
    create('2011', 50, 50, 60)

```

</details>

![02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/449867/b10f8258-8f7d-9fed-dff7-69384ed6c530.png)

とりあえず、早く作ることを優先したので、htmlから `問題文`, `選択肢`, `解答` を抜き出すことだけに集中しました。

苦労した点が年度によってフォーマットが違う問題があることです。
`【解答】` だったり、`解答】` だったり。。。
`<pre>` だったり、`<div>` だったり、、、

そんなこんなで、全パターンに対応しようとしたらコードがカオスになっていきました (´・ω・｀)
もっとちゃんと考えれば正規表現で何とかなるんだろうけど。。。

### 課題2：どうやって作るか

問題、選択肢、解答がJSONファイルで揃ったので、あとは画面に問題と選択肢を表示して、
選択肢がクリックされたら、解答と比較して正解か不正解かを返すだけです。
また、問題の正解不正解を記録する必要もあるので、問題の正解数とトータルをセッションに持たせることにしました。

機能としてまとめるなら以下です。
- 正解・不正解 判定機能
- 問題・選択肢 取得機能
- 問題一覧 取得機能
- 解答 取得機能
- 正解数・トータル 取得機能

サーバに関しては、無料レンタルサーバーの `XFREE` を使うことにしました。

https://www.xfree.ne.jp/

Dockerfileも作ってあるので、環境については以下を参考にしてください。

https://github.com/ryomeblog/hit

# 実装

とにかく早く作りたかったのでコードとか環境とか気にせず、PHPで作りました。
画面のデザインについては [Bootstrap5](https://getbootstrap.jp/) を使ってある程度整えました。
以下、コードです。（コード長いので折りたたみました。）

<details><summary>PHPコード</summary>

```php:index.php
<?php
session_start();
if (empty($_SESSION['score'])) {
    $_SESSION['score'] = 0;
}
if (empty($_SESSION['total'])) {
    $_SESSION['total'] = 0;
}
if (!empty($_POST['reset'])) {
    $_SESSION['score'] = 0;
    $_SESSION['total'] = 0;
}

$dir = './json/';
$dirlist = glob($dir . '*', GLOB_ONLYDIR);


?>
<!doctype html>
<html lang="en">

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <!-- Bootstrap CSS -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">

    <title>医療情報技師試験対策</title>
</head>

<body>
    <nav class="navbar navbar-expand-lg navbar-light bg-light">
        <div class="container-fluid">
            <a class="navbar-brand" href="./index.php">医療情報技師試験対策サイト</a>
            <form action="./index.php" method="post" class="navbar-toggler">
                <input name="reset" type="hidden" value="reset">
                <button type="submit" class="btn btn-secondary">リセット</button>
            </form>

            <ul class="navbar-nav">
                <li class="nav-item collapse navbar-collapse">
                    <form action="./index.php" method="post">
                        <input name="reset" type="hidden" value="reset">
                        <button type="submit" class="btn btn-secondary">リセット</button>
                    </form>
                </li>
                <li class="nav-item">
                    <button type="button" class="btn btn-success">
                        正解数： <span class="badge bg-secondary"><?php echo ($_SESSION['score']); ?>問</span>
                    </button>
                    <button type="button" class="btn btn-info">
                        トータル： <span class="badge bg-secondary"><?php echo ($_SESSION['total']); ?>問</span>
                    </button>
                </li>
            </ul>
        </div>
    </nav>
    <div class="container">
        <div class="row">
            <div class="col">
                <div class="card">
                    <div class="card-header">
                        <h5 class="card-title">このサイトについて</h5>
                    </div>
                    <div class="card-body">
                        このサイトは医療情報技師の勉強をするために作りました。<br />
                        以下のサイトを参考にして作っています。<br />
                        <br />
                        <a href="https://iryoujyouhou.wiki.fc2.com/wiki/トップページ"> https://iryoujyouhou.wiki.fc2.com/wiki/トップページ</a>
                    </div>
                </div>
                <br />
                <?php foreach (array_reverse($dirlist) as $dirname) { ?>
                    <div class="card">
                        <div class="card-header">
                            <h5 class="card-title"><?php echo (substr($dirname, -4)) ?>年度 医療情報技師試験対策</h5>
                        </div>
                        <div class="card-body">
                            <a href="./question.php?year=<?php echo (substr($dirname, -4)) ?>&kind=ipt" class="btn btn-primary">情報処理技術系</a>
                            <a href="./question.php?year=<?php echo (substr($dirname, -4)) ?>&kind=mis" class="btn btn-warning">医学医療系</a>
                            <a href="./question.php?year=<?php echo (substr($dirname, -4)) ?>&kind=mms" class="btn btn-success">医療情報システム系</a>
                        </div>
                    </div>
                <?php } ?>
            </div>
        </div>
    </div>

    <!-- Bootstrap JS -->
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js"></script>

</body>

</html>
```

```php:question.php
<?php
session_start();
if (empty($_SESSION['score'])) {
    $_SESSION['score'] = 0;
}
if (empty($_SESSION['total'])) {
    $_SESSION['total'] = 0;
}
if (empty($_GET["year"])) {
    $_GET["year"] = '2021';
}
if (empty($_GET["kind"])) {
    $_GET["kind"] = 'ipt';
}

$dir = './json/' . $_GET["year"] . '/';
$filelist = glob($dir . $_GET["kind"] . '*');


?>
<!doctype html>
<html lang="en">

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <!-- Bootstrap CSS -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">

    <title>医療情報技師試験対策</title>
</head>

<body>
    <nav class="navbar navbar-expand-lg navbar-light bg-light">
        <div class="container-fluid">
            <a class="navbar-brand" href="./index.php">医療情報技師試験対策サイト</a>
            <a href="./index.php?year=<?php echo ($_GET["year"]) ?>" class="btn btn-secondary navbar-toggler">戻る</a>
            <div class="collapse navbar-collapse">
                <a href="./index.php?year=<?php echo ($_GET["year"]) ?>" class="btn btn-secondary">戻る</a>
            </div>

            <ul class="navbar-nav">
                <li class="nav-item">
                    <button type="button" class="btn btn-success">
                        正解数： <span class="badge bg-secondary"><?php echo ($_SESSION['score']); ?>問</span>
                    </button>
                    <button type="button" class="btn btn-info">
                        トータル： <span class="badge bg-secondary"><?php echo ($_SESSION['total']); ?>問</span>
                    </button>
                </li>
            </ul>
        </div>
    </nav>
    <div class="container">
        <div class="row">
            <div class="col">
                <?php foreach ($filelist as $filename) { ?>
                    <div class="card">
                        <div class="card-header">
                            <h5 class="card-title">
                                医療情報技師試験 <?php
                                            switch ($_GET["kind"]) {
                                                case 'ipt':
                                                    echo ('情報処理技術系');
                                                    break;
                                                case 'mis':
                                                    echo ('医学医療系');
                                                    break;
                                                case 'mms':
                                                    echo ('医療情報システム系');
                                                    break;
                                            }
                                            ?> 問<?php echo (substr($filename, 21, 2)) ?>
                            </h5>
                        </div>
                        <div class="card-body">
                            <a href="./question2.php?year=<?php echo ($_GET["year"]) ?>&kind=<?php echo ($_GET["kind"]) ?>&num=<?php echo (substr($filename, 21, 2)) ?>" class="btn btn-primary">問題へ</a>
                        </div>
                    </div>
                <?php } ?>
            </div>
        </div>
    </div>

    <!-- Bootstrap JS -->
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js"></script>

</body>

</html>
```

```php:question2.php
<?php
session_start();
if (empty($_SESSION['score'])) {
    $_SESSION['score'] = 0;
}
if (empty($_SESSION['total'])) {
    $_SESSION['total'] = 0;
}
if (empty($_GET["year"])) {
    $_GET["year"] = '2021';
}
if (empty($_GET["kind"])) {
    $_GET["kind"] = 'ipt';
}
if (empty($_GET["num"])) {
    $_GET["num"] = '01';
}

$result = null;
$jsonUrl = './json/' . $_GET["year"] . '/' . $_GET["kind"] . '-' . $_GET["year"] . '-' . $_GET["num"] . '.json';

if (file_exists($jsonUrl)) {
    $json = file_get_contents($jsonUrl);
    $json = mb_convert_encoding($json, 'UTF8', 'ASCII,JIS,UTF-8,EUC-JP,SJIS-WIN');
    $obj = json_decode($json, true);

    if (strpos($obj["answer"], ',')) {
        $answers = explode(",", $obj["answer"]);
        if (!empty($_GET["select"])) {
            $ansFlg = true;
            foreach ($answers as $answer) {
                $flg = false;
                foreach ($_GET["select"] as $select) {
                    if ($answer == $select) {
                        $flg = true;
                    }
                }
                $ansFlg = $ansFlg && $flg;
            }
            if ($ansFlg) {
                $_SESSION['score'] = $_SESSION['score'] + 1;
                $_SESSION['total'] = $_SESSION['total'] + 1;
                $result = '<div class="alert alert-success" role="alert">正解です。</div>';
            } else {
                $_SESSION['total'] = $_SESSION['total'] + 1;
                $result = '<div class="alert alert-danger" role="alert">不正解です。</div>';
            }
        }
    } else {
        if (!empty($_GET["select"])) {
            if ($_GET["select"] == $obj["answer"]) {
                $_SESSION['score'] = $_SESSION['score'] + 1;
                $_SESSION['total'] = $_SESSION['total'] + 1;
                $result = '<div class="alert alert-success" role="alert">正解です。</div>';
            } else {
                $_SESSION['total'] = $_SESSION['total'] + 1;
                $result = '<div class="alert alert-danger" role="alert">不正解です。</div>';
            }
        }
    }

    $dir = './json/' . $_GET["year"] . '/';
    $filelist = glob($dir . $_GET["kind"] . '*');
    $nextFlg = false;
    $nextBtnFlg = false;
    $nextNum = '01';

    foreach ($filelist as $filename) {
        if ($nextFlg) {
            $nextBtnFlg = true;
            $nextNum = substr($filename, 21, 2);
            break;
        }
        if (strpos($filename, $_GET["num"] . '.json')) {
            $nextFlg = true;
        }
    }
    $backFlg = false;
    $backBtnFlg = false;
    $backNum = '01';

    foreach (array_reverse($filelist) as $filename) {
        if ($backFlg) {
            $backBtnFlg = true;
            $backNum = substr($filename, 21, 2);
            break;
        }
        if (strpos($filename, $_GET["num"] . '.json')) {
            $backFlg = true;
        }
    }
}

?>
<!doctype html>
<html lang="en">

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <!-- Bootstrap CSS -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">

    <title>医療情報技師試験対策</title>
</head>

<body>
    <nav class="navbar navbar-expand-lg navbar-light bg-light">
        <div class="container-fluid">
            <a class="navbar-brand" href="./index.php">医療情報技師試験対策サイト</a>
            <a class="navbar-toggler btn btn-secondary" href="./question.php?year=<?php echo ($_GET["year"]) ?>&kind=<?php echo ($_GET["kind"]) ?>">戻る</a>
            <div class="collapse navbar-collapse">
                <a class="btn btn-secondary" href="./question.php?year=<?php echo ($_GET["year"]) ?>&kind=<?php echo ($_GET["kind"]) ?>">戻る</a>
            </div>

            <ul class="navbar-nav">
                <li class="nav-item">

                    <button type="button" class="btn btn-success">
                        正解数： <span class="badge bg-secondary"><?php echo ($_SESSION['score']); ?>問</span>
                    </button>
                    <button type="button" class="btn btn-info">
                        トータル： <span class="badge bg-secondary"><?php echo ($_SESSION['total']); ?>問</span>
                    </button>
                </li>
            </ul>
        </div>
    </nav>

    <div class="container">
        <div class="row">
            <div class="col">
                <div class="card">
                    <div class="card-header">
                        <h5 class="card-title">
                            医療情報技師試験 <?php
                                        switch ($_GET["kind"]) {
                                            case 'ipt':
                                                echo ('情報処理技術系');
                                                break;
                                            case 'mis':
                                                echo ('医学医療系');
                                                break;
                                            case 'mms':
                                                echo ('医療情報システム系');
                                                break;
                                        }
                                        ?> 問<?php echo ($_GET["num"]) ?>
                        </h5>
                        <?php if ($nextBtnFlg) { ?>
                            <a href="./question2.php?year=<?php echo ($_GET["year"]) ?>&kind=<?php echo ($_GET["kind"]) ?>&num=<?php echo ($nextNum) ?>" class="btn btn-primary float-end">次の問題へ</a>
                        <?php } ?>
                        <?php if ($backBtnFlg) { ?>
                            <a href="./question2.php?year=<?php echo ($_GET["year"]) ?>&kind=<?php echo ($_GET["kind"]) ?>&num=<?php echo ($backNum) ?>" class="btn btn-secondary float-end">前の問題へ</a>
                        <?php } ?>
                    </div>
                    <div class="card-body">
                        <?php if (!empty($result)) {
                            echo ($result);
                        } ?>
                        <?php echo "<p>問題：" . nl2br($obj["question"]) . "</p>" ?>
                        <br />
                        <?php if (strpos($obj["answer"], ',')) { ?>
                            <form action="question2.php" method="get">
                                <input type="checkbox" class="btn-check" id="btn-check-outlined" autocomplete="off">

                                <input type="checkbox" class="btn-check" id="btncheck1" autocomplete="off" name="select[]" value="1">
                                <label for="btncheck1" class="btn btn-outline-primary">1: <?php echo ($obj["select1"]) ?></label>

                                <input type="checkbox" class="btn-check" id="btncheck2" autocomplete="off" name="select[]" value="2">
                                <label for="btncheck2" class="btn btn-outline-primary">2: <?php echo ($obj["select2"]) ?></label>

                                <input type="checkbox" class="btn-check" id="btncheck3" autocomplete="off" name="select[]" value="3">
                                <label for="btncheck3" class="btn btn-outline-primary">3: <?php echo ($obj["select3"]) ?></label>

                                <input type="checkbox" class="btn-check" id="btncheck4" autocomplete="off" name="select[]" value="4">
                                <label for="btncheck4" class="btn btn-outline-primary">4: <?php echo ($obj["select4"]) ?></label>

                                <input type="checkbox" class="btn-check" id="btncheck5" autocomplete="off" name="select[]" value="5">
                                <label for="btncheck5" class="btn btn-outline-primary">5: <?php echo ($obj["select5"]) ?></label>

                                <input name="year" type="hidden" value="<?php echo ($_GET["year"]) ?>">
                                <input name="kind" type="hidden" value="<?php echo ($_GET["kind"]) ?>">
                                <input name="num" type="hidden" value="<?php echo ($_GET["num"]) ?>">

                                <br />
                                <br />
                                <button type="submit" class="btn btn-primary">解答</button>
                            </form>
                        <?php } else { ?>
                            <a href="./question2.php?year=<?php echo ($_GET["year"]) ?>&kind=<?php echo ($_GET["kind"]) ?>&num=<?php echo ($_GET["num"]) ?>&select=1" class="btn btn-primary">1: <?php echo ($obj["select1"]) ?></a>
                            <a href="./question2.php?year=<?php echo ($_GET["year"]) ?>&kind=<?php echo ($_GET["kind"]) ?>&num=<?php echo ($_GET["num"]) ?>&select=2" class="btn btn-primary">2: <?php echo ($obj["select2"]) ?></a>
                            <a href="./question2.php?year=<?php echo ($_GET["year"]) ?>&kind=<?php echo ($_GET["kind"]) ?>&num=<?php echo ($_GET["num"]) ?>&select=3" class="btn btn-primary">3: <?php echo ($obj["select3"]) ?></a>
                            <a href="./question2.php?year=<?php echo ($_GET["year"]) ?>&kind=<?php echo ($_GET["kind"]) ?>&num=<?php echo ($_GET["num"]) ?>&select=4" class="btn btn-primary">4: <?php echo ($obj["select4"]) ?></a>
                            <a href="./question2.php?year=<?php echo ($_GET["year"]) ?>&kind=<?php echo ($_GET["kind"]) ?>&num=<?php echo ($_GET["num"]) ?>&select=5" class="btn btn-primary">5: <?php echo ($obj["select5"]) ?></a>
                        <?php } ?>
                    </div>
                </div>
                <?php if (!empty($result)) { ?>
                    <div class="card">
                        <div class="card-header">
                            <h5 class="card-title">答え</h5>
                        </div>
                        <div class="card-body">
                            <?php echo $obj["answer"] ?><br />
                            <br />
                            解説は<a href="<?php echo $obj["url"] ?>">こちら</a>
                        </div>
                    </div>
                <?php } ?>
            </div>
        </div>
    </div>

    <!-- Bootstrap JS -->
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js"></script>

</body>

</html>
```

</details>

# サイト紹介

サイトについては以下に公開しています。

【2025/05/09】
サイト再開のご要望のため、以下のサイトに更新しました。

https://ryomeblog.github.io/hit/

~~http://homebit2019.php.xdomain.jp/~~

### トップページ（index.php）

![07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/449867/d4987934-adf3-5987-611f-a3370eaa68c1.png)

トップページです。
勉強したい年度の `情報処理技術系`, `医学医療系`, `医療情報システム系` を選び、ボタンを押すと問題選択ページに遷移します。
また、トップページで `正解数` と `トータル` のリセットができます。

### 問題選択ページ（question.php）

![08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/449867/0dd8ae34-063e-c2e1-06d8-424edddf38a6.png)

問題選択ページです。
勉強したい問題の `問題へ` ボタンを押すと問題ページに遷移します。
また、戻るボタンでトップページに戻れます。

### 問題ページ（question2.php）

![11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/449867/9fc4541a-1ce6-5773-64fe-e550425aee64.png)

![10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/449867/de581a46-97c8-576e-aa34-eb2feace2368.png)

![12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/449867/75e9c48c-46b6-8c82-fd07-5aba00a54333.png)

![13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/449867/bb6630ae-e66b-51e2-9653-427490c08d99.png)

問題ページです。
解答だと思う選択肢を選び、ボタンを押すことで、正解か不正解か判定してメッセージが出ます。
選択問題の場合は、選択肢を複数選択し、解答ボタンを押すことで、正解か不正解か判定してメッセージが出ます。
問題を解くごとに右上の `トータル` が1増えます。
問題に正解するごとに右上の `正解数` が1増えます。
また、戻るボタンで問題選択ページに戻れます。

# 改善点
### コードが読みにくい

とにかくコードが読みにくくて、今後の改修が困難です。。。

今回は、サーバ２台用意するのが面倒だったり、
Swaggerとか書いてる時間なかったり、
勉強時間確保したいから短時間で作らないといけなかったり...etc
の理由からREST APIで作ることを断念しましたが、
作り直す機会があれば、フロントエンドとバックエンドを分けて作りたいなって思ってます。
（そんな機会ないけど。。。）

### 複数の選択肢から選ぶ問題が全部選択できてしまう

![04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/449867/aefca974-f1d1-7a3a-9875-9a01f4e216d1.png)

![06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/449867/0f088a6b-4ecc-3d0a-c29d-0dba8648ea41.png)

これは完全にバグなのですが、改修が面倒だったので仕様にしてます。（笑）
一応勉強するために作ったサイトなので、全問選択とか変な使い方する人はいないんじゃないかと。。。

### 問題が追加できない
[背景で紹介したサイト](#背景) からであれば、 [Pythonで作ったツール](#問題と選択肢、解答をどうやって持ってくるか) で新しい問題を追加できるのですが、
他の方法で問題を追加する方法が今のところありません。
（手書きでJSONファイル作れば追加できますが、、、）
今後は、問題を追加するページも作りたいですね。
（というか [Pythonツール](#問題と選択肢、解答をどうやって持ってくるか) はバッチ化しちゃおうかな。。。）

# GitHub
GitHubにソースコードを公開しています。

https://github.com/ryomeblog/hit

# まとめ

このWebサイトを作るのに約1日程度（夜に始めたので実質8時間くらい）かけました。
とりあえず、勉強に使える程度のWebサイトはできましたが、改善点も多く残ったので、今後に活かしていきたいです。

今後も生の開発体験談を載せていこうと思うので、読んでもらえると嬉しいです。
以上です。ここまで読んでいただき、ありがとうございました！！