I have these bellow classes in Unity engine for make match three game.:
using UnityEngine;

namespace Match3Game.Models
{
    [CreateAssetMenu(fileName = "NewPieceType", menuName = "Match3Game/PieceType")]
    public class PieceTypeData : ScriptableObject
    {
        public GamePiece[] gamePieces; // An array of GamePiece prefabs to use for this piece type.
    }
}
using UnityEngine;
using Match3Game.Models;

namespace Match3Game.Managers
{
    public class BoardManager : MonoBehaviour
    {
        public static BoardManager instance;
        public Transform parent;
        public GameObject backgroundPrefab; // Prefab for the board background.
        public PieceTypeData pieceTypes; // Single reference to a PieceTypeData instance
        public int boardWidth = 8;
        public int boardHeight = 8;

        private GameBoard _gameBoard; // Private field to store the GameBoard instance.

        public GameBoard GameBoard // Public property to access GameBoard.
        {
            get => _gameBoard;
        }

        private void Awake()
        {
            instance = this;
        }

        private void Start()
        {
            _gameBoard = new GameBoard(boardWidth, boardHeight, pieceTypes); // Pass pieceTypes to GameBoard
            CreateBackground();
            // Rendering is now handled in GameBoard during initialization.
        }

        private void CreateBackground()
        {
            for (int x = 0; x < boardWidth; x++)
            {
                for (int y = 0; y < boardHeight; y++)
                {
                    Instantiate(backgroundPrefab, new Vector3(x, y, 0), Quaternion.identity, parent);
                }
            }
        }

        public GamePiece GetGamePiece(int x, int y)
        {
            return _gameBoard.GetPiece(x, y);
        }

        // Additional methods for swap, find matches, etc.
    }
}
using UnityEngine;
using Match3Game.Models;
using Match3Game.Managers;
using System.Collections.Generic;
using System.Collections;

namespace Match3Game.Controller
{
    public class GameController : MonoBehaviour
    {
        private MatchFinder matchFinder;

        [SerializeField]
        private GamePiece selectedPiece; // Currently selected piece
        private Vector2Int selectedGridPosition; // Grid position of the selected piece
        private bool isSwapping = false; // To prevent multiple swaps simultaneously

        private void Start()
        {
            matchFinder = new MatchFinder(BoardManager.instance.GameBoard);
        }

        private void Update()
        {
            HandleInput();
        }

        private void HandleInput()
        {
            if (Input.GetMouseButtonDown(0) && !isSwapping)
            {
                Vector2 mousePos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
                Vector2Int gridPos = WorldToGridPosition(mousePos);

                // Check if the clicked position is within valid bounds
                if (IsValidGridPosition(gridPos))
                {
                    if (selectedPiece == null)
                    {
                        Debug.Log($"Mouse down on : {BoardManager.instance.GetGamePiece(gridPos.x, gridPos.y)}");

                        // Select the piece if none is selected
                        selectedPiece = BoardManager.instance.GetGamePiece(gridPos.x, gridPos.y);
                        selectedGridPosition = gridPos;
                        HighlightPiece(gridPos, true); // Optional highlight to show selection
                    }
                    else
                    {
                        // Check if we are trying to swap with a neighbor
                        if (IsAdjacent(selectedGridPosition, gridPos))
                        {
                            StartCoroutine(SwapPieces(selectedGridPosition, gridPos));
                        }

                        // Deselect the piece if nothing happened or if it's the same piece
                        HighlightPiece(selectedGridPosition, false);
                        selectedPiece = null;
                    }
                }
            }
        }

        private Vector2Int WorldToGridPosition(Vector2 worldPosition)
        {
            // Convert world position to grid coordinates
            return new Vector2Int(Mathf.FloorToInt(worldPosition.x), Mathf.FloorToInt(worldPosition.y));
        }

        private bool IsValidGridPosition(Vector2Int pos)
        {
            return pos.x >= 0 && pos.x < BoardManager.instance.GameBoard.Width && pos.y >= 0 && pos.y < BoardManager.instance.GameBoard.Height;
        }

        private bool IsAdjacent(Vector2Int pos1, Vector2Int pos2)
        {
            // Check if two positions are adjacent
            return (Mathf.Abs(pos1.x - pos2.x) == 1 && pos1.y == pos2.y) || (Mathf.Abs(pos1.y - pos2.y) == 1 && pos1.x == pos2.x);
        }

        private IEnumerator SwapPieces(Vector2Int pos1, Vector2Int pos2)
        {
            Debug.Log("SwapPieces ...");

            isSwapping = true;

            // swap pieces in the model
            BoardManager.instance.GameBoard.SwapPieces(pos1.x, pos1.y, pos2.x, pos2.y);

            // Here you might add animation for a smooth transition
            yield return new WaitForSeconds(0.2f); // Wait for animations if any

            // Check for matches
            var matches = matchFinder.FindMatches();

            if (matches.Count == 0)
            {
                // No match found, swap back
                // (Optionally handle a swap back animation here)
                BoardManager.instance.GameBoard.SwapPieces(pos1.x, pos1.y, pos2.x, pos2.y);
            }
            else
            {
                // Handle the matches, i.e., clear matches, update score, etc.

                // Example of clearing matches
                foreach (var match in matches)
                {
                    Debug.Log($"Matched Piece at: {match.x}, {match.y}");
                    // Logic to visually remove pieces and score updates can be implemented here
                }
            }

            isSwapping = false;
        }

        private void HighlightPiece(Vector2Int position, bool highlight)
        {
            // Implement logic to visually highlight the selected piece
            // This could be changing the color, adding an outline, etc.
            GameObject piece = GameObject.Find($"Piece_{position.x}_{position.y}");
            if (piece != null)
            {
                // Example: Changing the color of the piece
                var spriteRenderer = piece.GetComponent<SpriteRenderer>();
                if (spriteRenderer != null)
                {
                    spriteRenderer.color = highlight ? Color.yellow : Color.white; // Highlight effect
                }
            }
        }
    }
}
using System.Collections.Generic;
using System.Linq;

namespace Match3Game.Models
{
    public class MatchFinder
    {
        private readonly GameBoard _board;

        public MatchFinder(GameBoard board)
        {
            _board = board;
        }

        public List<(int x, int y)> FindMatches()
        {
            List<(int x, int y)> matches = new List<(int x, int y)>();

            for (int x = 0; x < _board.Width; x++)
            {
                for (int y = 0; y < _board.Height; y++)
                {
                    List<(int x, int y)> horizontalMatches = FindHorizontalMatches(x, y);
                    List<(int x, int y)> verticalMatches = FindVerticalMatches(x, y);

                    matches.AddRange(horizontalMatches);
                    matches.AddRange(verticalMatches);
                }
            }

            return matches.Distinct().ToList(); // Remove duplicates
        }

        private List<(int x, int y)> FindHorizontalMatches(int x, int y)
        {
            List<(int x, int y)> match = new List<(int x, int y)>();
            GamePiece firstPiece = _board.GetPiece(x, y);

            if (firstPiece == null) return match;

            match.Add((x, y));

            // Check to the right
            for (int i = x + 1; i < _board.Width; i++)
            {
                GamePiece neighbor = _board.GetPiece(i, y);
                if (neighbor != null && neighbor.Type == firstPiece.Type)
                {
                    match.Add((i, y));
                }
                else
                {
                    break;
                }
            }

            // Check to the left
            for (int i = x - 1; i >= 0; i--)
            {
                GamePiece neighbor = _board.GetPiece(i, y);
                if (neighbor != null && neighbor.Type == firstPiece.Type)
                {
                    match.Add((i, y));
                }
                else
                {
                    break;
                }
            }

            return match.Count >= 3 ? match : new List<(int x, int y)>();
        }

        private List<(int x, int y)> FindVerticalMatches(int x, int y)
        {
            List<(int x, int y)> match = new List<(int x, int y)>();
            GamePiece firstPiece = _board.GetPiece(x, y);

            if (firstPiece == null) return match;

            match.Add((x, y));

            // Check below
            for (int i = y + 1; i < _board.Height; i++)
            {
                GamePiece neighbor = _board.GetPiece(x, i);
                if (neighbor != null && neighbor.Type == firstPiece.Type)
                {
                    match.Add((x, i));
                }
                else
                {
                    break;
                }
            }

            // Check above
            for (int i = y - 1; i >= 0; i--)
            {
                GamePiece neighbor = _board.GetPiece(x, i);
                if (neighbor != null && neighbor.Type == firstPiece.Type)
                {
                    match.Add((x, i));
                }
                else
                {
                    break;
                }
            }

            return match.Count >= 3 ? match : new List<(int x, int y)>();
        }
    }
}
using Match3Game.Managers;
using UnityEngine;

namespace Match3Game.Models
{
    public class GameBoard
    {
        public int Width { get; private set; }
        public int Height { get; private set; }
        private GamePiece[,] board;

        public GameBoard(int width, int height, PieceTypeData pieceTypeData)
        {
            Width = width;
            Height = height;
            board = new GamePiece[width, height];
            InitializeBoard(pieceTypeData);
        }

        private void InitializeBoard(PieceTypeData pieceTypeData)
        {
            for (int x = 0; x < Width; x++)
            {
                for (int y = 0; y < Height; y++)
                {
                    // Randomly select a GamePiece from the provided PieceTypeData array
                    int randomIndex = Random.Range(0, pieceTypeData.gamePieces.Length);
                    GamePiece selectedPiecePrefab = pieceTypeData.gamePieces[randomIndex];

                    // Instantiate the prefab to create a new GamePiece
                    GamePiece newPiece = Object.Instantiate(selectedPiecePrefab, BoardManager.instance.parent);
                    newPiece.Type = selectedPiecePrefab.Type; //Directly assign the piece type.
                    newPiece.pieceName = selectedPiecePrefab.pieceName; //Directly assign the piece name.
                    newPiece.pieceColor = selectedPiecePrefab.pieceColor; //Directly assign the piece color.
                    board[x, y] = newPiece;

                    // Set the position in the scene
                    newPiece.transform.position = new Vector3(x, y, 1);
                }
            }
        }

        public GamePiece GetPiece(int x, int y) => board[x, y];

        public void SwapPieces(int x1, int y1, int x2, int y2)
        {
            GamePiece temp = board[x1, y1];
            board[x1, y1] = board[x2, y2];
            board[x2, y2] = temp;
        }

        // Additional methods to handle matching, clearing, etc.
    }
}
using UnityEngine;

namespace Match3Game.Models
{
    public enum PieceType
    {
        Red,
        Green,
        Blue,
        Yellow,
        Pink
    }

    public class GamePiece : MonoBehaviour
    {
        public string pieceName;
        public Color pieceColor;
        public PieceType Type; // Set this in the inspector for each prefab
    }
}
 when I mouse down in item and then mouse down a neighbor item I get these errors:
NullReferenceException: Object reference not set to an instance of an object
Match3Game.Models.MatchFinder.FindMatches () (at Assets/Scripts/Match3Game/Models/MatchFinder.cs:19)
Match3Game.Controller.GameController+<SwapPieces>d__10.MoveNext () (at Assets/Scripts/Match3Game/Controller/GameController.cs:93)
UnityEngine.SetupCoroutine.InvokeMoveNext (System.Collections.IEnumerator enumerator, System.IntPtr returnValueAddress) (at <ba783288ca164d3099898a8819fcec1c>:0)