Share via


C# - Tetris.Net

 

Overview

Tetris (Russian: «Тетрис», pronounced [ˈtɛtrʲɪs]) is a tile-matching puzzle video game, originally designed and programmed by Russian game designer Alexey Pajitnov.[https://en.wikipedia.org/wiki/Alexey_Pajitnov] It was released on June 6, 1984, while he was working for the Dorodnitsyn Computing Centre of the Academy of Science of the Soviet Union in Moscow. He derived its name from the Greek numerical prefix tetra- (all of the game's pieces contain four segments) and tennis, Pajitnov's favorite sport.

 

This is a .NET version of Tetris, that uses two extended DataGridViews, one as the falling blocks game board and one for the shapes preview, alongside the game board.

 

During gameplay, you can rotate the falling blocks clockwise by pressing the UP arrow button, or anti-clockwise by pressing Shift and the UP arrow. LEFT and RIGHT arrow buttons move the current shape left or right, and the DOWN arrow button increases the speed of descent. 
As you form complete rows on the bottom of the game board, those rows flash several times before disappearing, and your score (displayed in a label to the right of the game board) is incremented. As you progress in the game, the speed of descent of the falling shapes increases as you make more full rows.

 

 

The Game Class

The Game Class is an extended DataGridView, which allows no user input other than the arrow key game commands and the Shift key. All of the game play is coordinated by the Game class which raises custom events to pass score change information and shape changed information to the Form, which renders these changes in a Label for the score, and another extended DataGridView, which shows the current shape.

 

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
 
namespace tetris_cs
{
    /// <summary>
    /// Extended DataGridView 
    /// DoubleBuffered. Restricts user selection of cells.
    /// </summary>
    /// <remarks></remarks>
    class game : DataGridView
    {
        //constants used with Keypresses
        private const  int WM_LBUTTONDOWN = 0x201;
        private const  int WM_LBUTTONDBLCLK = 0x203;
        private const  int WM_KEYDOWN = 0x100;
        private const  int VK_LEFT = 0x25;
        private const  int VK_RIGHT = 0x27;
        private const  int VK_DOWN = 0x28;
        private const  int VK_UP = 0x26;
 
        //custom events
        public delegate  void IncrementScoreEventHandler(int newPoints);
        public event  IncrementScoreEventHandler IncrementScore;
        public delegate  void ShapeChangedEventHandler(Point[] shapePoints, string  shapeColor);
        public event  ShapeChangedEventHandler ShapeChanged;
         
        private int  rowCounter = 0;
 
        //set up timer handlers
        public game()
        {
            this.DoubleBuffered = true;
            tmr.Tick += tmr_Tick;
            flashtmr.Tick += flashtmr_Tick;
        }
 
        /// <summary>
        /// OnRowPrePaint
        /// Avoid DGV cell focussing
        /// </summary>
        /// <param name="e"></param>
        protected override  void OnRowPrePaint(DataGridViewRowPrePaintEventArgs e)
        {
            int p = (int)e.PaintParts;
            p -= (int)DataGridViewPaintParts.Focus;
            e.PaintParts = (DataGridViewPaintParts)p;
            base.OnRowPrePaint(e);
        }
 
        /// <summary>
        /// WndProc
        /// Avoid DGV focussing, and catch Keypresses
        /// </summary>
        /// <param name="m"></param>
        protected override  void WndProc(ref System.Windows.Forms.Message m)
        {
            if (m.Msg == WM_LBUTTONDBLCLK || m.Msg == WM_LBUTTONDOWN)
            {
                return;
            }
            else if  (m.Msg == WM_KEYDOWN)
            {
                //System.Diagnostics.Debugger.Break();
                if (m.WParam.ToInt32() == VK_LEFT)
                {
                    moveLeft();
                }
                else if  (m.WParam.ToInt32() == VK_RIGHT)
                {
                    moveRight();
                }
                else if  (m.WParam.ToInt32() == VK_DOWN)
                {
                    moveDown();
                }
                else if  (m.WParam.ToInt32() == VK_UP)
                {
                    rotateShape();
                }
                return;
            }
            base.WndProc(ref m);
        }
 
        //timers used in game play
        private Timer tmr = new Timer { Interval = 500 };
        private Timer flashtmr = new Timer { Interval = 125 };
 
        //variables used with flashtmr tick event
        private int  flashCounter = 1;
        private int  flashRow;
        private bool  missATick = false;
 
        //variables holding cell colors and shapes
        private string[][] gameGrid;
        private Shape currentShape;
        private List<Shape> listShapes = new List<Shape>();
 
        private Random r = new Random();
        private int  moveCounter = 0;
 
        //clears the game board and score and initiates a new game
        public void  newGame()
        {
            tmr.Stop();
            tmr.Interval = 500;
            listShapes.Clear();
            moveCounter = 0;
 
            gameGrid = new  string[30][];
            for (int x = 1; x <= 30; x++)
            {
                string[] row = new  string[20];
                gameGrid[x - 1] = (string[])row.Clone();
            }
            newShape();
            currentShape = listShapes[0];
            if (ShapeChanged != null) { 
                ShapeChanged(currentShape.CurrentPoints, currentShape.ShapeColor);
            }
            rowCounter = 0;
            tmr.Start();
            flashtmr.Start();
        }
 
        //creates a new falling shape
        private void  newShape()
        {
            string[] sc = { "R", "G", "B", "Y" };
            Shape ns = new  Shape(r.Next(0, 7), sc[r.Next(0, 4)]);
            listShapes.Add(ns);
            //currentShape = ns
            ns.TouchDown += currentShape_TouchDown;
            HasChanged(gameGrid, false, -1);
        }
 
        //responds to LEFT arrow button keydown
        public void  moveLeft()
        {
            if (currentShape == null)
            {
                return;
            }
            gameGrid = currentShape.moveLeft(gameGrid);
            HasChanged(gameGrid, false, -1);
        }
 
        //responds to RIGHT arrow button keydown
        public void  moveRight()
        {
            if (currentShape == null)
            {
                return;
            }
            gameGrid = currentShape.moveRight(gameGrid);
            HasChanged(gameGrid, false, -1);
        }
 
        //responds to DOWN arrow button keydown
        public void  moveDown()
        {
            do
            {
                for (int x = 0; x < listShapes.Count; x++)
                {
                    if (x > listShapes.Count - 1)
                    {
                        goto ContinueLabel1;
                    }
                    gameGrid = listShapes[x].moveDown(gameGrid);
                    HasChanged(gameGrid, false, -1);
                }
                break;
                ContinueLabel1:;
            } while  (true);
            moveCounter += 1;
        }
 
        //responds to UP arrow button keydown
        public void  rotateShape()
        {
            if (currentShape == null)
            {
                return;
            }
            gameGrid = currentShape.rotateShape(gameGrid);
            HasChanged(gameGrid, false, -1);
            if (ShapeChanged != null)
            {
                ShapeChanged(currentShape.CurrentPoints, currentShape.ShapeColor);
            }
        }
 
        //on tick, all shapes move down one row
        private void  tmr_Tick(object  sender, EventArgs e)
        {
            if (missATick)
            {
                return;
            }
            if (moveCounter >= 27)
            {
                moveCounter = 0;
 
                newShape();
                if (listShapes.Count == 1)
                {
                    currentShape = listShapes[0];
                    if (ShapeChanged != null)
                    {
                        ShapeChanged(currentShape.CurrentPoints, currentShape.ShapeColor);
                    }
                }
            }
            moveDown();
        }
 
        //responds to shape touchdown
        private void  currentShape_TouchDown(Shape sender)
        {
            if (sender.CurrentPoints.Any((p) => p.Y < 0))
            {
                tmr.Stop();
            }
            currentShape.TouchDown -= currentShape_TouchDown;
            listShapes.Remove(sender);
            if (listShapes.Count < 1)
            {
                currentShape = null;
                moveCounter = 27;
            }
            else
            {
                currentShape = listShapes[0];
                if (ShapeChanged != null)
                {
                    ShapeChanged(currentShape.CurrentPoints, currentShape.ShapeColor);
                }
            }
        }
 
        //clears full rows as they occur
        private void  flashtmr_Tick(object  sender, EventArgs e)
        {
            switch (flashCounter)
            {
                case 1:
                    flashRow = findFullRow();
                    if (flashRow > -1)
                    {
                        flashCounter = 2;
                        HasChanged(gameGrid, true, flashRow);
                    }
                    break;
                case 2:
                    flashCounter = 3;
                    HasChanged(gameGrid, false, -1);
                    break;
                case 3:
                    flashCounter = 4;
                    HasChanged(gameGrid, true, flashRow);
                    break;
                case 4:
                    List<string[]> newGrid = new  List<string[]>(gameGrid);
                    if (listShapes.Count == 0) { return; }
                    foreach (Point p in listShapes.Last().CurrentPoints)
                    {
                        if (p.Y > -1)
                        {
                            newGrid[p.Y][p.X] = "";
                        }
                    }
                    string[] newRow = new  string[20];
                    newGrid.RemoveAt(flashRow);
                    newGrid.Insert(0, newRow);
                    missATick = true;
                    gameGrid = newGrid.ToArray();
                    flashCounter = 1;
                    moveDown();
                    HasChanged(gameGrid, false, -1);
                    missATick = false;
                    rowCounter += 1;
                    if (rowCounter % 10 == 0)
                    {
                        tmr.Interval -= 40;
                        if (IncrementScore != null)
                            IncrementScore(((int)((1000 - tmr.Interval) * 0.35)));
                    }
                    else if  (rowCounter % 5 == 0)
                    {
                        tmr.Interval -= 20;
                        if (IncrementScore != null)
                            IncrementScore(((int)((1000 - tmr.Interval) * 0.25)));
                    }
                    else
                    {
                        if (IncrementScore != null)
                            IncrementScore(((int)((1000 - tmr.Interval) * 0.05)));
                    }
                    break;
            }
        }
 
        //finds full rows in DGV
        private int  findFullRow()
        {
            for (int x = 29; x >= 0; x--)
            {
                if (gameGrid[x].All((s) => !string.IsNullOrEmpty(s)))
                {
                    return x;
                }
            }
            return -1;
        }
 
        //renders the colors in the DGV
        private void  HasChanged(string[][] grid, bool  flash, int  flashRow)
        {
            Dictionary<string, Color> colors = new  Dictionary<string, Color>()
        {
            {"R", Color.Red},
            {"G", Color.Green},
            {"B", Color.Blue},
            {"Y", Color.Yellow}
        };
            Dictionary<string, Color> flashColors = new  Dictionary<string, Color>()
        {
            {"R", Color.FromArgb(255, 165, 165)},
            {"G", Color.FromArgb(165, 255, 165)},
            {"B", Color.FromArgb(165, 165, 255)},
            {"Y", Color.FromArgb(255, 255, 230)}
        };
            for (int y = 0; y <= 29; y++)
            {
                for (int x = 0; x <= 19; x++)
                {
                    if (string.IsNullOrEmpty(grid[y][x]))
                    {
                        this.Rows[y].Cells[x].Style.BackColor = Color.Black;
                    }
                    else
                    {
                        if (!flash || (flash && !(flashRow == y)))
                        {
                            this.Rows[y].Cells[x].Style.BackColor = colors[grid[y][x]];
                        }
                        else
                        {
                            this.Rows[y].Cells[x].Style.BackColor = flashColors[grid[y][x]];
                        }
                    }
                }
            }
        }
 
 
    }
}

The Shapes

The Shapes are defined by a Shape class. Only the lower-most shape - the currentshape - can be moved or rotated. On touchdown, the currentshape ceases to be a Shape and becomes just cell BackGroundColors in the DataGridView.

 

Conclusion

The .NET programming languages VB.Net and C# can be used, as shown in this example, to create some fairly advanced Graphical Games. Using OOP techniques, it's possible to create concise coding and fairly impressive output, not only in Gaming applications, but in all kinds of application.

 

Downloads

Vb.Net and C#

 

See Also

VB.Net version