/// <summary>
 /// Copy-constructor to provide a copy of the given CoordinateTracker.
 /// </summary>
 /// <param name="existing"></param>
 public CoordinateTracker(CoordinateTracker existing)
 {
     _coordToIdx = (int[, ])existing._coordToIdx.Clone();
     _coords     = (Coordinate[])existing._coords.Clone();
     NumTracked  = existing.NumTracked;
     _numAdded   = existing._numAdded;
 }
Exemple #2
0
 private bool _TryUnsetSquaresWhileSolvable(
     int totalNumSquaresToSet, int currentNumSet, CoordinateTracker setCoordinatesToTry,
     TPuzzle puzzle, CancellationToken?cancellationToken)
 {
     if (currentNumSet == totalNumSquaresToSet)
     {
         return(true);
     }
     while (setCoordinatesToTry.NumTracked > 0)
     {
         cancellationToken?.ThrowIfCancellationRequested();
         Coordinate randomCoord   = _GetRandomTrackedCoordinate(setCoordinatesToTry);
         int?       previousValue = puzzle[randomCoord.Row, randomCoord.Column];
         setCoordinatesToTry.Untrack(in randomCoord);
         if (!_TryUnsetSquareAt(randomCoord, puzzle.NumSetSquares, puzzle, cancellationToken))
         {
             continue;
         }
         if (_TryUnsetSquaresWhileSolvable(
                 totalNumSquaresToSet, currentNumSet - 1,
                 new CoordinateTracker(setCoordinatesToTry), puzzle, cancellationToken))
         {
             return(true);
         }
         // Child update failed :( replace value and try a different coordinate.
         puzzle[randomCoord.Row, randomCoord.Column] = previousValue;
     }
     return(false);
 }
Exemple #3
0
        /// <summary>
        /// Constructs a new puzzle whose data matches the given array.
        /// </summary>
        /// <param name="puzzleMatrix">
        /// The data for this Sudoku puzzle. Preset squares should be set, and unset squares should
        /// be null. A copy of this data is stored in this <c>Puzzle</c>.
        /// </param>
        public Puzzle(int?[,] puzzleMatrix)
        {
            NumSquares = puzzleMatrix.Length;
            Size       = puzzleMatrix.GetLength(0);
            if (Size != puzzleMatrix.GetLength(1))
            {
                throw new ArgumentException("Puzzle must be square.");
            }
            BoxSize = Size switch {
                1 => 1,
                4 => 2,
                9 => 3,
                16 => 4,
                25 => 5,
                _ => throw new ArgumentException("Size must be one of [1, 4, 9, 16, 25]."),
            };

            _squares            = (int?[, ])puzzleMatrix.Clone();
            _unsetCoordsTracker = new CoordinateTracker(Size);
            for (int row = 0; row < Size; row++)
            {
                for (int col = 0; col < Size; col++)
                {
                    if (!_squares[row, col].HasValue)
                    {
                        _unsetCoordsTracker.Add(new Coordinate(row, col));
                    }
                }
            }
            _allPossibleValues = new int[Size];
            for (int i = 0; i < Size; i++)
            {
                _allPossibleValues[i] = i + 1;
            }
        }
Exemple #4
0
 /// <summary>
 /// A copy constructor for an existing <c>Puzzle</c>.
 /// </summary>
 public Puzzle(Puzzle existing)
 {
     Size                = existing.Size;
     BoxSize             = existing.BoxSize;
     NumSquares          = existing.NumSquares;
     _squares            = (int?[, ])existing._squares.Clone();
     _unsetCoordsTracker = new CoordinateTracker(existing._unsetCoordsTracker);
     _allPossibleValues  = existing.AllPossibleValues.ToArray();
 }
Exemple #5
0
 /// <summary>
 /// Copy-constructor to provide a copy of the given CoordinateTracker.
 /// </summary>
 /// <param name="existing"></param>
 public CoordinateTracker(CoordinateTracker existing)
 {
     _coordToIdx = new int[existing._coordToIdx.Length][];
     for (int row = 0; row < _coordToIdx.Length; ++row)
     {
         _coordToIdx[row] = existing._coordToIdx[row].AsSpan().ToArray();
     }
     _coords    = existing._coords.AsSpan().ToArray();
     NumTracked = existing.NumTracked;
     _numAdded  = existing._numAdded;
 }
Exemple #6
0
 private static void _InitUnsetCoordsTrackerAndSquares(CoordinateTracker unsetCoordsTracker, Span <int?[]> squares)
 {
     for (int row = 0; row < squares.Length; row++)
     {
         squares[row] = new int?[squares.Length];
         for (int col = 0; col < squares.Length; col++)
         {
             unsetCoordsTracker.Add(new Coordinate(row, col));
         }
     }
 }
Exemple #7
0
        /// <summary>
        /// Constructs a new puzzle of the given side length.
        /// </summary>
        /// <param name="size">
        /// The side-length for this Sudoku puzzle. Must be a square of a whole number in the range [1, 25].
        /// </param>
        /// <exception cref="ArgumentException">
        /// Thrown if size is not the square of a whole number, or is outside the range [1, 25].
        /// </exception>
        public Puzzle(int size)
        {
            Size = size;
            switch (size)
            {
            case 1:
                NumSquares = 1;
                BoxSize    = 1;
                break;

            case 4:
                NumSquares = 4 * 4;
                BoxSize    = 2;
                break;

            case 9:
                NumSquares = 9 * 9;
                BoxSize    = 3;
                break;

            case 16:
                NumSquares = 16 * 16;
                BoxSize    = 4;
                break;

            case 25:
                NumSquares = 25 * 25;
                BoxSize    = 5;
                break;

            default:
                throw new ArgumentException("Size must be one of [1, 4, 9, 16, 25].");
            }
            _squares            = new int?[size, size];
            _unsetCoordsTracker = new CoordinateTracker(size);
            for (int row = 0; row < Size; row++)
            {
                for (int col = 0; col < Size; col++)
                {
                    if (!_squares[row, col].HasValue)
                    {
                        _unsetCoordsTracker.Add(new Coordinate(row, col));
                    }
                }
            }
            _allPossibleValues = new int[size];
            for (int i = 0; i < size; i++)
            {
                _allPossibleValues[i] = i + 1;
            }
        }
Exemple #8
0
        /// <summary>
        /// A copy constructor for an existing <c>Puzzle</c>.
        /// </summary>
        public Puzzle(Puzzle existing)
        {
            Size     = existing.Size;
            _squares = new int?[Size][];
            int i = 0;

            foreach (var row in existing._squares)
            {
                _squares[i++] = row.AsSpan().ToArray();
            }
            _unsetCoordsTracker = new CoordinateTracker(existing._unsetCoordsTracker);
            _allPossibleValues  = existing._allPossibleValues;
            CountPerUniqueValue = existing.CountPerUniqueValue;
        }
Exemple #9
0
 private static void _InitUnsetCoordsTracker(CoordinateTracker unsetCoordsTracker, ReadOnlySpan <int?[]> squares)
 {
     for (int row = 0; row < squares.Length; row++)
     {
         var squaresRow = squares[row];
         for (int col = 0; col < squaresRow.Length; col++)
         {
             if (!squaresRow[col].HasValue)
             {
                 unsetCoordsTracker.Add(new Coordinate(row, col));
             }
         }
     }
 }
Exemple #10
0
        /// <summary>
        /// Constructs a new puzzle of the given side length and possible values for each region.
        /// </summary>
        /// <param name="size">
        /// The side-length for this Sudoku puzzle.
        /// </param>
        /// <param name="allPossibleValues">
        /// List the possible values for a given region in the puzzle. A value should be repeated
        /// as many times as it can be used.
        /// </param>
        /// <exception cref="ArgumentException">
        /// Thrown if size is less than 1.
        /// </exception>
        public Puzzle(int size, ReadOnlySpan <int> allPossibleValues)
        {
            if (size < 1)
            {
                throw new ArgumentException($"{nameof(size)} must be >= 1.");
            }
            Size                = size;
            _squares            = new int?[size][];
            _unsetCoordsTracker = new CoordinateTracker(size);
            _InitUnsetCoordsTrackerAndSquares(_unsetCoordsTracker, _squares);
            _allPossibleValues = allPossibleValues.ToArray();
            var countPerUniqueValue = new Dictionary <int, int>(size);

            _InitCustomPossibleValues(allPossibleValues, countPerUniqueValue);
            CountPerUniqueValue = countPerUniqueValue;
        }
Exemple #11
0
        /// <summary>
        /// Constructs a new puzzle of the given side length. Assumes the standard possible values
        /// for each region (i.e. the numbers from [1, size]).
        /// </summary>
        /// <param name="size">
        /// The side-length for this Sudoku puzzle.
        /// </param>
        /// <exception cref="ArgumentException">
        /// Thrown if size is less than 1.
        /// </exception>
        public Puzzle(int size)
        {
            if (size < 1)
            {
                throw new ArgumentException($"{nameof(size)} must be >= 1.");
            }
            Size                = size;
            _squares            = new int?[size][];
            _unsetCoordsTracker = new CoordinateTracker(size);
            _InitUnsetCoordsTrackerAndSquares(_unsetCoordsTracker, _squares);
            _allPossibleValues = new int[Size];
            var countPerUniqueValue = new Dictionary <int, int>(size);

            _InitStandardPossibleValues(_allPossibleValues, countPerUniqueValue);
            CountPerUniqueValue = countPerUniqueValue;
        }
Exemple #12
0
        /// <summary>
        /// Constructs a new puzzle backed by the given array.
        ///
        /// The puzzle is backed directly by this array (i.e. modifying the array modifies the
        /// puzzle, and vice-versa). If this is not what you want, see
        /// <see cref="CopyFrom(int?[,])"/> and <see cref="CopyFrom(int?[][])"/>. Note that all
        /// future modifications should be done through this puzzle object, else this will be in an
        /// incorrect state.
        /// </summary>
        /// <param name="puzzleMatrix">
        /// The data for this Sudoku puzzle. Preset squares should be set, and unset squares should
        /// be null. The puzzle maintains a reference to this array.
        /// </param>
        /// <param name="allPossibleValues">
        /// List the possible values for a given region in the puzzle. A value should be repeated
        /// as many times as it can be used.
        /// </param>
        /// <exception cref="ArgumentException">
        /// Thrown if the given matrix is not square.
        /// </exception>
        public Puzzle(int?[][] puzzleMatrix, ReadOnlySpan <int> allPossibleValues)
        {
            Size = puzzleMatrix.Length;
            if (Size == 0 || Size != puzzleMatrix[0].Length)
            {
                throw new ArgumentException("Puzzle must be square with non-zero dimensions.");
            }

            _squares            = puzzleMatrix;
            _unsetCoordsTracker = new CoordinateTracker(Size);
            _InitUnsetCoordsTracker(_unsetCoordsTracker, _squares);
            _allPossibleValues = allPossibleValues.ToArray();
            var countPerUniqueValue = new Dictionary <int, int>(Size);

            _InitCustomPossibleValues(allPossibleValues, countPerUniqueValue);
            CountPerUniqueValue = countPerUniqueValue;
        }
Exemple #13
0
        private TPuzzle _Generate(int puzzleSize, int numSquaresToSet, CancellationToken?cancellationToken)
        {
            bool foundValidPuzzle = false;
            CoordinateTracker setCoordinates;

            while (true)
            {
                cancellationToken?.ThrowIfCancellationRequested();
                TPuzzle puzzle = _puzzleFactory.Invoke(puzzleSize);
                _FillPuzzle(puzzle);
                setCoordinates = new CoordinateTracker(puzzleSize);
                _TrackAllCoordinates(setCoordinates, puzzleSize);
                foundValidPuzzle = _TryUnsetSquaresWhileSolvable(
                    numSquaresToSet, puzzle.NumSquares, setCoordinates, puzzle, cancellationToken);
                if (foundValidPuzzle)
                {
                    return(puzzle);
                }
            }
        }
Exemple #14
0
 private Coordinate _GetRandomTrackedCoordinate(CoordinateTracker tracker) => tracker.TrackedCoords[_random.Next(0, tracker.NumTracked)];