public override LogicResult StepLogic(SudokuSolver sudokuSolver, StringBuilder logicalStepDescription, bool isBruteForcing) { var board = sudokuSolver.Board; for (int i = 0; i < HEIGHT; i++) { for (int j = 0; j < WIDTH; j++) { uint mask = board[i, j]; if (IsValueSet(mask)) { continue; } int maskValueCount = ValueCount(mask); if (maskValueCount == 2) { int val1 = MinValue(mask); int val2 = MaxValue(mask); if (val2 - val1 == 1) { // Two consecutive values in a cell means that all of its adjacent cells cannot be either of those values bool haveChanges = false; foreach (var otherPair in AdjacentCells(i, j)) { uint otherMask = board[otherPair.Item1, otherPair.Item2]; LogicResult findResult = sudokuSolver.ClearMask(otherPair.Item1, otherPair.Item2, mask); if (findResult == LogicResult.Invalid) { logicalStepDescription.Clear(); logicalStepDescription.Append($"{CellName(i, j)} with values {MaskToString(mask)} removes the only candidates {MaskToString(otherMask)} from {CellName(otherPair)}"); return(LogicResult.Invalid); } if (findResult == LogicResult.Changed) { if (!haveChanges) { logicalStepDescription.Append($"{CellName((i, j))} having candidates {MaskToString(mask)} removes those values from {CellName(otherPair)}"); haveChanges = true; } else { logicalStepDescription.Append($", {CellName(otherPair)}"); } } } if (haveChanges) { return(LogicResult.Changed); } } else if (val2 - val1 == 2) { // Values are 2 apart, which means adjacent cells can't be the value between those two int bannedVal = val1 + 1; uint clearMask = 1u << (bannedVal - 1); bool haveChanges = false; foreach (var otherPair in AdjacentCells(i, j)) { uint otherMask = board[otherPair.Item1, otherPair.Item2]; LogicResult findResult = sudokuSolver.ClearMask(otherPair.Item1, otherPair.Item2, clearMask); if (findResult == LogicResult.Invalid) { logicalStepDescription.Clear(); logicalStepDescription.Append($"{CellName(i, j)} with values {MaskToString(mask)} removes the only candidate {bannedVal} from {CellName(otherPair)}"); return(LogicResult.Invalid); } if (findResult == LogicResult.Changed) { if (!haveChanges) { logicalStepDescription.Append($"{CellName((i, j))} having candidates {MaskToString(mask)} removes {bannedVal} from {CellName(otherPair)}"); haveChanges = true; } else { logicalStepDescription.Append($", {CellName(otherPair)}"); } } } if (haveChanges) { return(LogicResult.Changed); } } } else if (maskValueCount == 3) { int minValue = MinValue(mask); int maxValue = MaxValue(mask); if (maxValue - minValue == 2) { // Three consecutive values means adjacent cells can't be the middle value int clearValue = minValue + 1; uint clearMask = ValueMask(clearValue); bool changed = false; foreach (var otherPair in AdjacentCells(i, j)) { LogicResult findResult = sudokuSolver.ClearMask(otherPair.Item1, otherPair.Item2, clearMask); if (findResult == LogicResult.Invalid) { logicalStepDescription.Clear(); logicalStepDescription.Append($"{CellName(i, j)} with values {MaskToString(mask)} removes the only candidate {clearValue} from {CellName(otherPair)}"); return(LogicResult.Invalid); } if (findResult == LogicResult.Changed) { if (!changed) { logicalStepDescription.Append($"{CellName((i, j))} having candidates {MaskToString(mask)} removes {clearValue} from {CellName(otherPair)}"); changed = true; } else { logicalStepDescription.Append($", {CellName(otherPair)}"); } } } if (changed) { return(LogicResult.Changed); } } } } } // Look for groups where a particular digit is locked to 2, 3, or 4 places // For the case of 2 places, if they are adjacent then neither can be a consecutive digit // For the case of 3 places, if they are all adjacent then the center one cannot be a consecutive digit // For all cases, any cell that is adjacent to all of them cannot be a consecutive digit // That last one should be a generalized version of the first two if we count a cell as adjacent to itself var valInstances = new (int, int)[MAX_VALUE];
public override LogicResult StepLogic(SudokuSolver sudokuSolver, StringBuilder logicalStepDescription, bool isBruteForcing) { var board = sudokuSolver.Board; // Look for single cells that can eliminate on its diagonals. // Some eliminations can only happen within the same box. for (int i = 0; i < HEIGHT; i++) { for (int j = 0; j < WIDTH; j++) { uint mask = board[i, j]; if (IsValueSet(mask)) { continue; } int valueCount = ValueCount(mask); if (valueCount <= 3) { int minValue = MinValue(mask); int maxValue = MaxValue(mask); if (maxValue - minValue == 2) { // Values 2 apart will always remove the center value, but if there are 3 candidates this only applies to the same box bool haveChanges = false; int removeValue = minValue + 1; uint removeValueMask = ValueMask(removeValue); foreach (var cell in DiagonalCells(i, j, valueCount != 2)) { LogicResult findResult = sudokuSolver.ClearMask(cell.Item1, cell.Item2, removeValueMask); if (findResult == LogicResult.Invalid) { logicalStepDescription.Clear(); logicalStepDescription.Append($"{CellName((i, j))} having candidates {MaskToString(mask)} removes the only candidate {removeValue} from {CellName(cell)}"); return(LogicResult.Invalid); } if (findResult == LogicResult.Changed) { if (!haveChanges) { logicalStepDescription.Append($"{CellName((i, j))} having candidates {MaskToString(mask)} remove {removeValue} from {CellName(cell)}"); haveChanges = true; } else { logicalStepDescription.Append($", {CellName(cell)}"); } } } if (haveChanges) { return(LogicResult.Changed); } } else if (maxValue - minValue == 1) { // Values 1 apart will always remove both values, but only for diagonals in the same box bool haveChanges = false; uint removeValueMask = ValueMask(minValue) | ValueMask(maxValue); foreach (var cell in DiagonalCells(i, j, true)) { LogicResult findResult = sudokuSolver.ClearMask(cell.Item1, cell.Item2, removeValueMask); if (findResult == LogicResult.Invalid) { logicalStepDescription.Clear(); logicalStepDescription.Append($"{CellName(i, j)} removes the only candidates {MaskToString(removeValueMask)} from {CellName(cell)}"); return(LogicResult.Invalid); } if (findResult == LogicResult.Changed) { if (!haveChanges) { logicalStepDescription.Append($"{CellName((i, j))} having candidates {minValue}{maxValue} remove those values from {CellName(cell)}"); haveChanges = true; } else { logicalStepDescription.Append($", {CellName(cell)}"); } } } if (haveChanges) { return(LogicResult.Changed); } } } } } // Look for groups where a particular digit is locked to 2, 3, or 4 places // Any cell that is diagonal to all of them cannot be either consecutive digit var valInstances = new (int, int)[MAX_VALUE];