俄罗斯方块原理分析-C# 控制台

C#控制台实现俄罗斯方块

原理

  1. 绘制区域-画板,将绘制区分为多行,每行多个小的色块区元素;
  2. 移动区域,也为一个绘图区,也包含行和行元素,但移动区域存在着色块;
  3. 移动区域在画板上呈现,记录移动区相对于画板左上角的偏移量决定呈现位置,相当于横纵坐标;
  4. 画板着色,当移动区域向下移动和画板区域由颜色重叠,则将本次移动之前的移动区域在画板上着色。

C#控制台实现俄罗斯方块

移动区域

  1. 移动区域分为 正方形、线形、凸形、不稳定形;
  2. 每个移动图形有相同的基类,行数、列数、行偏移量、列偏移量;
  3. 移动图形存在初始着色区。

旋转图形

关键点

图形生成器

  1. 图像生成器中默认初始化各种图形形状,并计算个图形的偏移量;
  2. 当需要生成时,将随机产生一个图形,并克隆产生一个全新对象出来;

碰撞

  1. 移动区域逐步移动;
  2. 每一次移动需要记录当前副本,并计算移动区和画板是否重复;如果着色区重叠则将本次移动之前的移动区域在画板上着色;
  3. 如果发生碰撞,则进行画板的行全着色统计、消除并进行图形下沉。

图形生成和碰撞

计算分数和消行

消行

旋转

  1. 需要创建一个新的对象,新对象的行数为旋转对象的列数,新对象行的元素个数为旋转对象行数;
  2. 然后进行行转列着色;

旋转

效果

图形变化

图形变化
图形变化

完整源码

开发环境:window10、Visual Studio 2019
注:不具备可玩性,UI需要换种呈现方式,晃得眼花,千万别玩-头大

Program.cs 程序入口

using ConsoleApp.Tetris;
using System;

namespace Tetris
{
    class Program
    {
        static void Main(string[] args)
        {
            Control control = new Control();
            control.Start();
            Console.ReadKey();
        }
    }
}

Control.cs 控制模块

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApp.Tetris
{
    public enum State
    {
        NotStarted = 0,
        Starting = 1,
        Pause = 2,
        GameOver = 10
    }
    public class Control
    {
        public static string Key_Left = "4";//左移
        public static string Key_Right = "6";//右移
        public static string Key_Rotate = "8";//旋转
        public static string Key_Down = "2";//向下
        public static string Key_Pause = "0";//暂停

        private Graph graphArea;
        private GraphFactory factory;
        private Graph currentMoveArea = null;
        private Graph lastValidArea = null;
        private int DefaulTurnTime = 200;
        private int CurrentTurnTime = 200;
        private string key = "";
        private  Graph validArea = null;

        private State state;
        public Control()
        {
            graphArea = new Graph(12, 20);
            factory = new GraphFactory(graphArea);
        }

        public void Start()
        {
            state = State.Starting;
            //刷新画板区域
            RefreshGraphArea();

            while (true)
            {
                //监听按键
                MonitorKey();
                //处理画板移动区域
                HandleGraphMoveArea();

                //检测结束 
                if (state == State.GameOver)
                    break;

                //处理画板 结算或移动继续移动
                HandleGraphArea();

                Task.Delay(CurrentTurnTime).Wait();
            }
        }

        /// <summary>
        /// 定时绘制画板
        /// </summary>
        private void RefreshGraphArea()
        {
            Task.Run(() => {
                while (true)
                {
                    if (state == State.Starting)
                    {
                        if (validArea != null)
                            validArea.Draw();
                        else
                            graphArea.Draw();
                    }
                    Task.Delay(CurrentTurnTime).Wait();
                    if (graphArea == null)
                        break;
                }
            });
        }

        /// <summary>
        /// 监听按键  
        /// </summary>
        private void MonitorKey()
        {
            while (Console.KeyAvailable)
            {
                var input = Console.ReadKey(true);
                key = input.KeyChar.ToString();
            }
        }

        /// <summary>
        /// 处理移动区域
        /// </summary>
        private void HandleGraphMoveArea()
        {
            //如果移动区域不存在就创建一个信息
            if (currentMoveArea == null)
            {
                CreateMoveArea();
            }
            //如果移动区域存在则移动
            else
            {
                MoveArea();
            }
        }

        /// <summary>
        /// 创建新的移动区域
        /// </summary>
        private void CreateMoveArea()
        {
            currentMoveArea = factory.CreateArea();            
            validArea = graphArea.DrawOtherGraph(currentMoveArea);
            //如果创建出来的图形 在画板中无法绘画 则结束
            if (validArea == null)
            {
                Console.WriteLine("game over");
                state = State.GameOver;
            }
        }

        /// <summary>
        /// 移动处理
        /// </summary>
        private void MoveArea()
        { 
            if (key == Key_Pause)
            {
                Pause();
            }
            if (state != State.Pause)
            {
                if (currentMoveArea != null)
                {
                    currentMoveArea.RowOffset += 1;
                }
                if (key == Key_Left)
                {
                    MoveLeft();
                }
                else if (key == Key_Right)
                {
                    MoveRight();
                }
                else if (key == Key_Rotate)
                {
                    Rotate();
                }
                else if (key == Key_Down)
                {
                    ChangeMoveSpeed();
                }

                validArea = graphArea.DrawOtherGraph(currentMoveArea);
            }

        }

        /// <summary>
        /// 暂停
        /// </summary>
        private void Pause()
        {
            if (state != State.GameOver)
                state = state == State.Pause ? State.Starting : State.Pause;
            key = "";
        }

        /// <summary>
        /// 改变移动(下降)速度
        /// </summary>
        private void ChangeMoveSpeed()
        {
            CurrentTurnTime = CurrentTurnTime / 2;
            key = "";
        }

        /// <summary>
        ///  每轮移动后 统计画板 
        /// </summary>
        private void HandleGraphArea()
        {

            if (validArea != null)
            {
                //移动区域移动后整个区域有效则记录有效区域,作为碰撞后结算依据
                lastValidArea = validArea;
            }
            else
            {
                Settlement();
            }
        }

        /// <summary>
        /// 碰撞 结算
        /// </summary>
        private void Settlement()
        {
            graphArea = lastValidArea;
            currentMoveArea = null;
            if (graphArea.RemoveRowAllColor())
                graphArea.Draw();
            CurrentTurnTime = DefaulTurnTime;
        }
        private void Rotate()
        {
            var temp = currentMoveArea.DeepClone();
            currentMoveArea = currentMoveArea.Rotate();
            if (currentMoveArea == null)
                currentMoveArea = temp;
            else
            {
                var moveArea = graphArea.DrawOtherGraph(currentMoveArea);
                if (moveArea == null)
                    currentMoveArea = temp;
            }

            key = "";
        }

        private void MoveRight()
        {
            currentMoveArea.ColumnOffset++;
            if (currentMoveArea.ColumnOffset + currentMoveArea.ColumnNumber >= graphArea.ColumnNumber)
                currentMoveArea.ColumnOffset = graphArea.ColumnNumber - currentMoveArea.ColumnNumber;

            var moveArea = graphArea.DrawOtherGraph(currentMoveArea);
            if (moveArea == null)
                currentMoveArea.ColumnOffset--;

            key = "";
        }

        private void MoveLeft()
        {
            currentMoveArea.ColumnOffset--;
            if (currentMoveArea.ColumnOffset < 0)
                currentMoveArea.ColumnOffset = 0;

            var moveArea = graphArea.DrawOtherGraph(currentMoveArea);
            if (moveArea == null)
                currentMoveArea.ColumnOffset++;

            key = "";
        }

      
    }
}

Graph.cs 图案文件

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization.Formatters.Binary;

namespace ConsoleApp.Tetris
{
    public class GraphFactory
    {
        public Graph[] GraphProducts  { get; set; }
        public Graph MainArea { get; set; }
        public GraphFactory(Graph area)
        {
            MainArea = area;
            GraphProducts = new Graph[] 
            { 
                new Line() { ColumnOffset = area.ColumnNumber / 2 }, 
                new Instability(){ ColumnOffset = area.ColumnNumber / 2 },
                new Sharp(){ ColumnOffset = area.ColumnNumber / 2 },
                new Square () { ColumnOffset = area.ColumnNumber / 2 }
            };
        }
        public Graph CreateArea()
        {
            byte[] buffer = Guid.NewGuid().ToByteArray();
            int iRoot = BitConverter.ToInt32(buffer, 0);
            Random random = new Random(iRoot);
            int index = random.Next(0, GraphProducts.Length);
            return GraphProducts[index].DeepClone();
        }
    }


    /// <summary>
    /// 口
    /// 口
    /// 口
    /// 口
    /// </summary>
    [Serializable]
    public class Line : Graph
    {
        public const int ROW = 4;
        public const int COLUMN = 1;

        public Line() : base(COLUMN, ROW)
        {
            foreach (var row in Rows)
            {
                foreach (var element in row.Elements)
                {
                    element.IsColor = true;
                    element.Color = ConsoleColor.Yellow;
                }
            }
        }
    }

    /// <summary>
    ///   口
    /// 口口
    /// 口
    /// </summary>
    [Serializable]
    public class Instability : Graph
    {

        public const int ROW = 3;
        public const int COLUMN = 2;

        public Instability() : base(COLUMN, ROW)
        {
            foreach (var row in Rows)
            {
                var rowIndex = Rows.IndexOf(row);
                foreach (var element in row.Elements)
                {
                    var columnIndex = row.Elements.IndexOf(element);
                    if (rowIndex == 0)
                    {
                        if (columnIndex == 1)
                            element.IsColor = true;
                    }
                    else if (rowIndex == 1)
                    {
                        element.IsColor = true;
                    }
                    else
                    {
                        if (columnIndex == 0)
                            element.IsColor = true;
                    }
                    element.Color = ConsoleColor.Green;
                }
            }
        }
    }

    /// <summary>
    ///   口
    /// 口口口
    /// </summary>
    [Serializable]
    public class Sharp : Graph
    {

        public const int ROW = 2;
        public const int COLUMN = 3;

        public Sharp() : base(COLUMN, ROW)
        {
            foreach (var row in Rows)
            {
                var rowIndex = Rows.IndexOf(row);
                foreach (var element in row.Elements)
                {
                    var columnIndex = row.Elements.IndexOf(element);
                    if (rowIndex == 0 && columnIndex == COLUMN / 2)
                    {
                        element.IsColor = true;
                    }
                    else if (rowIndex == 1)
                    {
                        element.IsColor = true;
                    }

                    element.Color = ConsoleColor.Red;
                }
            }
        }
    }

    /// <summary>
    /// 田
    /// </summary>
    [Serializable]
    public class Square : Graph
    {

        public const int ROW = 2;
        public const int COLUMN = 2;

        public Square() : base(COLUMN, ROW)
        {
            foreach (var row in Rows)
            {
                foreach (var element in row.Elements)
                {
                    element.IsColor = true;
                    element.Color = ConsoleColor.White;
                } 
            }
        }
    }

    [Serializable]
    public class Graph
    {
        public const string GRAGH_DISPAY = "口";
        public int ColumnOffset { get; set; }
        public int RowOffset { get; set; }
        public int ColumnNumber { get; set; }
        public int RowNumber { get; set; }

        public List<RowElement> Rows { get; set; }

        //移除行的数量
        public int RemoveRow { get; set; } = 0;

        public Graph(int columnNumber, int rowNumber)
        {
            ColumnOffset = 0;
            RowOffset = 0;
            ColumnNumber = columnNumber;
            RowNumber = rowNumber;

            Init();
        }
  
      
        public void Draw()
        {
            Console.Clear();

            for (int column = 0; column <= ColumnNumber; column++)
                Console.Write("--");
            Console.WriteLine();

            for (int row = 0; row < RowNumber; row++)
            {
                Console.Write("|");
                var rowElement = Rows[row];
                for (int column = 0; column < ColumnNumber; column++)
                {
                    var element = rowElement.Elements[column];

                    if (element.IsColor)
                    {
                        Console.ForegroundColor = element.Color;
                        Console.Write(GRAGH_DISPAY);
                        Console.ResetColor();
                    }
                    else
                        Console.Write("  ");
                }
                Console.Write("|");
                Console.WriteLine();
            }
            for (int column = 0; column <= ColumnNumber; column++)
                Console.Write("--");
            Console.WriteLine($"得分:{RemoveRow}");
        }


        private void Init()
        {
            Rows = new List<RowElement>();
            for (int row = 0; row < RowNumber; row++)
            {
                RowElement rowElement = CreateRow();
                Rows.Add(rowElement);
            }
        }


        private RowElement CreateRow()
        {
            var rowElement = new RowElement(ColumnNumber);
            for (int column = 0; column < ColumnNumber; column++)
            {
                rowElement.Elements.Add(new Element());
            }

            return rowElement;
        }

        /// <summary>
        /// 将一个图形 画到当前图形上
        /// </summary>
        /// <param name="otherGraph"></param>
        /// <returns>新的图形,如果有色块重叠则null</returns>
        public Graph DrawOtherGraph(Graph otherGraph)
        {
            Graph offsetArea = this.DeepClone();
            for (int row = 0; row < otherGraph.RowNumber; row++)
            {
                if (otherGraph.RowOffset < 0 || otherGraph.RowOffset + row >= RowNumber)
                    return null;

                var offsetRow = offsetArea.Rows[otherGraph.RowOffset + row];
                var areaRow = otherGraph.Rows[row];

                for (int column = 0; column < otherGraph.ColumnNumber; column++)
                {
                    if (otherGraph.ColumnOffset < 0 || otherGraph.ColumnOffset + column >= ColumnNumber)
                        return null;

                    var offsetColumnElement = offsetRow.Elements[otherGraph.ColumnOffset + column];
                    var areaRowColumnElement = areaRow.Elements[column];
                    if (offsetColumnElement.IsColor && areaRowColumnElement.IsColor)
                    {
                        return null;
                    }
                    else
                    {
                        if (areaRowColumnElement.IsColor)
                        {
                            offsetColumnElement.IsColor = areaRowColumnElement.IsColor;
                            offsetColumnElement.Color = areaRowColumnElement.Color;
                        }
                    }
                }
            }
            return offsetArea;
        }

        /// <summary>
        /// 旋转
        /// </summary>
        /// <param name="isRight"></param>
        /// <returns></returns>
        public virtual Graph Rotate(bool isRight = true)
        {
            //重新初始化一个图形
            Graph graph = new Graph(this.RowNumber, this.ColumnNumber);
            graph.ColumnOffset = ColumnOffset;
            graph.RowOffset = RowOffset;
            for (int i = 0; i < Rows.Count; i++)
            {
                var row = Rows[i];
                for (int j = 0; j < row.Elements.Count; j++)
                {
                    var element = row.Elements[j];
                    var graphElement = graph.Rows[j].Elements[i];
                    if (isRight)
                        graphElement = graph.Rows[j].Elements[graph.ColumnNumber - i - 1];

                    graphElement.IsColor = element.IsColor;
                    graphElement.Color = element.Color;
                }
            }
            return graph;

        }

        /// <summary>
        /// 移除全为颜色的行
        /// </summary>
        /// <returns></returns>
        public bool RemoveRowAllColor()
        {
            List<int> removeIndex = new List<int>();
            for (int row = RowNumber - 1; row >= 0; row--)
            {
                var rowElement = Rows[row];

                if (!rowElement.Elements.ToList().Any(column => column.IsColor == false))
                {
                    RemoveRow++;
                    rowElement.IsAllColor = true;
                    removeIndex.Add(row);
                }
            }

            Graph area = new Graph(ColumnNumber, RowNumber);
            int rowIndex = RowNumber - 1;

            for (int row = RowNumber - 1; row > 0; row--)
            {
                var rowElement = Rows[row];
                if (!rowElement.IsAllColor)
                {
                    area.Rows[rowIndex--] = rowElement;
                }
                if (row == 1 && rowIndex < RowNumber - 1)
                {
                    area.Rows[0] = CreateRow();
                }
            }
            Rows = area.Rows;

            return rowIndex < RowNumber - 1;
        }

        public Graph DeepClone()
        {
            using (Stream stream = new MemoryStream())
            {
                var binaryFormatter = new BinaryFormatter();
                binaryFormatter.Serialize(stream, this);
                stream.Seek(0, SeekOrigin.Begin);
                return binaryFormatter.Deserialize(stream) as Graph;
            }
        }

    }

    [Serializable]
    public class RowElement
    {
        public int ColumnNumber { get; set; }
        public List<Element> Elements { get; set; }
        public bool IsAllColor { get; set; } = false;
        public RowElement(int rowNumber)
        {
            ColumnNumber = rowNumber;
            Elements = new List<Element>();
        }
    }

    [Serializable]
    public class Element
    {
        public bool IsColor { get; set; } 
        public ConsoleColor Color { get; set; }
    }
}

猜你喜欢

转载自blog.csdn.net/yiquan_yang/article/details/107695510
今日推荐