/// <summary> /// Constructs a constraint that will enforce that the given <paramref name="squares"/> are /// magic squares based on the rows, columns, and, optionally, the diagonals. /// </summary> /// <param name="possibleValues"> /// The possible values that can be in the magic squares. /// </param> /// <param name="squares"> /// The locations of the magic squares. /// </param> /// <param name="includeDiagonals"> /// If true, values along the diagonals of the square must also sum to the magic number. /// </param> /// <exception cref="ArgumentException"> /// If the any of the given <paramref name="squares"/>' sizes are not compatible with the /// length of <paramref name="possibleValues"/>. /// </exception> public MagicSquaresConstraint(ReadOnlySpan <int> possibleValues, IEnumerable <Box> squares, bool includeDiagonals = true) { _size = possibleValues.Length; _magicSquares = squares.ToArray(); _squareSize = Boxes.IntSquareRoot(_size); _includeDiagonals = includeDiagonals; if (_magicSquares.Any( b => b.TopLeft.Row < 0 || b.TopLeft.Column < 0 || b.TopLeft.Row + b.Size > _size || b.TopLeft.Column + b.Size > _size || b.Size != _squareSize)) { throw new ArgumentException( $"Based on the {nameof(possibleValues)}, {nameof(squares)} must fit in a puzzle of size {_size} and have size {_squareSize}."); } _allPossibleValues = new BitVector(); for (int i = 0; i < possibleValues.Length; ++i) { if (_allPossibleValues.IsBitSet(possibleValues[i])) { throw new ArgumentException("Values must be unique."); } _allPossibleValues.SetBit(possibleValues[i]); } _possibleSets = MagicSquares.ComputeSets(possibleValues, _squareSize, _allPossibleValues); }
/// <inheritdoc/> public bool TryInitFor(IReadOnlyPuzzleWithMutablePossibleValues puzzle) { _boxSize = Boxes.IntSquareRoot(puzzle.Size); _puzzle = puzzle; _helper = new UniqueInXHelper(puzzle); return(true); }
/// <inheritdoc/> public override bool TryInit(IReadOnlyPuzzle puzzle, BitVector uniquePossibleValues) { _boxSize = Boxes.IntSquareRoot(puzzle.Size); _puzzle = puzzle; if (!base.TryInit(puzzle, uniquePossibleValues)) { _puzzle = null; return(false); } return(true); }
internal static void AssertMagicSquaresSatisfied( IReadOnlyPuzzle puzzle, int expectedSum, bool verifyDiagonals) { int boxSize = Boxes.IntSquareRoot(puzzle.Size); var boxes = new Box[puzzle.Size]; for (int boxIdx = 0; boxIdx < boxes.Length; ++boxIdx) { boxes[boxIdx] = new Box(Boxes.GetStartingBoxCoordinate(boxIdx, boxSize), boxSize); } AssertMagicSquaresSatisfied(puzzle, boxes, expectedSum, verifyDiagonals); }
/// <inheritdoc/> public bool TryInit(IReadOnlyPuzzleWithMutablePossibleValues puzzle) { int size = puzzle.Size; _puzzle = puzzle; _boxSize = Boxes.IntSquareRoot(puzzle.Size); _unsetRowValues = new BitVector[size]; _unsetRowValues.AsSpan().Fill(_puzzle.UniquePossibleValues); Span <BitVector> possibleValues = _unsetRowValues.AsSpan(); _unsetColumnValues = possibleValues.ToArray(); _unsetBoxValues = possibleValues.ToArray(); int boxIdx; for (int row = 0; row < size; row++) { for (int col = 0; col < size; col++) { boxIdx = Boxes.CalculateBoxIndex(new Coordinate(row, col), _boxSize); int?val = puzzle[row, col]; if (!val.HasValue) { continue; } if (!_unsetRowValues[row].IsBitSet(val.Value) || !_unsetColumnValues[col].IsBitSet(val.Value) || !_unsetBoxValues[boxIdx].IsBitSet(val.Value)) { return(false); } _unsetRowValues[row].UnsetBit(val.Value); _unsetColumnValues[col].UnsetBit(val.Value); _unsetBoxValues[boxIdx].UnsetBit(val.Value); } } foreach (Coordinate c in puzzle.GetUnsetCoords()) { _puzzle.IntersectPossibleValues(in c, _GetPossibleValues(in c)); if (_puzzle.GetPossibleValues(in c).IsEmpty) { return(false); } } return(true); }
/// <inheritdoc/> public bool TryInit(IReadOnlyPuzzle puzzle, BitVector uniquePossibleValues) { int size = puzzle.Size; _boxSize = Boxes.IntSquareRoot(size); if (size != _unsetRowValues?.Length) { _unsetRowValues = new BitVector[size]; _unsetRowValues.AsSpan().Fill(uniquePossibleValues); _unsetColValues = _unsetRowValues.AsSpan().ToArray(); _unsetBoxValues = _unsetRowValues.AsSpan().ToArray(); } else { _unsetRowValues.AsSpan().Fill(uniquePossibleValues); _unsetColValues.AsSpan().Fill(uniquePossibleValues); _unsetBoxValues.AsSpan().Fill(uniquePossibleValues); } int boxIdx = 0; for (int row = 0; row < size; row++) { for (int col = 0; col < size; col++) { if (col == 0) { boxIdx = (row / _boxSize) * _boxSize; } else if (col % _boxSize == 0) { boxIdx++; } int?val = puzzle[row, col]; if (!val.HasValue) { continue; } if (!_unsetRowValues[row].IsBitSet(val.Value) || !_unsetColValues ![col].IsBitSet(val.Value) ||
public void GetPossibleValues_MatchesGetPossibleBoxValues() { var puzzle = new PuzzleWithPossibleValues(new int?[][] { new int?[] { 1, null /* 4 */, null /* 3 */, 2 }, new int?[] { null /* 2 */, null /* 3 */, null /* 1 */, 4 }, new int?[] { null /* 4 */, 1, null /* 2 */, 3 }, new int?[] { 3, null /* 2 */, null /* 4 */, 1 } }); var rule = new MaxCountPerBoxRule(); Assert.True(rule.TryInit(puzzle, puzzle.UniquePossibleValues)); for (int row = 0; row < puzzle.Size; row++) { for (int column = 0; column < puzzle.Size; column++) { int box = Boxes.CalculateBoxIndex(new(row, column), Boxes.IntSquareRoot(puzzle.Size)); Assert.Equal( rule.GetMissingValuesForBox(box), rule.GetPossibleValues(new Coordinate(row, column))); } } }
public void GetPossibleValues_MatchesGetPossibleBoxValues() { var puzzle = new PuzzleWithPossibleValues(new int?[][] { new int?[] { 1, null /* 4 */, null /* 3 */, 2 }, new int?[] { null /* 2 */, null /* 3 */, null /* 1 */, 4 }, new int?[] { null /* 4 */, 1, null /* 2 */, 3 }, new int?[] { 3, null /* 2 */, null /* 4 */, 1 } }); var rule = new BoxUniquenessRule(); Assert.True(rule.TryInit(puzzle, puzzle.UniquePossibleValues)); IList <BitVector> possibleValuesByBox = _GetPossibleValuesByBox(puzzle.Size, rule); for (int row = 0; row < puzzle.Size; row++) { for (int column = 0; column < puzzle.Size; column++) { int box = Boxes.CalculateBoxIndex(new(row, column), Boxes.IntSquareRoot(puzzle.Size)); Assert.Equal( possibleValuesByBox[box], rule.GetPossibleValues(new Coordinate(row, column))); } } }
internal static void AssertStandardPuzzleSolved(IReadOnlyPuzzle puzzle) { Assert.Equal(0, puzzle.NumEmptySquares); var alreadyFound = new HashSet <int>(puzzle.Size); for (int row = 0; row < puzzle.Size; row++) { alreadyFound.Clear(); for (int col = 0; col < puzzle.Size; col++) { Assert.True(alreadyFound.Add(puzzle[row, col].Value), $"Value at ({row}, {col}) clashed with another value in that row!"); } } for (int col = 0; col < puzzle.Size; col++) { alreadyFound.Clear(); for (int row = 0; row < puzzle.Size; row++) { Assert.True(alreadyFound.Add(puzzle[row, col].Value), $"Value at ({row}, {col}) clashed with another value in that col!"); } } int boxSize = Boxes.IntSquareRoot(puzzle.Size); for (int box = 0; box < puzzle.Size; box++) { alreadyFound.Clear(); (int startRow, int startCol) = Boxes.GetStartingBoxCoordinate(box, boxSize); for (int row = startRow; row < startRow + boxSize; row++) { for (int col = startCol; col < startCol + boxSize; col++) { Assert.True(alreadyFound.Add(puzzle[row, col].Value), $"Value at ({row}, {col}) clashed with another value in that box!"); } } } }
public void IntSquareRoot_WithInvalidSize_Throws(int puzzleSize) { Assert.Throws <ArgumentException>(() => Boxes.IntSquareRoot(puzzleSize)); }
public void IntSquareRoot_WithValidSizes_IsCorrect(int puzzleSize, int expectedBoxSize) { Assert.Equal(expectedBoxSize, Boxes.IntSquareRoot(puzzleSize)); }