Sudoku Game in Unity

 

Sudoku Game in Unity - Complete Guide

Project Setup

1. Create New Unity Project

  • Open Unity Hub
  • Create new 2D project named "SudokuGame"
  • Unity version: 2021.3 LTS or newer recommended

2. Project Structure

Create the following folder structure in Assets:

Assets/
├── Scripts/
├── Prefabs/
├── Scenes/
├── UI/
└── Materials/

Scripts

Script 1: SudokuCell.cs

This script handles individual cell behavior.

using UnityEngine;
using UnityEngine.UI;
using TMPro;

public class SudokuCell : MonoBehaviour
{
    [Header("UI References")]
    public TextMeshProUGUI numberText;
    public Image background;
    
    [Header("Colors")]
    public Color normalColor = Color.white;
    public Color selectedColor = new Color(0.73f, 0.87f, 0.98f, 1f);
    public Color highlightedColor = new Color(1f, 0.98f, 0.77f, 1f);
    public Color givenColor = new Color(0.96f, 0.96f, 0.96f, 1f);
    public Color errorColor = new Color(1f, 0.8f, 0.8f, 1f);
    
    private int row;
    private int col;
    private int value;
    private bool isGiven;
    private bool isSelected;
    private bool hasError;
    
    private SudokuManager manager;
    
    public void Initialize(int r, int c, SudokuManager mgr)
    {
        row = r;
        col = c;
        manager = mgr;
        value = 0;
        isGiven = false;
        isSelected = false;
        hasError = false;
    }
    
    public void SetValue(int val, bool given = false)
    {
        value = val;
        isGiven = given;
        
        if (val == 0)
        {
            numberText.text = "";
        }
        else
        {
            numberText.text = val.ToString();
        }
        
        UpdateAppearance();
    }
    
    public int GetValue()
    {
        return value;
    }
    
    public bool IsGiven()
    {
        return isGiven;
    }
    
    public int GetRow() { return row; }
    public int GetCol() { return col; }
    
    public void Select()
    {
        isSelected = true;
        UpdateAppearance();
    }
    
    public void Deselect()
    {
        isSelected = false;
        UpdateAppearance();
    }
    
    public void SetHighlighted(bool highlighted)
    {
        if (!isSelected)
        {
            background.color = highlighted ? highlightedColor : (isGiven ? givenColor : normalColor);
        }
    }
    
    public void SetError(bool error)
    {
        hasError = error;
        UpdateAppearance();
    }
    
    private void UpdateAppearance()
    {
        if (isSelected)
        {
            background.color = selectedColor;
        }
        else if (hasError)
        {
            background.color = errorColor;
        }
        else if (isGiven)
        {
            background.color = givenColor;
            numberText.color = Color.black;
        }
        else
        {
            background.color = normalColor;
            numberText.color = new Color(0.09f, 0.46f, 0.82f, 1f); // Blue for user input
        }
    }
    
    public void OnCellClicked()
    {
        if (!isGiven)
        {
            manager.SelectCell(this);
        }
    }
}

Script 2: SudokuGenerator.cs

This script generates Sudoku puzzles.

using System.Collections.Generic;
using UnityEngine;

public class SudokuGenerator
{
    private System.Random random = new System.Random();
    
    public int[,] GenerateSolution()
    {
        int[,] grid = new int[9, 9];
        
        // Fill diagonal 3x3 boxes
        for (int box = 0; box < 9; box += 3)
        {
            FillBox(grid, box, box);
        }
        
        // Solve the rest
        SolveSudoku(grid);
        
        return grid;
    }
    
    private void FillBox(int[,] grid, int row, int col)
    {
        List<int> nums = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
        Shuffle(nums);
        
        int idx = 0;
        for (int i = 0; i < 3; i++)
        {
            for (int j = 0; j < 3; j++)
            {
                grid[row + i, col + j] = nums[idx++];
            }
        }
    }
    
    private void Shuffle<T>(List<T> list)
    {
        int n = list.Count;
        while (n > 1)
        {
            n--;
            int k = random.Next(n + 1);
            T value = list[k];
            list[k] = list[n];
            list[n] = value;
        }
    }
    
    public bool SolveSudoku(int[,] grid)
    {
        int row = -1;
        int col = -1;
        bool isEmpty = false;
        
        for (int i = 0; i < 9; i++)
        {
            for (int j = 0; j < 9; j++)
            {
                if (grid[i, j] == 0)
                {
                    row = i;
                    col = j;
                    isEmpty = true;
                    break;
                }
            }
            if (isEmpty) break;
        }
        
        if (!isEmpty) return true;
        
        List<int> nums = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
        Shuffle(nums);
        
        foreach (int num in nums)
        {
            if (IsValid(grid, num, row, col))
            {
                grid[row, col] = num;
                
                if (SolveSudoku(grid))
                    return true;
                
                grid[row, col] = 0;
            }
        }
        
        return false;
    }
    
    public bool IsValid(int[,] grid, int num, int row, int col)
    {
        // Check row
        for (int x = 0; x < 9; x++)
        {
            if (grid[row, x] == num)
                return false;
        }
        
        // Check column
        for (int x = 0; x < 9; x++)
        {
            if (grid[x, col] == num)
                return false;
        }
        
        // Check 3x3 box
        int boxRow = (row / 3) * 3;
        int boxCol = (col / 3) * 3;
        for (int i = 0; i < 3; i++)
        {
            for (int j = 0; j < 3; j++)
            {
                if (grid[boxRow + i, boxCol + j] == num)
                    return false;
            }
        }
        
        return true;
    }
    
    public int[,] CreatePuzzle(int[,] solution, int cellsToRemove)
    {
        int[,] puzzle = (int[,])solution.Clone();
        
        int removed = 0;
        while (removed < cellsToRemove)
        {
            int row = random.Next(9);
            int col = random.Next(9);
            
            if (puzzle[row, col] != 0)
            {
                puzzle[row, col] = 0;
                removed++;
            }
        }
        
        return puzzle;
    }
}

Script 3: SudokuManager.cs

Main game manager script.

using UnityEngine;
using UnityEngine.UI;
using TMPro;

public class SudokuManager : MonoBehaviour
{
    [Header("UI References")]
    public Transform gridParent;
    public GameObject cellPrefab;
    public TMP_Dropdown difficultyDropdown;
    public TextMeshProUGUI timerText;
    public TextMeshProUGUI remainingText;
    public TextMeshProUGUI mistakesText;
    public TextMeshProUGUI hintsText;
    
    [Header("Number Buttons")]
    public Button[] numberButtons;
    public Button clearButton;
    
    [Header("Game Buttons")]
    public Button newGameButton;
    public Button checkButton;
    public Button hintButton;
    public Button clearAllButton;
    public Button solveButton;
    
    private SudokuCell[,] cells = new SudokuCell[9, 9];
    private int[,] puzzle = new int[9, 9];
    private int[,] solution = new int[9, 9];
    private int[,] userGrid = new int[9, 9];
    
    private SudokuCell selectedCell;
    private SudokuGenerator generator;
    
    private int mistakes = 0;
    private int hintsUsed = 0;
    private float elapsedTime = 0f;
    private bool isTimerRunning = false;
    
    void Start()
    {
        generator = new SudokuGenerator();
        SetupGrid();
        SetupButtons();
        NewGame();
    }
    
    void Update()
    {
        if (isTimerRunning)
        {
            elapsedTime += Time.deltaTime;
            UpdateTimer();
        }
        
        HandleKeyboardInput();
    }
    
    void SetupGrid()
    {
        for (int row = 0; row < 9; row++)
        {
            for (int col = 0; col < 9; col++)
            {
                GameObject cellObj = Instantiate(cellPrefab, gridParent);
                SudokuCell cell = cellObj.GetComponent<SudokuCell>();
                cell.Initialize(row, col, this);
                cells[row, col] = cell;
            }
        }
    }
    
    void SetupButtons()
    {
        newGameButton.onClick.AddListener(NewGame);
        checkButton.onClick.AddListener(CheckSolution);
        hintButton.onClick.AddListener(GetHint);
        clearAllButton.onClick.AddListener(ClearAllUserInputs);
        solveButton.onClick.AddListener(SolvePuzzle);
        
        for (int i = 0; i < numberButtons.Length; i++)
        {
            int num = i + 1;
            numberButtons[i].onClick.AddListener(() => PlaceNumber(num));
        }
        
        clearButton.onClick.AddListener(() => PlaceNumber(0));
    }
    
    public void NewGame()
    {
        // Generate solution
        solution = generator.GenerateSolution();
        
        // Get difficulty
        int cellsToRemove = GetCellsToRemove();
        puzzle = generator.CreatePuzzle(solution, cellsToRemove);
        userGrid = (int[,])puzzle.Clone();
        
        // Display puzzle
        DisplayPuzzle();
        
        // Reset stats
        mistakes = 0;
        hintsUsed = 0;
        elapsedTime = 0f;
        isTimerRunning = true;
        UpdateStats();
    }
    
    int GetCellsToRemove()
    {
        switch (difficultyDropdown.value)
        {
            case 0: return 35; // Easy
            case 1: return 45; // Medium
            case 2: return 52; // Hard
            case 3: return 58; // Expert
            default: return 45;
        }
    }
    
    void DisplayPuzzle()
    {
        for (int row = 0; row < 9; row++)
        {
            for (int col = 0; col < 9; col++)
            {
                int value = puzzle[row, col];
                cells[row, col].SetValue(value, value != 0);
            }
        }
        
        if (selectedCell != null)
        {
            selectedCell.Deselect();
            selectedCell = null;
        }
    }
    
    public void SelectCell(SudokuCell cell)
    {
        // Deselect previous cell
        if (selectedCell != null)
        {
            selectedCell.Deselect();
        }
        
        // Clear all highlights
        ClearHighlights();
        
        // Select new cell
        selectedCell = cell;
        cell.Select();
        
        // Highlight related cells
        HighlightRelatedCells(cell.GetRow(), cell.GetCol());
    }
    
    void ClearHighlights()
    {
        for (int i = 0; i < 9; i++)
        {
            for (int j = 0; j < 9; j++)
            {
                cells[i, j].SetHighlighted(false);
            }
        }
    }
    
    void HighlightRelatedCells(int row, int col)
    {
        // Highlight row, column, and 3x3 box
        for (int i = 0; i < 9; i++)
        {
            cells[row, i].SetHighlighted(true); // Row
            cells[i, col].SetHighlighted(true); // Column
        }
        
        // 3x3 box
        int boxRow = (row / 3) * 3;
        int boxCol = (col / 3) * 3;
        for (int i = 0; i < 3; i++)
        {
            for (int j = 0; j < 3; j++)
            {
                cells[boxRow + i, boxCol + j].SetHighlighted(true);
            }
        }
    }
    
    public void PlaceNumber(int num)
    {
        if (selectedCell == null)
        {
            Debug.Log("Please select a cell first!");
            return;
        }
        
        if (selectedCell.IsGiven())
            return;
        
        int row = selectedCell.GetRow();
        int col = selectedCell.GetCol();
        
        selectedCell.SetError(false);
        
        if (num == 0)
        {
            selectedCell.SetValue(0);
            userGrid[row, col] = 0;
        }
        else
        {
            selectedCell.SetValue(num);
            userGrid[row, col] = num;
            
            // Check if it's wrong
            if (num != solution[row, col])
            {
                selectedCell.SetError(true);
                mistakes++;
            }
        }
        
        UpdateStats();
        CheckIfComplete();
    }
    
    public void CheckSolution()
    {
        bool hasErrors = false;
        
        for (int row = 0; row < 9; row++)
        {
            for (int col = 0; col < 9; col++)
            {
                cells[row, col].SetError(false);
                
                int userValue = userGrid[row, col];
                int correctValue = solution[row, col];
                
                if (userValue != 0 && userValue != correctValue)
                {
                    cells[row, col].SetError(true);
                    hasErrors = true;
                }
            }
        }
        
        if (!hasErrors)
        {
            bool complete = IsGridComplete();
            if (complete)
            {
                isTimerRunning = false;
                ShowMessage($"Congratulations! Solved in {FormatTime((int)elapsedTime)}!");
            }
            else
            {
                ShowMessage("No mistakes so far! Keep going!");
            }
        }
        else
        {
            ShowMessage("Some cells are incorrect (marked in red).");
        }
    }
    
    public void GetHint()
    {
        // Find empty cells
        for (int row = 0; row < 9; row++)
        {
            for (int col = 0; col < 9; col++)
            {
                if (puzzle[row, col] == 0 && userGrid[row, col] == 0)
                {
                    // Fill with correct value
                    userGrid[row, col] = solution[row, col];
                    cells[row, col].SetValue(solution[row, col]);
                    cells[row, col].SetError(false);
                    
                    hintsUsed++;
                    UpdateStats();
                    CheckIfComplete();
                    return;
                }
            }
        }
        
        ShowMessage("No hints available!");
    }
    
    public void SolvePuzzle()
    {
        for (int row = 0; row < 9; row++)
        {
            for (int col = 0; col < 9; col++)
            {
                if (!cells[row, col].IsGiven())
                {
                    cells[row, col].SetValue(solution[row, col]);
                    cells[row, col].SetError(false);
                    userGrid[row, col] = solution[row, col];
                }
            }
        }
        
        isTimerRunning = false;
        UpdateStats();
    }
    
    public void ClearAllUserInputs()
    {
        for (int row = 0; row < 9; row++)
        {
            for (int col = 0; col < 9; col++)
            {
                if (!cells[row, col].IsGiven())
                {
                    cells[row, col].SetValue(0);
                    cells[row, col].SetError(false);
                    userGrid[row, col] = 0;
                }
            }
        }
        
        if (selectedCell != null)
        {
            selectedCell.Deselect();
            selectedCell = null;
        }
        
        UpdateStats();
    }
    
    void UpdateStats()
    {
        int remaining = 0;
        for (int i = 0; i < 9; i++)
        {
            for (int j = 0; j < 9; j++)
            {
                if (userGrid[i, j] == 0)
                    remaining++;
            }
        }
        
        remainingText.text = remaining.ToString();
        mistakesText.text = mistakes.ToString();
        hintsText.text = hintsUsed.ToString();
    }
    
    void UpdateTimer()
    {
        timerText.text = FormatTime((int)elapsedTime);
    }
    
    string FormatTime(int seconds)
    {
        int mins = seconds / 60;
        int secs = seconds % 60;
        return $"{mins:00}:{secs:00}";
    }
    
    bool IsGridComplete()
    {
        for (int i = 0; i < 9; i++)
        {
            for (int j = 0; j < 9; j++)
            {
                if (userGrid[i, j] != solution[i, j])
                    return false;
            }
        }
        return true;
    }
    
    void CheckIfComplete()
    {
        if (IsGridComplete())
        {
            isTimerRunning = false;
            ShowMessage($"Congratulations! Solved in {FormatTime((int)elapsedTime)}!");
        }
    }
    
    void ShowMessage(string message)
    {
        Debug.Log(message);
        // You can implement a proper UI popup here
    }
    
    void HandleKeyboardInput()
    {
        if (selectedCell == null)
            return;
        
        // Number input
        for (int i = 1; i <= 9; i++)
        {
            if (Input.GetKeyDown(KeyCode.Alpha0 + i) || Input.GetKeyDown(KeyCode.Keypad0 + i))
            {
                PlaceNumber(i);
            }
        }
        
        // Clear
        if (Input.GetKeyDown(KeyCode.Backspace) || Input.GetKeyDown(KeyCode.Delete))
        {
            PlaceNumber(0);
        }
        
        // Arrow key navigation
        int row = selectedCell.GetRow();
        int col = selectedCell.GetCol();
        
        if (Input.GetKeyDown(KeyCode.UpArrow) && row > 0)
        {
            SelectCell(cells[row - 1, col]);
        }
        else if (Input.GetKeyDown(KeyCode.DownArrow) && row < 8)
        {
            SelectCell(cells[row + 1, col]);
        }
        else if (Input.GetKeyDown(KeyCode.LeftArrow) && col > 0)
        {
            SelectCell(cells[row, col - 1]);
        }
        else if (Input.GetKeyDown(KeyCode.RightArrow) && col < 8)
        {
            SelectCell(cells[row, col + 1]);
        }
    }
}

UI Setup Instructions

1. Create Canvas

  1. Right-click in Hierarchy → UI → Canvas
  2. Set Canvas Scaler to "Scale With Screen Size"
  3. Reference Resolution: 1920x1080

2. Create Sudoku Grid

  1. Create Empty GameObject under Canvas, name it "GridPanel"
  2. Add Grid Layout Group component:
    • Cell Size: 60x60
    • Spacing: 0, 0
    • Constraint: Fixed Column Count = 9
  3. Add Content Size Fitter:
    • Horizontal Fit: Preferred Size
    • Vertical Fit: Preferred Size

3. Create Cell Prefab

  1. Create UI → Image, name it "SudokuCell"
  2. Add Button component (remove navigation)
  3. Add child: UI → TextMeshPro - Text
    • Name it "NumberText"
    • Font Size: 36
    • Alignment: Center
  4. Add SudokuCell script to prefab
  5. Assign references in inspector
  6. Drag to Prefabs folder
  7. Delete from scene

4. Create Number Pad

  1. Create Panel under Canvas
  2. Add Grid Layout Group:
    • Cell Size: 80x80
    • Spacing: 10, 10
    • Columns: 5
  3. Create 9 buttons (1-9) + 1 clear button
  4. Add TextMeshPro text to each button

5. Create Control Buttons

Create buttons for:

  • New Game
  • Check
  • Hint
  • Clear All
  • Solve

6. Create Stats Panel

Create TextMeshPro texts for:

  • Timer
  • Remaining cells
  • Mistakes
  • Hints used

7. Create Difficulty Dropdown

  1. UI → Dropdown - TextMeshPro
  2. Add options: Easy, Medium, Hard, Expert

Assembly and Configuration

1. Setup SudokuManager GameObject

  1. Create empty GameObject, name it "SudokuManager"
  2. Add SudokuManager script
  3. Assign all references in inspector:
    • Grid Parent → GridPanel
    • Cell Prefab → SudokuCell prefab
    • All UI elements

2. Add Box Borders

To create the thick 3x3 box borders:

  1. Create 2 horizontal and 2 vertical UI Images
  2. Set color to dark gray/black
  3. Position them to create the grid lines
  4. Set width/height to 3-4 pixels

Additional Features (Optional)

Sound Manager Script

using UnityEngine;

public class SoundManager : MonoBehaviour
{
    public AudioClip buttonClick;
    public AudioClip numberPlace;
    public AudioClip errorSound;
    public AudioClip winSound;
    
    private AudioSource audioSource;
    
    void Awake()
    {
        audioSource = GetComponent<AudioSource>();
    }
    
    public void PlayButtonClick()
    {
        audioSource.PlayOneShot(buttonClick);
    }
    
    public void PlayNumberPlace()
    {
        audioSource.PlayOneShot(numberPlace);
    }
    
    public void PlayError()
    {
        audioSource.PlayOneShot(errorSound);
    }
    
    public void PlayWin()
    {
        audioSource.PlayOneShot(winSound);
    }
}

Build Settings

  1. File → Build Settings
  2. Add current scene
  3. Select platform (PC, Mac, Android, iOS)
  4. Player Settings:
    • Company Name
    • Product Name: Sudoku
    • Default Icon
  5. Build and Run

Tips for Improvement

  1. Add animations for cell selection
  2. Implement undo/redo functionality
  3. Add save/load game state
  4. Create different themes
  5. Add achievement system
  6. Implement daily challenges
  7. Add multiplayer functionality
  8. Create tutorial mode

Common Issues & Solutions

Issue: Cells not responding to clicks

  • Check EventSystem exists in scene
  • Verify Button component on cells
  • Check raycast target on Image

Issue: Grid layout not working

  • Verify Grid Layout Group settings
  • Check anchor points on parent
  • Ensure proper cell size

Issue: Numbers not displaying

  • Check TextMeshPro package installed
  • Verify font asset assigned
  • Check text size and color

Testing Checklist

  • [ ] New game generates valid puzzle
  • [ ] All difficulty levels work
  • [ ] Cell selection works
  • [ ] Number placement works
  • [ ] Error detection works
  • [ ] Hint system works
  • [ ] Timer counts correctly
  • [ ] Check solution validates correctly
  • [ ] Solve button completes puzzle
  • [ ] Clear all removes user input
  • [ ] Keyboard controls work
  • [ ] UI scales properly on different resolutions

This guide provides everything you need to build a complete Sudoku game in Unity!

Comments