Develop Sudoku game using basic Windows Form and C# Dotnet codes


Develop Sudoku puzzle game using basic Windows Form and C# Dotnet codes

Hello friends, Let’s see how we can create a Sudoku puzzle game step by step using Windows Form and C# DotNet in Visual Studio without using any complex/advance codes neither external libraries.



Prerequisites
  • Basic c# programming knowledge
  • Basic windows form application development knowledge
  • Visual studio 2010+  


Step 1.

Start a new Windows Form Project in Visual Studio which will provide you the default form template and add a panel as shows in the below screenshot. 

Develop Sudoku puzzle game using basic Windows Form and C# Dotnet codes





Step 2.

Add new class for sudoku cells which by inheriting the button class.

using System.Windows.Forms;

namespace sudoku
{
    class SudokuCell : Button
    {
        public int Value { get; set; }
        public bool IsLocked { get; set; }
        public int X { get; set; }
        public int Y { get; set; }

        public void Clear()
        {
            this.Text = string.Empty;
            this.IsLocked = false;
        }
    }
}


Add Code in the code behind for the form in order to generate sudoku cells in the panel and click events for each cells (You can go to the code behind by right click on the form and select view code menu).

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;

namespace sudoku
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();

            createCells();
        }

        SudokuCell[,] cells = new SudokuCell[9, 9];

        private void createCells()
        {
            for (int i = 0; i < 9; i++)
            {
                for (int j = 0; j < 9; j++)
                {
                    // Create 81 cells for with styles and locations based on the index
                    cells[i, j] = new SudokuCell();
                    cells[i, j].Font = new Font(SystemFonts.DefaultFont.FontFamily, 20);
                    cells[i, j].Size = new Size(40, 40);
                    cells[i, j].ForeColor = SystemColors.ControlDarkDark;
                    cells[i, j].Location = new Point(i * 40, j * 40);
                    cells[i, j].BackColor = ((i / 3) + (j / 3)) % 2 == 0 ? SystemColors.Control : Color.LightGray;
                    cells[i, j].FlatStyle = FlatStyle.Flat;
                    cells[i, j].FlatAppearance.BorderColor = Color.Black;
                    cells[i, j].X = i;
                    cells[i, j].Y = j;

                    // Assign key press event for each cells
                    cells[i, j].KeyPress += cell_keyPressed;

                    panel1.Controls.Add(cells[i, j]);
                }
            }
        }

        private void cell_keyPressed(object sender, KeyPressEventArgs e)
        {
            var cell = sender as SudokuCell;

            // Do nothing if the cell is locked
            if (cell.IsLocked)
                return;

            int value;

            // Add the pressed key value in the cell only if it is a number
            if (int.TryParse(e.KeyChar.ToString(), out value))
            {
                // Clear the cell value if pressed key is zero
                if (value == 0)
                    cell.Clear();
                else
                    cell.Text = value.ToString();

                cell.ForeColor = SystemColors.ControlDarkDark;
            }
        }
    }
}



Let's run the application and see if the sudoku cells loads correctly 

Develop Sudoku puzzle game using basic Windows Form and C# Dotnet codes



Step 3.

Add code to load values in the cells. We have the logic to find valid numbers for each cell.

public Form1()
{
    InitializeComponent();

    createCells();

    startNewGame();
}

private void startNewGame()
{
    loadValues();
}

private void loadValues()
{
    // Clear the values in each cells
    foreach (var cell in cells)
    {
        cell.Value = 0;
        cell.Clear();
    }

    // This method will be called recursively 
    // until it finds suitable values for each cells
    findValueForNextCell(0, -1);
}

Random random = new Random();

private bool findValueForNextCell(int i, int j)
{
    // Increment the i and j values to move to the next cell
    // and if the columsn ends move to the next row
    if (++j > 8)
    {
        j = 0;

        // Exit if the line ends
        if (++i > 8)
            return true;
    }

    var value = 0;
    var numsLeft = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9 };

    // Find a random and valid number for the cell and go to the next cell 
    // and check if it can be allocated with another random and valid number
    do
    {
        // If there is not numbers left in the list to try next, 
        // return to the previous cell and allocate it with a different number
        if (numsLeft.Count < 1)
        {
            cells[i, j].Value = 0;
            return false;
        }

        // Take a random number from the numbers left in the list
        value = numsLeft[random.Next(0, numsLeft.Count)];
        cells[i, j].Value = value;

        // Remove the allocated value from the list
        numsLeft.Remove(value);
    }
    while (!isValidNumber(value, i, j) || !findValueForNextCell(i, j));

    // TDO: Remove this line after testing
    cells[i, j].Text = value.ToString();

    return true;
}

private bool isValidNumber(int value, int x, int y)
{
    for (int i = 0; i < 9; i++)
    {
        // Check all the cells in vertical direction
        if (i != y && cells[x, i].Value == value)
            return false;

        // Check all the cells in horizontal direction
        if (i != x && cells[i, y].Value == value)
            return false;
    }
            
    // Check all the cells in the specific block
    for (int i = x - (x % 3); i < x - (x % 3) + 3; i++)
    {
        for (int j = y - (y % 3); j < y - (y % 3) + 3; j++)
        {
            if (i != x && j != y && cells[i, j].Value == value)
                return false;
        }
    }

    return true;
}

We have the code to be removed as all the values won’t be shown to the player. It was included just to make sure the values are valid and proper while testing.


Let's run the application and see if the values loads correctly 

Develop Sudoku puzzle game using basic Windows Form and C# Dotnet codes

Now we can remove the code for showing the values in the cell.


Step 4.

Add code to show values in random based cells in order to work as hints

private void startNewGame()
{
    loadValues();

    //Show values of 45 cells as hint
    showRandomValuesHints(45);
}

Random random = new Random();

private void showRandomValuesHints(int hintsCount)
{
    // Show value in random cells
    // The hints count is based on the level player choose
    for (int i = 0; i < hintsCount; i++)
    {
        var rX = random.Next(9);
        var rY = random.Next(9);

        // Style the hint cells differently and
        // lock the cell so that player can't edit the value
        cells[rX, rY].Text = cells[rX, rY].Value.ToString();
        cells[rX, rY].ForeColor = Color.Black;
        cells[rX, rY].IsLocked = true;
    }
}



Let's run the application and see if the hints loads correctly 

Develop Sudoku puzzle game using basic Windows Form and C# Dotnet codes

As shown in the screenshot the hints are in black font colour and the user inputs are slightly light coloured (marked with red in the image).


Step 5.

Now let’s add the following controls in the form.

  •  button for checking if the inputs are valid and if the player wins
  • button for clearing all the inputs
  • radio buttons for choosing the levels
  • button for starting new game
·       
Develop Sudoku puzzle game using basic Windows Form and C# Dotnet codes


Now let’s add click events for the check input button which will do check if the game ends too.

private void checkButton_Click(object sender, EventArgs e)
{
    var wrongCells = new List<SudokuCell>();

    // Find all the wrong inputs
    foreach (var cell in cells)
    {
        if (!string.Equals(cell.Value.ToString(), cell.Text))
        {
            wrongCells.Add(cell);
        }
    }

    // Check if the inputs are wrong or the player wins 
    if (wrongCells.Any())
    {
        // Highlight the wrong inputs 
        wrongCells.ForEach(x => x.ForeColor = Color.Red);
        MessageBox.Show("Wrong inputs");
    }
    else
    {
        MessageBox.Show("You Wins");
    }
}


Add events for the clear and start new game button.

private void clearButton_Click(object sender, EventArgs e)
{
    foreach (var cell in cells)
    {
        // Clear the cell only if it is not locked
        if (cell.IsLocked == false)
            cell.Clear();
    }
}

private void newGameButton_Click(object sender, EventArgs e)
{
    startNewGame();
}


Modify the start game method to consider what level the player has chosen.

private void startNewGame()
{
    loadValues();

    var hintsCount = 0;

    // Assign the hints count based on the 
    // level player chosen
    if (beginnerLevel.Checked)
        hintsCount = 45;
    else if (IntermediateLevel.Checked)
        hintsCount = 30;
    else if (AdvancedLevel.Checked)
        hintsCount = 15;

    showRandomValuesHints(hintsCount);
}




Let’s run the application and see the check, clear and new game button works

Develop Sudoku puzzle game using basic Windows Form and C# Dotnet codes











Enjoy Coding…!!!


Comments

  1. Hola! Esto ha sido muy útil, te agradezco mucho, como incluirias un botón que muestre una pista aleatoria??? Es algo que intento hacer y no encuentro la forma

    ReplyDelete
    Replies
    1. Como puede ver en el bloque Código del Paso 3, el método "loadValues ​​()" llama a otro método "findValueForNextCell (int i, int j)", que es recursivo. Ese método se llama para cada bloque en el rompecabezas del 0 al 80, y toma un número aleatorio y verifica si romperá la regla usando el método "isValidNumber (int value, int x, int y)". Si no es así, pasa al siguiente bloque, o bien, pasa al bloque anterior y toma otro número aleatorio allí. Estas cosas ocurren una y otra vez hasta que encuentra números válidos para todos los bloques. (Si no usamos estas lógicas, podemos terminar con algunos puntos muertos en el rompecabezas que hacen que el rompecabezas sea irresoluble - Todo esto sucede en milisegundos :-) )

      Delete


    2. As you can see in the Step 3 Code block, the method "loadValues()" calls another method "findValueForNextCell(int i, int j)", which is a recursive one. That method is been called against each blocks in the puzzle from 0th to 80th, and takes a random number and checks if it will break the rule using the method "isValidNumber(int value, int x, int y)". If it it won't, move to the next block, or else move to the previous block and takes another random number there. These stuffs happens over and over until it finds valid numbers for all blocks. (if we don't use these logics we may end up with some dead locks in the puzzle which makes the puzzle unsolvable - All this happens in milliseconds)

      Delete
    3. Thanks for sharing this. I enjoyed typing it up as you explained the sections - I always re-type code as it gives me a chance to understand each line as I go.
      However, I found a problem with the code that checks to see if the numbers entered match the generated numbers. The application reports "Wrong inputs" even though the data is valid. For example:
      8, 2,*9, 3, 5, 6, 7,*4,*1
      *6,*3, 4,*1,*7, 8, 9,*5, 2
      5, 1,*7, 4, 2, 9,*8, 3,*6
      4, 6, 5, 9,*1, 7,*3, 2,*8
      1,*9, 2,*6, 8, 3, 4, 7,*5
      *3, 7,*8,*5, 4, 2,*1,*6,*9
      *7,*4,*6, 2, 9, 1,*5, 8, 3
      9, 5, 3,*8,*6,*4,*2,*1,*7
      2,*8, 1,*7, 3,*5, 6,*9, 4
      (* = locked cell)
      my data:
      0,5 = 1
      2,5 = 2
      0,8 = 2
      2,8 = 1
      Application data:
      0,5 = 2
      2,5 = 1
      0,8 = 1
      2,8 = 2

      I will have a think about how this can be fixed. My guess is to ignore the random generated values and only check locked and user entered data validity?

      Delete

Post a Comment