//以前公開したテトリスのサンプルプログラムです。

//別記事に公開しているGameManager.csと一緒にご利用ください。

 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Tilemaps;

public class Tetris : MonoBehaviour
{
    //カメラ
    public GameObject mainCamera;
    //ゲームオブジェクトの読み込み
    public GameObject tileObj;
    //タイルマップ
    private Tilemap tilemap;
    //描画タイルの指定
    public TileBase[] tileChip;
    //枠の大きさ
    Vector2Int fSize;
    //タイルの情報を入れる2次元配列
    TileData[,] tileData;
    //仮想配列
    TileData[,] nextTileData;
    //落下中のオブジェクト情報
    DropTiles dropTiles;
    //一度の落下にかかる時間
    public float dropInterval;
    //消去の処理時間
    public float deleteInterval;
    //落下コルーチン
    IEnumerator DropCoroutine;

    //次のブロック表示用
    int rndBlock = 0;
    int nextBlock = 0;
    public GameObject objNextBlocks;
    Tilemap nextTilemap;

    //ゲーム操作フラグ管理
    public enum GAME_MODE
    {
        PLAY,
        WAIT,
        GAMEOVER,
    }
    public GAME_MODE gameMode;

    public void IntervalChange(float speed)
    {
        //数値の反映
        dropInterval = speed;
        deleteInterval = speed / 1.5f;
        if (deleteInterval < 0.5f) { deleteInterval = 0.5f; }
    }
    public void Tetris_Setup(Vector2Int siz)
    {
        //コンポーネントを取得
        tilemap = tileObj.GetComponent<Tilemap>();
        nextTilemap = objNextBlocks.GetComponent<Tilemap>();
        //タイルを全消去
        tilemap.ClearAllTiles();
        //配列の大きさを指定
        fSize = siz;
        //カメラの調整
        //次ブロックの表示位置も調整
        Camera_Setup();
        //配列のインスタンスを生成
        tileData = new TileData[fSize.x, fSize.y];
        nextTileData = new TileData[fSize.x, fSize.y];
        //クラスのインスタンス
        dropTiles = new DropTiles();
        //ゲームの状態
        gameMode = GAME_MODE.WAIT;
        //外周部の設置
        SetWall();
        //描画
        ViewTiles();
    }
    public void GameStart()
    {
        //ゲームの状態
        gameMode = GAME_MODE.PLAY;
        //次の次の生成タイルの指定
        rndBlock = UnityEngine.Random.Range(1, 8);
        //落下オブジェクトの生成
        Generate_DropBlock();
        //描画
        ViewTiles();
        //落下開始
        DropCoroutine = Cor_DropBlocks();
        StartCoroutine(DropCoroutine);
        //描画
        ViewTiles();
    }

    //落下コルーチン
    IEnumerator Cor_DropBlocks()
    {
        var gm = this.GetComponent<GameManager>();
        yield return new WaitForSeconds(dropInterval);
        while (true)
        {
            //落下可能な場合、落下処理
            if (Check_CanMove(Vector3Int.down))
            {
                Move_DropBlocks(Vector3Int.down);
            }
            //不可能な場合、ブロックのタイプdrop→消えるかチェック→block後に再生成
            else
            {
                //ブロックのタイプdrop→block
                foreach (Vector3Int pos in dropTiles.setPos)
                {
                    tileData[pos.x, pos.y].blockType = TileType.BLOCK;
                }
                //消える列があるかチェック
                //操作不可能に設定
                gameMode = GAME_MODE.WAIT;
                //消える列の取得
                List<int> delCol = CheckDeleteTiles();
                //消える列が存在する場合、ソート命令
                if (delCol.Count > 0)
                {
                    //列消去とスコア加算
                    DeleteTiles(delCol);
                    ViewTiles();
                    gm.Score_Update(delCol.Count);
                    yield return new WaitForSeconds(deleteInterval);
                    //列ソート
                    SortTiles(delCol);
                    ViewTiles();
                    yield return new WaitForSeconds(deleteInterval);
                }
                //再生成
                Generate_DropBlock();
                gameMode = GAME_MODE.PLAY;
            }
            //描画
            ViewTiles();
            yield return new WaitForSeconds(dropInterval);
        }
    }
    //ブロック消滅判定と処理
    //列消去
    void DeleteTiles(List<int> del)
    {
        for (int y = 1; y < fSize.y - 1; y++)
        {
            bool isDel = false;
            foreach (int d in del)
            {
                if (d == y) { isDel = true; }
            }
            if (isDel)
            {
                for (int x = 1; x < fSize.x - 1; x++)
                {
                    tileData[x, y].DataReset();
                }
            }
        }
    }
    //列ソート
    void SortTiles(List<int> del)
    {
        //仮想配列にコピー
        for (int y = 1; y < fSize.y - 1; y++)
        {
            //ソートして移動する数
            int sortCount = 0;
            foreach (int d in del)
            {
                if (d < y)
                {
                    sortCount++;
                }
            }
            for (int x = 1; x < fSize.x - 1; x++)
            {
                //落下後のブロックのみコピー
                if (tileData[x, y].blockType == TileType.BLOCK)
                {
                    nextTileData[x, y - sortCount] = tileData[x, y].Clone();
                }
            }
        }
        //仮想配列から使用中の配列へ
        for (int y = 1; y < fSize.y - 1; y++)
        {
            //ソートして移動する数
            int sortCount = 0;
            foreach (int d in del)
            {
                if (d < y)
                {
                    sortCount++;
                }
            }
            for (int x = 1; x < fSize.x - 1; x++)
            {
                tileData[x, y] = nextTileData[x, y].Clone();
                //仮想配列のデータ消去
                nextTileData[x, y].DataReset();
            }
        }
    }
    //列チェック→消える列を返す
    List<int> CheckDeleteTiles()
    {
        List<int> deleteLine = new List<int>() { };
        for (int y = 1; y < fSize.y - 1; y++)
        {
            //調べるタイルのリストを作成
            List<TileType> checktiles = new List<TileType>() { };
            for (int x = 1; x < fSize.x - 1; x++)
            {
                checktiles.Add(tileData[x, y].blockType);
            }
            //リストのタイルが全部TileType.Blockかチェック
            if (IsDelete(checktiles))
            {
                deleteLine.Add(y);
            }
        }
        return deleteLine;
    }
    //指定した列が全てブロック→true
    bool IsDelete(List<TileType> checkTiles)
    {
        bool del = true;
        for (int i = 0; i < checkTiles.Count; i++)
        {
            if (checkTiles[i] == TileType.NULL)
            {
                del = false;
            }
        }
        return del;
    }

    //ブロック生成
    void Generate_DropBlock()
    {
        //中心位置の指定
        Vector3Int center = new Vector3Int(fSize.x / 2, fSize.y - 3, 0);
        //次のタイルを指定
        nextBlock = rndBlock;
        //次の次の生成タイルの指定
        rndBlock = UnityEngine.Random.Range(1, 8);
        //次の次のタイルを、next位置においてPLに分かるようにする
        //座標リストを取得、描画
        nextTilemap.ClearAllTiles();
        foreach (Vector3Int pos in TileCalculation.blockList(rndBlock, 0))
        {
            nextTilemap.SetTile(pos, tileChip[rndBlock]);
        }

        //タイルを生成する位置を取得
        dropTiles.SetDropData(center, nextBlock, 0);
        //ゲームオーバー判定
        bool isGameover = false;
        //設置
        foreach (Vector3Int pos in dropTiles.setPos)
        {
            //タイルが重なっているか調べる
            if (tileData[pos.x, pos.y].blockType == TileType.BLOCK)
            {
                isGameover = true;
            }
            tileData[pos.x, pos.y].blockType = TileType.DROP;
            tileData[pos.x, pos.y].tileColor = nextBlock;
        }
        //ゲームオーバー
        if (isGameover) { Process_Gameover(); }
    }
    //ゲームオーバー処理
    void Process_Gameover()
    {
        //ゲームの状態
        gameMode = GAME_MODE.GAMEOVER;
        //コルーチン停止
        StopCoroutine(DropCoroutine);
        //ゲームオーバー時の状態を描画
        ViewTiles();
        //演出
        StartCoroutine(Cor_BlockGray());
    }
    //ゲームオーバー演出
    IEnumerator Cor_BlockGray()
    {
        var gm = this.GetComponent<GameManager>();
        yield return new WaitForSeconds(0.5f);
        for (int y = 1; y < fSize.y - 1; y++)
        {
            for (int x = 1; x < fSize.x - 1; x++)
            {
                if (tileData[x, y].blockType != TileType.NULL)
                {
                    tileData[x, y].tileColor = 9;
                }
            }
            ViewTiles();
            yield return new WaitForSeconds(0.1f);
        }
        //ゲームオーバー画面の表示
        yield return new WaitForSeconds(0.5f);
        gm.panelGameover.SetActive(true);
    }

    //0123を四方に配置,0:→ 1:↓ 2:← 3:↑(未使用)
    public void PushMoveButton(int num)
    {
        Vector3Int moveVec = Vector3Int.zero;
        switch (num)
        {
            //右
            case 0:
                moveVec = new Vector3Int(1, 0, 0);
                break;
            //下
            case 1:
                moveVec = new Vector3Int(0, -1, 0);
                break;
            //左
            case 2:
                moveVec = new Vector3Int(-1, 0, 0);
                break;
            //上
            case 3:
                moveVec = new Vector3Int(0, 1, 0);
                break;
        }
        //ゲームの状態判定
        if (gameMode == GAME_MODE.PLAY)
        {
            //移動判定
            if (Check_CanMove(moveVec))
            {
                //移動処理
                Move_DropBlocks(moveVec);
                //描画
                ViewTiles();
            }
        }
    }
    //ブロックの移動判定
    bool Check_CanMove(Vector3Int moveVec)
    {
        //移動判定
        bool canMove = true;
        //落下中のタイル座標リスト
        foreach (Vector3Int pos in dropTiles.setPos)
        {
            Vector3Int checkPos = pos + moveVec;
            if (tileData[checkPos.x, checkPos.y].blockType == TileType.WALL
                || tileData[checkPos.x, checkPos.y].blockType == TileType.BLOCK)
            {
                canMove = false;
            }
        }
        return canMove;
    }
    //ブロックの移動
    void Move_DropBlocks(Vector3Int moveVec)
    {
        //移動位置の描画タイルの消去
        foreach (Vector3Int pos in dropTiles.setPos)
        {
            tileData[pos.x, pos.y].blockType = TileType.NULL;
        }
        //落下オブジェの情報更新
        dropTiles.MoveCenter(moveVec);
        //位置情報を描画タイルに反映
        foreach (Vector3Int pos in dropTiles.setPos)
        {
            tileData[pos.x, pos.y].blockType = TileType.DROP;
            tileData[pos.x, pos.y].tileColor = dropTiles.shapeNum;
        }
    }
    //ブロックの回転入力
    public void Push_RotationButton(int rotDirection)
    {
        //座標チェック
        int rot = (dropTiles.rotNum + rotDirection) % 4;
        //回転に制限のあるブロック用の、回転の向き補正
        /*ブロックによって回転を制限
         * 回転無し:shapeNum=2
         * 回転2:shapeNum=4,5,7
        */
        if (dropTiles.shapeNum == 2)
        {
            rot = 0;
        }
        else if (dropTiles.shapeNum == 4
            || dropTiles.shapeNum == 5
            || dropTiles.shapeNum == 7)
        {
            rot %= 2;
        }
        //中心からの位置
        List<Vector3Int> tst = TileCalculation.blockList(dropTiles.shapeNum, rot);
        //補正
        List<Vector3Int> posList = new List<Vector3Int>() { };
        foreach (Vector3Int p in tst)
        {
            posList.Add(p + dropTiles.centerPos);
        }
        bool canRot = Check_CanRotate(posList);
        if (canRot)
        {
            //オブジェを消去
            foreach (Vector3Int pos in dropTiles.setPos)
            {
                tileData[pos.x, pos.y].DataReset();
            }
            //位置情報を描画タイルに反映
            dropTiles.SetDropData(dropTiles.centerPos, dropTiles.shapeNum, rot);
            foreach (Vector3Int pos in dropTiles.setPos)
            {
                tileData[pos.x, pos.y].blockType = TileType.DROP;
                tileData[pos.x, pos.y].tileColor = dropTiles.shapeNum;
            }
            //描画
            ViewTiles();
        }

    }
    //ブロックの回転判定
    bool Check_CanRotate(List<Vector3Int> posList)
    {
        //移動判定
        bool canMove = true;
        //落下中のタイル座標リスト
        foreach (Vector3Int checkPos in posList)
        {
            //範囲外の除去。もっと見ためよくしたい
            if (checkPos.x < 0
                || checkPos.y < 0
                || checkPos.x > fSize.x - 1
                || checkPos.y > fSize.y - 1)
            {
                canMove = false;
            }
            else
            {
                if (tileData[checkPos.x, checkPos.y].blockType == TileType.WALL
                || tileData[checkPos.x, checkPos.y].blockType == TileType.BLOCK)
                {
                    canMove = false;
                }
            }
        }
        return canMove;

    }
    //リセット、壁設置
    void SetWall()
    {
        for (int y = 0; y < fSize.y; y++)
        {
            for (int x = 0; x < fSize.x; x++)
            {
                //個別にインスタンスを作成
                tileData[x, y] = new TileData();
                nextTileData[x, y] = new TileData();
                //初期化
                tileData[x, y].DataReset();
                nextTileData[x, y].DataReset();
                //外周部を壁で囲う
                if (x == 0
                    || y == 0
                    || x == fSize.x - 1
                    || y == fSize.y - 1)
                {
                    tileData[x, y].blockType = TileType.WALL;
                }
            }
        }
    }
    //全体の描画命令
    void ViewTiles()
    {
        for (int y = 0; y < fSize.y; y++)
        {
            for (int x = 0; x < fSize.x; x++)
            {
                Vector3Int pos = new Vector3Int(x, y, 0);
                //空っぽの場合は表示しない
                if (tileData[x, y].blockType == TileType.NULL)
                {
                    tilemap.SetTile(pos, null);
                }
                //ブロックがある場合は、色の塗り分け(タイルの設置)
                else
                {
                    //タイルの設置
                    tilemap.SetTile(pos, tileChip[tileData[x, y].tileColor]);
                }
            }
        }
    }
    //カメラ、背景設定
    void Camera_Setup()
    {
        //カメラ位置と表示範囲の修正
        //大きさ修正
        mainCamera.GetComponent<Camera>().orthographicSize = fSize.y / 2 + 1;
        //タイルの中央のセル座標を指定
        Vector3Int tileDL = Vector3Int.zero;
        Vector3Int tileUR = new Vector3Int(fSize.x - 1, fSize.y - 1, 0);
        //タイルの中央のworld座標を取得
        Vector3 tileCenter_World
            = (tilemap.GetCellCenterWorld(tileDL)
            + tilemap.GetCellCenterWorld(tileUR)) / 2;
        //タイルの中央位置に、カメラを持ってくる
        mainCamera.GetComponent<Transform>().position
            = new Vector3(tileCenter_World.x, tileCenter_World.y, -10);

        //次ブロックの位置を調整
        Vector3Int nextVec = new Vector3Int(fSize.x + 1, fSize.y - 2, 0);
        objNextBlocks.GetComponent<Transform>().position
            = tilemap.GetCellCenterWorld(nextVec);
        objNextBlocks.GetComponent<Transform>().localScale
            = new Vector3(0.5f, 0.5f, 1);
    }
}

//ブロックの種類
public enum TileType
{
    NULL,//何もない
    WALL,//壁(破壊不可能
    BLOCK,//ブロック(列を揃えれば消える
    DROP,//落下中のブロック
}
//タイルの描画と判定に使うクラス
public class TileData
{
    //種類
    public TileType blockType;
    //ブロックの色
    public int tileColor;
    //初期化
    public void DataReset()
    {
        blockType = TileType.NULL;
        tileColor = 0;
    }
    //データコピー。複製インスタンスを返す
    public TileData Clone()
    {
        return (TileData)MemberwiseClone();
    }
}
//落下中のタイル情報
public class DropTiles
{
    //落下オブジェの中心座標
    public Vector3Int centerPos;
    //落下オブジェの形状
    public int shapeNum;
    //落下オブジェの回転位置(0~3
    public int rotNum;
    //中心(0,0,0)から見た生成位置のリスト
    public List<Vector3Int> setPos = new List<Vector3Int>() { };
    //落下オブジェの情報を入力
    public void SetDropData(Vector3Int center, int type, int rot)
    {
        //リストのリセット
        setPos.Clear();
        //中心位置
        centerPos = center;
        //形状
        shapeNum = type;
        //回転位置
        rotNum = rot;
        //中心から見たブロック位置
        List<Vector3Int> pos = TileCalculation.blockList(shapeNum, rotNum);
        //中心と周囲の座標リスト→実際の配置座標
        foreach (Vector3Int p in pos)
        {
            setPos.Add(p + centerPos);
        }
    }

    //中心座標の更新
    public void MoveCenter(Vector3Int moveVec)
    {
        centerPos += moveVec;
        //付随したデータ更新
        SetDropData(centerPos, shapeNum, rotNum);
    }
}

//計算処理
public static class TileCalculation
{
    //座標の初期配置リスト
    public static List<Vector3Int> blockList(int shapeNum, int rotNum)
    {
        List<Vector3Int> pos = new List<Vector3Int>() { };

        switch (shapeNum)
        {
            //テスト用
            case 0:
                /*
                 * □
                 */
                pos.Add(new Vector3Int(0, 0, 0));
                break;
            case 1:
                /*
                 * ■
                 * ■□■
                 */
                pos.Add(new Vector3Int(-1, 1, 0));
                pos.Add(new Vector3Int(-1, 0, 0));
                pos.Add(new Vector3Int(0, 0, 0));
                pos.Add(new Vector3Int(1, 0, 0));
                break;
            case 2:
                /*
                 * ■■
                 * □■
                 */
                pos.Add(new Vector3Int(1, 1, 0));
                pos.Add(new Vector3Int(1, 0, 0));
                pos.Add(new Vector3Int(0, 1, 0));
                pos.Add(new Vector3Int(0, 0, 0));
                break;
            case 3:
                /*
                 *   ■
                 * ■□■
                 */
                pos.Add(new Vector3Int(-1, 0, 0));
                pos.Add(new Vector3Int(0, 0, 0));
                pos.Add(new Vector3Int(1, 0, 0));
                pos.Add(new Vector3Int(1, 1, 0));
                break;
            case 4:
                /*
                 * ■
                 * ■□
                 *  ■
                 */
                pos.Add(new Vector3Int(-1, 1, 0));
                pos.Add(new Vector3Int(-1, 0, 0));
                pos.Add(new Vector3Int(0, 0, 0));
                pos.Add(new Vector3Int(0, -1, 0));
                break;
            case 5:
                /*
                 *  ■
                 * ■□
                 * ■
                 */
                pos.Add(new Vector3Int(0, 1, 0));
                pos.Add(new Vector3Int(0, 0, 0));
                pos.Add(new Vector3Int(-1, 0, 0));
                pos.Add(new Vector3Int(-1, -1, 0));
                break;
            case 6:
                /*
                 *  ■
                 * ■□■
                 */
                pos.Add(new Vector3Int(-1, 0, 0));
                pos.Add(new Vector3Int(0, 0, 0));
                pos.Add(new Vector3Int(1, 0, 0));
                pos.Add(new Vector3Int(0, 1, 0));
                break;
            case 7:
                /*
                 * ■■□■
                 */
                pos.Add(new Vector3Int(-1, 0, 0));
                pos.Add(new Vector3Int(0, 0, 0));
                pos.Add(new Vector3Int(1, 0, 0));
                pos.Add(new Vector3Int(2, 0, 0));
                break;
        }
        pos = rotPos(Vector3Int.zero, pos, rotNum);
        return pos;
    }

    //回転後の座標リストを返すだけ
    public static List<Vector3Int> rotPos(Vector3Int center, List<Vector3Int> posList, int rotCount)
    {
        List<Vector3Int> rotList = new List<Vector3Int>() { };
        rotCount = rotCount % 4;
        foreach (Vector3Int pos in posList)
        {
            Vector3Int nextP = Vector3Int.zero;
            //指定された原点から見た座標位置
            Vector3Int checkP = pos - center;
            switch (rotCount)
            {
                case 0:
                    nextP = new Vector3Int(checkP.x, checkP.y, 0);
                    break;
                case 1:
                    //90度
                    nextP = new Vector3Int(-checkP.y, checkP.x, 0);
                    break;
                case 2:
                    //180度
                    nextP = new Vector3Int(-checkP.x, -checkP.y, 0);
                    break;
                case 3:
                    //270度
                    nextP = new Vector3Int(checkP.y, -checkP.x, 0);
                    break;
            }
            nextP += center;
            rotList.Add(nextP);
        }
        return rotList;
    }
}