The_Morellonomicon The_Morellonomicon - 1 month ago 15
C# Question

Creating Specific Class Instances Through a Subclass in C#

I will use my specific use-case to describe my question, but it should be more broadly applicable considering there may be other applications where one would want to create a subclass based on some defaults. This isn't meant to be a "do my homework for me" question.

I'm currently working on a simple Tetris game, where I have defined my playing-field as a 2-dimensional array filled with bools. I did this in a class Grid to add functions and split up my code. I have developed a function that would let me check if i can add another Grid on top of it at a certain location to check if a tetromino can be moved to a certain spot. (not both bools on one location true)

Since tetrominos (also a grid) come in predefined shapes and sizes, it's only necessary to create each shape once, and then I can just set my current falling block to a copy of that predefined tetromino to manipulate as I wish.

Now I know of two ways of initialising these predefined shapes: starting them in the main Tetris class in an initialiser where I call a Grid(Columns, Rows) once for each tetromino and manually set the correct coordinates to true, or creating a second constructor in the Grid class that takes a char (the tetromino names L, J, S, Z, T, X, I) and initialises a 3x3 or 4x4 grid using the other constructor that I already built, then manually sets the correct coordinates to true again.

Both of these methods add clutter to these classes which feels ugly. I was hoping it were possible instead to use a subclass, considering technically the tetriminos are a specific type of grid.

Now the constructor in the subclass for as far as I can find can only pass on default parameters or parameters that were given to the subclass constructor, like so:

class Grid
{
bool[,] grid;

public Grid(int x, int y)
{
// Creates grid, fills it with bools.
}
}

class Block : Grid
{
public Block(char blockShape, int x, int y) : base(x, y)
{
// Add additional logic here.
}
}


Now this would require me to pass on the dimensions of the tetromino, which feels strange considering this will be preset. What I would much prefer is something along these lines:

class Block : Grid
{
public Block(string blockShape)
{
if ("IX".Contains(blockShape))
{
this = new Grid(4, 4);
// Add additional logic here.
}
if ("JLSZT".Contains(blockShape))
{
this = new Grid(3, 3);
// Add additional logic here.
}
}
}


Is something along these lines possible? If so, how could it be done? If not, is there a clean alternative solution that doesn't clutter my Grid or Tetris class? Should I be doing something else?

Answer

I'd simply use static readonly fields. Tetrominos are immutable, you only need to initialize them once and reuse them as many times as you want.

Also, I'm not very much convinced about tetrominos deriving from Grid. To me, they are conceptually very diferent things; the former are preset immutable blocks, the latter is the dynamic and changing playing field. I wouldn't conflate these two elements at all. I'd create a specific Tetromino class:

public class Tetromino
{
    public static readonly Tetromino J = new Tetromino(new[,] { { false, false, true }, .... });
    public static readonly Tetromino L = new Terromino(new[,] { { true, false, false } .... });
    //and so on...

    private readonly bool[,] grid;
    private Tetromino(bool[,] shape) //disallow any other Terronimos from being built.
    {
         this.shape = shape;
         Width = shape.GetLength(0);
         Height = shape.GetLength(1);
    }

    public int Height { get; }
    public int Width { get; }
    public bool this[int row, int col] => shape[row, col];
}

And now inside your Tetris or Grid class you'd work with Tetrominos without caring what shape they really are. To spawn a specific one, you'd simply use the corresponding field; myPlayingGrid.Add(Tetromino.J)

Comments