C#【进阶】俄罗斯方块

俄罗斯方块


在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

Test1_场景切换相关

BeginScene.cs
namespace CSharp俄罗斯方块
{
    internal class BeginScene : BegionOrEndScene
    {
        public BeginScene()
        {
            strTitle = "俄罗斯方块";
            strOne = "开始游戏";
        }
        public override void EnterJDoSomthing()
        {
            if (nowSelIndex == 0)
            {
                Game.ChangeScene(E_SceneType.Game);
            }
            else
            {
                Environment.Exit(0);
            }
        }
    }
}

BegionOrEndScene.cs
namespace CSharp俄罗斯方块
{
    abstract internal class BegionOrEndScene : ISceneUpdate
    {
        protected int nowSelIndex = 0;
        protected string strTitle;
        protected string strOne;

        public abstract void EnterJDoSomthing();
        public void Update()
        {
            Console.ForegroundColor = ConsoleColor.White;
            Console.SetCursorPosition(Game.w / 2 - strTitle.Length, 5);
            Console.Write(strTitle);

            Console.SetCursorPosition(Game.w / 2 - strOne.Length, 8);
            Console.ForegroundColor = nowSelIndex == 0 ? ConsoleColor.Red : ConsoleColor.White;
            Console.Write(strOne);

            Console.SetCursorPosition(Game.w / 2 - 4, 10);
            Console.ForegroundColor = nowSelIndex == 1 ? ConsoleColor.Red : ConsoleColor.White;
            Console.Write("结束游戏");

            switch(Console.ReadKey(true).Key)
            {
                case ConsoleKey.W:
                    nowSelIndex--;
                    if (nowSelIndex < 0)
                    {
                        nowSelIndex = 1;
                    }
                    break;
                case ConsoleKey.S:
                    nowSelIndex++;
                    if (nowSelIndex > 1)
                    {
                        nowSelIndex = 0;
                    }
                    break;
                case ConsoleKey.J:
                    EnterJDoSomthing();
                    break;
            }
        }
    }
}

EndScene.cs
namespace CSharp俄罗斯方块
{
    internal class EndScene : BegionOrEndScene
    {
        public EndScene()
        {
            strTitle = "结束游戏";
            strOne = "回到开始界面";
        }
        public override void EnterJDoSomthing()
        {
            if(nowSelIndex == 0)
            {
                Game.ChangeScene(E_SceneType.Begin);
            }
            else
            {
                Environment.Exit(0);
            }
        }
    }
}

Game.cs
namespace CSharp俄罗斯方块
{
    enum E_SceneType
    {
        Begin,
        Game,
        End,
    }
    internal class Game
    {
        public const int w = 50;
        public const int h = 35;

        public static ISceneUpdate nowScene;

        public Game()
        {
            Console.CursorVisible = false;
            Console.SetWindowSize(w, h);
            Console.SetBufferSize(w, h);

            ChangeScene(E_SceneType.Begin);
        }

        public void start()
        {
            while (true)
            {
                if (nowScene != null)
                {
                    nowScene.Update();
                }
            }
        }

        public static  void ChangeScene(E_SceneType type)
        {
            Console.Clear();
            switch (type)
            {
                case E_SceneType.Begin:
                    nowScene = new BeginScene();
                    break;
                case E_SceneType.Game:
                    nowScene = new GameScene();
                    break;
                case E_SceneType.End:
                    nowScene = new EndScene();
                    break;
            }
        }
    }

}

GameScene.cs
namespace CSharp俄罗斯方块
{
    internal class GameScene : ISceneUpdate
    {
        Map map;

        BlockWorker blockWorker;

        //bool isRunning;

        //新开线程
        //Thread inputThread;

        public GameScene()
        {
            map = new Map(this);
            blockWorker = new BlockWorker();

            InputThread.Instance.inputEvent += CheckInputThread;

            //isRunning = true;
            //inputThread = new Thread(CheckInputThread);
            设置成后台线程(生命周期随主线程)
            //inputThread.IsBackground = true;
            开启线程
            //inputThread.Start();
        }

        public void StopThread()
        {
            //isRunning = false;
            //inputThread = null;
            //移除输入事件监听
            InputThread.Instance.inputEvent -= CheckInputThread;
        }
        private void CheckInputThread()
        {
            //while (isRunning)
            //{
                //检测输入,防止输入卡程序
                if (Console.KeyAvailable)
                {
                    //避免影响主线程,在输入后加锁
                    lock (blockWorker)
                    {
                        switch (Console.ReadKey(true).Key)
                        {
                            case ConsoleKey.LeftArrow:
                                //判断变形
                                if (blockWorker.CanChange(E_Change_Type.Left, map))
                                    blockWorker.Change(E_Change_Type.Left);
                                break;
                            case ConsoleKey.RightArrow:
                                if (blockWorker.CanChange(E_Change_Type.Right, map))
                                    blockWorker.Change(E_Change_Type.Right);
                                break;
                            case ConsoleKey.A:
                                if (blockWorker.CanMoveR_L(E_Change_Type.Left, map))
                                    blockWorker.MoveR_L(E_Change_Type.Left);
                                break;
                            case ConsoleKey.D:
                                if (blockWorker.CanMoveR_L(E_Change_Type.Right, map))
                                    blockWorker.MoveR_L(E_Change_Type.Right);
                                break;
                            case ConsoleKey.S:
                                if (blockWorker.CanDown(map))
                                    blockWorker.AutoMove();
                                break;
                        }
                    }
                }
            //}
        }
        public void Update()
        {
            lock (blockWorker)
            {
                map.Draw();
                blockWorker.Draw();

                if (blockWorker.CanDown(map))
                    blockWorker.AutoMove();
            }

            //线程休眠减速
            Thread.Sleep(100);


        }
    }
}

ISceneUpdate.cs
namespace CSharp俄罗斯方块
{
    /// <summary>
    /// 场景更新接口
    /// </summary>
    internal interface ISceneUpdate
    {
        void Update();
    }
}

Test2_绘制对象基类和枚举信息

DrawObject.cs
namespace CSharp俄罗斯方块
{
    /// <summary>
    /// 绘制类型
    /// </summary>
    enum E_DrawType
    {
        /// <summary>
        /// 墙壁
        /// </summary>
        Wall,
        /// <summary>
        /// 正方形方块
        /// </summary>
        Cube,
        /// <summary>
        /// 直线
        /// </summary>
        Line,
        /// <summary>
        /// 坦克
        /// </summary>
        Tank,
        /// <summary>
        /// 左梯子
        /// </summary>
        Left_Ladder,
        /// <summary>
        /// 右梯子
        /// </summary>
        Right_Ladder,
        /// <summary>
        /// 左长梯子
        /// </summary>
        Left_Long_ladder,
        /// <summary>
        /// 右长梯子
        /// </summary>
        Right_Long_ladder,

    }
    internal class DrawObject : IDraw
    {
        public Position pos;

        public E_DrawType type;

        public DrawObject(E_DrawType type)
        {
            this.type = type;
        }
        public DrawObject(E_DrawType type, int x, int y) : this(type)
        {
            pos = new Position(x, y);
        }

        public void Draw()
        {
            if (pos.y < 0)
                return;
            Console.SetCursorPosition(pos.x, pos.y);
            switch (type)
            {
                case E_DrawType.Wall:
                    Console.ForegroundColor = ConsoleColor.Red;
                    break;
                case E_DrawType.Cube:
                    Console.ForegroundColor = ConsoleColor.Blue;
                    break;
                case E_DrawType.Line:
                    Console.ForegroundColor = ConsoleColor.Green;
                    break;
                case E_DrawType.Tank:
                    Console.ForegroundColor = ConsoleColor.Cyan;
                    break;
                case E_DrawType.Left_Ladder:
                case E_DrawType.Right_Ladder:
                    Console.ForegroundColor = ConsoleColor.Magenta;
                    break;
                case E_DrawType.Left_Long_ladder:
                case E_DrawType.Right_Long_ladder:
                    Console.ForegroundColor = ConsoleColor.Yellow;
                    break;
            }
            Console.Write("■");
        }

        //擦除
        public void ClearDraw()
        {
            if (pos.y < 0)
                return;
            Console.SetCursorPosition(pos.x, pos.y);
            Console.Write("  ");
        }

        /// <summary>
        /// 附着墙壁方法
        /// </summary>
        /// <param name="type"></param>
        public void ChangeType(E_DrawType type)
        {
            this.type = type;
        }
    }
}

IDraw.cs
namespace CSharp俄罗斯方块
{
    internal interface IDraw
    {
        void Draw();
    }
}

Position.cs
namespace CSharp俄罗斯方块
{
    internal struct Position
    {
        public int x;
        public int y;

        public Position(int x, int y)
        {
            this.x = x;
            this.y = y;
        }

        public static bool operator ==(Position left, Position right)
        {
            if (left.x == right.x && left.y == right.y) { return true; }
            return false;
        }
        public static bool operator !=(Position left, Position right)
        {
            if (left.x == right.x && left.y == right.y) { return false; }
            return true;
        }

        public static Position operator +(Position left, Position right)
        {
            return new Position(left.x + right.x, left.y + right.y);
        }
    }

}

Test3_地图相关

Map.cs
namespace CSharp俄罗斯方块
{
    internal class Map : IDraw
    {
        //固定墙壁
        private List<DrawObject> walls = new List<DrawObject>();

        //动态墙壁
        public List<DrawObject> dynamicWalls = new List<DrawObject>();

        private GameScene nowGameScene;

        //为了外部能快速得到地图边界
        //动态墙壁的宽容量,小方块横向个数
        public int w;
        public int h;

        //记录每一行有多少个小方块的容器,索引就是行号
        private int[] recordInfo;
        public Map(GameScene scene)
        {
            nowGameScene = scene;
            h = Game.h - 6;
            recordInfo = new int[h];
            w = 0;
            //绘制横向墙壁
            for (int i = 0; i < Game.w; i += 2)
            {
                walls.Add(new DrawObject(E_DrawType.Wall, i, h));
                w++;
            }
            w -= 2;

            for (int i = 0; i < h; i++)
            {
                walls.Add(new DrawObject(E_DrawType.Wall, 0, i));
                walls.Add(new DrawObject(E_DrawType.Wall, Game.w - 2, i));
            }
        }

        public void Draw()
        {
            for (int i = 0; i < walls.Count; i++)
            {
                walls[i].Draw();
            }

            for (int i = 0; i < dynamicWalls.Count; i++)
            {
                dynamicWalls[i].Draw();
            }
        }
        public void ClearDraw()
        {
            for (int i = 0; i < dynamicWalls.Count; i++)
            {
                dynamicWalls[i].ClearDraw();
            }
        }

        public void AddWalls(List<DrawObject> walls)
        {
            for (int i = 0; i < walls.Count; i++)
            {
                //掉下来的方块变成墙壁类型
                walls[i].ChangeType(E_DrawType.Wall);
                dynamicWalls.Add(walls[i]);

                if (walls[i].pos.y<=0)
                {
                    nowGameScene.StopThread();
                    Game.ChangeScene(E_SceneType.End);
                    return;
                }

                //进行添加动态墙壁行的计数
                recordInfo[h - walls[i].pos.y - 1]++;
            }
            ClearDraw();
            CheckClear();
            Draw();

        }

        /// <summary>
        /// 检测是否跨层
        /// </summary>
        public void CheckClear()
        {
            //遍历时安全移除,添加待移除数组
            List<DrawObject> delList = new List<DrawObject>();
            //要选择记录行中有多少个方块的容器
            //数组判断这一行是否满
            //遍历数组,检测数组里面存的数是不是w-2
            for (int i = 0; i < recordInfo.Length; i++)
            {
                //如果这行满了需要移除
                if (recordInfo[i] == w)
                {
                    //1、这一行的所有方块移除
                    for (int j = 0; j < dynamicWalls.Count; j++)
                    {
                        //通过动态方块的y计算它在哪一行
                        if (i == h - dynamicWalls[j].pos.y - 1)
                        {
                            //待移除添加到记录列表
                            delList.Add(dynamicWalls[j]);
                        }
                        //2、要这一行之上的所有方块下移
                        else if (h - dynamicWalls[j].pos.y - 1 > i)
                        {
                            dynamicWalls[j].pos.y++;
                        }
                    }
                    //移除待删小方块
                    for (int j = 0;j < delList.Count; j++)
                    {
                        dynamicWalls.Remove(delList[j]);
                    }
                    //3、记录小方块数量的数组从上到下迁移
                    for (int j = i; j < recordInfo.Length-1; j++)
                    {
                        recordInfo[j] = recordInfo[j + 1];
                    }
                    //置空最顶层
                    recordInfo[recordInfo.Length - 1] = 0;
                    //利用递归,一层一层检测
                    CheckClear();
                    break;
                }
            }
        }
    }
}

Test4_坐标信息类

在这里插入图片描述
在这里插入图片描述

BlockInfo.cs
namespace CSharp俄罗斯方块
{
    internal class BlockInfo
    {
        private List<Position[]> list;

        public BlockInfo(E_DrawType type)
        {
            list = new List<Position[]>();

            switch (type)
            {
                case E_DrawType.Cube:
                    list.Add(new Position[3] { new Position(2, 0), new Position(0, 1), new Position(2, 1) });
                    break;
                case E_DrawType.Line:
                    list.Add([new Position(0, -1), new Position(0, 1), new Position(0, 2)]);
                    list.Add([new Position(-4, 0), new Position(-2, 0), new Position(2, 0)]);
                    list.Add([new Position(0, -2), new Position(0, -1), new Position(0, 1)]);
                    list.Add([new Position(-2, 0), new Position(2, 0), new Position(4, 0)]);
                    break;
                case E_DrawType.Tank:
                    list.Add([new Position(-2, 0), new Position(2, 0), new Position(0, 1)]);
                    list.Add([new Position(0, -1), new Position(-2, 0), new Position(0, 1)]);
                    list.Add([new Position(0, -1), new Position(-2, 0), new Position(2, 0)]);
                    list.Add([new Position(0,-1), new Position(2, 0), new Position(0, 1)]);
                    break;
                case E_DrawType.Left_Ladder:
                    list.Add([new Position(0, -1), new Position(2, 0), new Position(2, 1)]);
                    list.Add([new Position(2, 0), new Position(-2, 1), new Position(0, 1)]);
                    list.Add([new Position(-2, -1), new Position(-2, 0), new Position(0, 1)]);
                    list.Add([new Position(0, -1), new Position(2, -1), new Position(-2, 0)]);
                    break;
                case E_DrawType.Right_Ladder:
                    list.Add([new Position(0, -1), new Position(-2, 0), new Position(-2, 1)]);
                    list.Add([new Position(-2, -1), new Position(0, -1), new Position(2, 0)]);
                    list.Add([new Position(2, -1), new Position(2, 0), new Position(0, 1)]);
                    list.Add([new Position(-2, 0), new Position(0, 1), new Position(2, 1)]);
                    break;
                case E_DrawType.Left_Long_ladder:
                    list.Add([new Position(-2, -1), new Position(0, -1), new Position(0, 1)]);
                    list.Add([new Position(2, 1), new Position(-2, 0), new Position(2, 0)]);
                    list.Add([new Position(0, -1), new Position(0, 1), new Position(2, 1)]);
                    list.Add([new Position(-2, 0), new Position(2, 0), new Position(-2, 1)]);
                    break;
                case E_DrawType.Right_Long_ladder:
                    list.Add([new Position(0, -1), new Position(2, -1), new Position(0, 1)]);
                    list.Add([new Position(-2, 0), new Position(2, 1), new Position(2, 0)]);
                    list.Add([new Position(0, -1), new Position(0, 1), new Position(-2, 1)]);
                    list.Add([new Position(-2, 0), new Position(2, 0), new Position(-2, -1)]);
                    break;
                default:
                    break;
            }
        }

        public Position[] this[int index]
        {
            get
            {
                if (index < 0)
                    return list[0];
                else if (index >= list.Count)
                    return list[list.Count - 1];
                else
                    return list[index];
                
            }
        }

        public int Count { get => list.Count; }
    }
}

Test5_板砖工人类

BlockWorker.cs
namespace CSharp俄罗斯方块
{
    enum E_Change_Type
    {
        Left,
        Right,
    }
    internal class BlockWorker : IDraw
    {
        //方块们
        private List<DrawObject> blocks;
        //用Dictionary方便查找
        private Dictionary<E_DrawType, BlockInfo> blockInfoDic;
        //随机创建的方块具体形态信息
        private BlockInfo nowBlockInfo;
        //当前形态的索引
        private int nowInfoIndex;

        public BlockWorker()
        {
            blockInfoDic = new Dictionary<E_DrawType, BlockInfo>()
            {
                { E_DrawType.Cube,new BlockInfo(E_DrawType.Cube)},
                { E_DrawType.Line,new BlockInfo(E_DrawType.Line)},
                { E_DrawType.Tank,new BlockInfo(E_DrawType.Tank)},
                { E_DrawType.Left_Ladder,new BlockInfo(E_DrawType.Left_Ladder)},
                { E_DrawType.Right_Ladder,new BlockInfo(E_DrawType.Right_Ladder)},
                { E_DrawType.Left_Long_ladder,new BlockInfo(E_DrawType.Left_Long_ladder)},
                { E_DrawType.Right_Long_ladder,new BlockInfo(E_DrawType.Right_Long_ladder)},
            };
            RandomCreatBlock();
        }

        public void Draw()
        {
            for (int i = 0; i < blocks.Count; i++)
            {
                blocks[i].Draw();
            }
        }

        /// <summary>
        /// 随机创建方块
        /// </summary>
        public void RandomCreatBlock()
        {
            Random r = new Random();
            E_DrawType type = (E_DrawType)r.Next(1, 8);
            blocks = new List<DrawObject>()
            {
                new DrawObject(type),
                new DrawObject(type),
                new DrawObject(type),
                new DrawObject(type),
            };
            //定义0,0相对坐标
            blocks[0].pos = new Position(24, -5);

            nowBlockInfo = blockInfoDic[type];
            //随机当前形态
            nowInfoIndex = r.Next(0, nowBlockInfo.Count);
            //取出原点坐标信息
            Position[] pos = nowBlockInfo[nowInfoIndex];
            for (int i = 0; i < pos.Length; i++)
            {
                //通过原点坐标信息绘制四个方块
                blocks[i + 1].pos = pos[i] + blocks[0].pos;
            }
        }

        //擦除方法
        public void ClearDraw()
        {
            for (int i = 0; i < blocks.Count; i++)
            {
                blocks[i].ClearDraw();
            }
        }
        /// <summary>
        /// 方块变形
        /// </summary>
        /// <param name="type">左,右</param>
        public void Change(E_Change_Type type)
        {
            //擦除之前的方块
            ClearDraw();

            switch (type)
            {
                case E_Change_Type.Left:
                    nowInfoIndex--;
                    if (nowInfoIndex < 0)
                        nowInfoIndex = nowBlockInfo.Count - 1;
                    break;
                case E_Change_Type.Right:
                    nowInfoIndex++;
                    if (nowInfoIndex >= nowBlockInfo.Count)
                        nowInfoIndex = 0;
                    break;
            }
            //得到索引,取出来
            Position[] pos = nowBlockInfo[nowInfoIndex];
            for (int i = 0; i < pos.Length; i++)
            {
                blocks[i + 1].pos = pos[i] + blocks[0].pos;
            }

            //擦除之后绘制
            Draw();
        }
        /// <summary>
        /// 判断能否变形
        /// </summary>
        /// <param name="type">地图方向</param>
        /// <param name="map">地图墙壁</param>
        /// <returns></returns>
        public bool CanChange(E_Change_Type type, Map map)
        {
            //临时变量用来模拟
            int nowIndex = nowInfoIndex;
            switch (type)
            {
                case E_Change_Type.Left:
                    nowIndex--;
                    if (nowIndex < 0)
                        nowIndex = nowBlockInfo.Count - 1;
                    break;
                case E_Change_Type.Right:
                    nowIndex++;
                    if (nowIndex >= nowBlockInfo.Count)
                        nowIndex = 0;
                    break;
            }
            //临时索引信息判断是否和墙壁重合
            Position[] nowPos = nowBlockInfo[nowIndex];

            //判断是否超出地图边界
            Position tempPos;
            for (int i = 0; i < nowPos.Length; i++)
            {
                tempPos = blocks[0].pos + nowPos[i];
                if (tempPos.x < 2 || tempPos.x >= Game.w - 2 || tempPos.y >= map.h)
                {
                    return false;
                }
            }
            //判断是否和其他方块重合
            for (int i = 0; i < nowPos.Length; i++)
            {
                tempPos = blocks[0].pos + nowPos[i];
                for (int j = 0; j < map.dynamicWalls.Count; j++)
                {
                    if (tempPos == map.dynamicWalls[j].pos)
                    {
                        return false;
                    }
                }
            }
            return true;
        }
        /// <summary>
        /// 方块左右移动
        /// </summary>
        /// <param name="type">左,右</param>
        public void MoveR_L(E_Change_Type type)
        {
            //移动之前擦除
            ClearDraw();
            //根据传入的类型,决定左右移动,得到偏移位置
            Position movePos = new Position(type == E_Change_Type.Left ? -2 : 2, 0);
            for (int i = 0; i < blocks.Count; i++)
            {
                blocks[i].pos += movePos;
            }

            //移动之后绘制
            Draw();
        }

        /// <summary>
        /// 判断能否移动
        /// </summary>
        /// <param name="type">左,右移动</param>
        /// <returns></returns>
        public bool CanMoveR_L(E_Change_Type type, Map map)
        {
            Position movePos = new Position(type == E_Change_Type.Left ? -2 : 2, 0);
            //左右边界重合,预判断pos
            Position pos;
            for (int i = 0; i < blocks.Count; i++)
            {
                pos = blocks[i].pos + movePos;
                if (pos.x < 2 || pos.x >= Game.w - 2)
                    return false;
            }
            //动态方块重合
            for (int i = 0; i < blocks.Count; i++)
            {
                pos = blocks[i].pos + movePos;
                for (int j = 0; j < map.dynamicWalls.Count; j++)
                {
                    if (pos == map.dynamicWalls[j].pos)
                        return false;
                }
            }
            return true;
        }

        //方块自动下落
        public void AutoMove()
        {
            ClearDraw();
            Position downMove = new Position(0, 1);
            for (int i = 0; i < blocks.Count; i++)
            {
                blocks[i].pos += downMove;
            }
            Draw();
        }
        public bool CanDown(Map map)
        {
            Position downPos = new Position(0, 1);
            //预下落位置
            Position pos;
            //边界
            for (int i = 0; i < blocks.Count; i++)
            {
                pos = blocks[i].pos + downPos;
                if (pos.y >= map.h)
                {
                    //碰到地图,此时变成地图的一部分
                    map.AddWalls(blocks);
                    //创建新的随机方块
                    RandomCreatBlock();
                    return false;
                }
            }
            //动态方块
            for (int i = 0; i < blocks.Count; i++)
            {
                pos = blocks[i].pos + downPos;
                for (int j = 0; j < map.dynamicWalls.Count; j++)
                {
                    if (pos == map.dynamicWalls[j].pos)
                    {
                        //碰到方块,此时变成地图的一部分
                        map.AddWalls(blocks);
                        RandomCreatBlock();
                        return false;
                    }
                }
            }

            return true;
        }


    }
}

Test6_输入模块

InputThread.cs
namespace CSharp俄罗斯方块
{
    internal class InputThread
    {

        //线程成员变量
        Thread inputThread;
        //输入检测事件
        public event Action inputEvent;

        private static InputThread instance = new InputThread();
        public static InputThread Instance
        {
            get
            {
                return instance;
            }
        }
        private InputThread()
        { 
            inputThread = new Thread(InputCheck);
            inputThread.IsBackground = true;
            inputThread.Start();
        }
        private void InputCheck()
        {
            while (true)
            {
                inputEvent?.Invoke();
            }
        }
    }
}

Program.cs

using CSharp俄罗斯方块;

Game game = new Game();
game.start();
  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值