エンジニア初心者でもできる!JavaScriptでWordleゲームを作る

目次

Wordleとは?ゲームのルールと魅力を理解しよう

あわせて読みたい
Wordle - A daily word game Guess the hidden word in 6 tries. A new puzzle is available each day.

Wordleの基本ルールと遊び方

Wordleは2021年に大ブームを起こした5文字の英単語当てゲームです。プレイヤーは6回の推測チャンスで正解の単語を当てることが目標となります。

意味
緑(Green)正しい文字が正しい位置答えが「CRANE」で「C」を1文字目に入力
黄(Yellow)正しい文字だが位置が違う答えが「CRANE」で「R」を1文字目に入力
灰(Gray)答えに含まれない文字答えが「CRANE」で「B」を入力

なぜWordleが学習題材として優秀なのか

Wordleは初心者プログラマーにとって理想的な学習プロジェクトです。以下の理由で多くのプログラミングスクールでも採用されています:

  • ルールがシンプルで理解しやすい
  • HTML/CSS/JavaScriptの基本を全て学べる
  • 段階的に機能を追加できる
  • ポートフォリオ作品として見栄えが良い
  • ユーザーインターフェースの設計経験が積める

必要な技術スタックと学習できるスキル

技術使用場面学習できるスキル
HTMLゲーム画面の構造作成セマンティックマークアップ、アクセシビリティ
CSS見た目とレイアウトGrid/Flexbox、レスポンシブデザイン、アニメーション
JavaScriptゲームロジックDOM操作、イベント処理、データ構造、アルゴリズム
Local Storage進捗保存ブラウザストレージAPI、データ永続化

開発環境の準備とプロジェクト構成

必要なファイル構成とディレクトリ設計

Wordleゲームは単純な構成で開始できます。以下のファイル構成を推奨します:

wordle-game/
├── index.html          # メインのHTMLファイル
├── style.css           # スタイルシート
├── script.js           # JavaScript(ゲームロジック)
├── words.js            # 単語リスト
└── README.md           # プロジェクト説明

開発に必要なツールとエディタ設定

効率的な開発のために以下のツールを準備しましょう:

ツール用途推奨設定
Visual Studio CodeコードエディタLive Server拡張機能をインストール
Chrome DevToolsデバッグとテストConsoleタブを常時開いてエラー確認
Gitバージョン管理コミットは機能単位で小分けに
GitHub Pages無料ホスティング完成後の公開用

HTMLの基本構造とメタ情報の設定

まずは基本となるHTMLファイルを作成します。レスポンシブ対応とSEO対策も考慮した構造にしましょう:

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="description" content="JavaScript で作成した Wordle ゲーム">
    <title>Wordle Game - 5文字単語当てゲーム</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <header>
        <h1>Wordle</h1>
        <p>6回のチャンスで5文字の単語を当てよう!</p>
    </header>
    
    <main>
        <div id="game-container">
            <!-- ゲーム要素はJavaScriptで動的に生成 -->
        </div>
    </main>

    <script src="words.js"></script>
    <script src="script.js"></script>
</body>
</html>

ゲーム画面のUI設計とHTML構造

ワイヤーフレームから始める画面設計

Wordleの画面は主に3つの要素で構成されています。ユーザビリティを考慮した配置が重要です:

  • ゲームボード:6行5列のマス目(推測入力エリア)
  • 仮想キーボード:画面タップでの文字入力
  • ステータス表示:ゲーム結果や操作ガイド

セマンティックなHTML構造の実装

アクセシビリティと保守性を考慮したHTML構造を作成します。適切な要素選択とARIA属性の使用がポイントです:

<main id="game-container">
    <!-- ゲームボード -->
    <section id="game-board" aria-label="単語推測ボード">
        <!-- JavaScript で動的に生成される 6x5 のグリッド -->
    </section>

    <!-- メッセージ表示エリア -->
    <div id="message" role="status" aria-live="polite"></div>

    <!-- 仮想キーボード -->
    <section id="keyboard" aria-label="仮想キーボード">
        <div class="keyboard-row">
            <button class="key" data-key="Q">Q</button>
            <button class="key" data-key="W">W</button>
            <!-- 他のキー... -->
        </div>
        <div class="keyboard-row">
            <button class="key special" data-key="ENTER">ENTER</button>
            <button class="key special" data-key="BACKSPACE">⌫</button>
        </div>
    </section>
</main>

アクセシビリティを考慮したマークアップ

要素アクセシビリティ対応実装方法
ゲームボードスクリーンリーダー対応aria-label、role属性の使用
キーボードフォーカス管理tabindex、keyboard navigationの実装
状態変化動的な情報通知aria-live=”polite”の活用
色による表現色覚異常への配慮パターンやアイコンの併用

CSSでゲームらしい見た目を作成

Wordleらしいデザインの実装方法

オリジナルのWordleに近い見た目を再現しつつ、独自性も加えた設計にします。重要なのは視認性とユーザビリティです:

/* 基本設定とCSS変数 */
:root {
    --color-correct: #6aaa64;    /* 緑:正解位置 */
    --color-present: #c9b458;    /* 黄:文字は正解だが位置違い */
    --color-absent: #787c7e;     /* 灰:不正解文字 */
    --color-empty: #ffffff;      /* 白:未入力 */
    --color-border: #d3d6da;     /* ボーダー色 */
    --font-family: 'Arial', sans-serif;
}

body {
    margin: 0;
    padding: 20px;
    font-family: var(--font-family);
    background-color: #f7f7f7;
    display: flex;
    flex-direction: column;
    align-items: center;
    min-height: 100vh;
}

header {
    text-align: center;
    margin-bottom: 2rem;
}

header h1 {
    font-size: 2.5rem;
    font-weight: bold;
    color: #333;
    margin: 0;
    letter-spacing: 0.05em;
}

CSS Grid・Flexboxを活用したレイアウト

ゲームボードは CSS Grid を使用して実装します。レスポンシブ対応も考慮した設計にしましょう:

/* ゲームボードのグリッドレイアウト */
#game-board {
    display: grid;
    grid-template-rows: repeat(6, 1fr);
    gap: 5px;
    margin-bottom: 2rem;
    padding: 10px;
}

.board-row {
    display: grid;
    grid-template-columns: repeat(5, 1fr);
    gap: 5px;
}

.tile {
    width: 62px;
    height: 62px;
    border: 2px solid var(--color-border);
    display: flex;
    justify-content: center;
    align-items: center;
    font-size: 2rem;
    font-weight: bold;
    color: #333;
    background-color: var(--color-empty);
    text-transform: uppercase;
    transition: all 0.2s ease;
}

/* 状態別のタイルスタイル */
.tile.correct {
    background-color: var(--color-correct);
    border-color: var(--color-correct);
    color: white;
}

.tile.present {
    background-color: var(--color-present);
    border-color: var(--color-present);
    color: white;
}

.tile.absent {
    background-color: var(--color-absent);
    border-color: var(--color-absent);
    color: white;
}

レスポンシブ対応とモバイルファースト

モバイルデバイスでも快適にプレイできるよう、レスポンシブデザインを実装します:

/* キーボードのレスポンシブデザイン */
#keyboard {
    max-width: 500px;
    width: 100%;
}

.keyboard-row {
    display: flex;
    justify-content: center;
    margin-bottom: 8px;
    gap: 6px;
}

.key {
    min-width: 44px;  /* タッチしやすい最小サイズ */
    height: 58px;
    border: none;
    border-radius: 4px;
    background-color: #d3d6da;
    color: #333;
    font-weight: bold;
    cursor: pointer;
    transition: background-color 0.1s ease;
    font-size: 0.9rem;
}

.key:hover {
    background-color: #bbb;
}

.key.special {
    min-width: 65px;
    font-size: 0.8rem;
}

/* タブレット・モバイル対応 */
@media (max-width: 768px) {
    .tile {
        width: 50px;
        height: 50px;
        font-size: 1.5rem;
    }
    
    .key {
        min-width: 36px;
        height: 48px;
        font-size: 0.8rem;
    }
    
    header h1 {
        font-size: 2rem;
    }
}

@media (max-width: 480px) {
    .tile {
        width: 45px;
        height: 45px;
        font-size: 1.2rem;
    }
    
    .key {
        min-width: 32px;
        height: 42px;
        font-size: 0.7rem;
    }
}

JavaScriptでゲームロジックを実装

ゲーム状態管理とデータ構造の設計

効率的なゲーム状態管理のため、適切なデータ構造を設計しましょう。オブジェクト指向の考え方を取り入れます:

// ゲーム状態を管理するクラス
class WordleGame {
    constructor() {
        this.targetWord = '';           // 正解の単語
        this.currentRow = 0;            // 現在の行(0-5)
        this.currentCol = 0;            // 現在の列(0-4)
        this.gameState = 'playing';     // playing, won, lost
        this.guesses = [];              // 推測の履歴
        this.board = [];                // ゲームボードの状態
        this.keyboardState = {};        // キーボードの色状態
        
        this.initializeBoard();
        this.setTargetWord();
    }

    // ボードの初期化
    initializeBoard() {
        this.board = Array(6).fill(null).map(() => 
            Array(5).fill({ letter: '', state: 'empty' })
        );
    }

    // ランダムな単語を選択
    setTargetWord() {
        const words = WORD_LIST; // words.js で定義
        this.targetWord = words[Math.floor(Math.random() * words.length)].toUpperCase();
        console.log('Target word:', this.targetWord); // 開発用
    }

    // 文字入力の処理
    handleKeyPress(key) {
        if (this.gameState !== 'playing') return;

        if (key === 'ENTER') {
            this.submitGuess();
        } else if (key === 'BACKSPACE') {
            this.deleteLetter();
        } else if (key.length === 1 && /[A-Z]/.test(key)) {
            this.addLetter(key);
        }
    }
}

単語判定システムの構築

Wordleの核となる単語判定ロジックを実装します。正確な判定のため、文字の重複も考慮する必要があります:

// 推測単語の判定ロジック
evaluateGuess(guess) {
    const result = Array(5).fill('absent');
    const targetLetters = this.targetWord.split('');
    const guessLetters = guess.split('');
    
    // 第1段階:正確な位置の文字をチェック(緑)
    for (let i = 0; i < 5; i++) {
        if (guessLetters[i] === targetLetters[i]) {
            result[i] = 'correct';
            targetLetters[i] = null; // マーク済み
            guessLetters[i] = null;  // マーク済み
        }
    }
    
    // 第2段階:存在するが位置が違う文字をチェック(黄)
    for (let i = 0; i < 5; i++) {
        if (guessLetters[i] !== null) {
            const targetIndex = targetLetters.indexOf(guessLetters[i]);
            if (targetIndex !== -1) {
                result[i] = 'present';
                targetLetters[targetIndex] = null; // 使用済みにマーク
            }
        }
    }
    
    return result;
}

// 推測の送信処理
submitGuess() {
    if (this.currentCol !== 5) {
        this.showMessage('5文字入力してください');
        return;
    }

    const currentGuess = this.getCurrentGuess();
    
    // 辞書チェック(オプション)
    if (!this.isValidWord(currentGuess)) {
        this.showMessage('辞書にない単語です');
        this.shakeRow(this.currentRow);
        return;
    }

    // 判定実行
    const evaluation = this.evaluateGuess(currentGuess);
    this.updateBoard(this.currentRow, currentGuess, evaluation);
    this.updateKeyboard(currentGuess, evaluation);
    
    // ゲーム終了判定
    if (currentGuess === this.targetWord) {
        this.gameState = 'won';
        this.showMessage('正解です!');
        this.celebrateWin();
    } else if (this.currentRow === 5) {
        this.gameState = 'lost';
        this.showMessage(`正解は ${this.targetWord} でした`);
    } else {
        this.currentRow++;
        this.currentCol = 0;
    }
}

キーボード入力とバリデーション処理

物理キーボードと仮想キーボードの両方に対応した入力処理を実装します:

// イベントリスナーの設定
setupEventListeners() {
    // 物理キーボード対応
    document.addEventListener('keydown', (e) => {
        const key = e.key.toUpperCase();
        
        if (key === 'ENTER' || key === 'BACKSPACE') {
            this.handleKeyPress(key);
        } else if (/^[A-Z]$/.test(key)) {
            this.handleKeyPress(key);
        }
        
        e.preventDefault();
    });

    // 仮想キーボード対応
    document.querySelectorAll('.key').forEach(button => {
        button.addEventListener('click', (e) => {
            const key = e.target.dataset.key;
            this.handleKeyPress(key);
        });
    });
}

// 文字の追加
addLetter(letter) {
    if (this.currentCol < 5) {
        this.board[this.currentRow][this.currentCol] = {
            letter: letter,
            state: 'filled'
        };
        this.updateTile(this.currentRow, this.currentCol, letter);
        this.currentCol++;
    }
}

// 文字の削除
deleteLetter() {
    if (this.currentCol > 0) {
        this.currentCol--;
        this.board[this.currentRow][this.currentCol] = {
            letter: '',
            state: 'empty'
        };
        this.updateTile(this.currentRow, this.currentCol, '');
    }
}

// 現在の推測を取得
getCurrentGuess() {
    return this.board[this.currentRow]
        .map(cell => cell.letter)
        .join('');
}

ユーザビリティとアニメーション効果

スムーズなアニメーション実装

ユーザー体験を向上させるため、適切なアニメーション効果を追加します。タイルの反転アニメーションが特に重要です:

/* タイル反転アニメーション */
@keyframes flipTile {
    0% {
        transform: rotateX(0);
    }
    50% {
        transform: rotateX(90deg);
    }
    100% {
        transform: rotateX(0);
    }
}

.tile.flip {
    animation: flipTile 0.6s ease-in-out;
}

/* 文字入力時のポップアニメーション */
@keyframes popTile {
    0% {
        transform: scale(1);
    }
    50% {
        transform: scale(1.1);
    }
    100% {
        transform: scale(1);
    }
}

.tile.pop {
    animation: popTile 0.1s ease;
}

/* 不正解時の揺れアニメーション */
@keyframes shakeRow {
    0%, 100% {
        transform: translateX(0);
    }
    20%, 60% {
        transform: translateX(-10px);
    }
    40%, 80% {
        transform: translateX(10px);
    }
}

.board-row.shake {
    animation: shakeRow 0.5s ease;
}

フィードバック機能とエラーハンドリング

ユーザーの操作に対する適切なフィードバックを実装します:

// メッセージ表示システム
showMessage(text, duration = 2000) {
    const messageElement = document.getElementById('message');
    messageElement.textContent = text;
    messageElement.classList.add('show');
    
    setTimeout(() => {
        messageElement.classList.remove('show');
    }, duration);
}

// 行の揺れアニメーション
shakeRow(rowIndex) {
    const row = document.querySelector(`[data-row="${rowIndex}"]`);
    row.classList.add('shake');
    
    setTimeout(() => {
        row.classList.remove('shake');
    }, 500);
}

// タイルの反転アニメーション(判定結果表示)
animateRowReveal(rowIndex, evaluation) {
    const tiles = document.querySelectorAll(`[data-row="${rowIndex}"] .tile`);
    
    tiles.forEach((tile, index) => {
        setTimeout(() => {
            tile.classList.add('flip');
            
            // アニメーション中間で色変更
            setTimeout(() => {
                tile.classList.remove('filled');
                tile.classList.add(evaluation[index]);
            }, 300);
            
            // アニメーション終了後にクラス削除
            setTimeout(() => {
                tile.classList.remove('flip');
            }, 600);
        }, index * 100); // 順次実行
    });
}

// 勝利時のお祝いアニメーション
celebrateWin() {
    const tiles = document.querySelectorAll(`[data-row="${this.currentRow}"] .tile`);
    
    tiles.forEach((tile, index) => {
        setTimeout(() => {
            tile.classList.add('celebrate');
        }, index * 100);
    });
}

ローカルストレージを使った進捗保存

ユーザーの進捗を保存し、ページをリロードしても継続できる機能を実装します:

// ゲーム状態の保存
saveGameState() {
    const gameData = {
        targetWord: this.targetWord,
        currentRow: this.currentRow,
        currentCol: this.currentCol,
        gameState: this.gameState,
        board: this.board,
        keyboardState: this.keyboardState,
        timestamp: Date.now()
    };
    
    localStorage.setItem('wordleGameState', JSON.stringify(gameData));
}

// ゲーム状態の読み込み
loadGameState() {
    const savedData = localStorage.getItem('wordleGameState');
    
    if (!savedData) return false;
    
    try {
        const gameData = JSON.parse(savedData);
        
        // 24時間以内のデータのみ復元
        const oneDay = 24 * 60 * 60 * 1000;
        if (Date.now() - gameData.timestamp > oneDay) {
            localStorage.removeItem('wordleGameState');
            return false;
        }
        
        // 状態復元
        this.targetWord = gameData.targetWord;
        this.currentRow = gameData.currentRow;
        this.currentCol = gameData.currentCol;
        this.gameState = gameData.gameState;
        this.board = gameData.board;
        this.keyboardState = gameData.keyboardState;
        
        this.restoreUI();
        return true;
        
    } catch (error) {
        console.error('Failed to load game state:', error);
        localStorage.removeItem('wordleGameState');
        return false;
    }
}

// 統計データの管理
updateStats(won) {
    const stats = this.getStats();
    
    stats.gamesPlayed++;
    if (won) {
        stats.gamesWon++;
        stats.currentStreak++;
        stats.maxStreak = Math.max(stats.maxStreak, stats.currentStreak);
        stats.guessDistribution[this.currentRow]++;
    } else {
        stats.currentStreak = 0;
    }
    
    localStorage.setItem('wordleStats', JSON.stringify(stats));
}

パフォーマンス最適化とデプロイ準備

コードの最適化とベストプラクティス

パフォーマンスと保守性を向上させるための最適化を行います:

最適化項目実装方法効果
DOM操作の最小化DocumentFragmentの使用、バッチ更新レンダリング性能向上
イベントデリゲーション親要素での一括イベント処理メモリ使用量削減
debounce/throttle頻繁なイベント処理の制御CPU負荷軽減
CSS transitionGPUアクセラレーション活用アニメーション性能向上
// 効率的なDOM操作の例
updateBoard(rowIndex, guess, evaluation) {
    const fragment = document.createDocumentFragment();
    const tiles = document.querySelectorAll(`[data-row="${rowIndex}"] .tile`);
    
    // バッチでスタイル更新
    requestAnimationFrame(() => {
        tiles.forEach((tile, index) => {
            tile.textContent = guess[index];
            tile.dataset.state = evaluation[index];
        });
    });
}

// メモリリークを防ぐイベントリスナー管理
class EventManager {
    constructor() {
        this.listeners = [];
    }
    
    addEventListener(element, event, handler) {
        element.addEventListener(event, handler);
        this.listeners.push({ element, event, handler });
    }
    
    removeAllListeners() {
        this.listeners.forEach(({ element, event, handler }) => {
            element.removeEventListener(event, handler);
        });
        this.listeners = [];
    }
}

デバッグ手法とテスト方法

効率的なデバッグとテストのためのアプローチを実装します:

// デバッグ用のヘルパー関数
class DebugHelper {
    static enableDebugMode() {
        window.wordleDebug = {
            revealAnswer: () => console.log('Answer:', game.targetWord),
            setAnswer: (word) => { game.targetWord = word.toUpperCase(); },
            skipToRow: (row) => { game.currentRow = row; },
            showGameState: () => console.log('Game State:', game)
        };
        console.log('Debug mode enabled. Use window.wordleDebug');
    }
    
    static logGameState(game) {
        console.group('Game State');
        console.log('Target:', game.targetWord);
        console.log('Current Row:', game.currentRow);
        console.log('Current Col:', game.currentCol);
        console.log('Game Status:', game.gameState);
        console.groupEnd();
    }
}

// 簡単なユニットテスト
function runTests() {
    const game = new WordleGame();
    
    // 判定ロジックのテスト
    const testCases = [
        { target: 'CRANE', guess: 'CRANE', expected: ['correct', 'correct', 'correct', 'correct', 'correct'] },
        { target: 'CRANE', guess: 'CLEAR', expected: ['correct', 'absent', 'present', 'absent', 'correct'] },
        { target: 'SPEED', guess: 'ERASE', expected: ['present', 'absent', 'absent', 'absent', 'present'] }
    ];
    
    testCases.forEach(({ target, guess, expected }, index) => {
        game.targetWord = target;
        const result = game.evaluateGuess(guess);
        const passed = JSON.stringify(result) === JSON.stringify(expected);
        console.log(`Test ${index + 1}: ${passed ? 'PASS' : 'FAIL'}`, { target, guess, result, expected });
    });
}

無料ホスティングサービスでの公開手順

完成したゲームを無料で公開する方法を解説します。GitHub Pages が最も簡単でおすすめです:

ステップ作業内容注意点
1. リポジトリ作成GitHubで新しいリポジトリを作成Public設定にする
2. ファイルアップロード全てのファイルをpushindex.htmlが必須
3. Pages設定Settings > Pages で main branch を選択数分で反映される
4. 動作確認公開URLでテストプレイモバイルでも確認
# Git での公開手順
git init
git add .
git commit -m "Initial commit: Wordle game implementation"
git branch -M main
git remote add origin https://github.com/yourusername/wordle-game.git
git push -u origin main

# GitHub Pages URL例
# https://yourusername.github.io/wordle-game/

完成後のチェックリスト

カテゴリチェック項目確認方法
機能性正しい単語判定ができる様々なパターンでテストプレイ
ユーザビリティ直感的な操作が可能初心者にプレイしてもらう
レスポンシブモバイルで正常動作実機での操作確認
アクセシビリティキーボード操作対応Tabキーのみでプレイ
パフォーマンススムーズなアニメーション低性能デバイスでの確認
データ永続化進捗が保存されるページリロード後の状態確認

Wordleゲームの実装を通じて、現代的なウェブ開発の基礎を学ぶことができます。HTML/CSS/JavaScriptの連携、ユーザビリティの考慮、パフォーマンス最適化など、実際のプロジェクトで必要となるスキルを総合的に身につけられる優秀な学習題材です。

完成したゲームは、あなたのポートフォリオの重要な作品となるでしょう。ユーザーフィードバックを集めて、さらなる改良を続けてください。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次