Help With Creating a Simple Generic Grid

I want to create a custom (but simple) grid system. I want GridBase to contain a 2D array of objects. It should be flexible enough to accept many different types of objects, but also require that they all share some common fields and behavior (like X and Y coordinates and a GetDisplayString() method).

It seems like a given that I’ll need generics and inheritance, but the bare-bones version of the system (shown below) won’t compile. The way I have things set up, I can’t find any valid way to declare and initialize a GridBase object. Nothing I put inside that first pair of angle brackets makes the compiler happy.

What am I doing wrong?

public class Program
{
    public static void Main()
    {
        GridBase<Cell<int>> intGrid = new GridBase<Cell<int>>();
    }
}

public class GridBase<T> where T : Cell<T>
{
    private T[,] cells;
}

public class Cell<T>
{
    private T data;
}

I think it’s failing because you have created what I’d call a generic type recursion. In your declaration T is supposed to be a Cell but then what does Cell mean? Resolving it means replacing T with Cell, therefore it is Cell<Cell>, oh wait there still is a T there, let’s resolve that with Cell, so Cell<Cell<Cell>> … ohoh. I guess the error you are getting is related to that.

You can resolve this in two ways.

A) by using two generic parameters (T1 is handed down to Cell, T2 is for the GridBase):

public class Program
{
    public static void Main()
    {
        GridBase<int, Cell<int>> intGrid = new GridBase<int, Cell<int>>();
    }
}

public class GridBase<T1,T2> where T2 : Cell<T1>
{
    private T2[,] cells;
}

public class Cell<T>
{
    private T data;
}

B) by reducing and fixing GridBase on using Cell.

public class Program
{
    public static void Main()
    {
        GridBase<int> intGrid = new GridBase<int>();
    }
}

public class GridBase<T>
{
    private Cell<T>[,] cells;
}

public class Cell<T>
{
    private T data;
}

BUT you mentioned all your Ts having a common base (quote: “… require that they all share some common fields and behavior …”). So before you do this with generics I’d ask if it is also feasible to try it with an interface or abstract base class instead. Like this:

public class Program
{
    public static void Main()
    {
        GridBase intGrid = new GridBase();
    }
}

public class GridBase
{
    private ICell[,] cells;
}

public interface ICell
{
    // Aren't we all the same on the outside?
    public int GetValue();
}
1 Like

I think the fundamental reason I started doing it with generics is because I want the cells to be extremely flexible. I want to be able to make a grid of int cells, string cells, Foo cells, whatever I want, without rewriting a bunch of code. I still don’t understand generics very well, but I do think I understand that this flexibility is at the core of what they’re all about.

Correct me if I’m wrong, but doing away with the generics will take away this flexibility. If I want a grid of strings, defining an interface or abstract class with a public int GetValue() method just won’t do. And if I do define the interface like that, and give GridBase an array of ICell, then the grid class will be equally limited. Am I wrong here?

You’re not wrong. I just question the value of a grid. It’s not like a grid-handling system is hard to write or buggalicious or anything. Just write one when you need it, and write precisely the one you need.

This is the last one I wrote, the core of it, just a week or so ago for my monster maze game:

    MM3DCell[,] Cells;

    public int Across { get { return Cells.GetLength(0); } }
    public int Down { get { return Cells.GetLength(1); } }

    public MonsterMaze3D( int across, int down)
    {
        Cells = new MM3DCell[across,down];

        for (int j = 0; j < Cells.GetLength(1); j++)
        {
            for (int i = 0; i < Cells.GetLength(0); i++)
            {
                Cells[i,j] = new MM3DCell();
            }
        }
    }

Over the course of development in the past week that maze container got knowledge about the player and the monster so it effectively became a handy central “global storage place” and effectively became the game’s global state: three Vector2Ints for the player, the monster and the exit, and three integers for their 0-3 facing direction… Done!

The game is done and out now so no need to really ever use that code anywhere else. It’s not like it’s hard code to write or debug, I’ll just write a fresh one for my next grid game, and it will contain exactly what it needs.

And in fact, this one ended up only having a single bool:

 public class MM3DCell
{
    public bool blocked;
}

Although I could trivially add other stuff to that class if I want, like “I’ve been here” or even linkages to lists of treasures or whatever.

The key is, it does exactly what I want and does no more. No more clutter or confusion. A grid is just a grid. It’s like a slightly-spiffier purpose-built array, not much more. There’s like 21 lines of code total above, not a lot of places for bugs to hide out.

EDIT: it just has these getters / setters:

     public void SetBlocked( int x, int y, bool blocked)
    {
        Cells[x, y].blocked = blocked;
    }
    public bool GetBlocked( int x, int y)
    {
        return Cells[x, y].blocked;
    }

Everything else is outside of it.

If building a super flexible Grid Collection class for use in many projects is your goal then you are right. For a concrete implementation for just one game I am not sure. You could probably get away with some utils or extension methods for two dimensional arrays in most cases. The reliance on that much generic code should spark some questions if it’s really necessary.

Maybe it helps to look at it from the grid perspective. What do you need the grid for exactly? And more importantly, does the grid NEED to know all those implementation details? Are you making it generic only to avoid some casting when reading from it? Could you use a 2D array instead and implement the “grid” stuff as “static” methods in some Utils class which operates on any 2D array?

To build on what Kurt-Dekker said. Is the logic you perform on the grid really that complex that it justifies the creation of the generic grid?

Another suggestion. Make a Cell base class which has all the grid relevant data and methods and from it derive a generic Cell. That way you can still wrap any data you want in a Cell but keep the generics and the data knowledge out of your Grid. Do you truly need different Cells or is it just that data T?

All that said. If that’s still the solution you feel most comfortable with. Go for it. There is nothing inherently wrong with it.

I hadn’t expected to meet with so much resistance! Getting pushback like this from more experienced devs definitely makes me pause and re-evaluate my intentions.

I guess there are a few reasons I want flexible grid and cell classes:

  • I love making grid-based games. I expect to make a lot of them. With an overarching grid class, I could make lots of different kinds of grids for different games. PuzzleGrid, ConwayGrid, InventoryGrid, whatever. PuzzleGrid could have a Solve() method, ConwayGrid could have a Simulate() method, and they could all share the same basic grid logic without require constant rewrites of that logic.
  • One of my favorite Unity devs on YouTube has a custom grid class that uses generics. He says (and has shown) that it keeps coming in handy in many situations, like inventories, city builders, etc. Since he also has a lot more experience than me, I’d like to explore whether his solution can work for me, too.
  • The cells will often end up being MonoBehaviours on actual GameObjects in the scene, so that they can have independently updating colors, sizes, states, etc. Since they’re going to have their own class anyway, it seems natural to let them hold the data T, rather than storing it separately.

These things being said, I can definitely see now that I need to be more careful with generics than I was being before. I’ll explore your suggestions and see which works best for my needs. :slight_smile:

1 Like

Hey, it’s just guidance. Don’t let it discourage you. Always keep in mind that stranger developers do not know what you do: the point of your entire codebase. We do not know what you’re doing this for exactly. We obviously can guess, but we always try to cover all the bases we know.

Keep in mind that CodeMonkey (I assume you’re talking about him), didn’t use his grid in giant applications. Sure a generic grid is super if you’re making small games, like tic-tac-toe, amoeba, small puzzles, inventories, etc. But if you want, for example, build a Civilization game, you need a more performant grid with less convoluted code and more efficient storage.

The important thing is: just do it, build it, see if it works and if it works, how does it work. :slight_smile:

3 Likes

Hey, another CodeMonkey viewer! Or, at least, you’ve seen enough of his videos to know about his grid class. Yeah, I have no current plans for huge games that need lots of optimizations. Small- to medium-scale games are more my comfort zone right now.

And I didn’t mean to seem discouraged. I was just surprised by the pushback, not bothered. After all, generics are one of those things the good programmers use, right? :stuck_out_tongue: But this thread has reminded me that every level of added complexity should have a very specific reason behind it.

1 Like