Prototyping a puzzle game in C#

I had an idea recently for a puzzle game. Now I’ve had ideas for games in the past, but after second inspection they were either way too complicated for me to take on by myself or the idea didn’t hold up. This one, however, seemed to be a winner, so I decided to spend some time throwing together a prototype.

Over the last year I’ve taken a couple of online courses about Unity with the hope that I can pick up enough skills to be able to create my own game someday. I think this could be the perfect game to finally give it a shot. The problem for me is that I have trouble remembering the details of Unity while focusing on trying to make a real prototype of this game. Something’s going to slip, either I spend every 2 minutes googling on how to do basic things, or I spent my time focusing on the actualy game logic. In this case, the game logic was going to be complex, so I wanted 100% focus on that for now.

In my day job I work with WPF and C# all day long so I’m very comfortable with it (“very comfortable” is about as much praise as I’ll ever give myself in any skill, I was asked in an interview once to rate my skills in C# and I ended up saying 6/10, for a job I wanted). So the obvious thing for me was to throw together a prototype using what I work best with, and that’s what I’m going to talk about here.

First thing you’ll need is an MVVM library. Not to toot my own horn, but I created JustMVVM for this purpose, so feel free to use it if you want, it’s available on Nuget too.

This puzzle game idea I have could be generically described as similar to Tetris, so I have a basic grid and some blocks. Here’s the View layout, I’ve stripped out some of the less useful stuff.

<Window xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
        xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
        xmlns:local="clr-namespace:PuzzlePrototype"
        Title="MainWindow"
        SizeToContent="WidthAndHeight"
        WindowStartupLocation="CenterScreen"
        mc:Ignorable="d">

    <Window.DataContext>
        <local:MainWindowViewModel />
    </Window.DataContext>

    <Grid>
        <ItemsControl x:Name="GameGrid"
                      ItemsSource="{Binding Blocks}">

            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <Canvas Width="{Binding Width}"
                            Height="{Binding Height}"
                            Background="#FF444444">
                    </Canvas>
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>

            <ItemsControl.ItemContainerStyle>
                <Style>
                    <Setter Property="Canvas.Bottom"
                            Value="{Binding Bottom}" />
                    <Setter Property="Canvas.Left"
                            Value="{Binding Left}" />
                </Style>
            </ItemsControl.ItemContainerStyle>

            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <Rectangle Canvas.Left="{Binding Left}"
                               Canvas.Bottom="{Binding Bottom}"
                               Width="{Binding BlockSize}"
                               Height="{Binding BlockSize}"
                               Stroke="White">
                        <Rectangle.Fill>
                            <SolidColorBrush Color="{Binding Color}" />
                        </Rectangle.Fill>
                    </Rectangle>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
    </Grid>
</Window>

The ItemsControl just contains a collection of blocks, allowing you to bind them to the ViewModel using MVVM.

There’s also a little magic that can be added to the Canvas inside the ItemsControl.ItemsPanel called FluidMoveBehavior. If you add this to the View, you’ll automatically get animated transitions when the blocks move positions. It’s pretty much up to you if you want to make a simple blocky game, or want a little more smooth animation in it. Here’s the FluidMoveBehavior

<Canvas Width="{Binding Width}"
        Height="{Binding Height}"
        Background="#FF444444">
    <i:Interaction.Behaviors>
        <ei:FluidMoveBehavior AppliesTo="Children"
                                Duration="0:0:0.5">
            <ei:FluidMoveBehavior.EaseY>
                <SineEase EasingMode="EaseIn" />
            </ei:FluidMoveBehavior.EaseY>
            <ei:FluidMoveBehavior.EaseX>
                <SineEase EasingMode="EaseIn" />
            </ei:FluidMoveBehavior.EaseX>
        </ei:FluidMoveBehavior>
    </i:Interaction.Behaviors>
</Canvas>

The SineEase can be substituted for other easing functions.

Here’s the bare bones for the ViewModel to get things going –

public class MainWindowViewModel : MVVMBase
{
    public const int NUM_COLS = 10;
    public const int NUM_ROWS = 16;
    public const int BLOCK = 40;

    private ObservableCollection<Block> _blocks;
    public ObservableCollection<Block> Blocks
    {
        get { return _blocks; }
        set
        {
            _blocks = value;
            OnPropertyChanged();
        }
    }

    public double Width { get { return NUM_COLS * BLOCK; } }
    public double Height { get { return NUM_ROWS * BLOCK; } }

    public MainWindowViewModel()
    {
        Blocks = new ObservableCollection<Block>();
    }
}

For this prototype, it’s easiest to have all your grid size code in one place, so I’ve stored them in constants at the top. The Width and Height are bound to the view so it’ll scale appropriately. In this case, we’re creating a window 400 x 640, where each block is 40 x 40.

This is also where I ended up throwing all of my code while I was testing this out, but feel free to make it as complicated or simple as you place.

Finally, the last class required is Block.cs.

public class Block : MVVMBase
{
    public static List<Color> ColorList = new List<Color>()
    {
        (Color)ColorConverter.ConvertFromString("#860DFF"),
        (Color)ColorConverter.ConvertFromString("#3D0CE8"),
        (Color)ColorConverter.ConvertFromString("#000CFF"),
        (Color)ColorConverter.ConvertFromString("#0C50E8"),
        (Color)ColorConverter.ConvertFromString("#0D97FF")
    };

    public int BlockSize { get; set; } = MainWindowViewModel.BLOCK;

    private double _bottom;
    public double Bottom
    {
        get { return _bottom; }
        set
        {
            _bottom = value;
            OnPropertyChanged();
        }
    }

    private double _left;
    public double Left
    {
        get { return _left; }
        set
        {
            _left = value;
            OnPropertyChanged();
        }
    }

    private int _x;
    public int X
    {
        get { return _x; }
        set
        {
            _x = value;
            Left = _x * MainWindowViewModel.BLOCK;
        }
    }

    private int _y;
    public int Y
    {
        get { return _y; }
        set
        {
            _y = value;
            Bottom = _y * MainWindowViewModel.BLOCK;
        }
    }

    public Color Color { get; set; }

    private static Random _random = new Random(DateTime.Now.Millisecond);

    public Block(int x, int y)
        : this(x, y, ColorList[_random.Next(ColorList.Count)])
    { }

    public Block(int x, int y, Color color)
    {
        X = x;
        Y = y;

        Color = color;
    }

    public override string ToString()
    {
        return $"({ X }, {Y}), { Color.ToString() }";
    }
}

There’s not a lot to it really, most of this code just exists to scale your block units (0, 0) to (9, 15) to WPF pixel coordinates.

The reason I created this was to stop myself wasting any of my (little) free time navigating Unity. Hopefully some day in the future I’ll be so proficient in Unity that I won’t need this middle step, but for now it worked perfectly. If and when I’m happy with my game, I’ll make sure it all works and then focus on recreating the hard work in Unity to create the cross-platform game I’m looking for.

Hopefully some of you folks out there can also benefit from creating something in a familiar environment with the language you’re most confortable with.

All this source code is available on Github here.

Let me know what you think in the comments.