1

I’m doing some OO Analysis to work out the relationships between game components so I can design the classes. I’ll end up in C#.

Requirements

My game will include:

Pieces: Blocks, Cards, Counters

Places: Grids, Stacks

Placement of pieces possible:

  • Blocks can be placed on grids (Need grid, x,y)
  • Cards can be placed on stacks (Need stack, position)
  • Counters can be placed on grids (Need grid, x, y)
  • Counters can also be placed on top of stacks (Need stack, position)

Analysis

  1. Piece as either an interface(IPiece) or an abstract class(Piece). (which is best?)
  2. Block, Card & Counter as implementations of IPiece or subclasses of Piece.
  3. Place as an interface(IPlace) or an abstract class(Place). (which is best?)
  4. Grid & Stack as implementations of IPlace or subclasses of Place.

Then I get confused...

Every Piece HAS-A Place so there’s a relationship there. It's a one-to-one relationship as every piece must have one and only one place.

But how do I ensure that Blocks can only go on grids, Cards only on stacks and Counters on either?

Thanks in advance for any help.

Cortado-J
  • 2,035
  • 2
  • 20
  • 32
  • Is your question about the language itself or about the design of the classes? (I'm guessing the second but want to be sure because I have had some surprises on interpretating questions) – MVCDS Apr 13 '15 at 13:35
  • It's about the design of the classes. (Though it would be helpful if the design lends itself to C# - e..g not multiple inheritance!) – Cortado-J Apr 13 '15 at 23:51

2 Answers2

3

But how do I ensure that Blocks can only go on grids, Cards only on stacks and Counters on either?

You can leverage generics and generic type constraints to enforce these rules at compile-time

public interface IPiece<TPlace> where TPlace : IPlace
{
    TPlace Place { get; }

    void PlaceOn(TPlace place);
}

public class Block : IPiece<Grid>
{
    private Grid _place;

    public Grid Place
    {
        get { return _place; }
    }

    public void PlaceOn(Grid place)
    {
        _place = place;
    }
}
dcastro
  • 66,540
  • 21
  • 145
  • 155
  • Thanks dcastro - I haven't used interfaces much and neither have I used generics much so this is at the edge of my understanding but really helpful. – Cortado-J Apr 14 '15 at 07:46
0

It is often claimed that you should favor composition over inheritance. With that in mind, I've been playing around with this idea a little bit and this is what I've come up with:

class Grid
{ }

class Stack
{ }

abstract class Location
{
    private Location() {}

    public class GridLocation : Location
    {
        public Grid Grid { get; set; }

        public int X { get; set; }

        public int Y { get; set; }
    }

    public class StackLocation : Location
    {
        public Stack Stack { get; set; }

        public int Position { get; set; }
    }
}

abstract class Visual
{

}

class Block
{
    public Visual Visual { get; set; }

    public Location.GridLocation Location { get; set; }
}

class Card
{
    public Visual Visual { get; set; }

    public Location.StackLocation Location { get; set; }
}

class Counter
{
    public Visual Visual { get; set; }

    public Location Location { get; set; }
}

class Game
{
    public IEnumerable<Block> Blocks { get; set; }

    public IEnumerable<Card> Cards { get; set; }

    public IEnumerable<Counter> Counters { get; set; }

    public IEnumerable<Tuple<Location.StackLocation, Visual>> StackVisuals
    {
        get
        {
            var cardVisuals =
                Cards.Select (c => Tuple.Create(c.Location, c.Visual));

            var counterVisuals =
                Counters.Select (c => Tuple.Create(c.Location, c.Visual))
                .OfType<Tuple<Location.StackLocation, Visual>>();

            return cardVisuals.Concat(counterVisuals).OrderBy (v => v.Item1.Position);
        }
    }

    public IEnumerable<Tuple<Location.GridLocation, Visual>> GridVisuals
    {
        get
        {
            var blockVisuals =
                Blocks.Select (b => Tuple.Create(b.Location, b.Visual));

            var counterVisuals =
                Counters.Select (c => Tuple.Create(c.Location, c.Visual))
                .OfType<Tuple<Location.GridLocation, Visual>>();

            return blockVisuals.Concat(counterVisuals).OrderBy (v => new { v.Item1.X, v.Item1.Y });
        }
    }
}

I used a placeholder Visual class in the example just to show how you might unify entities with a certain location type when the need arises (e.g. StackVisuals and GridVisuals).

Community
  • 1
  • 1
Daniel Pratt
  • 12,007
  • 2
  • 44
  • 61
  • Also really interesting. Thankyou Daniel. I don't follow what the Visuals addition is about - could you explain a little more? – Cortado-J Apr 14 '15 at 02:44
  • If you are going to render a game piece onto a game board, you will need (at a minimum) the position of the piece and a graphical representation of the piece. `Visual` is just a placeholder for some graphical representation of a game piece. – Daniel Pratt Apr 14 '15 at 12:38