演習 - ゲーム ロジック

完了

この演習では、アプリにゲーム ロジックを追加して、フルに機能するゲームを完成させます。

Blazor について説明するというトピックに沿ってこのチュートリアルを進めるために、ゲームを管理するためのロジックを含む GameState というクラスを提供します。

ゲームの状態の追加

GameState クラスをプロジェクトに追加し、依存関係の挿入を通じてシングルトン サービスとしてコンポーネントで使用できるようにしましょう。

  1. GameState.cs ファイルをプロジェクトのルートにコピーします。

  2. プロジェクトのルートにある Program.cs ファイルを開き、次のステートメントを追加して GameState をアプリ内のシングルトン サービスとして構成します。

    builder.Services.AddSingleton<GameState>();
    

    これで、GameState クラスのインスタンスを Board コンポーネントに挿入できるようになりました。

  3. Board.razor ファイルの先頭に次の @inject ディレクティブを追加します。 次のディレクティブは、ゲームの現在の状態をコンポーネントに挿入します。

    @inject GameState State
    

    Board コンポーネントをゲームの状態に接続できるようになりました。

状態のリセット

まず、Board コンポーネントが最初に画面に描画されたときのゲームの状態をリセットしましょう。 コンポーネントの初期化時にゲームの状態をリセットするコードをいくつか追加します。

  1. 次のように、Board.razor ファイルの下部にある @code ブロック内に、OnInitialized メソッドを追加して ResetBoard を呼び出します。

    @code {
        protected override void OnInitialized()
        {
            State.ResetBoard();
        }
    }
    

    ボードが最初にユーザーに表示されると、状態はゲームの開始時にリセットされます。

ゲーム ピースの作成

次に、配置可能な 42 個のゲーム ピースを割り当ててみましょう。 ゲーム ピースは、ボード上の 42 個の HTML 要素によって参照される配列として表すことができます。 列と行の位置を持つ CSS クラスのセットを割り当てることで、これらのピースを移動して配置できます。

  1. ゲーム ピースを保持するために、以下のようにコード ブロック内で文字列配列フィールドを定義します。

    private string[] pieces = new string[42];
    
  2. 同じコンポーネントの HTML セクションに、42 個の span タグを作成するコードをゲーム ピースごとに 1 つずつ追加します。

    @for (var i = 0; i < 42; i++)
    {
       <span class="@pieces[i]"></span>
    }
    

    完全なコードは次のようになります。

    <div>
        <div class="board">
        @for (var i = 0; i < 42; i++)
        {
            <span class="container">
                <span></span>
            </span>
        }
        </div>
        @for (var i = 0; i < 42; i++)
        {
           <span class="@pieces[i]"></span>
        }
    </div>
    @code {
        private string[] pieces = new string[42];
    
        protected override void OnInitialized()
        {
            State.ResetBoard();
        }
    }
    

    これにより、各ゲーム ピース スパンの CSS クラスに空の文字列が割り当てられます。 CSS クラスが空の文字列の場合、スタイルが適用されないので、ゲーム ピースは画面に表示されません。

ゲーム ピースの配置の処理

プレーヤーが列にピースを配置するときに処理するメソッドを追加してみましょう。 GameState クラスでは、ゲーム ピースに正しい行を割り当てる方法が認識されており、そのピースが着地した行が報告されます。 この情報を使用して、プレーヤーの色、ピースの最終的な場所、および CSS ドロップ アニメーションを表す CSS クラスを割り当てることができます。

このメソッドには PlayPiece という名前を付けます。これはプレーヤーが選択した列を指定する入力パラメーターを受け取ります。

  1. 前の手順で定義した pieces 配列の下に、このコードを追加します。

    private void PlayPiece(byte col)
    {
        var player = State.PlayerTurn;
        var turn = State.CurrentTurn;
        var landingRow = State.PlayPiece(col);
        pieces[turn] = $"player{player} col{col} drop{landingRow}";
    }
    

PlayPiece コードの動作を次に示します。

  1. 送信された col という名前の列にピースを配置し、そのピースが着地した行をキャプチャするように、ゲームの状態に指示します。
  2. 次に、ゲーム ピースに割り当てる 3 つの CSS クラスを定義して、現在動作しているプレーヤー、ピースが配置された列、ピースが着地した行を識別できます。
  3. メソッドの最後の行では、これらのクラスが pieces 配列内のそのゲーム ピースに割り当てられます。

提供された Board.razor.css を見ると、列、行、プレーヤー ターンに一致する CSS クラスが存在することがわかります。

その結果生じる効果として、このメソッドが呼び出されると、ゲーム ピースが列に配置され、最下部の行まで落下するようにアニメーション化されます。

列の選択

次に、プレーヤーが列を選択して新しい PlayPiece メソッドを呼び出ることができるように、いくつかのコントロールを配置する必要があります。 この列にピースを落とすことができることを示すために "🔽" という文字を使用します。

  1. 先頭の <div> タグの上に、クリック可能なボタンの行を追加します。

    <nav>
        @for (byte i = 0; i < 7; i++)
        {
            var col = i;
            <span title="Click to play a piece" @onclick="() => PlayPiece(col)">🔽</span>
        }
    </nav>
    

    @onclick 属性では、クリック イベントのイベント ハンドラーを指定します。 しかし、UI イベントを処理するには、''対話型レンダリング モード'' を使用して Blazor コンポーネントをレンダリングする必要があります。 既定では、Blazor コンポーネントはサーバーから静的にレンダリングされます。 @rendermode 属性を使用して、コンポーネントに対話型レンダリング モードを適用できます。

  2. InteractiveServer レンダー モードを使用するように、Home ページ上の Board コンポーネントを更新します。

    <Board @rendermode="InteractiveServer" />
    

    InteractiveServer レンダー モードでは、ブラウザーとの WebSocket 接続を介してサーバーからコンポーネントの UI イベントを処理します。

  3. これらの変更を使用してアプリを実行します。 このようになるはずです。

    Connect Four ボードのスクリーンショット。

    より良い方法として、最上部にある下矢印ボタンの 1 つを選択すると、次の動作を実現できます。

    Connect Four アニメーションのスクリーンショット。

大きな進歩です。 ボードにピースを追加できるようになりました。 GameState オブジェクトは、2 人のプレーヤー間を行き来できるので有用です。 その他の下矢印ボタンを選択して、結果を確認します。

勝利とエラー処理

現在の構成でゲームをプレイすると、同じ列に多すぎるピースを入れようとしたときと、一方のプレイヤーがゲームに勝利したときにエラーが発生することがわかります。

いくつかのエラー処理とインジケーターをボードに追加することで、ゲームの現在の状態を明確にしましょう。 ボードの上かつ落下ボタンの下に、ステータス領域を追加します。

  1. nav 要素の後に、次のマークアップを挿入します。

    <article>
        @winnerMessage  <button style="@ResetStyle" @onclick="ResetGame">Reset the game</button>
        <br />
        <span class="alert-danger">@errorMessage</span>
        <span class="alert-info">@CurrentTurn</span>
    </article>
    

    このマークアップを使用すると、次のインジケーターを表示できます。

    • ゲームの勝者の発表
    • ゲームを再起動できるボタン
    • エラー メッセージ
    • 現在のプレーヤーのターン

    次に、これらの値を設定するいくつかのロジックを入力しましょう。

  2. pieces 配列の後に次のコードを追加します。

    private string[] pieces = new string[42];
    private string winnerMessage = string.Empty;
    private string errorMessage = string.Empty;
    
    private string CurrentTurn => (winnerMessage == string.Empty) ? $"Player {State.PlayerTurn}'s Turn" : "";
    private string ResetStyle => (winnerMessage == string.Empty) ? "display: none;" : "";
    
    • CurrentTurn プロパティは、winnerMessage の状態と GameStatePlayerTurn プロパティに基づいて自動的に計算されます。
    • ResetStyleWinnerMessage の内容に基づいて計算されます。 winnerMessage がある場合、リセット ボタンが画面に表示されるようにします。
  3. ピースの配置時にエラー メッセージを処理してみましょう。 エラー メッセージをクリアする行を追加し、try...catch ブロックで PlayPiece メソッドのコードをラップして、例外が発生した場合の errorMessage を設定します。

    errorMessage = string.Empty;
    try
    {
        var player = State.PlayerTurn;
        var turn = State.CurrentTurn;
        var landingRow = State.PlayPiece(col);
        pieces[turn] = $"player{player} col{col} drop{landingRow}";
    }
    catch (ArgumentException ex)
    {
        errorMessage = ex.Message;
    }
    

    エラー ハンドラー インジケーターは単純で、ブートストラップ CSS フレームワークを使用して危険モードでエラーが表示されます。

    この時点での、ボードとピースを含むゲームのスクリーンショット。

  4. 次に、ゲームを再起動するためにボタンでトリガーする ResetGame メソッドを追加してみましょう。 現在、ゲームを再起動する唯一の方法は、ページを更新することです。 このコードを使用すると、同じページを維持できます。

    void ResetGame()
    {
        State.ResetBoard();
        winnerMessage = string.Empty;
        errorMessage = string.Empty;
        pieces = new string[42];
    }
    

    これで、ResetGame メソッドに次のロジックが追加されました。

    • ボードの状態をリセットします。
    • インジケーターを非表示にします。
    • pieces 配列を 42 個の文字列の空の配列にリセットします。

    この更新プログラムでは、再びゲームをプレイできるようにする必要があります。プレーヤーのターンを宣言し、最終的にゲームの完了を宣言するインジケータが、ボードのすぐ上に表示されています。

    ゲームの終了を示すスクリーンショット。

    リセット ボタンを選択することができないという問題がまだ存在します。 PlayPiece メソッド内にゲームの終了を検出するロジックをいくつか追加してみましょう。

  5. PlayPiecetry...catch ブロックの後に switch 式を追加して、ゲームに勝者がいるかどうかを検出してみましょう。

    winnerMessage = State.CheckForWin() switch
    {
        GameState.WinState.Player1_Wins => "Player 1 Wins!",
        GameState.WinState.Player2_Wins => "Player 2 Wins!",
        GameState.WinState.Tie => "It's a tie!",
        _ => ""
    };
    

    CheckForWin メソッドは、どのプレーヤーがゲームに勝ったか (勝者がいる場合)、またはゲームが引き分けであるかを報告する列挙型を返します。 この switch 式は、ゲーム オーバー状態が発生した場合に winnerMessage フィールドを適切に設定します。

    ゲームをプレイしてゲーム終了シナリオに到達すると、次のインジケーターが表示されます。

    ゲームのリセットを示すスクリーンショット。

まとめ

ここでは Blazor について多くのことを学び、すっきりとした小さなゲームを構築しました。 学習したスキルは次のとおりです。

  • コンポーネントを作成しました
  • そのコンポーネントをホーム ページに追加しました
  • 依存関係の挿入を使用してゲームの状態を管理しました
  • ピースを配置してゲームをリセットするイベント ハンドラーを使用して、ゲームをインタラクティブにしました
  • ゲームの状態を報告するエラー ハンドラーを記述しました
  • コンポーネントにパラメーターを追加しました

私たちが構築したプロジェクトは単純なゲームであり、それを使ってできることはたくさんあります。 それを改善するために何か課題を求めていますか?

課題

次の課題を考えてみましょう。

  • このアプリをより小さくするために、既定のレイアウトと余分なページを削除します。
  • 任意の有効な CSS カラー値を渡すことができるように、Board コンポーネントのパラメーターを改善します。
  • CSS や HTML レイアウトを使用してインジケーターの外観を改善します。
  • 効果音を導入します。
  • 視覚インジケーターを追加し、列がいっぱいになったときに下矢印ボタンが使用されないようにします。
  • ブラウザーで友達とプレイできるように、ネットワーク機能を追加します。
  • Blazor アプリケーションを使用して .NET MAUI にゲームを挿入し、携帯電話やタブレットでプレイする。

コーディングをお楽しみください。