1. ホーム
  2. Web プログラミング
  3. ジャバスクリプト
  4. javascriptのヒント

HTML+CSS+JavaScriptで簡単な三目並べゲームを作成する。

2022-01-13 06:08:35

デモアドレス

HTMLの実装

まずheadセクションに、後で作成するcssとjavascriptのファイルを入れておきます。また、ItimというGoogleのフォントも入れています。

<link rel="stylesheet" href="style.css" rel="external nofollow" >
<link rel="preconnect" href="https://fonts.gstatic.com" rel="external nofollow" >
<link href="https://fonts.googleapis.com/css2?family=Itim&display=swap" rel="external nofollow" rel=" stylesheet">
<script src="index.js"></script>


HTMLのボディはかなりシンプルになります。すべてを包むために、mainタグを使い、class backgroundを適用します。mainラッパーの内側には、5つのセクションがあります。

最初のセクションには、タイトルh1だけを入れます。

2番目の部分は、現在誰が番をしているのかを表示します。表示では、現在のユーザーに応じてXまたはOを含むスパンを用意しています。このスパンにクラスを適用して、テキストに色を付けています。

3つ目は、ゲーム盤を保持する部分です。コンテナクラスがあるので、タイルを正しく配置することができます。このセクションでは、ボード内のタイルとして機能する9つのdivがあります。

4番目のセクションは、最終的な試合結果を発表する役割を担います。デフォルトでは空になっているので、javascriptからその内容を変更します。

最後の部分は、再起動ボタンを含むコントロールを保存します。

<main class="background">
        <section class="title">
            <h1>tic-tac-toe</h1>
        </section>
        <section class="display">
            Player <span class="display-player playerX">X</span> of the turn
        </section>
        <section class="container">
            <div class="tile"></div>
            <div class="tile"></div>
            <div class="tile"></div>
            <div class="tile"></div>
            <div class="tile"></div>
            <div class="tile"></div>
            <div class="tile"></div>
            <div class="tile"></div>
            <div class="tile"></div>
        </section>
        <section class="display announcer hide"></section>
        <section class="controls">
            <button id="reset">Restart</button>
        </section>
    </main>


CSSの追加

CSSの一行一行について詳しく説明することはしませんが、ソースコードで全コードを見ることができます。

まず、style.cssファイルを作成し、ブラウザで定義されたマージンやパディングを削除し、HTMLに含めたGoogleフォントを文書全体に設定することにします。

* {
    padding: 0;
    margin: 0;
    font-family: 'Itim', cursive;
}


次に追加しなければならない重要なことは、ボードのスタイルです。私たちはCSSグリッドを使用してボードを作成します。列と行のために3倍の33%のスペースを提供することで、コンテナを2つに分割することができます。私たちはこれを、最大幅を設定し、コンテナを中央に配置することで、行います margin: 0 auto; .

.container {
    margin: 0 auto;
    display: grid;
    grid-template-columns: 33% 33% 33%;
    grid-template-rows: 33% 33% 33%;
    max-width: 300px;
}


次に、インボードタイルのスタイルを追加します。小さな白いボーダーを適用し、幅と高さの最小値を100ピクセルに設定します。Flexboxを利用し、中央のコンテンツを設定します。 justify-content align-items to center . 大きなフォントサイズを与えて、それを適用することにします。 cursor: pointer こうすることで、ユーザーはそのフィールドがクリック可能であることを知ることができます。

.tile {
    border: 1px solid white;
    min-width: 100px;
    min-height: 100px;
    display: flex;
    justify-content: center;
    align-items: center;
    font-size: 50px;
    cursor: pointer;
}


2人のプレーヤーをよりよく区別するために、2つの異なる色を使用しています。そのために、2つのユーティリティクラスを作成します。プレイヤーXは緑色、プレイヤーOは青色です。

.playerX {
    color: #09C372;
}

.playerO {
    color: #498AFB;
}


Javascript部分の実装

javascript ファイルを <head> . これは、ブラウザが HTML 本体を解析する前に、私たちのスクリプトが読み込まれるために必要なことです。この関数にすべてを含めたくない場合は、スクリプトのマークアップにdeferを追加したり、スクリプトのマークアップをボディに移動したりするのも自由です。

window.addEventListener('DOMContentLoaded', () => {

});


まず、DOM ノードへの参照を保存します。document.querySelectorAll()を使用することにします。配列にしたいのですが、この関数は NodeList を返すので、Array.from() を使用する必要があります。プレーヤー表示、リセットボタン、アナウンサーへの参照も取得します。

const tiles = Array.from(document.querySelectorAll('.tiles'));
const playerDisplay = document.querySelector('.display-player');
const resetButton = document.querySelector('#reset');
const announcer = document.querySelector('.announcer');


次に、ゲームを制御するために必要なグローバル変数を追加します。9つの空の文字列を含む配列で碁盤を初期化します。これは、ボード上の各ブロックのXとOの値を保持します。currentPlayerは、現在のターンでアクティブなプレーヤーのフラグを保持します。このisGameActive変数は、誰かが勝つか、ゲームが引き分けで終わるまで、trueのままです。この場合、falseにセットして、残りのブロックがリセットされるまで非アクティブになるようにします。ゲーム終了時の状態を表す定数を3つ用意しました。スペルミスを防ぐために、これらの定数を使用します。

let board = ['', '', '', '', '', '', '', '', ''];
let currentPlayer = 'X';
let isGameActive = true;

const PLAYERX_WON = 'PLAYERX_WON';
const PLAYERO_WON = 'PLAYERO_WON';
const TIE = 'TIE';


次のステップでは、ボード上のすべての勝ちポジションを格納します。各サブ配列に、ゲームに勝つことができる3つの位置のインデックスを格納します。つまり、この[0, 1, 2]は、最初の水平線がプレーヤーによって占められる場合を表します。この配列を使って、勝者がいるかどうかを判断することになります。

/*
   Indexes within the board
   [0] [1] [2]
   [3] [4] [5]
   [6] [7] [8]
*/

const winningConditions = [
   [0, 1, 2],
   [3, 4, 5],
   [6, 7, 8],
   [0, 3, 6],
   [1, 4, 7],
   [2, 5, 8],
   [0, 4, 8],
   [2, 4, 6]
];


では、いくつかのユーティリティ関数を書いてみましょう。isValidAction関数では、ユーザが有効なアクションを実行したいのかどうかを判断しています。もしタイルの内部テキストが XorO であれば、無効なアクションとして false を返し、そうでなければタイルは空なのでアクションは有効である。

const isValidAction = (tile) => {
    if (tile.innerText === 'X' || tile.innerText === 'O'){
        return false;
    }

    return true;
};


次のユーティリティ関数は、非常にシンプルなものになります。この関数では、インデックスを引数として取り、ボード配列の対応する要素を現在のプレーヤーのシンボルに設定します。

const updateBoard = (index) => {
   board[index] = currentPlayer;
}


プレイヤーの変更を処理するための小さな関数を書きます。この関数では、まず playerDisplay.string テンプレート リテラルの player${currentPlayer} が、現在のプレイヤーに応じて playerX または playerO になるようにします。次に、3項式を使用して現在のプレーヤーの値を変更します。ユーザーの値を変更したので、playerDisplay の innerText を更新し、新しいプレイヤー クラスを適用する必要があります。

const changePlayer = () => {
    playerDisplay.classList.remove(`player${currentPlayer}`);
    currentPlayer = currentPlayer === 'X' ? 'O' : 'X';
    playerDisplay.innerText = currentPlayer;
    playerDisplay.classList.add(`player${currentPlayer}`);
}


次に、最終的なゲーム結果をアナウンスするアナウンサー関数を書きます。これは、結果に基づいてアナウンサーのDOMノードを更新するために、エンドゲームの種類とinnerTextを受け取ります。最後の行では、アナウンサーはゲームが終了するまでデフォルトで非表示になっているため、hiddenクラスを削除する必要があります。

const announce = (type) => {
    switch(type){
       case PLAYERO_WON:
            announcer.innerHTML = 'Player <span class="playerO">O</span> Won';
            break;
       case PLAYERX_WON:
            announcer.innerHTML = 'Player <span class="playerX">X</span> Won';
            break;
       case TIE:
            announcer.innerText = 'Tie';
        }
    announcer.classList.remove('hide');
};


<スパン 次に、このプロジェクトで最も興味深い部分の一つである、結果の評価を書いていきます。まず、変数 roundWon を作成し、false に初期化します。次に、winConditions 配列を繰り返し、盤上の各勝利条件をチェックします。例えば、2回目の繰り返しでは、これらの値をチェックします:board3, board4, board5.

また、勝利条件に空のブロックがあると勝てないので、いずれかのフィールドが空の場合、continueを呼び出し、次の反復にスキップするように最適化を行います。すべてのフィールドが等しい場合は勝者となるので、roundWonをtrueに設定し、forループを中断します。これ以上繰り返すと計算が無駄になるからです。

ループの後、変数roundWonの値をチェックし、それがtrueであれば、勝者を宣言し、ゲームをinactiveに設定することになります。勝者がいない場合は、盤面に空のカードがないかどうかを調べ、勝者がおらず、空のカードもない場合は、引き分けを宣言します。

function handleResultValidation() {
  let roundWon = false;
  for (let i = 0; i <= 7; i++) {
    const winCondition = winningConditions[i];
    const a = board[winCondition[0]];
    const b = board[winCondition[1]];
    const c = board[winCondition[2]];
    if (a === "" || b === "" || c === "") {
      continue;
    }
    if (a === b && b === c) {
      roundWon = true;
      break;
    }
  }

  if (roundWon) {
    announce(currentPlayer === "X" ? PLAYERX_WON : PLAYERO_WON);
    isGameActive = false;
    return;
  }

  if (!board.includes("")) announce(TIE);
}


次に、ユーザーのアクションを処理します。この関数は、タイルとインデックスを引数として受け取ります。この関数は、ユーザーがブロックをクリックしたときに呼び出されます。まず、それが有効なアクションかどうかをチェックする必要があります。また、ゲームが現在アクティブかどうかもチェックします。両方が真であれば、現在のプレーヤーのシンボルでタイルを更新し、対応するクラスを追加し、ボード配列を更新するinnerTextします。すべてが更新されたので、ゲームが終了したかどうかをチェックする必要があります。そこで、handleResultValidation() を呼び出します。最後に、このchangePlayerメソッドを呼び出して、他のプレイヤーに手番を渡す必要があります。

const userAction = (tile, index) => {
  if (isValidAction(tile) && isGameActive) {
    tile.innerText = currentPlayer;
    tile.classList.add(`player${currentPlayer}`);
    updateBoard(index);
    handleResultValidation();
    changePlayer();
  }
};


ゲームを正しく動作させるためには、タイルにイベントリスナーを追加する必要があります。これは、タイルの配列をループして、それぞれのタイルにイベントリスナーを追加することで実現できます。(パフォーマンスを上げるには、コンテナにのみイベントリスナーを追加し、イベントバブリングを使用して親タイルのクリックをキャッチすることもできますが、この方が初心者には分かりやすいと思います)。

tiles.forEach( (tile, index) => {
    tile.addEventListener('click', () => userAction(tile, index));
});


1つだけ、ゲームをリセットする機能を見逃しました。これを行うために、resetBoard関数を書きます。この関数では、9つの空の文字列からなる碁盤をXに設定し、ゲームをアクティブに設定し、アナウンサーを削除し、プレーヤーを元に戻します(定義上Xは常にスタートします)。

最後に、ブロックを繰り返し処理して innerText を空の文字列に戻し、ブロックからプレイヤー固有のクラスを削除します。

const resetBoard = () => {
    board = ['', '', '', '', '', '', '', '', ''];
    isGameActive = true;
    announcer.classList.add('hide');

    if (currentPlayer === 'O') {
        changePlayer();
    }

    tiles.forEach(tile => {
        tile.innerText = '';
        tile.classList.remove('playerX');
        tile.classList.remove('playerO');
    });
}


あとは、この関数をリセットボタンのクリックイベントハンドラとして登録するだけです。

resetButton.addEventListener('click', resetBoard);


<スパン 以上、友達と楽しく遊べる機能満載の三目並べが完成しました。

以上、簡単な三目並べゲームを作るためのHTML+CSS+JavaScriptの詳細を説明しました。HTML+CSS+JavaScriptについては、Script Houseの他の関連記事もご参照ください。