LoginSignup
1
1

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-10-26

5. 結果表示ページ


<< 前の記事 【④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の切断について


次の記事 >> 【⑥ランキングページを作る】
6. ランキング表示ページ
  6-1. 表示切り替え機能
  6-2. 入力値の保持 - タグ
  6-3. ランキングの表示
   6-3-1. テーブルの表示
   6-3-2. テーブルデザイン

ここではプレイヤーの回答の正解/不正解選択した難易度回答時間リセット回数を表示するページを実装します。
結果ページ.png

result.php
<?php

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

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

// 回答時間を算出
$end_time = microtime(true);
$start_time = $_SESSION['start_time'];
$time = sprintf('%05.2f', $end_time - $start_time) . '秒';

// 回答を変数に格納
$answer = $_POST['answer'];

// セッション変数を変数に格納
$name = $_SESSION['name'];
$difficulty = $_SESSION['difficulty'];
$permission = $_SESSION['permission'];
$correct = $_SESSION['correct'];
$count = sprintf('%02d', $_SESSION['count']) . '回';

// セッションの放棄
$_SESSION = [];
setcookie(session_name(), '', time() - 1, '/');
session_destroy();

// 回答時間が100秒以上の場合は値を上書き
if ($time > 100) {
    $time = '100秒以上';
}

// リセット回数が100回以上の場合は値を上書き
if ($count > 100) {
    $count = '100回以上';
}

// 正解・不正解によるメッセージの分岐
if (html_entity_decode($correct) === $answer) {
    $result = '正解です!';
} else {
    $result = '不正解です。。。';
}

// 正解かつ許可されていた場合のみDBに登録
if ($result === '正解です!' && $permission === '許可する') {

    // db接続
    require_once('db_connect.php');

    // 新規登録処理
    $sql = 'INSERT INTO rankings (name, difficulty, time, reset) VALUES (?, ?, ?, ?)';
    $stmt = $dbh->prepare($sql);
    $stmt->execute([$name, $difficulty, $time, $count]);
}

?>
<!DOCTYPE html>
<html lang="ja">
    <head>
        <meta charset="UTF-8">
        <title>間違い探し</title>
    </head>
    <body>
        <h1><small>結果は...<?= $result ?></small></h1>
        <h2><small>難易度: <?= $difficulty; ?></small></h2>
        <h2><small>回答時間: <?= $time; ?></small></h2>
        <h2><small>リセット回数: <?= $count; ?></small></h2>
        <button type="button" onclick="location.href='start.php'">スタートページへ</button>
        <button type="button" onclick="location.href='ranking.php'">ランキングページへ</button></a>
    </body>
</html>

セッションの開始や放棄はこれまでのファイルと同様です。

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

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

find_the_mistake.php」ではセッション変数のみ存在確認していましたが、
ここでは押されたボタンのvalue属性の値POSTされているはずなので、
その値がない場合もスタートページへリダイレクトさせています。

5-2. 回答時間の算出

result.php
// 回答時間を算出
$end_time = microtime(true);
$start_time = $_SESSION['start_time'];
$time = sprintf('%05.2f', round($end_time - $start_time, 2)) . '秒';

find_the_mistake.php」で開始時間を記録したのと同様に、
microtime()を用いて終了時間$end_timeへ格納しています。

次いで、開始時間をセッション変数から$start_timeへ格納し、
sprintf()の第2引数として「終了時間 - 開始時間」の引き算1の結果を渡しています。

sprintf()は第2引数の文字列を、
第1引数で指定したフォーマットの文字列に変換する関数で、
%05.2fは「小数第2位まで0埋めした5桁の浮動小数点数」を意味します。

これらの各文字は指定子といい、次のような書式で記述します。
[%][符号指定子][パディング指定子][アライメント指定子][表示幅指定子][精度指定子][型指定子]

今回は符合指定子とアライメント指定子以外を下記のように指定しています。

  • %・・・フォーマットとして認識させるためのパーセント文字2
  • 0・・・パディング指定子 指定した桁数に満たない部分を埋める記号(今回は0埋め)
  • 5・・・表示幅指定子 表示する最低桁数
  • .2・・・精度指定子 小数点以下の最低表示桁数
  • f・・・型指定子 引数を浮動小数点数として表示する

詳細に関しては下記もご確認下さい。

PHPマニュアル「PHP: sprintf - Manual
Let'sプログラミング ~初心者の方を対象としたプログラミングの総合学習サイト~
指定の形式にフォーマット(sprintf) - 文字列関数 - PHP関数

変換後、最後尾には文字「秒」を連結させ、変数$timeへ格納しています。

これで回答時間が算出できました。

5-3. 各変数への格納

result.php
// 回答を変数に格納
$answer = $_POST['answer'];

// セッション変数を変数に格納
$name = $_SESSION['name'];
$difficulty = $_SESSION['difficulty'];
$permission = $_SESSION['permission'];
$correct = $_SESSION['correct'];
$count = sprintf('%02d', $_SESSION['count']) . '回';

POSTされた値はプレイヤーの回答として$answerに格納しています。

またこのページからの望ましくないページ遷移を防ぐため3
スタートページと同様にセッションを放棄させます。

問題表示ページのアクセス制限は$_SESSIONが定義されているかどうかで判定しているので、結果ページへやってきた時点でセッションを放棄させることによりよって問題表示ページヘ戻ることは出来なくなります。

これにより、PHPのスクリプト終了時点でセッション変数は初期化されているので、`タグ内で出力させるためには別の変数に格納しておく必要があります。

リセット回数は上記「回答時間の算出」で用いたsprintf()を用いて、「0埋めした2桁の小数値」のフォーマットに変換してから$countに格納しています。

5-4. 保存する回答時間とカウント数の上限設定

result.php
// 回答時間が100秒以上の場合は値を上書き
if ($time > 100) {
    $time = '100秒以上';
}

// リセット回数が100回以上の場合は値を上書き
if ($count > 100) {
    $count = '100回以上';
}

ここまでのコードでは回答時間とリセット回数に上限は存在しません。

このままでも構わないのですが、

  • ランキングページのレイアウト崩れ
  • 不必要に大きな値が表示されること

を事前に防ぐために、今回は制限を追加します。

具体的には、DBに保存される回答時間・カウント数がそれぞれ

  • 100秒以上
  • 100回以上

の場合に定型文に置き換わるようにしています。

5-5. 正解/不正解による表示メッセージの分岐

result.php
// 正解・不正解によるメッセージの分岐
if (html_entity_decode($correct) === $answer) {
    $result = '正解です!';
} else {
    $result = '不正解です。。。';
}

最も初歩的な条件分岐です。

注意点として、選択された文字が絵文字だった場合
$answerに格納された値(=POSTされた値)はブラウザへの出力を経ている為、
HTMLエンティティとしてではなく絵文字そのものとなっています。

一方、$correctに格納された値(=セッション変数として保持されていた値)は、
ブラウザへの出力はないままなのでHTMLエンティティのまま格納されています。

この為、正解かどうか(=文字列が一致しているかどうか)を判定する為には、

  • セッション変数として保持されていた値をデコードする
  • POSTされた値をHTMLエンティティ化する

のどちらかを実行する必要があります。今回は前者を採用しています。

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

後者の場合は下記関数が使えます。

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

5-6. ランキングへの登録

プレイヤーの回答が正解かつランキングへの登録が許可されている場合のみ、DBへ保存します。

result.php
// 正解かつ許可されていた場合のみDBに登録
if ($result === '正解です!' && $permission === '許可する') {

    // db接続
    require_once('db_connect.php');

    // 新規登録処理
    $sql = 'INSERT INTO rankings (name, difficulty, time, reset) VALUES (?, ?, ?, ?)';
    $stmt = $dbh->prepare($sql);
    $stmt->execute([$name, $difficulty, $time, $count]);
}

5-6-1. ランキングテーブルの構成

まず先に、データを保存するランキングテーブルを構築する必要があります。詳しい方法については「MySQL テーブル作成」などで検索すればたくさんヒットすると思いますので、ここでは割愛します。

create_rankings_table.sql
CREATE TABLE `rankings` (
  `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
  `name` varchar(20) NOT NULL,
  `difficulty` varchar(10) NOT NULL,
  `time` varchar(10) NOT NULL,
  `reset` varchar(15) NOT NULL,
  PRIMARY KEY(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;

一般的なテーブル構造となっています。各カラムの概要は以下です。

  • id・・・主キー/符号なし/オートインクリメント属性が指定されたidカラム
  • name・・・入力された名前を保存する可変長文字列/20字までのカラム
  • difficulty・・・選択された難易度を保存する可変長文字列/10字までのカラム
  • time・・・回答時間を保存する可変長文字列/10字までのカラム
  • reset・・・リセット回数を保存する可変長文字列/15字までのカラム

いわゆる「寿司ビール問題」を回避する為に、文字セットutf8mb4を、照合順序utf8mb4_binをデフォルトとしています。

Qiita「寿司ビール問題① 初心者→中級者へのSTEP20/25 - Qiita」 by @kamohicokamo さん

また脚注1.でも少し触れましたが、一部の絵文字の文字数が正常にカウントされません(2倍にカウントされる)ので、nameカラムの文字数上限はバリデーションで制限した10文字の2倍の20文字としてます。

テーブル名やカラム名の命名規則に関しては下記記事が参考になります。

Qiita「データベースオブジェクトの命名規約 - Qiita」 by @genzouw さん

5-6-2. ランキングへの登録

result.php
    // db接続
    require_once('db_connect.php');

    // 新規登録処理
    $sql = 'INSERT INTO rankings (name, difficulty, time, reset) VALUES (?, ?, ?, ?)';
    $stmt = $dbh->prepare($sql);
    $stmt->execute([$name, $difficulty, $time, $count]);

require_once()でDB接続設定用ファイル「db_connect.php」を呼び出してDB接続した後、INSERT文を用いてrankingsテーブルにデータを挿入しています。

SQL文では疑問符プレースホルダーを用いたSQL文を記述し、prepare()メソッドによってDBサーバーにプリペアドステートメントとしてSQL文を渡しています(今回のDB接続設定では静的プレースホルダーを選択している為)。

PHPマニュアル「PHP: PDO - Manual
PHPマニュアル「PHP: PDO::prepare - Manual

prepare()メソッドはPDOStatementオブジェクトを返すのでそれを$stmtに格納し、次いでPDOStatementクラスのexecute()メソッドによって、

  • execute()の引数に渡された値とプレースホルダーのバインド
  • SQL文の実行

を行っています。

PHPマニュアル「PHP: PDOStatement - Manual
PHPマニュアル「PHP: PDOStatement::execute - Manual

bindValue()メソッドを使用せずにexecute()の引数に値を配列として渡す場合の注意点として、NULL以外はすべてPDO::PARAM_STR扱いになるというのがあります。

今回のテーブルはidカラム以外はすべて可変長文字列カラムですので問題ありませんが、テーブルのカラムが文字列以外の型の場合execute()の引数はにし、prepare()execute()の間でbindValue()メソッドを使用した方がいいです。

Qiita「PHPでデータベースに接続するときのまとめ - PDO::prepare → PDOStatement::execute の2ステップでクエリを実行する - Qiita」 by @mpyw さん

結果を表示させ、ランキングに登録させることができました。
最後に、ランキングページを実装します。


<< 前の記事 【④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の切断について


次の記事 >> 【⑥ランキングページを作る】
6. ランキング表示ページ
  6-1. 表示切り替え機能
  6-2. 入力値の保持 - タグ
  6-3. ランキングの表示
   6-3-1. テーブルの表示
   6-3-2. テーブルデザイン


  1. microtime()のタイムスタンプはUnixエポックからの経過マイクロ秒数となっています。このため「新しい方(ここでは$end_time)」から「古い方($start_time)」を引いた値が経過時間のマイクロ秒数となります。 

  2. これがないと各文字が指定子として認識されなくなります。また、この文字自体は指定子ではありません。 

  3. 例えば「このページからもう一度問題表示ページへ戻る」などです。個人的には「問題表示ページから結果ページへは一方通行としたい」と考えたためこのような実装となりました。 

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