/// <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; }
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); }
/// <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; } }
/// <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(); }
/// <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; }
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)); } } }
/// <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; } }
/// <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; }
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)); } } } }
/// <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; }
/// <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; }
/// <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; }
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); } } }
private Coordinate _GetRandomTrackedCoordinate(CoordinateTracker tracker) => tracker.TrackedCoords[_random.Next(0, tracker.NumTracked)];