Creating the Lights Out Game in XAML/C#

Creating the Lights out game

In an earlier post, I wrote about creating a simple game using C#/XAML.  After finishing this game, I realized it would be easy to create other games based on the UI and game logic that I used for that game and then decided to try another related game, Lights Out.  In this game, you try and get all of the squares in a grid to turn from white to blue.  The following video shows the game running:

Your browser does not support the video tag.

Note: You must have an HTML5 capable browser to view in browser, click here to download for offline.

 

High level Game logic

Game class

This class has all of the game, UI, and input logic. The design is similar to how the Simon game worked. The rendering code, input code, and code used for moving the user through the various levels is all in this class for now.

Level class

The level class contains the state for a level and is referenced by the game to draw the current level and contains the code for generating levels.

LevelSolver class

This class was a helper class that I created to solve levels.  I pretty much just added this to the game because creating a solver for a game like this seemed like a fun challenge.  This class takes a level and then graphs the moves using various strategies until it finds a solution.

When the application starts, the Game class is instantiated and this creates the UI for the current level and adds handlers for clicks onto squares onto the game board. While the app is running, user input manipulates the current game state based on the click handlers.  Pretty simple!

UI and Controlling the Game

Rendering the game UI

To render the game UI, a canvas is passed to the Game.  Dimensions for the canvas are determined and the canvas is partitioned based on the game board settings.  After the game dimensions are determined, squares and their respective handlers are dynamically created.  The following code shows how this is done:

        void SetupSquare(int row, int col)
        {
            // calculate and set left, top, and size
            double left = _leftBoardBound + (_squareSize * row) + (_squareMargin * row);
            double top = _topBoardBound + (_squareSize * col) + (_squareMargin * col);

            // Create the rect
            Rectangle toAdd = new Rectangle();
            toAdd.Fill = ColorHelper.getOffBrush();
            toAdd.Width = _squareSize;
            toAdd.Height = _squareSize;
            toAdd.Name = "R" + row.ToString() + "C" + col.ToString();

            _canvas.Children.Add(toAdd);

            //Canvas.SetTop(toAdd, top);
            //Canvas.SetLeft(toAdd, left);

            double begintime = (row+(col*_rows)) * 50;

            _squares[row][col] = toAdd;

            startupAnimation(begintime ,left, top, toAdd);

            // set handler for click event
            toAdd.PointerPressed += new Windows.UI.Xaml.Input.PointerEventHandler(toAdd_PointerPressed);
        }

Handling clicks

To handle when the user clicks a square, the square event handler will call a utility function that toggles the squares surrounding the toggled square.  The following code shows how this is done:

void toAdd_PointerPressed(object sender, Windows.UI.Xaml.Input.PointerEventArgs e)
        {
            for (int row = 0; row < _rows; row++)
            {
                for (int col = 0; col < _cols; col++)
                {
                    if (_squares[row][col] == (Rectangle)sender)
                    {
                        theLevel.toggleSq(row, col);
                        if (theLevel.isComplete() && !_enableLevelEditor)
                        {
                            LevelComplete();
                        }
                        return;
                    }
                }
            }
        }

The following code shows the toggle square code for the Level class:

        public void toggleSq(int row, int col)
        {
            toggleSqHelper(row, col);
            toggleSqHelper(row - 1, col);
            toggleSqHelper(row + 1, col);
            toggleSqHelper(row, col + 1);
            toggleSqHelper(row, col - 1);

        }

        public void toggleSqHelper(int row, int col)
        {
            if (row < 0 || row >= _rows)
            {
                return;
            }
            if (col < 0 || col >= _cols)
            {
                return;
            }

            int onOff = toggleSquare(row, col);

            Rectangle curr = _squares[row][col];

            // Do the animation that flips between colors
            Storyboard storyboard = new Storyboard();
            ColorAnimation ca = new ColorAnimation();

            PowerEase pe = new PowerEase();
            pe.EasingMode = EasingMode.EaseInOut;
            ca.EasingFunction = pe;

            Color from, to;
            SolidColorBrush brush = new SolidColorBrush();

            if ((onOff == 1))
            {
                // set curr fill to a solid color for animation (otherwise get a type error because SolidBrush != GradientBrush)
                curr.Fill = new SolidColorBrush(Colors.Blue);
                from = Colors.Blue;
                to = Colors.White;
            }
            else
            {
                // set curr fill to a solid color for animation (otherwise get a type error because SolidBrush != GradientBrush)
                curr.Fill = new SolidColorBrush(Colors.White);
                from = Colors.White;
                to = Colors.Blue;
            }
            ca.EnableDependentAnimation = true;
            ca.From = from;
            ca.To = to;

            Storyboard.SetTarget(ca, curr.Fill);
            Storyboard.SetTargetProperty(ca, "Color");

            ca.Duration = DurationHelper.FromTimeSpan(TimeSpan.FromMilliseconds(250));

            storyboard.Children.Add(ca);

            _colorBackQueue.Enqueue(curr);

            storyboard.Completed += new Windows.UI.Xaml.EventHandler(storyboard_Completed);

            storyboard.Begin();
        }

I’m not going to go too deeply into the details, but you should get the general idea from the code.

Adding animations for game elements

In the various points where UI is animated, storyboards are created and these storyboards animate the various canvas elements.  For example, the following code will render the animation run at the application startup:

void startupAnimation(double begintime, double xpos, double ypos, Rectangle target)
        {
            Storyboard storyboard = new Storyboard();
            DoubleAnimation da = new DoubleAnimation();

            da.From = 0;
            da.To = xpos;
            da.Duration = DurationHelper.FromTimeSpan(TimeSpan.FromMilliseconds(500));
            da.EnableDependentAnimation = true;
            da.EasingFunction = new PowerEase();

            da.BeginTime = TimeSpan.FromMilliseconds(begintime);

            TranslateTransform tt = new TranslateTransform();            

            tt.Y = ypos;
            tt.X = 0;

            target.RenderTransform = tt;

            Storyboard.SetTarget(da, tt);
            Storyboard.SetTargetProperty(da, "X");

            storyboard.Children.Add(da);

            storyboard.Begin();
        }

This animation just moves a square into the screen off of the left side and slides in squares in a straight horizontal line.

The solver

The solver is really the interesting piece of code for this game, I’ll be adding a short breakdown of my solution in my next post.

Other interesting problems and errata

When I was creating the game initially, I had just toggled all of the bits to create game boards. This potentially could create unsolvable games.  So, to address this, I reverse simulate a game using the Level class. The animation for completing the level was actually done using a loop as opposed to storyboard logic.  The UI updates at just the right rate to flash the screen, but this should probably be done using storyboards.  In yet another future post, I will be taking this app and giving it a much better look and feel consistent with the Metro design language.  Hopefully I can complete it in time for the app store code contest, but I’m not crossing my fingers.