public void Apply(MutablePuzzle puzzle, RegionQueue changedRegions) { while (changedRegions.TryDequeueOfType(RegionType.Box, out int boxIndex)) { var box = puzzle.Puzzle.Boxes[boxIndex]; var placedDigits = box.GetPlacedDigits(); for (int digit = 1; digit <= Puzzle.LineLength; digit++) { var value = SudokuValues.FromHumanValue(digit); if (placedDigits.HasAnyOptions(value)) { continue; } var rows = SudokuValues.None; var cols = SudokuValues.None; for (var i = 0; i < Puzzle.LineLength; i++) { var cellValue = box[i]; var cellCoord = box.GetCoordinate(i); if (!cellValue.IsSingle && cellValue.HasAnyOptions(value)) { rows = rows.AddOptions(SudokuValues.FromIndex(cellCoord.Row)); cols = cols.AddOptions(SudokuValues.FromIndex(cellCoord.Column)); } } if (rows.IsSingle) { var region = puzzle.Puzzle.Rows[rows.ToIndex()]; var anyChanged = RemoveFromOtherBoxesInRegion(puzzle, region, value, boxIndex); if (anyChanged) { #if DEBUG Program.Debugger.AddAction($"{digit}s in {box} remove others in {region}."); #endif return; } } if (cols.IsSingle) { var region = puzzle.Puzzle.Columns[cols.ToIndex()]; var anyChanged = RemoveFromOtherBoxesInRegion(puzzle, region, value, boxIndex); if (anyChanged) { #if DEBUG Program.Debugger.AddAction($"{digit}s in {box} remove others in {region}."); #endif return; } } } } }
public IndexablePosition SetValue(RegionType type, SudokuValues newValue) { var offset = GetOffset(type); var mask = GetMask(offset); return(new IndexablePosition((_values & ~mask) | (newValue.Values << offset))); }
public static Puzzle FromString(string puzzle) { var positions = new IndexablePosition[LineLength * LineLength]; for (var i = 0; i < positions.Length; i++) { var value = SudokuValues.FromCharacter(puzzle[i]); var coord = new Coordinate(RegionType.Row, i); positions[coord.GlobalRowIndex] = positions[coord.GlobalRowIndex].SetRowValue(value); positions[coord.GlobalColumnIndex] = positions[coord.GlobalColumnIndex].SetColumnValue(value); positions[coord.GlobalBoxIndex] = positions[coord.GlobalBoxIndex].SetBoxValue(value); } return(new Puzzle(positions)); }
public Sudoku(string input) { Size = 9; var inputLines = input.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries); if (inputLines.Count() != Size) { throw new ArgumentException("Lines do not match size!"); } var entries = inputLines .Select(line => line.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)); if (!entries.All(e => e.Count() == Size)) { throw new ArgumentException("Elements do not match size!"); } var trimmedEntries = entries.Select(l => l.Select(el => el.Trim())); Func <Box, bool> isBoxDefault = (box) => box.Right == 0 || box.Bottom == 0; this.SudokuValues = new Dictionary <Position, IValue>(); this.BoxesByPosition = new Dictionary <Position, Box>(); for (int row = 0; row < Size; row++) { var currentLine = trimmedEntries.ElementAt(row); for (int col = 0; col < Size; col++) { var currentElement = currentLine.ElementAt(col); var success = int.TryParse(currentElement, out int parsedRes); var currentValue = new Value(); if (success) { currentValue.Solve(parsedRes); } var box = Boxes.SingleOrDefault(b => b.Left <= col && b.Right >= col && b.Top <= row && b.Bottom >= row); var position = new Position(row, col); BoxesByPosition.Add(position, box); if (isBoxDefault(box)) { throw new ArgumentException("Could not find box!"); } SudokuValues.Add(position, currentValue); } } }
public void RemoveOptions(Coordinate coordinate, SudokuValues removedOptions) { var row = coordinate.GlobalRowIndex; var col = coordinate.GlobalColumnIndex; var box = coordinate.GlobalBoxIndex; var currentValue = _cells[row].RowValue; var newValue = currentValue.RemoveOptions(removedOptions); _cells[row] = _cells[row].SetRowValue(newValue); _cells[col] = _cells[col].SetColumnValue(newValue); _cells[box] = _cells[box].SetBoxValue(newValue); _currentChangeSet.Queue.Enqueue(RegionType.Row, row / Puzzle.LineLength); _currentChangeSet.Queue.Enqueue(RegionType.Column, col / Puzzle.LineLength); _currentChangeSet.Queue.Enqueue(RegionType.Box, box / Puzzle.LineLength); }
private void FindTuple(MutablePuzzle puzzle, RegionQueue regions, Region region, SudokuValues placedDigits) { var combinations = Helpers.GetCombinationIndices(Puzzle.LineLength, _size); for (int i = 0; i < combinations.Length; i++) { var comb = combinations[i]; if (placedDigits.HasAnyOptions(comb)) { // skip any tuples that include placed digits continue; } var positions = region.GetPositions(comb); if (positions.GetOptionCount() != _size) { // these digits are placeable in more than tupleSize spots, so not hidden tuple continue; } // we found a hidden tuple! var options = ArrayPool <int> .Shared.Rent(Puzzle.LineLength); var count = positions.CopyIndices(options); var opposite = comb.Invert(); var anyChanged = false; for (var index = 0; index < count; index++) { var cell = region[options[index]]; if (!cell.IsSingle && cell.HasAnyOptions(opposite)) { var update = region.RemoveOptions(options[index], opposite); puzzle.RemoveOptions(update); anyChanged = true; regions.Enqueue(RegionType.Row, update.Coordinate.Row); regions.Enqueue(RegionType.Column, update.Coordinate.Column); regions.Enqueue(RegionType.Box, update.Coordinate.Box); } } ArrayPool <int> .Shared.Return(options); if (anyChanged) { #if DEBUG Program.Debugger.AddAction($"Hidden {comb} tuple in {region}"); #endif return; } } }
public CellUpdate(SudokuValues removedOptions, Coordinate coordinate) { RemovedOptions = removedOptions; Coordinate = coordinate; }
private bool RemoveFromOtherBoxesInRegion(MutablePuzzle puzzle, Region region, SudokuValues value, int boxIndex) { var anyChanged = false; for (int i = 0; i < Puzzle.LineLength; i++) { var coords = region.GetCoordinate(i); if (coords.Box == boxIndex) { continue; } var cell = region[i]; if (!cell.IsSingle && cell.HasAnyOptions(value)) { var update = new CellUpdate(value, coords); puzzle.RemoveOptions(update); anyChanged = true; } } return(anyChanged); }
public IndexablePosition SetBoxValue(SudokuValues newValue) => new IndexablePosition((_values & ~_boxMask) | (newValue.Values << _boxOffset));
public IndexablePosition SetColumnValue(SudokuValues newValue) => new IndexablePosition((_values & ~_colMask) | (newValue.Values << _colOffset));
public IndexablePosition SetRowValue(SudokuValues newValue) => new IndexablePosition((_values & ~_rowMask) | (newValue.Values << _rowOffset));
public IndexablePosition(SudokuValues rowValue, SudokuValues columnValue, SudokuValues boxValue) : this((rowValue.Values << _rowOffset) | (columnValue.Values << _colOffset) | (boxValue.Values << _boxOffset)) { }
private void FindTuple(MutablePuzzle puzzle, RegionQueue regions, Region region, SudokuValues placedDigits) { var combinations = Helpers.GetCombinationIndices(Puzzle.LineLength, _size); for (int j = 0; j < combinations.Length; j++) { var comb = combinations[j]; if (placedDigits.HasAnyOptions(comb)) { // skip any tuples that include placed digits continue; } var indices = ArrayPool <int> .Shared.Rent(_size); var count = comb.CopyIndices(indices); var possibleValues = SudokuValues.None; for (int i = 0; i < count; i++) { var cell = region[indices[i]]; possibleValues = possibleValues.AddOptions(cell); } ArrayPool <int> .Shared.Return(indices); var optionsCount = possibleValues.GetOptionCount(); if (optionsCount == _size) { // we found a tuple! for (var i = 0; i < Puzzle.LineLength; i++) { var otherCell = region[i]; if (otherCell.IsSingle) { continue; } if (!otherCell.HasAnyOptions(possibleValues)) { continue; } if (comb.HasAnyOptions(SudokuValues.FromIndex(i))) { continue; } var newCell = region.RemoveOptions(i, possibleValues); puzzle.RemoveOptions(newCell); regions.Enqueue(RegionType.Row, newCell.Coordinate.Row); regions.Enqueue(RegionType.Column, newCell.Coordinate.Column); regions.Enqueue(RegionType.Box, newCell.Coordinate.Box); #if DEBUG Program.Debugger.AddAction($"{possibleValues} tuple in {region}."); #endif } } } }