【第一回】C#でテトリス作成

【第一回】C#でテトリス作成

目次

はじめに
MVCモデルとは
プロジェクト作成
要件定義
実装 Model編
ゲーム盤 GameBoardクラス実装
ゲーム全体の管理 TetrisGameクラス実装
まとめ

はじめに

引続きC#の学習の為にアプリを作成してみます。
今回はテトリスを作成してみたいと思います。
以前、テトリスを作成したことがあるのですが、その際は参考にしていたサイトが途中までしか更新されず
一人で続きを作成する知識もなかったので頓挫してしまいました。
Claudeを使用しつつ改めて最後まで作成しきる様子を執筆していきます。

MVCモデルとは

簡単にMVCモデルとは何かを説明します。
Model・・・データとビジネスロジックを管理
View・・・UIを担当
Controller・・・ModelとViewの仲介役
上記の頭文字をとってMVCと呼ばれるものです。
アプリの構造を3つに分離してそれぞれに役割を持たせることにより、コードが整理され理解しやすくなったり
各構造が独立しているため、機能の追加・修正がやりやすくなったりというメリットがあります。

プロジェクト作成

いつも通りVisual Studioにてプロジェクト作成から始めていきます。
今回はテトリスを作成するのでMVCモデルと呼ばれる形で作成していきます。
まずVisual Studioを起動して「新しいプロジェクトの作成」をクリック
テンプレートはASP.NET Core Webアプリにてプロジェクト作成から始めていきます。
今回はテトリスを作成するのでMVCモデルと呼ばれる形で作成していきます。
まずVisual Studioを起動して「新しいプロジェクトの作成」をクリック。
テンプレートはASP.NET Core Webアプリ(Model-View-Controller)を選択します。
プロジェクト名を入力して作成
作成するとソリューションエクスプローラーと呼ばれる場所にMVCでフォルダが生成されます。

要件定義

少し順番は前後してしまいましたが要件定義を簡単にしておこうと思います。
・盤面サイズ:横10マス×縦20マス
・テトリミノ:7種類(I、O、T、S、Z、J、L)
・ソフトドロップ・ハードドロップ
・スコアシステム
・レベルアップシステム
以上の機能を簡単に要件定義として設定します。

実装 Model編

では早速コードを書き始めていきたいと思います。
まずはModelsフォルダに、テトリミノ(落ちてくるブロック)のクラスを作っていきます。
フォルダ内で新しくクラスを作成します。
クラス名はTetrominoとし、下記のコードを書きます。

<code>using System.Text.Json.Serialization;

namespace TetrisGame.Models
{
    /// &lt;summary>
    /// テトロミノ(テトリスのブロック)を表すクラス
    /// &lt;/summary>
    public class Tetromino
    {
        /// &lt;summary>
        /// テトロミノの種類
        /// &lt;/summary>
        public enum TetrominoType
        {
            I, // 直線型
            O, // 正方型  
            T, // T字型
            S, // S字型
            Z, // Z字型
            J, // J字型
            L  // L字型
        }

        /// &lt;summary>
        /// テトロミノの種類
        /// &lt;/summary>
        public TetrominoType Type { get; set; }

        /// &lt;summary>
        /// 現在のX座標(ゲーム盤上の位置)
        /// &lt;/summary>
        public int X { get; set; }

        /// &lt;summary>
        /// 現在のY座標(ゲーム盤上の位置)
        /// &lt;/summary>
        public int Y { get; set; }

        /// &lt;summary>
        /// 回転状態(0-3: 0度、90度、180度、270度)
        /// &lt;/summary>
        public int Rotation { get; set; }

        /// &lt;summary>
        /// テトロミノの形状データ(4x4のジャグ配列、JSONシリアライズ対応)
        /// &lt;/summary>
        public bool&#91;]&#91;] Shape { get; set; }

        /// &lt;summary>
        /// デフォルトコンストラクタ(JSONデシリアライズ用)
        /// &lt;/summary>
        public Tetromino()
        {
            Type = TetrominoType.I;
            X = 3; // 中央寄りに修正
            Y = 0;
            Rotation = 0;
            Shape = GetInitialShape(TetrominoType.I);
        }

        /// &lt;summary>
        /// 通常のコンストラクタ
        /// &lt;/summary>
        public Tetromino(TetrominoType type)
        {
            Type = type;
            X = 3; // スポーン位置を調整(より安全な位置)
            Y = 0;
            Rotation = 0;
            Shape = GetInitialShape(type);
        }

        /// &lt;summary>
        /// JSON用のコンストラクタ
        /// &lt;/summary>
        &#91;JsonConstructor]
        public Tetromino(TetrominoType type, int x, int y, int rotation, bool&#91;]&#91;] shape)
        {
            Type = type;
            X = x;
            Y = y;
            Rotation = rotation % 4; // 回転状態を正規化
            Shape = shape ?? GetInitialShape(type);
        }

        /// &lt;summary>
        /// テトロミノの種類に応じた初期形状を取得
        /// 形状の位置を最適化
        /// &lt;/summary>
        private bool&#91;]&#91;] GetInitialShape(TetrominoType type)
        {
            // 4x4のジャグ配列を初期化
            bool&#91;]&#91;] shape = new bool&#91;4]&#91;];
            for (int i = 0; i &lt; 4; i++)
            {
                shape&#91;i] = new bool&#91;4];
            }

            switch (type)
            {
                case TetrominoType.I: // 直線型 ████
                    shape&#91;1]&#91;0] = shape&#91;1]&#91;1] = shape&#91;1]&#91;2] = shape&#91;1]&#91;3] = true;
                    break;

                case TetrominoType.O: // 正方型 ██
                    shape&#91;0]&#91;1] = shape&#91;0]&#91;2] = shape&#91;1]&#91;1] = shape&#91;1]&#91;2] = true; //      ██
                    break;

                case TetrominoType.T: // T字型 ███
                    shape&#91;0]&#91;1] = true;                                           //       █
                    shape&#91;1]&#91;0] = shape&#91;1]&#91;1] = shape&#91;1]&#91;2] = true;             //      ███
                    break;

                case TetrominoType.S: // S字型  ██
                    shape&#91;0]&#91;1] = shape&#91;0]&#91;2] = true;                            //       ██
                    shape&#91;1]&#91;0] = shape&#91;1]&#91;1] = true;                            //      ██
                    break;

                case TetrominoType.Z: // Z字型 ██
                    shape&#91;0]&#91;0] = shape&#91;0]&#91;1] = true;                            //      ██
                    shape&#91;1]&#91;1] = shape&#91;1]&#91;2] = true;                            //       ██
                    break;

                case TetrominoType.J: // J字型 █
                    shape&#91;0]&#91;0] = true;                                          //      █
                    shape&#91;1]&#91;0] = shape&#91;1]&#91;1] = shape&#91;1]&#91;2] = true;             //      ███
                    break;

                case TetrominoType.L: // L字型   █
                    shape&#91;0]&#91;2] = true;                                          //        █
                    shape&#91;1]&#91;0] = shape&#91;1]&#91;1] = shape&#91;1]&#91;2] = true;             //      ███
                    break;
            }

            return shape;
        }

        /// &lt;summary>
        /// テトロミノを右に90度回転
        /// &lt;/summary>
        public void RotateClockwise()
        {
            // O型は回転しても形状が変わらないのでスキップ
            if (Type == TetrominoType.O)
                return;

            Rotation = (Rotation + 1) % 4;
            Shape = RotateShapeClockwise(Shape);
        }

        /// &lt;summary>
        /// 4x4配列を時計回りに90度回転
        /// &lt;/summary>
        private bool&#91;]&#91;] RotateShapeClockwise(bool&#91;]&#91;] original)
        {
            bool&#91;]&#91;] rotated = new bool&#91;4]&#91;];
            for (int i = 0; i &lt; 4; i++)
            {
                rotated&#91;i] = new bool&#91;4];
            }

            for (int i = 0; i &lt; 4; i++)
            {
                for (int j = 0; j &lt; 4; j++)
                {
                    rotated&#91;j]&#91;3 - i] = original&#91;i]&#91;j];
                }
            }

            return rotated;
        }

        /// &lt;summary>
        /// テトロミノを左に移動
        /// &lt;/summary>
        public void MoveLeft()
        {
            X--;
        }

        /// &lt;summary>
        /// テトロミノを右に移動  
        /// &lt;/summary>
        public void MoveRight()
        {
            X++;
        }

        /// &lt;summary>
        /// テトロミノを下に移動
        /// &lt;/summary>
        public void MoveDown()
        {
            Y++;
        }

        /// &lt;summary>
        /// テトロミノの実際の境界を取得(デバッグ用)
        /// &lt;/summary>
        public (int minX, int maxX, int minY, int maxY) GetBounds()
        {
            int minX = 4, maxX = -1, minY = 4, maxY = -1;

            for (int row = 0; row &lt; 4; row++)
            {
                for (int col = 0; col &lt; 4; col++)
                {
                    if (Shape&#91;row]&#91;col])
                    {
                        minX = Math.Min(minX, col);
                        maxX = Math.Max(maxX, col);
                        minY = Math.Min(minY, row);
                        maxY = Math.Max(maxY, row);
                    }
                }
            }

            return (minX, maxX, minY, maxY);
        }

        /// &lt;summary>
        /// テトロミノのクローンを作成
        /// &lt;/summary>
        public Tetromino Clone()
        {
            var clonedShape = new bool&#91;4]&#91;];
            for (int i = 0; i &lt; 4; i++)
            {
                clonedShape&#91;i] = new bool&#91;4];
                for (int j = 0; j &lt; 4; j++)
                {
                    clonedShape&#91;i]&#91;j] = Shape&#91;i]&#91;j];
                }
            }

            return new Tetromino(Type, X, Y, Rotation, clonedShape);
        }
    }
}</code>

ゲーム盤 GameBoardクラス実装

続けてゲーム盤の実装をします。
新しくGameBoardクラスを作成し、以下のコードを書きます。
こちらはゲーム盤(プレイエリア)での設定部分を実装しています。

<code>using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json.Serialization;

namespace TetrisGame.Models
{
    /// &lt;summary>
    /// テトリスのゲーム盤を管理するクラス
    /// &lt;/summary>
    public class GameBoard
    {
        /// &lt;summary>
        /// ゲーム盤の幅(マス数)
        /// &lt;/summary>
        public const int Width = 10;

        /// &lt;summary>
        /// ゲーム盤の高さ(マス数)  
        /// &lt;/summary>
        public const int Height = 20;

        /// &lt;summary>
        /// ゲーム盤の状態(0=空、1以上=ブロックあり)
        /// JSON シリアライズ用に int&#91;]&#91;] (ジャグ配列) を使用
        /// &lt;/summary>
        public int&#91;]&#91;] Board { get; set; }

        /// &lt;summary>
        /// デフォルトコンストラクタ
        /// &lt;/summary>
        public GameBoard()
        {
            InitializeBoard();
        }

        /// &lt;summary>
        /// JSON デシリアライズ用コンストラクタ
        /// &lt;/summary>
        &#91;JsonConstructor]
        public GameBoard(int&#91;]&#91;] board)
        {
            if (board == null || board.Length != Height)
            {
                InitializeBoard();
            }
            else
            {
                Board = board;
                // 各行の長さをチェック・修正
                for (int i = 0; i &lt; Height; i++)
                {
                    if (Board&#91;i] == null || Board&#91;i].Length != Width)
                    {
                        Board&#91;i] = new int&#91;Width];
                    }
                }
            }
        }

        /// &lt;summary>
        /// ゲーム盤を初期化
        /// &lt;/summary>
        private void InitializeBoard()
        {
            Board = new int&#91;Height]&#91;];
            for (int i = 0; i &lt; Height; i++)
            {
                Board&#91;i] = new int&#91;Width];
            }
            ClearBoard();
        }

        /// &lt;summary>
        /// ゲーム盤をクリア(全て0にする)
        /// &lt;/summary>
        public void ClearBoard()
        {
            for (int row = 0; row &lt; Height; row++)
            {
                for (int col = 0; col &lt; Width; col++)
                {
                    Board&#91;row]&#91;col] = 0;
                }
            }
        }

        /// &lt;summary>
        /// 指定位置の値を取得(安全アクセス)
        /// &lt;/summary>
        public int GetCell(int row, int col)
        {
            if (row &lt; 0 || row >= Height || col &lt; 0 || col >= Width)
                return -1; // 範囲外
            return Board&#91;row]&#91;col];
        }

        /// &lt;summary>
        /// 指定位置に値を設定(安全アクセス)
        /// &lt;/summary>
        public void SetCell(int row, int col, int value)
        {
            if (row >= 0 &amp;&amp; row &lt; Height &amp;&amp; col >= 0 &amp;&amp; col &lt; Width)
            {
                Board&#91;row]&#91;col] = value;
            }
        }

        /// &lt;summary>
        /// テトロミノが指定位置に配置可能かチェック
        /// &lt;/summary>
        public bool CanPlaceTetromino(Tetromino tetromino, int x, int y)
        {
            if (tetromino?.Shape == null) return false;

            for (int row = 0; row &lt; 4; row++)
            {
                if (tetromino.Shape.Length &lt;= row || tetromino.Shape&#91;row] == null)
                    continue;

                for (int col = 0; col &lt; 4; col++)
                {
                    if (tetromino.Shape&#91;row].Length &lt;= col)
                        continue;

                    // テトロミノのこの位置にブロックがない場合はスキップ
                    if (!tetromino.Shape&#91;row]&#91;col])
                        continue;

                    // ゲーム盤上の実際の座標を計算
                    int boardX = x + col;
                    int boardY = y + row;

                    // 範囲外チェック
                    if (boardX &lt; 0 || boardX >= Width || boardY >= Height)
                        return false;

                    // 上端は許可(テトロミノが画面上から入ってくる)
                    if (boardY &lt; 0)
                        continue;

                    // 既存のブロックとの衝突チェック
                    if (GetCell(boardY, boardX) != 0)
                        return false;
                }
            }

            return true;
        }

        /// &lt;summary>
        /// テトロミノをゲーム盤に固定する
        /// &lt;/summary>
        public void PlaceTetromino(Tetromino tetromino)
        {
            if (tetromino?.Shape == null) return;

            for (int row = 0; row &lt; 4; row++)
            {
                if (tetromino.Shape.Length &lt;= row || tetromino.Shape&#91;row] == null)
                    continue;

                for (int col = 0; col &lt; 4; col++)
                {
                    if (tetromino.Shape&#91;row].Length &lt;= col)
                        continue;

                    if (tetromino.Shape&#91;row]&#91;col])
                    {
                        int boardX = tetromino.X + col;
                        int boardY = tetromino.Y + row;

                        // 範囲内の場合のみ配置
                        if (boardX >= 0 &amp;&amp; boardX &lt; Width &amp;&amp; boardY >= 0 &amp;&amp; boardY &lt; Height)
                        {
                            // テトロミノの種類+1を保存(0は空きマス用)
                            SetCell(boardY, boardX, (int)tetromino.Type + 1);
                        }
                    }
                }
            }
        }

        /// &lt;summary>
        /// 完成した行を見つけて削除し、削除行数を返す
        /// &lt;/summary>
        public int ClearCompletedLines()
        {
            List&lt;int> completedLines = new List&lt;int>();

            // 完成した行を探す
            for (int row = 0; row &lt; Height; row++)
            {
                bool isLineComplete = true;
                for (int col = 0; col &lt; Width; col++)
                {
                    if (GetCell(row, col) == 0)
                    {
                        isLineComplete = false;
                        break;
                    }
                }

                if (isLineComplete)
                {
                    completedLines.Add(row);
                }
            }

            // 完成した行を削除(下から上へ)
            foreach (int lineIndex in completedLines.OrderByDescending(x => x))
            {
                RemoveLine(lineIndex);
            }

            return completedLines.Count;
        }

        /// &lt;summary>
        /// 指定した行を削除し、上の行を下に移動
        /// &lt;/summary>
        private void RemoveLine(int lineIndex)
        {
            // 削除する行より上の行を1つずつ下に移動
            for (int row = lineIndex; row > 0; row--)
            {
                for (int col = 0; col &lt; Width; col++)
                {
                    SetCell(row, col, GetCell(row - 1, col));
                }
            }

            // 一番上の行をクリア
            for (int col = 0; col &lt; Width; col++)
            {
                SetCell(0, col, 0);
            }
        }

        /// &lt;summary>
        /// ゲームオーバー判定
        /// より堅牢な判定ロジックに修正
        /// &lt;/summary>
        public bool IsGameOver()
        {
            // 上から数行をチェック(スポーン領域)
            for (int row = 0; row &lt; 2; row++)
            {
                for (int col = 0; col &lt; Width; col++)
                {
                    if (GetCell(row, col) != 0)
                        return true;
                }
            }
            return false;
        }

        /// &lt;summary>
        /// デバッグ用:ボードの状態を文字列で表示
        /// &lt;/summary>
        public string GetDebugString()
        {
            var lines = new List&lt;string>();
            for (int row = 0; row &lt; Height; row++)
            {
                var line = "";
                for (int col = 0; col &lt; Width; col++)
                {
                    line += GetCell(row, col) == 0 ? "." : "#";
                }
                lines.Add($"{row:D2}|{line}|");
            }
            return string.Join("\n", lines);
        }
    }
}</code>

ゲーム全体の管理 TetrisGameクラス実装

次にゲーム全体の管理を行うTetrisGameクラスを実装します。
新しくTetrisGameクラスを作成し、以下のコードを書きます。

<code>using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json.Serialization;

namespace TetrisGame.Models
{
    /// &lt;summary>
    /// テトリスゲーム全体を管理するクラス(自動落下対応版)
    /// &lt;/summary>
    public class TetrisGame
    {
        /// &lt;summary>
        /// ゲーム盤
        /// &lt;/summary>
        public GameBoard Board { get; set; }

        /// &lt;summary>
        /// 現在落下中のテトロミノ
        /// &lt;/summary>
        public Tetromino? CurrentPiece { get; set; }

        /// &lt;summary>
        /// 次に出現するテトロミノ
        /// &lt;/summary>
        public Tetromino? NextPiece { get; set; }

        /// &lt;summary>
        /// 現在のスコア
        /// &lt;/summary>
        public int Score { get; set; }

        /// &lt;summary>
        /// 消去した行数
        /// &lt;/summary>
        public int Lines { get; set; }

        /// &lt;summary>
        /// 現在のレベル
        /// &lt;/summary>
        public int Level { get; set; }

        /// &lt;summary>
        /// ゲーム終了フラグ
        /// &lt;/summary>
        public bool IsGameOver { get; set; }

        /// &lt;summary>
        /// ゲーム一時停止フラグ
        /// &lt;/summary>
        public bool IsPaused { get; set; }

        /// &lt;summary>
        /// 最後にテトロミノが自動落下した時刻
        /// &lt;/summary>
        public DateTime LastDropTime { get; set; }

        /// &lt;summary>
        /// 落下間隔(ミリ秒)
        /// レベルに応じて速度を調整
        /// &lt;/summary>
        &#91;JsonIgnore]
        public int DropInterval => Math.Max(50, 800 - Level * 50);

        /// &lt;summary>
        /// テトロミノ生成用の乱数(シリアライズ対象外)
        /// &lt;/summary>
        &#91;JsonIgnore]
        private Random _random;

        /// &lt;summary>
        /// デフォルトコンストラクタ
        /// &lt;/summary>
        public TetrisGame()
        {
            InitializeGame();
        }

        /// &lt;summary>
        /// JSON デシリアライズ用コンストラクタ
        /// &lt;/summary>
        &#91;JsonConstructor]
        public TetrisGame(GameBoard board, Tetromino? currentPiece, Tetromino? nextPiece,
                         int score, int lines, int level, bool isGameOver, bool isPaused,
                         DateTime lastDropTime)
        {
            Board = board ?? new GameBoard();
            CurrentPiece = currentPiece;
            NextPiece = nextPiece;
            Score = Math.Max(0, score); // 負の値を防ぐ
            Lines = Math.Max(0, lines);
            Level = Math.Max(1, level); // レベルは最低1
            IsGameOver = isGameOver;
            IsPaused = isPaused;
            LastDropTime = lastDropTime == default ? DateTime.Now : lastDropTime;
            _random = new Random();

            // NextPiece が null の場合は生成
            if (NextPiece == null)
            {
                NextPiece = GenerateRandomTetromino();
            }

            // 復元後の状態チェック
            ValidateGameState();
        }

        /// &lt;summary>
        /// ゲーム初期化
        /// &lt;/summary>
        private void InitializeGame()
        {
            Board = new GameBoard();
            Score = 0;
            Lines = 0;
            Level = 1;
            IsGameOver = false;
            IsPaused = false;
            _random = new Random();
            LastDropTime = DateTime.Now;

            // 最初のテトロミノを生成
            NextPiece = GenerateRandomTetromino();
            SpawnNextPiece();

            Console.WriteLine("&#91;DEBUG] TetrisGame初期化完了");
        }

        /// &lt;summary>
        /// ゲーム状態の妥当性をチェック
        /// &lt;/summary>
        private void ValidateGameState()
        {
            if (CurrentPiece != null &amp;&amp; !Board.CanPlaceTetromino(CurrentPiece, CurrentPiece.X, CurrentPiece.Y))
            {
                Console.WriteLine("&#91;DEBUG] 無効な CurrentPiece 状態を検出 - ゲームオーバーに設定");
                IsGameOver = true;
            }
        }

        /// &lt;summary>
        /// ランダムなテトロミノを生成
        /// &lt;/summary>
        private Tetromino GenerateRandomTetromino()
        {
            var types = Enum.GetValues&lt;Tetromino.TetrominoType>();
            var randomType = types&#91;_random.Next(types.Length)];
            return new Tetromino(randomType);
        }

        /// &lt;summary>
        /// 次のテトロミノをゲーム盤に出現させる
        /// &lt;/summary>
        private void SpawnNextPiece()
        {
            CurrentPiece = NextPiece;
            if (CurrentPiece != null)
            {
                // スポーン位置をリセット
                CurrentPiece.X = 3;
                CurrentPiece.Y = 0;
            }

            NextPiece = GenerateRandomTetromino();

            Console.WriteLine($"&#91;DEBUG] 新しいピース出現: {CurrentPiece?.Type} at ({CurrentPiece?.X}, {CurrentPiece?.Y})");

            // 新しいピースが配置できない場合はゲームオーバー
            if (CurrentPiece != null &amp;&amp; !Board.CanPlaceTetromino(CurrentPiece, CurrentPiece.X, CurrentPiece.Y))
            {
                Console.WriteLine("&#91;DEBUG] 新しいピースが配置できません - ゲームオーバー");
                IsGameOver = true;
            }
        }

        /// &lt;summary>
        /// テトロミノを左に移動
        /// &lt;/summary>
        public bool MoveLeft()
        {
            if (CurrentPiece == null || IsGameOver || IsPaused)
            {
                Console.WriteLine("&#91;DEBUG] MoveLeft: 移動不可能な状態");
                return false;
            }

            if (Board.CanPlaceTetromino(CurrentPiece, CurrentPiece.X - 1, CurrentPiece.Y))
            {
                CurrentPiece.MoveLeft();
                Console.WriteLine($"&#91;DEBUG] 左移動成功: 新しい位置 ({CurrentPiece.X}, {CurrentPiece.Y})");
                return true;
            }

            Console.WriteLine("&#91;DEBUG] 左移動失敗: 衝突または範囲外");
            return false;
        }

        /// &lt;summary>
        /// テトロミノを右に移動
        /// &lt;/summary>
        public bool MoveRight()
        {
            if (CurrentPiece == null || IsGameOver || IsPaused)
            {
                Console.WriteLine("&#91;DEBUG] MoveRight: 移動不可能な状態");
                return false;
            }

            if (Board.CanPlaceTetromino(CurrentPiece, CurrentPiece.X + 1, CurrentPiece.Y))
            {
                CurrentPiece.MoveRight();
                Console.WriteLine($"&#91;DEBUG] 右移動成功: 新しい位置 ({CurrentPiece.X}, {CurrentPiece.Y})");
                return true;
            }

            Console.WriteLine("&#91;DEBUG] 右移動失敗: 衝突または範囲外");
            return false;
        }

        /// &lt;summary>
        /// テトロミノを下に移動(ソフトドロップ)
        /// &lt;/summary>
        public bool MoveDown()
        {
            if (CurrentPiece == null || IsGameOver || IsPaused)
            {
                Console.WriteLine("&#91;DEBUG] MoveDown: 移動不可能な状態");
                return false;
            }

            Console.WriteLine($"&#91;DEBUG] MoveDown開始: Current piece at ({CurrentPiece.X}, {CurrentPiece.Y})");

            if (Board.CanPlaceTetromino(CurrentPiece, CurrentPiece.X, CurrentPiece.Y + 1))
            {
                CurrentPiece.MoveDown();
                LastDropTime = DateTime.Now; // 落下タイマーをリセット
                Console.WriteLine($"&#91;DEBUG] MoveDown成功: 新しい位置 ({CurrentPiece.X}, {CurrentPiece.Y})");
                return true;
            }
            else
            {
                // 下に移動できない場合はピースを固定
                Console.WriteLine("&#91;DEBUG] MoveDown失敗: ピースを固定します");
                PlaceCurrentPiece();
                return false;
            }
        }

        /// &lt;summary>
        /// テトロミノを回転
        /// ウォールキック機能付き
        /// &lt;/summary>
        public bool RotatePiece()
        {
            if (CurrentPiece == null || IsGameOver || IsPaused)
            {
                Console.WriteLine("&#91;DEBUG] RotatePiece: 回転不可能な状態");
                return false;
            }

            // O型は回転しても同じなのでスキップ
            if (CurrentPiece.Type == Tetromino.TetrominoType.O)
            {
                return true;
            }

            // 現在の状態を保存
            var originalPiece = CurrentPiece.Clone();

            // 回転を試行
            CurrentPiece.RotateClockwise();

            // 通常位置で配置可能かチェック
            if (Board.CanPlaceTetromino(CurrentPiece, CurrentPiece.X, CurrentPiece.Y))
            {
                Console.WriteLine($"&#91;DEBUG] 回転成功: 新しい回転状態 {CurrentPiece.Rotation}");
                return true;
            }

            // ウォールキックを試行(左右に1マスずつ)
            int&#91;] wallKickOffsets = { -1, 1, -2, 2 };

            foreach (int offset in wallKickOffsets)
            {
                if (Board.CanPlaceTetromino(CurrentPiece, CurrentPiece.X + offset, CurrentPiece.Y))
                {
                    CurrentPiece.X += offset;
                    Console.WriteLine($"&#91;DEBUG] ウォールキック成功: オフセット {offset}, 新しい位置 ({CurrentPiece.X}, {CurrentPiece.Y})");
                    return true;
                }
            }

            // 回転できない場合は元に戻す
            CurrentPiece.Type = originalPiece.Type;
            CurrentPiece.X = originalPiece.X;
            CurrentPiece.Y = originalPiece.Y;
            CurrentPiece.Rotation = originalPiece.Rotation;
            CurrentPiece.Shape = originalPiece.Shape;
            Console.WriteLine("&#91;DEBUG] 回転失敗: 元の状態に戻しました");
            return false;
        }

        /// &lt;summary>
        /// ハードドロップ(一気に底まで落とす)
        /// &lt;/summary>
        public int HardDrop()
        {
            if (CurrentPiece == null || IsGameOver || IsPaused)
            {
                Console.WriteLine("&#91;DEBUG] HardDrop: 実行不可能な状態");
                return 0;
            }

            int dropDistance = 0;
            while (Board.CanPlaceTetromino(CurrentPiece, CurrentPiece.X, CurrentPiece.Y + 1))
            {
                CurrentPiece.MoveDown();
                dropDistance++;
            }

            Console.WriteLine($"&#91;DEBUG] ハードドロップ: {dropDistance}マス落下");

            // ピースを固定
            PlaceCurrentPiece();

            // ハードドロップのボーナス点
            Score += dropDistance * 2;

            return dropDistance;
        }

        /// &lt;summary>
        /// 自動落下処理(時間経過でテトロミノを下に移動)
        /// &lt;/summary>
        public void Update()
        {
            if (IsGameOver || IsPaused || CurrentPiece == null)
                return;

            // 落下間隔をチェック
            var now = DateTime.Now;
            var timeSinceLastDrop = now.Subtract(LastDropTime);

            if (timeSinceLastDrop.TotalMilliseconds >= DropInterval)
            {
                Console.WriteLine("&#91;DEBUG] 自動落下実行");
                MoveDown();
            }
        }

        /// &lt;summary>
        /// 現在のテトロミノをゲーム盤に固定
        /// &lt;/summary>
        private void PlaceCurrentPiece()
        {
            if (CurrentPiece == null)
            {
                Console.WriteLine("&#91;DEBUG] PlaceCurrentPiece: CurrentPiece が null");
                return;
            }

            Console.WriteLine($"&#91;DEBUG] ピース固定開始: {CurrentPiece.Type} at ({CurrentPiece.X}, {CurrentPiece.Y})");

            // ピースをボードに配置
            Board.PlaceTetromino(CurrentPiece);

            // 完成した行をクリア
            int clearedLines = Board.ClearCompletedLines();

            if (clearedLines > 0)
            {
                Console.WriteLine($"&#91;DEBUG] {clearedLines}行消去");

                // スコア計算(一度に多く消すほど高得点)
                int lineScore = clearedLines switch
                {
                    1 => 100 * Level,   // シングル
                    2 => 300 * Level,   // ダブル  
                    3 => 500 * Level,   // トリプル
                    4 => 800 * Level,   // テトリス!
                    _ => 100 * Level * clearedLines
                };

                Score += lineScore;
                Lines += clearedLines;

                // レベルアップ判定(10行消去で1レベルアップ)
                int newLevel = Lines / 10 + 1;
                if (newLevel > Level)
                {
                    Level = newLevel;
                    Console.WriteLine($"&#91;DEBUG] レベルアップ!新しいレベル: {Level}");
                }

                Console.WriteLine($"&#91;DEBUG] スコア更新: +{lineScore} (合計: {Score})");
            }

            // ソフトドロップのボーナス
            Score += 1;

            // ゲームオーバー判定
            if (Board.IsGameOver())
            {
                Console.WriteLine("&#91;DEBUG] ゲームオーバー判定: true");
                IsGameOver = true;
                return;
            }

            // 次のピースを出現
            Console.WriteLine("&#91;DEBUG] 次のピースを出現させます");
            SpawnNextPiece();
        }

        /// &lt;summary>
        /// ゲームをリセット
        /// &lt;/summary>
        public void Reset()
        {
            Console.WriteLine("&#91;DEBUG] ゲームリセット開始");
            InitializeGame();
            Console.WriteLine("&#91;DEBUG] ゲームリセット完了");
        }

        /// &lt;summary>
        /// ゲームの一時停止/再開
        /// &lt;/summary>
        public void TogglePause()
        {
            if (!IsGameOver)
            {
                IsPaused = !IsPaused;
                if (!IsPaused)
                {
                    // 再開時は落下タイマーをリセット
                    LastDropTime = DateTime.Now;
                }
                Console.WriteLine($"&#91;DEBUG] ゲーム {(IsPaused ? "一時停止" : "再開")}");
            }
        }

        /// &lt;summary>
        /// ゲームの状態を文字列で取得(デバッグ用)
        /// &lt;/summary>
        public override string ToString()
        {
            return $"Score: {Score}, Lines: {Lines}, Level: {Level}, " +
                   $"GameOver: {IsGameOver}, Paused: {IsPaused}, " +
                   $"CurrentPiece: {CurrentPiece?.Type} at ({CurrentPiece?.X},{CurrentPiece?.Y})";
        }

        /// &lt;summary>
        /// 次のピースが配置可能な座標を予測(ゴーストピース用)
        /// &lt;/summary>
        public int GetGhostPieceY()
        {
            if (CurrentPiece == null) return -1;

            int ghostY = CurrentPiece.Y;
            while (Board.CanPlaceTetromino(CurrentPiece, CurrentPiece.X, ghostY + 1))
            {
                ghostY++;
            }
            return ghostY;
        }
    }
}</code>

まとめ

今回はここまでとします。
現状では下記の3クラスを作成しました。
1,Tetrimino.cs テトリミノ(落ちてくるブロック)
2,GameBoard.cs ゲーム盤の管理
3,TetrisGame.cs ゲーム全体の制御
次回はController部分を作成します。
では、また次回お会いしましょう!!