“Fifteenth Puzzle”–wrapping it up

let’s finally put it all together and get to the implementation details of the Silverlight-application. As this is mostly basic stuff I will do this without as many words as I did the last articles on this series. There is no big picture hidden inside the rest of the code (indeed it did not even take the time and made some kind of ViewModel or whatever, instead using the good all – spaghetti like code behind).

First I like to wrap the F# code that gets interfaced from other .net languages a bit – they just don’t play nice with curried functions and all that. So I make a (mutable) class Puzzle with all the stuff the game will need finally. This isn’t this much we just need some way to initialize puzzles, check their state (solved?) and make a move if possible.

So let’s just wrap a PuzzleState inside a field and we’re on our way:

type Puzzle private (initialState : PuzzleState) =

    let dim = PuzzleOps.dim initialState
    let mutable state = initialState
    let isSolved() = isSolved state

    member puzzle.Dim : int = dim

    member puzzle.State : PuzzleState = state

    member puzzle.IsSolved : bool = isSolved()

    member puzzle.GetTileAt (r : int, c : int) = getTile state (r,c)
    member puzzle.Coord2Index (r : int, c : int) = coord2index dim (r,c)

    member puzzle.MoveTileAt (r : int, c : int) =
        state <- moveTileAt state (r, c)

    static member InitFromFlatConfig (tiles : Tile []) =
        let state = initTiles tiles
        new Puzzle(state)

    static member InitRandom (dim : int) =
        let state = initRandom dim
        new Puzzle(state)

As you can see this is just a dumped down container to hold the only mutable state and make a nice public facing interface to play with it. You might wonder why just hose methods? (For instance why the Coord2Index but not vice-versa?) Well I just made the ones public I needed in the development of the game Zwinkerndes Smiley

The Silverlight-Application

Ok – let’s switch languages (enter C#) and let’s have a look at the Silverlight-application.

Passing the puzzle-config

I have to be able to give the application a predefined puzzle-state as well as generate random puzzles. The second point can be handled just by calling the InitRandom-Method above but the first task is more involved.

Of course I just go and provide the config packed into the InitParams (named TileConfig) of the Silverlight-Application (just as our flattened representation – number-1 on the tile, highest number for the empty one – separated by semi-colons)  but we have to parse them. I don’t provide much for error-correction so make sure to use valid ones:

        private void ApplicationStartup(object sender, StartupEventArgs e)
        {
            int[] tileConfig = null;
            if (e.InitParams.ContainsKey("TileConfig"))
                tileConfig = ParseTileConfig(e.InitParams["TileConfig"]);

            this.RootVisual = new MainPage(tileConfig);
        }

        private int[] ParseTileConfig(string initParam)
        {
            var values = initParam.Split(';');
            var list = new List<int>();
            foreach (var v in values)
            {
                int i;
                if (!int.TryParse(v, out i)) return null;
                list.Add(i);
            }

            return list.ToArray();
        }

The UI

The UI is very basic – just the reset-button and a Grid that will hold the tiles (filled and configured in code Enttäuschtes Smiley) and some ugly gradient:

    <Grid x:Name="LayoutRoot" Background="White">
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <Button Content="Reset" Grid.Row="1"
                Click="OnResetGame" />
        <Border BorderBrush="#FF627777"
                BorderThickness="5"
                CornerRadius="2"
                HorizontalAlignment="Center"
                VerticalAlignment="Center"
                Name="GameboardBorder">
            <Border.Background>
                <LinearGradientBrush EndPoint="0.5,1"
                                     StartPoint="0.5,0">
                    <GradientStop Color="#FF617676"
                                  Offset="0" />
                    <GradientStop Color="#FFC5CBCB"
                                  Offset="1" />
                </LinearGradientBrush>
            </Border.Background>
            <Grid x:Name="Gameboard">

            </Grid>
        </Border>
    </Grid>

The CODE-Behind

There are two constructors – one without parameters (as always to make the XAML-designer work, as I like to see what I get into) and one doing all the configuration. The first one will simply call the second:

        private readonly int[] _staticTileConfig;
        private const int BoardSize = 4;
        private Core.Puzzle _puzzle;
        private readonly Brush _boarderBrush = new SolidColorBrush(Colors.Black);
        private readonly Brush _winBrush = new SolidColorBrush(Colors.Green);
        private readonly Brush _normalBrush;
        private readonly Dictionary<int, UIElement> _tiles = new Dictionary<int, UIElement>();
        private bool _solved;

        public MainPage()
            : this(null)
        {
        }

        public MainPage(int[] tileConfig )
        {
            InitializeComponent();
            _normalBrush = GameboardBorder.Background;
            _staticTileConfig = tileConfig;

            CreateGame();
        }

By the way: those brushes are a quick and dirty hack to make the games background green if solved and reset the brush back after reset. On top of this I set the tiles background to the same brush the grid is using (the gradient) – looks nice an is sufficient for this small project.

As you can see the constructor does its XAML magic (by calling InitializeComponent) and then sets the _staticTileConfig to either null (first CTOR) or a valid flat representation that will get passed from the App/InitParams. Next the CreateGame method will initialize our game and initialize the board:

        private void CreateGame()
        {
            _solved = false;
            GameboardBorder.Background = _normalBrush;

            _puzzle = _staticTileConfig != null ? Core.Puzzle.InitFromFlatConfig(_staticTileConfig) :  Core.Puzzle.InitRandom(BoardSize);
            InitializeBoard(_puzzle);
        }

        void InitializeBoard(Core.Puzzle puzzle)
        {

            var dim = puzzle.Dim;

            _tiles.Clear();
            Gameboard.Children.Clear();
            Gameboard.RowDefinitions.Clear();
            Gameboard.ColumnDefinitions.Clear();
            for (var i = 0; i < dim; i++)
            {
                Gameboard.ColumnDefinitions.Add(new ColumnDefinition());
                Gameboard.RowDefinitions.Add(new RowDefinition());
            }

            for(var r = 0; r < dim; r++)
                for (var c = 0; c < dim; c++)
                {
                    var value = puzzle.GetTileAt(r, c);
                    if (value == dim * dim) value = 0;

                    var tile = CreateTile(value);
                    Grid.SetRow(tile, r);
                    Grid.SetColumn(tile, c);
                    Gameboard.Children.Add(tile);

                    int row = r;
                    int col = c;
                    tile.MouseLeftButtonDown += (s, e) => OnTileClicked(row, col);
                    var index = puzzle.Coord2Index(r, c);
                    _tiles[index] = tile;
                }

            if (_solved) GameboardBorder.Background = _winBrush;
        }

        private FrameworkElement CreateTile(int value)
        {
            var view = new Viewbox {Margin = new Thickness(2)};
            if (value > 0)
            {
                var frame = new Border
                                {
                                    BorderBrush = _boarderBrush,
                                    Background = _normalBrush,
                                    BorderThickness = new Thickness(1),
                                    Width = 20,
                                    Height = 20,
                                };
                var text = new TextBlock
                               {
                                   Text = value.ToString(),
                                   HorizontalAlignment = HorizontalAlignment.Center,
                                   VerticalAlignment = VerticalAlignment.Center
                               };
                view.Child = frame;
                frame.Child = text;
            }
            return view;
        }

So it will first reset the game-state (and Background) and then create a new static or random puzzle based on the params on the ctor above.

The InitializeBoard method just creates the tiles (using the CreateTile method) and hooks the MouseLeftButtonDown-Event so we get notified if a tile was clicked. Notice that CreateTile will only create tiles for values greater zero and InitializeBoard will set the right value to zero.

Finally the OnTileClicked-handler, that will get called if a tile is clicked) will just call the puzzles MoveTileAt-method, check if the puzzle is now solved and reinitialize the board with InitializeBoard:

        private void OnTileClicked(int row, int column)
        {
            if (_solved) return;

            _puzzle.MoveTileAt(row, column);
            _solved = _puzzle.IsSolved;
            InitializeBoard(_puzzle);
        }

        private void OnResetGame(object sender, RoutedEventArgs e)
        {
            CreateGame();
        }

That’ it – we are done.

I hope you enjoyed this series. Please fell free to write some comments, complaints or questions right below.

Thank you dear reader.

PS: you can grab the full source-code here – enjoy.