LoginSignup
1
1

More than 3 years have passed since last update.

HTML・PHP・MySQLだけで作る間違い探しゲーム【③問題表示ページを作る】

Last updated at Posted at 2020-10-26

3. 問題表示ページの実装


<< 前の記事 【②スタートページを作る】
2. スタートページの実装
  2-1. セッション
   2-1-1. セッションの開始
   2-1-2. セッションの放棄
  2-2. バリデーション
   2-2-1. 無効な送信の拒否
   2-2-2. 名前のバリデーション
    2-2-2-1. 半角スペースのみ無効
    2-2-2-2. 文字数
    2-2-2-3. 空白文字/制御文字無効
   2-2-3. エラーメッセージの表示
   2-2-4. 入力値の保持 - タグ
  2-3. 問題表示ページへの遷移


次の記事 >> 【④DB接続設定ファイルを作る】
4. DB接続設定
  4-1. PDOオブジェクトの属性
   4-1-1. フェッチ形式の指定
   4-1-2. エラーモードの設定
    4-1-2-1. エラーモードの違いによるエラー文の違い
   4-1-3. エミュレーションの設定
    4-1-3-1. プリペアドステートメント
    4-1-3-2. エミュレーション
  4-2. 例外発生時の処理
   4-2-1. HTTPヘッダの送信
   4-2-2. 処理の中断とエラーメッセージの表示
  4-3. DBの切断について

ここではゲームのコアとなる問題を生成するページを実装します。

難易度「易しい」の問題例
問題ページ_易しい.png
難易度「難しい」の問題例
問題ページ_難しい.png

find_the_mistake.php
<?php

// セッションの再開
session_start();

// 無効なアクセスの拒否
if (empty($_SESSION)) {
    header('Location:start.php');
    exit();
}

// リセット回数の計測開始
if (isset($_SESSION['count'])) {
    $_SESSION['count']++;
} else {
    $_SESSION['count'] = 0;
}

// ターゲット配列の設定
if ($_SESSION['difficulty'] === '難しい(漢字)') {
    $chars = [
        ['猫', '描'],
        ['犬', '大'],
        ['幸', '辛'],
        ['白', '臼'],
        ['矢', '失'],
        ['力', '刀'],
        ['防', '妨'],
        ['土', '士'],
        ['卵', '卯'],
        ['巨', '臣'],
        ['寒', '塞'],
        ['旅', '族'],
        ['車', '東'],
        ['釘', '針']
    ];
} else {
    $chars = [
        ['&#x1f415;', '&#x1f408;'],
        ['&#x1f405;', '&#x1f406;'],
        ['&#x1f98e;', '&#x1f40d;'],
        ['&#x1f433;', '&#x1f42c;'],
        ['&#x1f339;', '&#x1f337;'],
        ['&#x1f34a;', '&#x1f34b;'],
        ['&#x1f34e;', '&#x1f351;'],
        ['&#x1f955;', '&#x1f336;'],
        ['&#x1f96f;', '&#x1f95e;'],
        ['&#x1f358;', '&#x1f359;'],
        ['&#x1f341;', '&#x1f342;'],
        ['&#x1f332;', '&#x1f333;'],
        ['&#x1f47b;', '&#x1f47d;'],
        ['&#x1f396;', '&#x1f3c5;']
    ];
}

// ターゲット配列のシャッフル
shuffle($chars);
for ($i = 0; $i < count($chars); $i++) {
    shuffle($chars[$i]);
}

// 正解と選択対象配列を設定
$correct = $chars[0][0];
$_SESSION['correct'] = $correct;
for ($j = 0; $j <= 19; $j++) {
    for ($k = 0; $k <= 19; $k++) {
        $targets[$j][$k] = $chars[0][1];
    }
}

// 正解のターゲットを選択対象配列に1つだけ挿入
$key = range(0, 19);
$key1 = array_rand($key);
$key2 = array_rand($key);
$targets[$key1][$key2] = $correct;

// 開始時刻を記録
$_SESSION['start_time'] = microtime(true);

?>
<!DOCTYPE html>
<html lang="ja">
    <head>
        <meta charset="UTF-8">
        <title>間違い探し</title>
    </head>
    <body>
        <h1><?= $correct; ?>を見つけよう!</h1>
        <p>
            <small>難易度: <?= $_SESSION['difficulty']; ?></small>
            <button type="button" onclick="location.href='find_the_mistake.php'">分かるか!(リセット)</button>
            <button type="button" onclick="location.href='start.php'">スタートページへ</button>
        </p>
        <form action="result.php" method="POST">
            <table>
                <?php foreach ($targets as $target): ?>
                <tr>
                    <?php for ($l = 0; $l < count($target); $l++): ?>
                    <td><input type="submit" name="answer" value="<?= $target[$l]; ?>"></td>
                    <?php endfor; ?>
                </tr>
                <?php endforeach; ?>
            </table>
        </form>
    </body>
</html>

3-1. 無効なアクセスの拒否

find_the_mistake.php
// 無効なアクセスの拒否
if (empty($_SESSION)) {
    header('Location:start.php');
    exit();
}

不正な画面遷移とエラーを防ぐために、
start.php」で設定したセッション変数がない場合は、
スタートページにリダイレクトされるようにしています。

3-2. リセット回数の計測

このゲームでは、問題が難しい場合にリセットできる機能を実装しており、
リセットボタンが押されると問題生成ページが再描写されます。

この時、セッション変数にcountをキーとしたリセット回数を記録させています。

セッション変数とすることで、結果ページでもリセット回数を取り扱えるようになります。

find_the_mistake.php
// リセット回数の計測開始
if (isset($_SESSION['count'])) {
    $_SESSION['count']++;
} else {
    $_SESSION['count'] = 0;
}
  • 既に設定されていた場合はインクリメント
  • それ以外では0を設定

とすることでリセット回数が正しくカウントされるようにしています。

3-3. 問題用文字配列の設定

find_the_mistake.php
// ターゲット配列の設定
if ($_SESSION['difficulty'] === '難しい(漢字)') {
    $chars = [
        ['猫', '描'],
        ['犬', '大'],
        ['幸', '辛'],
        ['白', '臼'],
        ['矢', '失'],
        ['力', '刀'],
        ['防', '妨'],
        ['土', '士'],
        ['卵', '卯'],
        ['巨', '臣'],
        ['寒', '塞'],
        ['旅', '族'],
        ['車', '東'],
        ['釘', '針']
    ];
} else {
    $chars = [
        ['&#x1f415;', '&#x1f408;'],
        ['&#x1f405;', '&#x1f406;'],
        ['&#x1f98e;', '&#x1f40d;'],
        ['&#x1f433;', '&#x1f42c;'],
        ['&#x1f339;', '&#x1f337;'],
        ['&#x1f34a;', '&#x1f34b;'],
        ['&#x1f34e;', '&#x1f351;'],
        ['&#x1f955;', '&#x1f336;'],
        ['&#x1f96f;', '&#x1f95e;'],
        ['&#x1f358;', '&#x1f359;'],
        ['&#x1f341;', '&#x1f342;'],
        ['&#x1f332;', '&#x1f333;'],
        ['&#x1f47b;', '&#x1f47d;'],
        ['&#x1f396;', '&#x1f3c5;']
    ];
}

// ターゲット配列のシャッフル
shuffle($chars);
for ($i = 0; $i < count($chars); $i++) {
    shuffle($chars[$i]);
}

// 正解と選択対象配列を設定
$correct = $chars[0][0];
$_SESSION['correct'] = $correct;
for ($j = 0; $j <= 19; $j++) {
    for ($k = 0; $k <= 19; $k++) {
        $targets[$j][$k] = $chars[0][1];
    }
}

// 正解のターゲットを選択対象配列に1つだけ挿入
$key = range(0, 19);
$key1 = array_rand($key);
$key2 = array_rand($key);
$targets[$key1][$key2] = $correct;

3-3-1. 文字ペア配列の選択

選択された難易度によって漢字か絵文字かを分岐させます。

find_the_mistake.php
// ターゲット配列の設定
if ($_SESSION['difficulty'] === '難しい(漢字)') {
    $chars = [
        ['猫', '描'],
        ['犬', '大'],
        ['幸', '辛'],
        ['白', '臼'],
        ['矢', '失'],
        ['力', '刀'],
        ['防', '妨'],
        ['土', '士'],
        ['卵', '卯'],
        ['巨', '臣'],
        ['寒', '塞'],
        ['旅', '族'],
        ['車', '東'],
        ['釘', '針']
    ];
} else {
    $chars = [
        ['&#x1f415;', '&#x1f408;'],
        ['&#x1f405;', '&#x1f406;'],
        ['&#x1f98e;', '&#x1f40d;'],
        ['&#x1f433;', '&#x1f42c;'],
        ['&#x1f339;', '&#x1f337;'],
        ['&#x1f34a;', '&#x1f34b;'],
        ['&#x1f34e;', '&#x1f351;'],
        ['&#x1f955;', '&#x1f336;'],
        ['&#x1f96f;', '&#x1f95e;'],
        ['&#x1f358;', '&#x1f359;'],
        ['&#x1f341;', '&#x1f342;'],
        ['&#x1f332;', '&#x1f333;'],
        ['&#x1f47b;', '&#x1f47d;'],
        ['&#x1f396;', '&#x1f3c5;']
    ];
}

正解・不正解のペア関係を維持するため1に、
多次元配列として変数$charsに格納しています。

絵文字は&XXX;という形式で記述されていますが、
これはHTMLエンティティと呼ばれるもので、
これにより見えない文字や標準キーボードでは入力が難しい文字を容易に取り扱えます

また、viエディタ上でのバグを防ぐこともできます2

MDN web docs「Entity (エンティティ) - MDN Web Docs 用語集: ウェブ関連用語の定義 | MDN


今回用いた各エンティティとwebページ上で表示される絵文字の対応はこちら
&#x1f415; => 「🐕」と &#x1f408; => 「🐈」
&#x1f405; => 「🐅」と &#x1f406; => 「🐆」
&#x1f98e; => 「🦎」と &#x1f40d; => 「🐍」
&#x1f433; => 「🐳」と &#x1f42c; => 「🐬」
&#x1f339; => 「🌹」と &#x1f337; => 「🌷」
&#x1f34a; => 「🍊」と &#x1f34b; => 「🍋」
&#x1f34e; => 「🍎」と &#x1f351; => 「🍑」
&#x1f955; => 「🥕」と &#x1f336; => 「🌶」
&#x1f96f; => 「🥯」と &#x1f95e; => 「🥞」
&#x1f358; => 「🍘」と &#x1f359; => 「🍙」
&#x1f341; => 「🍁」と &#x1f342; => 「🍂」
&#x1f332; => 「🌲」と &#x1f333; => 「🌳」
&#x1f47b; => 「👻」と &#x1f47d; => 「👽」
&#x1f396; => 「🎖」と &#x1f3c5; => 「🏅」

絵文字のHTMLエンティティを調べるには下記サイトが参考になります。

Let's EMOJI「Unicode 13.0 絵文字 (Unicode 13.0 Emoji) | Let's EMOJI

3-3-2. 文字ペア配列及び文字ペアのシャッフル

難易度がどちらの場合でも、正解・不正解のペアは14組あるので、
正解・不正解関係は保ったままでこれらをシャッフルし、問題にランダム性を付与します。
これによりリセットする度に異なる問題が表示されるようになります。

find_the_mistake.php
// ターゲット配列のシャッフル
shuffle($chars);
for ($i = 0; $i < count($chars); $i++) {
    shuffle($chars[$i]);
}

配列のシャッフルにはshuffle()が使えます。

shuffle()は「シャッフルされた配列」ではなく、
シャッフルが成功したかどうかの真偽値」を返しますので、
変数に格納する必要はありません。

PHPマニュアル「PHP: shuffle - Manual

まず、文字ペアの多次元配列を格納した$charsをシャッフルし、
14組の文字ペアの並び(=多次元配列の第1層)」をランダムにします。

続いて、for構文を用いて再帰的にシャッフルすることで、
文字ペアの中での並び(=多次元配列の第2層)」もランダムにします3

こうすることで「ペアの中でいつもどちらかが正解/不正解」となってしまうのを防ぎ、
ゲーム性を保てます。

正解・不正解のペアが14組あり、ペアの中でも正解・不正解が入れ替わることから、
各難易度毎に14 × 2 = 28通りの問題を生成できます(ゲーム全体では56通り)。

最初に変数$charsに格納した文字ペアの多次元配列にペアを加えることで、
更にパターンを増やすこともできます。

3-3-3. 正解文字と不正解文字配列の設定

ランダムにシャッフルされた文字ペア配列から1文字だけ選択して正解の文字とします。
次いで、「正解の文字とペアになっている文字」を用いて不正解文字の配列を生成します。

find_the_mistake.php
// 正解と選択対象配列を設定
$correct = $chars[0][0];
$_SESSION['correct'] = $correct;
for ($j = 0; $j <= 19; $j++) {
    for ($k = 0; $k <= 19; $k++) {
        $targets[$j][$k] = $chars[0][1];
    }
}

3-3-3-1. 正解文字の設定

$correct正解とする文字を代入しています。
文字ペアの多次元配列を再帰的にシャッフルしたので、
$chars[0][0]には、
28(14 × 2)文字からランダムに選択された1文字
が格納されています。

結果ページを生成するファイルで正解/不正解の判定を行うので、
セッション変数にもcorrectをキーとして正解文字を格納しています。

3-3-3-2. 不正解文字配列の設定

次いで、正解文字と対になる不正解文字を、
$targets20 × 20の多次元配列として格納しています。

対になる文字は$charsの正解文字と同じ階層にある異なるキーの値のはずなので、
今回のコードでは$chars[0][1]が該当します。

この不正解文字を、変数$j,$k0 ~ 19の範囲で2重の繰り返し処理を回す中で、
$targetsに対しキーを[$j][$k]とした値とすることで、多次元配列を生成しています4

これで正解文字と不正解文字配列の設定が完了しました。

3-3-4. 問題用文字配列の生成

不正解文字配列のランダムな1つの値のみを正解文字に置き換えることで、
問題用の文字配列を作り出します。

find_the_mistake.php
// 正解のターゲットを選択対象配列に1つだけ挿入
$key = range(0, 19);
$key1 = array_rand($key);
$key2 = array_rand($key);
$targets[$key1][$key2] = $correct;

まず、range()を用いて値が0 ~ 19の範囲の整数を値としてもつ配列を生成し、
$keyに格納しています。

その後、array_rand()によって$keyの配列からランダムにキーを取り出します。
これを2回繰り返してそれぞれ$key1$key2に格納することで、
1 ~ 19の範囲内のランダムな整数を選択しました。

後はこの$key1$key2を不正解文字配列$targetsキーとして指定することで、
多次元配列の中のランダムな1つの値」を指定しています5

PHPマニュアル「PHP: range - Manual
PHPマニュアル「PHP: array_rand - Manual

これでようやく問題用の文字配列が完成しました!

3-4. 開始時刻の記録

回答時間を計測する為に問題が表示される直前に開始時刻を記録します。

find_the_mistake.php
// 開始時刻を記録
$_SESSION['start_time'] = microtime(true);

選択された文字の正解/不正解判定と同様に、結果ページで判定するので、
セッション変数として保存しています。

microtime()現在のタイムスタンプをマイクロ秒まで返す関数で、

  • 引数を指定しない場合はmsec sec形式の文字列
  • trueを指定した場合はfloat型のマイクロ秒

を、それぞれ返します。

PHPマニュアル「PHP: microtime - Manual

結果ページでは引き算で回答時間を算出するので、
計算しやすいタイムスタンプ形式となるように引数にはtrueを指定しています。

3-5. 選択肢の描写

問題用の文字配列を、繰り返し処理によって描写し、20×20のボタンを表示させます。

find_the_mistake.php
        <form action="result.php" method="POST">
            <table>
                <?php foreach ($targets as $target): ?>
                <tr>
                    <?php for ($l = 0; $l < count($target); $l++): ?>
                    <td><input type="submit" name="answer" value="<?= $target[$l]; ?>"></td>
                    <?php endfor; ?>
                </tr>
                <?php endforeach; ?>
            </table>
        </form>

<form>タグの中でsubmitタイプの<input>タグを繰り返し表示させることで、
どのボタンを押しても「result.php」へ回答がPOSTされるようになっています。

不正解用文字配列を生成させた時と同様に、
2重の繰り返し処理によって多次元配列の値を表示させます。

まず<?php foreach ($targets as $target): ?>によって
第1層の繰り返し処理します。
繰り返しの対象を<tr>タグで囲んでおり、ボタン群の行の繰り返しに相当します。

上記foreach構文の中で更に繰り返し処理
<?php for ($l = 0; $l < count($target); $l++): ?>
を実行することで、第2層を表示させます6
こちらは<td>タグで囲っており、各行内の列の繰り返しに相当します。

これらの繰り返しにより、
「20個の<td>タグ内のsubmitボタン」

「それぞれ20個の<tr>タグで囲われた」
計400個のボタン群を表示させることができます。

これで問題を表示させることができました!
次は結果ページを実装する前に、DB接続設定ファイルを作成します。


<< 前の記事 【②スタートページを作る】
2. スタートページの実装
  2-1. セッション
   2-1-1. セッションの開始
   2-1-2. セッションの放棄
  2-2. バリデーション
   2-2-1. 無効な送信の拒否
   2-2-2. 名前のバリデーション
    2-2-2-1. 半角スペースのみ無効
    2-2-2-2. 文字数
    2-2-2-3. 空白文字/制御文字無効
   2-2-3. エラーメッセージの表示
   2-2-4. 入力値の保持 - タグ
  2-3. 問題表示ページへの遷移


次の記事 >> 【④DB接続設定ファイルを作る】
4. DB接続設定
  4-1. PDOオブジェクトの属性
   4-1-1. フェッチ形式の指定
   4-1-2. エラーモードの設定
    4-1-2-1. エラーモードの違いによるエラー文の違い
   4-1-3. エミュレーションの設定
    4-1-3-1. プリペアドステートメント
    4-1-3-2. エミュレーション
  4-2. 例外発生時の処理
   4-2-1. HTTPヘッダの送信
   4-2-2. 処理の中断とエラーメッセージの表示
  4-3. DBの切断について


  1. 「間違い探し」なので単に「ランダムに文字を選択させる」だと簡単に見分けがつく文字同士が表示される可能性があり、ゲーム性が損なわれます。この為「似た文字のペア」を設定する必要があります。 

  2. viやvimで直接絵文字を打つとカーソルの位置がおかしくなったり謎の空白が出現するなど、めちゃくちゃバグります。vimにはあまり詳しくないので、回避できる策があるなら教えて欲しいです... 

  3. 多次元配列に対してshuffle()を用いた場合、最上位層のみシャッフルされます。再帰的にシャッフルするには別途for構文やforeach構文などの繰り返し処理を用いる必要があります。 

  4. 文字ペアの多次元配列のシャッフル時にfor構文で$iを使用したので、今回はアルファベット順に$j, $kとしています。$i$j, 実際は、$kのどちらかは同じでも問題ありません(for構文の最初の式で初期化される為。$j$kは同時に使用するので分ける必要あり)が、ややこしいので筆者はこのやり方を好んでいます。 

  5. array_rand()ランダムなキーを返すことに注意してください。今回の場合、$key1$key2に格納されているのは$key値ではなくキーです。$keyの配列のキーと値が全く同じである為ややこしいですが、キーにインデックス番号以外が用いられた配列を扱う際は注意が必要です。 

  6. 脚注4.と同じように、カウンター変数はアルファベット順に$lとしています。 

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1