CSS javascript PHP ゲーム

JavaScriptでタイピング練習ゲームを作成しよう!その3 ローマ字選択の実装

概要

本記事では、ローマ字タイピング練習ゲームを作成する手順を紹介します。このゲームはオンラインランキング機能を備えており、ユーザーが自分のスコアを他のユーザーと比較することができます。さらに、ローマ字の入力方法を選択できるカスタマイズ機能も実装されています。

実際に下記から試すことが出来ます。

https://chemtoollab.com/game/sakuya-typev1/index.html

使用例

このタイピング練習ゲームは、以下のような用途に利用できます。

  • 日本語学習者がローマ字入力の練習をするため
  • タイピング速度と精度を向上させるため
  • 楽しみながらタイピングスキルを競うため

必要なプログラム

本ゲームを実行するために必要なファイルは以下の通りです:

  1. index.html - ゲームのHTMLファイル
  2. style.css - ゲームのスタイルシート
  3. script.js - ゲームのJavaScriptファイル
  4. word.json - ゲームで使用する単語リスト
  5. create_db.php - SQLiteデータベースの作成スクリプト
  6. save_ranking.php - ランキングを保存するスクリプト
  7. get_rankings.php - ランキングを取得するスクリプト
  8. alter_db.php - データベースの変更スクリプト

導入方法

1. サーバー環境の準備

  1. PHPとSQLiteが利用可能なWebサーバーを用意します。
  2. 必要に応じてレンタルサーバーを契約してください。

2. ファイルのアップロード

  1. 上記のファイルをすべてサーバーにアップロードします。
  2. ファイルのパーミッションを設定し、必要に応じて以下のコマンドを実行します(UNIXベースのサーバーの場合):
    chmod 755 create_db.php save_ranking.php get_rankings.php alter_db.php chmod 644 index.html style.css script.js word.json

3. データベースの作成

  1. ブラウザでcreate_db.phpにアクセスし、データベースを作成します
    http://ドメイン/create_db.php
  2. これにより、rankings.dbファイルが作成され、必要なテーブルが準備されます。

使用手順

1. ゲームの起動

  1. ブラウザでindex.htmlにアクセスします
    http://ドメイン/index.html

2. ローマ字入力方法の選択

  1. タイトル画面でローマ字の入力方法をラジオボタンで選択します。
  2. 選択した設定はブラウザに記憶され、次回アクセス時にも反映されます。

3. ゲームの開始

  1. 「ゲームを開始」ボタンをクリックしてゲームを開始します。
  2. 出題された日本語のローマ字を入力し、正確に入力できればスコアが加算されます。間違えると減点されます。

4. ゲーム終了とランキング登録

  1. 10問終了後、スコアと経過時間が表示されます。
  2. ランクインした場合は名前とコメントを入力してランキングに登録します。

5. ランキングの表示

  1. タイトル画面で「ランキング表示」ボタンをクリックすると、現在のランキングが表示されます。

注意点

  1. サーバーにPHPとSQLiteが正しくインストールされていることを確認してください。
  2. セキュリティ上の理由から、データベースファイルやPHPスクリプトのパーミッション設定に注意してください。
  3. データベースファイルが正しく作成されない場合は、サーバーのエラーログを確認してください。

プログラム

下記のコードは前回の記事と同じなので、そちらを参照ください。

  • style.css
  • create_db.php
  • save_ranking.php
  • get_rankings.php
  • alter_db.php

前回から変更となったコードは下記になります。

index.html

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>タイピング練習ゲーム</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <div class="container">
        <h1>タイピング練習ゲーム</h1>
        <div id="title-screen">
            <p>ゲームを始める前に、入力モードが半角英数字になっていることを確認してください。</p>
            <button id="start-button">ゲームを開始</button>
            <button id="show-ranking-button">ランキング表示</button>
            <form id="romaji-selection-form">
                <div>
                    <label>「し」</label>
                    <input type="radio" name="shi" value="si"> si
                    <input type="radio" name="shi" value="shi" checked> shi
                </div>
                <div>
                    <label>「しゃ」</label>
                    <input type="radio" name="sha" value="sha" checked> sha
                    <input type="radio" name="sha" value="sya"> sya
                </div>
                <div>
                    <label>「しゅ」</label>
                    <input type="radio" name="shu" value="shu" checked> shu
                    <input type="radio" name="shu" value="syu"> syu
                </div>
                <div>
                    <label>「しょ」</label>
                    <input type="radio" name="sho" value="sho" checked> sho
                    <input type="radio" name="sho" value="syo"> syo
                </div>
                <div>
                    <label>「ち」</label>
                    <input type="radio" name="chi" value="chi" checked> chi
                    <input type="radio" name="chi" value="ti"> ti
                </div>
                <div>
                    <label>「つ」</label>
                    <input type="radio" name="tsu" value="tsu" checked> tsu
                    <input type="radio" name="tsu" value="tu"> tu
                </div>
                <div>
                    <label>「ちゃ」</label>
                    <input type="radio" name="cha" value="cha" checked> cha
                    <input type="radio" name="cha" value="tya"> tya
                </div>
                <div>
                    <label>「ちゅ」</label>
                    <input type="radio" name="chu" value="chu" checked> chu
                    <input type="radio" name="chu" value="tyu"> tyu
                </div>
                <div>
                    <label>「ちぇ」</label>
                    <input type="radio" name="che" value="che" checked> che
                    <input type="radio" name="che" value="tye"> tye
                </div>
                <div>
                    <label>「ちょ」</label>
                    <input type="radio" name="cho" value="cho" checked> cho
                    <input type="radio" name="cho" value="tyo"> tyo
                </div>
                <div>
                    <label>「ふ」</label>
                    <input type="radio" name="fu" value="fu" checked> fu
                    <input type="radio" name="fu" value="hu"> hu
                </div>
                <div>
                    <label>「じ」</label>
                    <input type="radio" name="ji" value="ji" checked> ji
                    <input type="radio" name="ji" value="zi"> zi
                </div>
                <div>
                    <label>「じゃ」</label>
                    <input type="radio" name="ja" value="ja" checked> ja
                    <input type="radio" name="ja" value="zya"> zya
                </div>
                <div>
                    <label>「じゅ」</label>
                    <input type="radio" name="ju" value="ju" checked> ju
                    <input type="radio" name="ju" value="zyu"> zyu
                </div>
                <div>
                    <label>「じぇ」</label>
                    <input type="radio" name="je" value="je" checked> je
                    <input type="radio" name="je" value="zye"> zye
                </div>
                <div>
                    <label>「じょ」</label>
                    <input type="radio" name="jo" value="jo" checked> jo
                    <input type="radio" name="jo" value="zyo"> zyo
                </div>
                <div>
                    <label>「ぁ」</label>
                    <input type="radio" name="xa" value="xa" checked> xa
                    <input type="radio" name="xa" value="la"> la
                </div>
                <div>
                    <label>「ぃ」</label>
                    <input type="radio" name="xi" value="xi" checked> xi
                    <input type="radio" name="xi" value="li"> li
                </div>
                <div>
                    <label>「ぅ」</label>
                    <input type="radio" name="xu" value="xu" checked> xu
                    <input type="radio" name="xu" value="lu"> lu
                </div>
                <div>
                    <label>「ぇ」</label>
                    <input type="radio" name="xe" value="xe" checked> xe
                    <input type="radio" name="xe" value="le"> le
                </div>
                <div>
                    <label>「ぉ」</label>
                    <input type="radio" name="xo" value="xo" checked> xo
                    <input type="radio" name="xo" value="lo"> lo
                </div>
            </form>
        </div>
        <div id="game-screen" style="display: none;">
            <div id="word-container">
                <div id="japanese-word"></div>
                <div id="romaji-word"></div>
            </div>
            <input type="text" id="input-field" autofocus>
            <div id="score">スコア: 0</div>
            <div id="progress">1/10</div>
            <div id="timer">タイマー: 0 秒</div>
            <button id="restart-button" style="display: none;">再スタート</button>
        </div>
        <div id="ranking-screen" style="display: none;">
            <h2>ランキング</h2>
            <div id="ranking-list-container">
                <table id="ranking-table">
                    <thead>
                        <tr>
                            <th>順位</th>
                            <th>名前</th>
                            <th>スコア</th>
                            <th>時間 (秒)</th>
                            <th>日時</th>
                            <th>コメント</th>
                        </tr>
                    </thead>
                    <tbody id="ranking-list"></tbody>
                </table>
            </div>
            <button id="back-button">戻る</button>
        </div>
        <div id="name-input-screen" style="display: none;">
            <h2>ランキング入り!</h2>
            <p>名前とコメントを入力してください:</p>
            <label for="name-input-field">名前 (50字以内):</label>
            <input type="text" id="name-input-field" placeholder="名前" maxlength="50">
            <label for="comment-input-field">コメント (100字以内):</label>
            <input type="text" id="comment-input-field" placeholder="コメント" maxlength="100">
            <button id="submit-name-button">送信</button>
            <div id="name-input-score">スコア: 0</div>
            <div id="name-input-timer">タイマー: 0 秒</div>
            <div id="name-input-date">日時: </div>
            <div id="name-input-rank"></div>
            <p>ランクが反映されるまでには1分程度かかります。</p>
        </div>
        <div id="game-over-screen" style="display: none;">
            <h2>ゲーム終了</h2>
            <div id="game-over-score">スコア: 0</div>
            <div id="game-over-timer">タイマー: 0 秒</div>
            <button id="title-button">タイトルに戻る</button>
            <button id="new-game-button">ゲームを新しく始める</button>
        </div>
    </div>
    <script src="script.js"></script>
</body>
</html>

script.js

let words = [];
let currentWordIndex = 0;
let score = 0;
let timer = 0;
let timerInterval;
const totalRounds = 10;
let rankings = [];
let romajiOptions = {};

const japaneseWordElement = document.getElementById('japanese-word');
const romajiWordElement = document.getElementById('romaji-word');
const inputField = document.getElementById('input-field');
const scoreElement = document.getElementById('score');
const progressElement = document.getElementById('progress');
const timerElement = document.getElementById('timer');
const restartButton = document.getElementById('restart-button');
const startButton = document.getElementById('start-button');
const showRankingButton = document.getElementById('show-ranking-button');
const titleScreen = document.getElementById('title-screen');
const gameScreen = document.getElementById('game-screen');
const rankingScreen = document.getElementById('ranking-screen');
const rankingList = document.getElementById('ranking-list');
const backButton = document.getElementById('back-button');
const nameInputScreen = document.getElementById('name-input-screen');
const nameInputField = document.getElementById('name-input-field');
const commentInputField = document.getElementById('comment-input-field');
const submitNameButton = document.getElementById('submit-name-button');
const gameOverScreen = document.getElementById('game-over-screen');
const titleButton = document.getElementById('title-button');
const newGameButton = document.getElementById('new-game-button');
const gameOverScoreElement = document.getElementById('game-over-score');
const gameOverTimerElement = document.getElementById('game-over-timer');
const nameInputScoreElement = document.getElementById('name-input-score');
const nameInputTimerElement = document.getElementById('name-input-timer');
const nameInputDateElement = document.getElementById('name-input-date');
const nameInputRankElement = document.getElementById('name-input-rank');

let newEntry = null;

function updateRomajiOptions() {
    romajiOptions.shi = document.querySelector('input[name="shi"]:checked').value;
    romajiOptions.sha = document.querySelector('input[name="sha"]:checked').value;
    romajiOptions.shu = document.querySelector('input[name="shu"]:checked').value;
    romajiOptions.sho = document.querySelector('input[name="sho"]:checked').value;
    romajiOptions.chi = document.querySelector('input[name="chi"]:checked').value;
    romajiOptions.tsu = document.querySelector('input[name="tsu"]:checked').value;
    romajiOptions.cha = document.querySelector('input[name="cha"]:checked').value;
    romajiOptions.chu = document.querySelector('input[name="chu"]:checked').value;
    romajiOptions.che = document.querySelector('input[name="che"]:checked').value;
    romajiOptions.cho = document.querySelector('input[name="cho"]:checked').value;
    romajiOptions.fu = document.querySelector('input[name="fu"]:checked').value;
    romajiOptions.ji = document.querySelector('input[name="ji"]:checked').value;
    romajiOptions.ja = document.querySelector('input[name="ja"]:checked').value;
    romajiOptions.ju = document.querySelector('input[name="ju"]:checked').value;
    romajiOptions.je = document.querySelector('input[name="je"]:checked').value;
    romajiOptions.jo = document.querySelector('input[name="jo"]:checked').value;
    romajiOptions.xa = document.querySelector('input[name="xa"]:checked').value;
    romajiOptions.xi = document.querySelector('input[name="xi"]:checked').value;
    romajiOptions.xu = document.querySelector('input[name="xu"]:checked').value;
    romajiOptions.xe = document.querySelector('input[name="xe"]:checked').value;
    romajiOptions.xo = document.querySelector('input[name="xo"]:checked').value;

    // Save selections to localStorage
    localStorage.setItem('romajiOptions', JSON.stringify(romajiOptions));
}

function convertRomaji(word) {
    return word.replace(/\(shi\)/g, romajiOptions.shi)
               .replace(/\(sha\)/g, romajiOptions.sha)
               .replace(/\(shu\)/g, romajiOptions.shu)
               .replace(/\(sho\)/g, romajiOptions.sho)
               .replace(/\(chi\)/g, romajiOptions.chi)
               .replace(/\(tsu\)/g, romajiOptions.tsu)
               .replace(/\(cha\)/g, romajiOptions.cha)
               .replace(/\(chu\)/g, romajiOptions.chu)
               .replace(/\(che\)/g, romajiOptions.che)
               .replace(/\(cho\)/g, romajiOptions.cho)
               .replace(/\(fu\)/g, romajiOptions.fu)
               .replace(/\(ji\)/g, romajiOptions.ji)
               .replace(/\(ja\)/g, romajiOptions.ja)
               .replace(/\(ju\)/g, romajiOptions.ju)
               .replace(/\(je\)/g, romajiOptions.je)
               .replace(/\(jo\)/g, romajiOptions.jo)
               .replace(/\(xa\)/g, romajiOptions.xa)
               .replace(/\(xi\)/g, romajiOptions.xi)
               .replace(/\(xu\)/g, romajiOptions.xu)
               .replace(/\(xe\)/g, romajiOptions.xe)
               .replace(/\(xo\)/g, romajiOptions.xo);
}

async function loadWords() {
    try {
        const response = await fetch('word.json');
        if (!response.ok) {
            throw new Error('Network response was not ok');
        }
        const data = await response.json();
        if (data.words.length < totalRounds) {
            alert('ファイル内の単語が不足しています。');
            return;
        }
        words = data.words.sort(() => 0.5 - Math.random()).slice(0, totalRounds);
        startGame();
    } catch (error) {
        console.error('Failed to load words:', error);
        alert('単語リストの読み込みに失敗しました。');
    }
}

async function getRankings() {
    try {
        const response = await fetch('get_rankings.php');
        const data = await response.json();

        // Debugging output
        console.log('Fetched data:', data);

        // Check if data is an array
        if (Array.isArray(data)) {
            rankings = data;
        } else {
            console.error('Invalid data format', data);
            throw new Error('Invalid data format');
        }
    } catch (error) {
        console.error('Error fetching rankings:', error);
        alert('ランキングの取得に失敗しました。');
    }
}

function startGame() {
    titleScreen.style.display = 'none';
    rankingScreen.style.display = 'none';
    nameInputScreen.style.display = 'none';
    gameOverScreen.style.display = 'none';
    gameScreen.style.display = 'block';
    currentWordIndex = 0;
    score = 0;
    timer = 0;
    inputField.value = '';
    inputField.disabled = false;
    restartButton.style.display = 'none';
    resetSubmitButton();
    updateScore();
    updateProgress();
    updateTimer();
    startTimer();
    nextWord();
}

function resetSubmitButton() {
    submitNameButton.disabled = false;
    submitNameButton.style.backgroundColor = '';
}

function startTimer() {
    timerInterval = setInterval(() => {
        timer++;
        updateTimer();
    }, 1000);
}

function stopTimer() {
    clearInterval(timerInterval);
}

function updateScore() {
    scoreElement.textContent = `スコア: ${score}`;
}

function updateProgress() {
    progressElement.textContent = `${Math.min(currentWordIndex + 1, totalRounds)}/${totalRounds}`;
}

function updateTimer() {
    timerElement.textContent = `タイマー: ${timer} 秒`;
}

function nextWord() {
    if (currentWordIndex >= totalRounds) {
        endGame();
        return;
    }
    const wordPair = words[currentWordIndex];
    japaneseWordElement.textContent = wordPair.japanese;
    romajiWordElement.textContent = convertRomaji(wordPair.romaji);
    inputField.value = '';
    inputField.focus();
}

function checkInput() {
    const userInput = inputField.value.trim();
    const currentWord = convertRomaji(words[currentWordIndex].romaji);

    if (currentWord.startsWith(userInput)) {
        if (userInput === currentWord) {
            score += 10;
            currentWordIndex++;
            updateScore();
            if (currentWordIndex < totalRounds) {
                updateProgress();
                nextWord();
            } else {
                endGame();
            }
        } else {
            romajiWordElement.textContent = currentWord.slice(userInput.length);
        }
    } else {
        score -= 5;
        updateScore();
        inputField.value = userInput.slice(0, -1);
    }
}

function endGame() {
    stopTimer();
    updateProgress();
    japaneseWordElement.textContent = 'ゲーム終了';
    romajiWordElement.textContent = '';
    inputField.disabled = true;
    restartButton.style.display = 'block';
    gameOverScoreElement.textContent = `スコア: ${score}`;
    gameOverTimerElement.textContent = `タイマー: ${timer} 秒`;
    checkRanking();
}

function checkRanking() {
    newEntry = { score: score, time: timer, date: new Date().toLocaleString() };
    let rank = -1;

    if (rankings.length < 10) {
        rank = rankings.length + 1;
    } else {
        for (let i = 0; i < rankings.length; i++) {
            if (score > rankings[i].score || (score === rankings[i].score && timer < rankings[i].time)) {
                rank = i + 1;
                break;
            }
        }
    }

    if (rank !== -1 && rank <= 10) {
        nameInputScoreElement.textContent = `スコア: ${score}`;
        nameInputTimerElement.textContent = `タイマー: ${timer} 秒`;
        nameInputDateElement.textContent = `日時: ${newEntry.date}`;
        nameInputRankElement.textContent = `${rank}位にランクイン!`;
        nameInputScreen.style.display = 'block';
        gameScreen.style.display = 'none';
    } else {
        gameOverScreen.style.display = 'block';
        gameScreen.style.display = 'none';
    }
}

submitNameButton.addEventListener('click', async () => {
    submitNameButton.disabled = true;
    submitNameButton.style.backgroundColor = 'grey';

    const name = nameInputField.value.trim();
    const comment = commentInputField.value.trim();

    if (name.length === 0 || name.length > 50) {
        alert('名前は1~50字以内で入力してください。');
        submitNameButton.disabled = false;
        submitNameButton.style.backgroundColor = '';
        return;
    }

    if (comment.length > 100) {
        alert('コメントは100字以内で入力してください。');
        submitNameButton.disabled = false;
        submitNameButton.style.backgroundColor = '';
        return;
    }

    newEntry.name = name;
    newEntry.comment = comment;
    const response = await fetch('save_ranking.php', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json'
        },
        body: JSON.stringify(newEntry)
    });

    const result = await response.json();
    if (result.status === 'success') {
        nameInputScreen.style.display = 'none';
        showRanking();
    } else {
        alert('ランキングの保存に失敗しました。');
        submitNameButton.disabled = false;
        submitNameButton.style.backgroundColor = '';
    }
});

startButton.addEventListener('click', async () => {
    try {
        updateRomajiOptions();
        await getRankings();
        await loadWords();
    } catch (error) {
        console.error('Error during game start:', error);
        alert('ゲームの開始に失敗しました。');
    }
});

restartButton.addEventListener('click', async () => {
    try {
        await loadWords();
    } catch (error) {
        console.error('Error during game restart:', error);
        alert('ゲームの再開に失敗しました。');
    }
});

showRankingButton.addEventListener('click', async () => {
    try {
        await showRanking();
    } catch (error) {
        console.error('Error during ranking display:', error);
        alert('ランキングの表示に失敗しました。');
    }
});

backButton.addEventListener('click', () => {
    rankingScreen.style.display = 'none';
    titleScreen.style.display = 'block';
});

titleButton.addEventListener('click', () => {
    gameOverScreen.style.display = 'none';
    titleScreen.style.display = 'block';
});

newGameButton.addEventListener('click', async () => {
    try {
        await loadWords();
    } catch (error) {
        console.error('Error during new game start:', error);
        alert('新しいゲームの開始に失敗しました。');
    }
});

inputField.addEventListener('input', checkInput);

async function showRanking() {
    try {
        const response = await fetch('get_rankings.php');
        const data = await response.json();

        // Debugging output
        console.log('Fetched data:', data);

        // Check if data is an array
        if (Array.isArray(data)) {
            rankings = data;
        } else {
            console.error('Invalid data format', data);
            throw new Error('Invalid data format');
        }

        rankingList.innerHTML = '';
        data.forEach((entry, index) => {
            const row = document.createElement('tr');
            row.innerHTML = `
                <td>${index + 1}位</td>
                <td>${entry.name}</td>
                <td>${entry.score}</td>
                <td>${entry.time}</td>
                <td>${entry.date}</td>
                <td>${entry.comment}</td>
            `;
            rankingList.appendChild(row);
        });
        rankingScreen.style.display = 'block';
        titleScreen.style.display = 'none';
    } catch (error) {
        console.error('Error fetching rankings:', error);
        alert('ランキングの取得に失敗しました。');
    }
}

// Load saved romaji options from localStorage
window.addEventListener('load', () => {
    const savedRomajiOptions = JSON.parse(localStorage.getItem('romajiOptions'));
    if (savedRomajiOptions) {
        for (const [key, value] of Object.entries(savedRomajiOptions)) {
            const radio = document.querySelector(`input[name="${key}"][value="${value}"]`);
            if (radio) {
                radio.checked = true;
            }
        }
        updateRomajiOptions();
    }
});

word.json

指定のローマ字が変更をカッコ書きで認識するようにプログラムしています。カッコ書きの指定は下記になります。

(shi) si or shi
(sha) sha or sya
(shu) shu or syu
(sho) sho or syo
(chi) chi or ti
(tsu) tu or tsu
(cha) cha or tya
(chu) chu or tyu
(che) che or tye
(cho) cho or tyo
(fu) fu or hu
(ji) ji or zi
(ja) ja or zya
(ju) ju or zyu
(je) je or zye
(jo) jo or zyo
(xa) xa or la
(xi) xi or li
(xu) xu or lu
(xe) xe or le
(xo) xo or lo

下記がコードの例です。10問以上のjsonファイルを用意してください。

{
    "words": [
        {
            "japanese": "咲耶です。忍びません",
            "romaji": "sakuyadesu.(shi)nobimasenn"
        },
        {
            "japanese": "岩爺",
            "romaji": "gann(ji)i"
        },
        {
            "japanese": "クリプト絵巻",
            "romaji": "kuriputoemaki"
        },
        {
            "japanese": "チェンジ",
            "romaji": "(che)nn(ji)"
        },
        {
            "japanese": "はじめての任務",
            "romaji": "ha(ji)metenoninnmu"
        },
        {
            "japanese": "コンガ師匠、お願いしますよ",
            "romaji": "konnga(shi)(sho)u,onegai(shi)masuyo"
        },
        {
            "japanese": "大ピンチ!風魔シティ",
            "romaji": "daipinn(chi)!(fu)uma(shi)te(xi)"
        },
        {
            "japanese": "岩爺の力",
            "romaji": "gann(ji)ino(chi)kara"
        },
        {
            "japanese": "戦のはじまり",
            "romaji": "ikusanoha(ji)mari"
        },
        {
            "japanese": "バトル",
            "romaji": "batoru"
        },
        {
            "japanese": "さよなら、おじいちゃん",
            "romaji": "sayonara,o(ji)i(cha)n"
        },
        {
            "japanese": "クリプトニンジャ咲耶",
            "romaji": "kuriputoninn(ja)sakuya"
        },
        {
            "japanese": "大丈夫",
            "romaji": "dai(jo)ubu"
        },
        {
            "japanese": "新たなる戦い",
            "romaji": "aratanarutatakai"
        },
        {
            "japanese": "二人のハヤテ",
            "romaji": "(fu)tarinohayate"
        },
        {
            "japanese": "甲賀の宝物",
            "romaji": "kouganotakaramono"
        },
        {
            "japanese": "アウンとアトザ",
            "romaji": "aunntoatoza"
        },
        {
            "japanese": "おまつり",
            "romaji": "oma(tsu)ri"
        },
        {
            "japanese": "鬼",
            "romaji": "oni"
        },
        {
            "japanese": "弁天",
            "romaji": "benntenn"
        },
        {
            "japanese": "狐白",
            "romaji": "kohaku"
        },
        {
            "japanese": "咲耶VS狐白",
            "romaji": "sakuyavskohaku"
        },
        {
            "japanese": "鬼道衆",
            "romaji": "onidou(shu)u"
        },
        {
            "japanese": "滅びの術",
            "romaji": "horobino(ju)(tsu)"
        },
        {
            "japanese": "大団円",
            "romaji": "daidannenn"
        }
    ]
}

なお、下記からファイルをダウンロードできます。

まとめ

本記事では、ローマ字タイピング練習ゲームの作成手順を紹介しました。このゲームはオンラインランキング機能を備えており、ユーザーが自分のスコアを他のユーザーと比較することができます。また、ローマ字の入力方法をカスタマイズする機能も提供しています。これにより、楽しくタイピングスキルを向上させることができます。

ゲームを試すことが出来ます:[ここにリンクを追加]

以上で、ローマ字タイピング練習ゲームの作り方についての説明を終わります。興味のある方は、ぜひ自分で実装してみてください。

-CSS, javascript, PHP, ゲーム