CSS javascript PHP ゲーム

Javascriptでアニメを題材にした4択クイズゲームの作り方:オンラインランキング機能付き

概要

この記事では、アニメを題材にしたオンラインランキング機能を備えた4択クイズゲームの作成方法について説明します。ゲームはHTML5、CSS、JavaScriptを使って構築され、PHPとSQLiteを利用してスコアを保存し、トップ10のプレイヤーのランキングを表示します。プレイヤーは10問のクイズに挑戦し、正解数と回答時間に基づいてランキングが決定されます。

下記で実際にプレイできます。

https://chemtoollab.com/game/quiz/v1.3/index.html

使用例

このアニメクイズゲームは、友達や家族と一緒に楽しむことができます。さらに、オンラインランキング機能を活用して他のプレイヤーとスコアを競い合うことも可能です。アニメファン向けのイベントや集まりでの使用も最適です。また、教育用ツールとしても利用でき、楽しく知識を深めることができます。

必要なプログラム

  • HTMLファイル (index.html)
  • CSSファイル (styles.css)
  • JavaScriptファイル (script.js)
  • PHPファイル (init_db.php, submit_score.php, get_rankings.php)
  • SQLiteデータベース (ranking.db)
  • クイズデータファイル (4choice.json)

インストール方法

  1. サーバー準備:
    • 任意のレンタルサーバーを用意します。PHPとSQLiteがサポートされている必要があります。
  2. ファイルのアップロード:
    • 以下のファイルをサーバーにアップロードします。
      • index.html
      • styles.css
      • script.js
      • init_db.php
      • submit_score.php
      • get_rankings.php
      • 4choice.json
  3. データベースの初期化:
    • サーバー上でinit_db.phpを一度実行してデータベースを初期化します。これは、ブラウザでinit_db.phpにアクセスすることで実行できます。

使用手順

  1. ゲームの開始:
    • ブラウザでindex.htmlにアクセスし、「ゲームスタート」ボタンをクリックしてゲームを開始します。
    • ゲームが開始されると、10問のクイズが出題されます。各問題の上部には「問題No.」と「第話より」の情報が表示されます。
  2. 回答の選択:
    • 各問題に対して、4つの選択肢が表示されます。正しいと思う回答をクリックします。
    • 正解の場合はスコアが加算され、不正解の場合はタイムペナルティが加算されます。
  3. 結果の表示:
    • すべての問題に回答すると、スコアと経過時間が表示されます。
    • ランキングに登録するためのフォームが表示され、名前とコメントを入力します。
  4. ランキングの表示:
    • ランキングページでは、上位10位までのプレイヤーのスコアが表示されます。プレイヤーの名前、正解数、時間、日付、コメント、ブラウザ情報が含まれます。

注意点

  • PHPファイルが正しく動作するためには、サーバーがPHPとSQLiteをサポートしている必要があります。
  • 4choice.jsonファイルの形式に注意し、クイズデータが正しく記述されていることを確認してください。
  • セキュリティ対策として、サーバー側で入力データのバリデーションとサニタイズを行うことを推奨します。

プログラム

以下に、必要なプログラムファイルを示します。

index.html

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>アニメを題材にした4択クイズゲーム</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div class="container" id="title-screen">
<h1>アニメを題材にした4択クイズゲーム</h1>
<button id="start-game-btn" class="start-btn">ゲームスタート</button><br>
<button id="view-rankings-btn" class="start-btn">ランキング表示</button>
</div>
<div class="container" id="quiz-container" style="display:none;">
<h1>アニメを題材にした4択クイズゲーム</h1>
<div id="question-info"></div>
<div id="question-container">
<p id="question"></p>
<p id="question-counter"></p>
</div>
<div id="answer-buttons" class="btn-grid">
<button class="btn" id="btn1"></button><br>
<button class="btn" id="btn2"></button><br>
<button class="btn" id="btn3"></button><br>
<button class="btn" id="btn4"></button><br>
</div>
<div id="result-container">
<p id="result"></p>
</div>
<div id="timer-container">
<p id="timer">Time: 0秒</p>
</div>
<button id="return-btn" class="return-btn" style="display:none;">タイトルに戻る</button>
</div>
<div class="container" id="ranking-form" style="display:none;">
<h1>ランキング登録</h1>
<form id="rank-form">
<label for="name">名前 (50字以内):</label><br>
<input type="text" id="name" name="name" maxlength="50"><br>
<label for="comment">コメント (100字以内):</label><br>
<input type="text" id="comment" name="comment" maxlength="100"><br>
<button type="button" id="submit-score-btn" class="start-btn">登録</button>
</form>
</div>
<div class="container" id="rankings" style="display:none;">
<h1>ランキング</h1>
<div class="table-container">
<table id="ranking-table">
<thead>
<tr>
<th>順位</th>
<th>名前</th>
<th>正解数</th>
<th>時間</th>
<th>日付</th>
<th>コメント</th>
<th>ブラウザ</th>
</tr>
</thead>
<tbody>
<!-- ランキングデータがここに挿入されます -->
</tbody>
</table>
</div>
<button id="return-to-title-btn" class="return-btn">タイトルに戻る</button>
</div>
<script src="script.js"></script>
</body>
</html>

styles.css

body {
font-family: Arial, sans-serif;
background-color: #f7f7f7;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
}

.container {
text-align: center;
background-color: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
width: 90%;
max-width: 600px;
}

.btn-grid {
display: grid;
gap: 10px;
}

.btn {
background-color: #4CAF50;
color: white;
border: none;
padding: 10px;
font-size: 16px;
cursor: pointer;
border-radius: 5px;
width: 100%;
}

.btn:hover {
background-color: #45a049;
}

.start-btn, .return-btn {
margin-top: 20px;
padding: 10px 20px;
font-size: 18px;
cursor: pointer;
}

.btn.disabled {
background-color: #d3d3d3;
cursor: not-allowed;
}

input[type=text] {
width: 80%;
padding: 10px;
margin: 10px 0;
box-sizing: border-box;
}

/* 追加: テーブルのスタイル */
.table-container {
width: 100%;
max-height: 300px;
overflow-x: auto;
overflow-y: auto;
margin-top: 20px;
}

table {
width: 100%;
border-collapse: collapse;
}

th, td {
border: 1px solid #ddd;
padding: 8px;
text-align: center;
word-wrap: break-word;
}

th {
background-color: #4CAF50;
color: white;
}

/* 追加: 名前とコメントの改行スタイル */
td.name, td.comment {
max-width: 10ch;
white-space: normal;
word-wrap: break-word;
}

script.js

let questions = [];
let currentQuestionIndex = 0;
let score = 0;
let correctAnswers = 0;
let startTime;
let timerInterval;
let penaltyTime = 0;
const totalQuestions = 10; // 問題数を10問に変更
let currentRankings = [];
let playerRank = -1;
let playerTime = 0;

document.getElementById('start-game-btn').addEventListener('click', startGame);
document.getElementById('view-rankings-btn').addEventListener('click', showRankings);
document.getElementById('return-btn').addEventListener('click', returnToTitle);
document.getElementById('submit-score-btn').addEventListener('click', submitScore);
document.getElementById('return-to-title-btn').addEventListener('click', returnToTitle);

function startGame() {
document.getElementById('title-screen').style.display = 'none';
document.getElementById('quiz-container').style.display = 'block';

fetch('4choice.json')
.then(response => response.json())
.then(data => {
questions = data;
shuffleArray(questions);
questions = questions.slice(0, totalQuestions);
currentQuestionIndex = 0;
score = 0;
correctAnswers = 0;
startTime = new Date().getTime();
penaltyTime = 0;
playerRank = -1;
playerTime = 0;
document.getElementById('return-btn').style.display = 'none';
fetchRankings();
showQuestion();
startTimer();
})
.catch(error => console.error('Error loading questions:', error));
}

function showQuestion() {
if (currentQuestionIndex >= questions.length) {
showResults();
return;
}

const question = questions[currentQuestionIndex];
const questionInfo = `問題No.${question.No} <br> 第${question.story}話${question.time}より`;
document.getElementById('question-info').innerHTML = questionInfo;
document.getElementById('question').innerText = question.question;
document.getElementById('question-counter').innerText = `${currentQuestionIndex + 1}/${totalQuestions}`;
const answers = [question.correct, question.wrong1, question.wrong2, question.wrong3];
shuffleArray(answers);

document.getElementById('btn1').innerText = answers[0];
document.getElementById('btn2').innerText = answers[1];
document.getElementById('btn3').innerText = answers[2];
document.getElementById('btn4').innerText = answers[3];

enableButtons();

document.getElementById('btn1').onclick = () => checkAnswer(answers[0]);
document.getElementById('btn2').onclick = () => checkAnswer(answers[1]);
document.getElementById('btn3').onclick = () => checkAnswer(answers[2]);
document.getElementById('btn4').onclick = () => checkAnswer(answers[3]);
}

function checkAnswer(selectedAnswer) {
const question = questions[currentQuestionIndex];
const resultElement = document.getElementById('result');
disableButtons();
if (selectedAnswer === question.correct) {
score++;
correctAnswers++;
resultElement.innerText = '正解!';
} else {
penaltyTime += 30;
resultElement.innerText = '不正解';
}
currentQuestionIndex++;
setTimeout(() => {
resultElement.innerText = '';
showQuestion();
}, 1000);
}

function showResults() {
const elapsedTime = Math.floor((new Date().getTime() - startTime) / 1000) + penaltyTime;
playerTime = elapsedTime;
clearInterval(timerInterval);
const resultElement = document.getElementById('result');
resultElement.innerHTML = `ゲーム終了!<br>スコア: ${score}/${questions.length}<br>時間: ${elapsedTime}秒`;

checkRanking(correctAnswers, elapsedTime);

document.getElementById('quiz-container').style.display = 'none';
document.getElementById('ranking-form').style.display = 'block';

if (playerRank > -1) {
const rankInfo = document.createElement('p');
rankInfo.id = 'rank-info';
rankInfo.innerHTML = `おめでとうございます!<br>あなたは${playerRank + 1}位にランクインしました。<br>正解数: ${correctAnswers}, スコア: ${playerTime}秒`;
document.getElementById('ranking-form').insertBefore(rankInfo, document.getElementById('rank-form'));
}

document.getElementById('submit-score-btn').disabled = false;
}

function startTimer() {
const timerElement = document.getElementById('timer');
timerInterval = setInterval(() => {
const elapsedTime = Math.floor((new Date().getTime() - startTime) / 1000) + penaltyTime;
timerElement.innerText = `Time: ${elapsedTime}秒`;
}, 1000);
}

function shuffleArray(array) {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
}

function disableButtons() {
document.getElementById('btn1').classList.add('disabled');
document.getElementById('btn2').classList.add('disabled');
document.getElementById('btn3').classList.add('disabled');
document.getElementById('btn4').classList.add('disabled');
document.getElementById('btn1').onclick = null;
document.getElementById('btn2').onclick = null;
document.getElementById('btn3').onclick = null;
document.getElementById('btn4').onclick = null;
}

function enableButtons() {
document.getElementById('btn1').classList.remove('disabled');
document.getElementById('btn2').classList.remove('disabled');
document.getElementById('btn3').classList.remove('disabled');
document.getElementById('btn4').classList.remove('disabled');
}

function returnToTitle() {
document.getElementById('result').innerText = '';
document.getElementById('question-counter').innerText = '';
document.getElementById('timer').innerText = 'Time: 0秒';
document.getElementById('quiz-container').style.display = 'none';
document.getElementById('ranking-form').style.display = 'none';
document.getElementById('rankings').style.display = 'none';
document.getElementById('title-screen').style.display = 'block';

// リセット
const rankInfo = document.getElementById('rank-info');
if (rankInfo) {
rankInfo.remove();
}
document.getElementById('name').value = '';
document.getElementById('comment').value = '';
document.getElementById('submit-score-btn').disabled = true;
}

function submitScore() {
const name = document.getElementById('name').value;
const comment = document.getElementById('comment').value;
document.getElementById('submit-score-btn').disabled = true;

fetch('submit_score.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
correct_answers: correctAnswers,
seconds: playerTime,
name: name,
comment: comment
})
})
.then(response => response.json())
.then(data => {
if (data.status === 'success') {
showRankings();
} else {
alert('エラーが発生しました: ' + data.message);
}
})
.catch(error => console.error('Error:', error));
}

function showRankings() {
fetch('get_rankings.php')
.then(response => response.json())
.then(data => {
const rankingTableBody = document.querySelector('#ranking-table tbody');
rankingTableBody.innerHTML = '';
data.forEach((ranking, index) => {
const row = document.createElement('tr');
row.innerHTML = `
<td>${index + 1}</td>
<td class="name">${ranking.name}</td>
<td>${ranking.correct_answers}</td>
<td>${ranking.seconds}秒</td>
<td>${ranking.date}</td>
<td class="comment">${ranking.comment}</td>
<td>${ranking.browser}</td>
`;
rankingTableBody.appendChild(row);
});

document.getElementById('title-screen').style.display = 'none';
document.getElementById('ranking-form').style.display = 'none';
document.getElementById('rankings').style.display = 'block';
})
.catch(error => console.error('Error:', error));
}

function fetchRankings() {
fetch('get_rankings.php')
.then(response => response.json())
.then(data => {
currentRankings = data;
})
.catch(error => console.error('Error:', error));
}

function checkRanking(correctAnswers, elapsedTime) {
for (let i = 0; i < currentRankings.length; i++) {
if (correctAnswers > currentRankings[i].correct_answers ||
(correctAnswers === currentRankings[i].correct_answers && elapsedTime < currentRankings[i].seconds)) {
playerRank = i;
return;
}
}
if (currentRankings.length < 10) {
playerRank = currentRankings.length;
}
}

init_db.php

<?php
$database = 'ranking.db';

try {
    $db = new SQLite3($database);
    $db->exec("CREATE TABLE IF NOT EXISTS rankings (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                correct_answers INTEGER NOT NULL,
                seconds INTEGER NOT NULL,
                name TEXT NOT NULL,
                date TEXT NOT NULL,
                comment TEXT,
                browser TEXT NOT NULL
              )");
    echo "Database initialized successfully.";
} catch (Exception $e) {
    echo "Error: " . $e->getMessage();
}
?>

submit_score.php

<?php
$database = 'ranking.db';

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $data = json_decode(file_get_contents('php://input'), true);
    $correct_answers = $data['correct_answers'];
    $seconds = $data['seconds'];
    $name = $data['name'];
    $comment = $data['comment'];
    $browser = $_SERVER['HTTP_USER_AGENT'];
    $date = date('Y-m-d H:i:s');

    try {
        $db = new SQLite3($database);
        $stmt = $db->prepare("INSERT INTO rankings (correct_answers, seconds, name, date, comment, browser) VALUES (?, ?, ?, ?, ?, ?)");
        $stmt->bindValue(1, $correct_answers, SQLITE3_INTEGER);
        $stmt->bindValue(2, $seconds, SQLITE3_INTEGER);
        $stmt->bindValue(3, $name, SQLITE3_TEXT);
        $stmt->bindValue(4, $date, SQLITE3_TEXT);
        $stmt->bindValue(5, $comment, SQLITE3_TEXT);
        $stmt->bindValue(6, $browser, SQLITE3_TEXT);
        $stmt->execute();
        echo json_encode(['status' => 'success']);
    } catch (Exception $e) {
        echo json_encode(['status' => 'error', 'message' => $e->getMessage()]);
    }
}
?>

get_rankings.php

<?php
$database = 'ranking.db';

try {
    $db = new SQLite3($database);
    $results = $db->query("SELECT correct_answers, seconds, name, date, comment, browser FROM rankings ORDER BY correct_answers DESC, seconds ASC LIMIT 10");
    $rankings = [];
    while ($row = $results->fetchArray(SQLITE3_ASSOC)) {
        $rankings[] = $row;
    }
    echo json_encode($rankings);
} catch (Exception $e) {
    echo json_encode(['status' => 'error', 'message' => $e->getMessage()]);
}
?>

4choice.json

下記コードを参考に、問題を10問以上用意してください! ※下記はアニメ「忍ばない!クリプトニンジャ咲耶」のクイズです。

[
    {"No": 1, "story": 1, "time": "0:53", "question": "闘いで多くのクランが滅び、残ったクランでないものは次のうちどれ", "correct": "加賀", "wrong1": "甲賀", "wrong2": "伊賀", "wrong3": "雑賀"},
    {"No": 2, "story": 1, "time": "1:31", "question": "咲耶の携帯の色に近いものはどれ", "correct": "緑", "wrong1": "紫", "wrong2": "赤", "wrong3": "青"}
]

なお4choice.jsonは下記を参考にcsvファイルからjsonファイルを簡単に作成することが出来ます。

上記のすべてのファイルは、こちらから一式ダウンロードできます。

まとめ

この記事では、アニメを題材にしたオンラインランキング機能付き4択クイズゲームの作成方法について説明しました。このゲームは、HTML5、CSS、JavaScriptを使用し、PHPとSQLiteを利用してオンラインランキング機能を実装しています。クイズ形式のゲームは、教育やエンターテイメントに最適で、多くの人に楽しんでいただけることを期待しています

-CSS, javascript, PHP, ゲーム